mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
no message
This commit is contained in:
@@ -38,7 +38,7 @@
|
|||||||
D01AC91F1DD5E09000E8160F /* EditAccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91E1DD5E09000E8160F /* EditAccessoryPanelNode.swift */; };
|
D01AC91F1DD5E09000E8160F /* EditAccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91E1DD5E09000E8160F /* EditAccessoryPanelNode.swift */; };
|
||||||
D01B27951E38F3BF0022A4C0 /* ItemListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27941E38F3BF0022A4C0 /* ItemListControllerNode.swift */; };
|
D01B27951E38F3BF0022A4C0 /* ItemListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27941E38F3BF0022A4C0 /* ItemListControllerNode.swift */; };
|
||||||
D01B27991E39144C0022A4C0 /* ItemListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27981E39144C0022A4C0 /* ItemListController.swift */; };
|
D01B27991E39144C0022A4C0 /* ItemListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27981E39144C0022A4C0 /* ItemListController.swift */; };
|
||||||
D01B279B1E39386C0022A4C0 /* SettingsControllerEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279A1E39386C0022A4C0 /* SettingsControllerEntries.swift */; };
|
D01B279B1E39386C0022A4C0 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279A1E39386C0022A4C0 /* SettingsController.swift */; };
|
||||||
D01B279D1E394A500022A4C0 /* NotificationsAndSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */; };
|
D01B279D1E394A500022A4C0 /* NotificationsAndSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */; };
|
||||||
D01B279F1E394BD70022A4C0 /* InAppNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279E1E394BD70022A4C0 /* InAppNotificationSettings.swift */; };
|
D01B279F1E394BD70022A4C0 /* InAppNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279E1E394BD70022A4C0 /* InAppNotificationSettings.swift */; };
|
||||||
D01B27A41E394FC90022A4C0 /* SecuritySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27A31E394FC90022A4C0 /* SecuritySettings.swift */; };
|
D01B27A41E394FC90022A4C0 /* SecuritySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27A31E394FC90022A4C0 /* SecuritySettings.swift */; };
|
||||||
@@ -92,6 +92,9 @@
|
|||||||
D03ADB4B1D70443F005A521C /* ReplyAccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */; };
|
D03ADB4B1D70443F005A521C /* ReplyAccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */; };
|
||||||
D03ADB4D1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4C1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift */; };
|
D03ADB4D1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4C1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift */; };
|
||||||
D03ADB4F1D70546B005A521C /* AccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */; };
|
D03ADB4F1D70546B005A521C /* AccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */; };
|
||||||
|
D03E5E091E55C49C0029569A /* DebugAccountsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E5E081E55C49C0029569A /* DebugAccountsController.swift */; };
|
||||||
|
D03E5E0F1E55F8B90029569A /* ChannelVisibilityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E5E0E1E55F8B90029569A /* ChannelVisibilityController.swift */; };
|
||||||
|
D0486F0A1E523C8500091F0C /* GroupInfoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0486F091E523C8500091F0C /* GroupInfoController.swift */; };
|
||||||
D049EAE21E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */; };
|
D049EAE21E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */; };
|
||||||
D049EAE41E44949F00A2CD3A /* HorizontalStickerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */; };
|
D049EAE41E44949F00A2CD3A /* HorizontalStickerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */; };
|
||||||
D049EAE61E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE51E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift */; };
|
D049EAE61E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE51E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift */; };
|
||||||
@@ -170,6 +173,10 @@
|
|||||||
D050F2131E48B61500988324 /* PhoneInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2121E48B61500988324 /* PhoneInputNode.swift */; };
|
D050F2131E48B61500988324 /* PhoneInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2121E48B61500988324 /* PhoneInputNode.swift */; };
|
||||||
D050F2161E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2151E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift */; };
|
D050F2161E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2151E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift */; };
|
||||||
D050F2181E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2171E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift */; };
|
D050F2181E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2171E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift */; };
|
||||||
|
D0561DDF1E56FE8200E6B9E9 /* ItemListSingleLineInputItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DDE1E56FE8200E6B9E9 /* ItemListSingleLineInputItem.swift */; };
|
||||||
|
D0561DE11E57153000E6B9E9 /* ItemListActivityTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE01E57153000E6B9E9 /* ItemListActivityTextItem.swift */; };
|
||||||
|
D0561DE61E57424700E6B9E9 /* ItemListMultilineTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE51E57424700E6B9E9 /* ItemListMultilineTextItem.swift */; };
|
||||||
|
D0561DE81E574C3200E6B9E9 /* ChannelAdminsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0561DE71E574C3200E6B9E9 /* ChannelAdminsController.swift */; };
|
||||||
D0568AAD1DF198130022E7DA /* AudioWaveformNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0568AAC1DF198130022E7DA /* AudioWaveformNode.swift */; };
|
D0568AAD1DF198130022E7DA /* AudioWaveformNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0568AAC1DF198130022E7DA /* AudioWaveformNode.swift */; };
|
||||||
D0568AAF1DF1B3920022E7DA /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0568AAE1DF1B3920022E7DA /* HapticFeedback.swift */; };
|
D0568AAF1DF1B3920022E7DA /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0568AAE1DF1B3920022E7DA /* HapticFeedback.swift */; };
|
||||||
D05811941DD5F9380057C769 /* TelegramApplicationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */; };
|
D05811941DD5F9380057C769 /* TelegramApplicationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */; };
|
||||||
@@ -243,6 +250,7 @@
|
|||||||
D0B843DB1DAAB138005F29E1 /* ItemListPeerActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843DA1DAAB138005F29E1 /* ItemListPeerActionItem.swift */; };
|
D0B843DB1DAAB138005F29E1 /* ItemListPeerActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843DA1DAAB138005F29E1 /* ItemListPeerActionItem.swift */; };
|
||||||
D0B844561DAC3AEE005F29E1 /* PresenceStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */; };
|
D0B844561DAC3AEE005F29E1 /* PresenceStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */; };
|
||||||
D0B844581DAC44E8005F29E1 /* PeerPresenceStatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */; };
|
D0B844581DAC44E8005F29E1 /* PeerPresenceStatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */; };
|
||||||
|
D0B98E7F1E575D2C008084B1 /* ChannelBlacklistController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B98E7E1E575D2C008084B1 /* ChannelBlacklistController.swift */; };
|
||||||
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */; };
|
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */; };
|
||||||
D0BA6F851D784ECD0034826E /* ChatInterfaceStateInputPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */; };
|
D0BA6F851D784ECD0034826E /* ChatInterfaceStateInputPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */; };
|
||||||
D0BA6F881D784F880034826E /* ChatMessageSelectionInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */; };
|
D0BA6F881D784F880034826E /* ChatMessageSelectionInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */; };
|
||||||
@@ -253,6 +261,7 @@
|
|||||||
D0C932361E0988C60074F044 /* ChatButtonKeyboardInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */; };
|
D0C932361E0988C60074F044 /* ChatButtonKeyboardInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */; };
|
||||||
D0C932381E09E0EA0074F044 /* ChatBotInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */; };
|
D0C932381E09E0EA0074F044 /* ChatBotInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */; };
|
||||||
D0C9323C1E0B4AE90074F044 /* DataAndStorageSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */; };
|
D0C9323C1E0B4AE90074F044 /* DataAndStorageSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */; };
|
||||||
|
D0CE1BD31E51BC6100404327 /* DebugController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE1BD21E51BC6100404327 /* DebugController.swift */; };
|
||||||
D0D03AE31DECACB700220C46 /* ManagedAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D03AE21DECACB700220C46 /* ManagedAudioSession.swift */; };
|
D0D03AE31DECACB700220C46 /* ManagedAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D03AE21DECACB700220C46 /* ManagedAudioSession.swift */; };
|
||||||
D0D03AE51DECAE8900220C46 /* ManagedAudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D03AE41DECAE8900220C46 /* ManagedAudioRecorder.swift */; };
|
D0D03AE51DECAE8900220C46 /* ManagedAudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D03AE41DECAE8900220C46 /* ManagedAudioRecorder.swift */; };
|
||||||
D0D03B081DECB0FE00220C46 /* diag_range.c in Sources */ = {isa = PBXBuildFile; fileRef = D0D03AE81DECB0FE00220C46 /* diag_range.c */; };
|
D0D03B081DECB0FE00220C46 /* diag_range.c in Sources */ = {isa = PBXBuildFile; fileRef = D0D03AE81DECB0FE00220C46 /* diag_range.c */; };
|
||||||
@@ -358,13 +367,6 @@
|
|||||||
D0F69DE31D6B8A420046BCD6 /* ListControllerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DDC1D6B8A420046BCD6 /* ListControllerItem.swift */; };
|
D0F69DE31D6B8A420046BCD6 /* ListControllerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DDC1D6B8A420046BCD6 /* ListControllerItem.swift */; };
|
||||||
D0F69DE41D6B8A420046BCD6 /* ListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DDD1D6B8A420046BCD6 /* ListControllerNode.swift */; };
|
D0F69DE41D6B8A420046BCD6 /* ListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DDD1D6B8A420046BCD6 /* ListControllerNode.swift */; };
|
||||||
D0F69DE51D6B8A420046BCD6 /* ListControllerSpacerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DDE1D6B8A420046BCD6 /* ListControllerSpacerItem.swift */; };
|
D0F69DE51D6B8A420046BCD6 /* ListControllerSpacerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DDE1D6B8A420046BCD6 /* ListControllerSpacerItem.swift */; };
|
||||||
D0F69DEF1D6B8A6C0046BCD6 /* AuthorizationCodeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DE81D6B8A6C0046BCD6 /* AuthorizationCodeController.swift */; };
|
|
||||||
D0F69DF01D6B8A6C0046BCD6 /* AuthorizationCodeControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DE91D6B8A6C0046BCD6 /* AuthorizationCodeControllerNode.swift */; };
|
|
||||||
D0F69DF11D6B8A6C0046BCD6 /* AuthorizationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DEA1D6B8A6C0046BCD6 /* AuthorizationController.swift */; };
|
|
||||||
D0F69DF21D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DEB1D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift */; };
|
|
||||||
D0F69DF31D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DEC1D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift */; };
|
|
||||||
D0F69DF41D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DED1D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift */; };
|
|
||||||
D0F69DF51D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DEE1D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift */; };
|
|
||||||
D0F69DFE1D6B8A880046BCD6 /* AvatarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF71D6B8A880046BCD6 /* AvatarNode.swift */; };
|
D0F69DFE1D6B8A880046BCD6 /* AvatarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF71D6B8A880046BCD6 /* AvatarNode.swift */; };
|
||||||
D0F69DFF1D6B8A880046BCD6 /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */; };
|
D0F69DFF1D6B8A880046BCD6 /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */; };
|
||||||
D0F69E001D6B8A880046BCD6 /* ChatListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */; };
|
D0F69E001D6B8A880046BCD6 /* ChatListControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */; };
|
||||||
@@ -424,7 +426,6 @@
|
|||||||
D0F69E771D6B8C340046BCD6 /* ContactsSectionHeaderAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E711D6B8C340046BCD6 /* ContactsSectionHeaderAccessoryItem.swift */; };
|
D0F69E771D6B8C340046BCD6 /* ContactsSectionHeaderAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E711D6B8C340046BCD6 /* ContactsSectionHeaderAccessoryItem.swift */; };
|
||||||
D0F69E781D6B8C340046BCD6 /* ContactsVCardItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E721D6B8C340046BCD6 /* ContactsVCardItem.swift */; };
|
D0F69E781D6B8C340046BCD6 /* ContactsVCardItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E721D6B8C340046BCD6 /* ContactsVCardItem.swift */; };
|
||||||
D0F69E7C1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E7A1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift */; };
|
D0F69E7C1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E7A1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift */; };
|
||||||
D0F69E7D1D6B8C470046BCD6 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E7B1D6B8C470046BCD6 /* SettingsController.swift */; };
|
|
||||||
D0F69E881D6B8C850046BCD6 /* FastBlur.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F69E7F1D6B8C850046BCD6 /* FastBlur.h */; };
|
D0F69E881D6B8C850046BCD6 /* FastBlur.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F69E7F1D6B8C850046BCD6 /* FastBlur.h */; };
|
||||||
D0F69E891D6B8C850046BCD6 /* FastBlur.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E801D6B8C850046BCD6 /* FastBlur.m */; };
|
D0F69E891D6B8C850046BCD6 /* FastBlur.m in Sources */ = {isa = PBXBuildFile; fileRef = D0F69E801D6B8C850046BCD6 /* FastBlur.m */; };
|
||||||
D0F69E8A1D6B8C850046BCD6 /* FFMpegSwResample.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F69E811D6B8C850046BCD6 /* FFMpegSwResample.h */; };
|
D0F69E8A1D6B8C850046BCD6 /* FFMpegSwResample.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F69E811D6B8C850046BCD6 /* FFMpegSwResample.h */; };
|
||||||
@@ -497,7 +498,7 @@
|
|||||||
D01AC91E1DD5E09000E8160F /* EditAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditAccessoryPanelNode.swift; sourceTree = "<group>"; };
|
D01AC91E1DD5E09000E8160F /* EditAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditAccessoryPanelNode.swift; sourceTree = "<group>"; };
|
||||||
D01B27941E38F3BF0022A4C0 /* ItemListControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListControllerNode.swift; sourceTree = "<group>"; };
|
D01B27941E38F3BF0022A4C0 /* ItemListControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListControllerNode.swift; sourceTree = "<group>"; };
|
||||||
D01B27981E39144C0022A4C0 /* ItemListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListController.swift; sourceTree = "<group>"; };
|
D01B27981E39144C0022A4C0 /* ItemListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListController.swift; sourceTree = "<group>"; };
|
||||||
D01B279A1E39386C0022A4C0 /* SettingsControllerEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsControllerEntries.swift; sourceTree = "<group>"; };
|
D01B279A1E39386C0022A4C0 /* SettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
|
||||||
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsAndSounds.swift; sourceTree = "<group>"; };
|
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsAndSounds.swift; sourceTree = "<group>"; };
|
||||||
D01B279E1E394BD70022A4C0 /* InAppNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppNotificationSettings.swift; sourceTree = "<group>"; };
|
D01B279E1E394BD70022A4C0 /* InAppNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppNotificationSettings.swift; sourceTree = "<group>"; };
|
||||||
D01B27A31E394FC90022A4C0 /* SecuritySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecuritySettings.swift; sourceTree = "<group>"; };
|
D01B27A31E394FC90022A4C0 /* SecuritySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecuritySettings.swift; sourceTree = "<group>"; };
|
||||||
@@ -551,6 +552,9 @@
|
|||||||
D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyAccessoryPanelNode.swift; sourceTree = "<group>"; };
|
D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyAccessoryPanelNode.swift; sourceTree = "<group>"; };
|
||||||
D03ADB4C1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateAccessoryPanels.swift; sourceTree = "<group>"; };
|
D03ADB4C1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateAccessoryPanels.swift; sourceTree = "<group>"; };
|
||||||
D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPanelNode.swift; sourceTree = "<group>"; };
|
D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPanelNode.swift; sourceTree = "<group>"; };
|
||||||
|
D03E5E081E55C49C0029569A /* DebugAccountsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugAccountsController.swift; sourceTree = "<group>"; };
|
||||||
|
D03E5E0E1E55F8B90029569A /* ChannelVisibilityController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelVisibilityController.swift; sourceTree = "<group>"; };
|
||||||
|
D0486F091E523C8500091F0C /* GroupInfoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupInfoController.swift; sourceTree = "<group>"; };
|
||||||
D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalStickersChatContextPanelNode.swift; sourceTree = "<group>"; };
|
D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalStickersChatContextPanelNode.swift; sourceTree = "<group>"; };
|
||||||
D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalStickerGridItem.swift; sourceTree = "<group>"; };
|
D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalStickerGridItem.swift; sourceTree = "<group>"; };
|
||||||
D049EAE51E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputRecentStickerPacksItem.swift; sourceTree = "<group>"; };
|
D049EAE51E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputRecentStickerPacksItem.swift; sourceTree = "<group>"; };
|
||||||
@@ -629,6 +633,10 @@
|
|||||||
D050F2121E48B61500988324 /* PhoneInputNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneInputNode.swift; sourceTree = "<group>"; };
|
D050F2121E48B61500988324 /* PhoneInputNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhoneInputNode.swift; sourceTree = "<group>"; };
|
||||||
D050F2151E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceCountrySelectionController.swift; sourceTree = "<group>"; };
|
D050F2151E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceCountrySelectionController.swift; sourceTree = "<group>"; };
|
||||||
D050F2171E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceCountrySelectionControllerNode.swift; sourceTree = "<group>"; };
|
D050F2171E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceCountrySelectionControllerNode.swift; sourceTree = "<group>"; };
|
||||||
|
D0561DDE1E56FE8200E6B9E9 /* ItemListSingleLineInputItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListSingleLineInputItem.swift; sourceTree = "<group>"; };
|
||||||
|
D0561DE01E57153000E6B9E9 /* ItemListActivityTextItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListActivityTextItem.swift; sourceTree = "<group>"; };
|
||||||
|
D0561DE51E57424700E6B9E9 /* ItemListMultilineTextItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListMultilineTextItem.swift; sourceTree = "<group>"; };
|
||||||
|
D0561DE71E574C3200E6B9E9 /* ChannelAdminsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdminsController.swift; sourceTree = "<group>"; };
|
||||||
D0568AAC1DF198130022E7DA /* AudioWaveformNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioWaveformNode.swift; sourceTree = "<group>"; };
|
D0568AAC1DF198130022E7DA /* AudioWaveformNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioWaveformNode.swift; sourceTree = "<group>"; };
|
||||||
D0568AAE1DF1B3920022E7DA /* HapticFeedback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
|
D0568AAE1DF1B3920022E7DA /* HapticFeedback.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
|
||||||
D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramApplicationContext.swift; sourceTree = "<group>"; };
|
D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramApplicationContext.swift; sourceTree = "<group>"; };
|
||||||
@@ -705,6 +713,7 @@
|
|||||||
D0B843DA1DAAB138005F29E1 /* ItemListPeerActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListPeerActionItem.swift; sourceTree = "<group>"; };
|
D0B843DA1DAAB138005F29E1 /* ItemListPeerActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemListPeerActionItem.swift; sourceTree = "<group>"; };
|
||||||
D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceStrings.swift; sourceTree = "<group>"; };
|
D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceStrings.swift; sourceTree = "<group>"; };
|
||||||
D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresenceStatusManager.swift; sourceTree = "<group>"; };
|
D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresenceStatusManager.swift; sourceTree = "<group>"; };
|
||||||
|
D0B98E7E1E575D2C008084B1 /* ChannelBlacklistController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelBlacklistController.swift; sourceTree = "<group>"; };
|
||||||
D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputPanelNode.swift; sourceTree = "<group>"; };
|
D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputPanelNode.swift; sourceTree = "<group>"; };
|
||||||
D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateInputPanels.swift; sourceTree = "<group>"; };
|
D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateInputPanels.swift; sourceTree = "<group>"; };
|
||||||
D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionInputPanelNode.swift; sourceTree = "<group>"; };
|
D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionInputPanelNode.swift; sourceTree = "<group>"; };
|
||||||
@@ -715,6 +724,7 @@
|
|||||||
D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatButtonKeyboardInputNode.swift; sourceTree = "<group>"; };
|
D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatButtonKeyboardInputNode.swift; sourceTree = "<group>"; };
|
||||||
D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBotInfoItem.swift; sourceTree = "<group>"; };
|
D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatBotInfoItem.swift; sourceTree = "<group>"; };
|
||||||
D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataAndStorageSettingsController.swift; sourceTree = "<group>"; };
|
D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataAndStorageSettingsController.swift; sourceTree = "<group>"; };
|
||||||
|
D0CE1BD21E51BC6100404327 /* DebugController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugController.swift; sourceTree = "<group>"; };
|
||||||
D0D03AE21DECACB700220C46 /* ManagedAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedAudioSession.swift; sourceTree = "<group>"; };
|
D0D03AE21DECACB700220C46 /* ManagedAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedAudioSession.swift; sourceTree = "<group>"; };
|
||||||
D0D03AE41DECAE8900220C46 /* ManagedAudioRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedAudioRecorder.swift; sourceTree = "<group>"; };
|
D0D03AE41DECAE8900220C46 /* ManagedAudioRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedAudioRecorder.swift; sourceTree = "<group>"; };
|
||||||
D0D03AE81DECB0FE00220C46 /* diag_range.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = diag_range.c; sourceTree = "<group>"; };
|
D0D03AE81DECB0FE00220C46 /* diag_range.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = diag_range.c; sourceTree = "<group>"; };
|
||||||
@@ -820,13 +830,6 @@
|
|||||||
D0F69DDC1D6B8A420046BCD6 /* ListControllerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListControllerItem.swift; sourceTree = "<group>"; };
|
D0F69DDC1D6B8A420046BCD6 /* ListControllerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListControllerItem.swift; sourceTree = "<group>"; };
|
||||||
D0F69DDD1D6B8A420046BCD6 /* ListControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListControllerNode.swift; sourceTree = "<group>"; };
|
D0F69DDD1D6B8A420046BCD6 /* ListControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListControllerNode.swift; sourceTree = "<group>"; };
|
||||||
D0F69DDE1D6B8A420046BCD6 /* ListControllerSpacerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListControllerSpacerItem.swift; sourceTree = "<group>"; };
|
D0F69DDE1D6B8A420046BCD6 /* ListControllerSpacerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListControllerSpacerItem.swift; sourceTree = "<group>"; };
|
||||||
D0F69DE81D6B8A6C0046BCD6 /* AuthorizationCodeController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationCodeController.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69DE91D6B8A6C0046BCD6 /* AuthorizationCodeControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationCodeControllerNode.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69DEA1D6B8A6C0046BCD6 /* AuthorizationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationController.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69DEB1D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationPasswordController.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69DEC1D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationPasswordControllerNode.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69DED1D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationPhoneController.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69DEE1D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationPhoneControllerNode.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69DF71D6B8A880046BCD6 /* AvatarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarNode.swift; sourceTree = "<group>"; };
|
D0F69DF71D6B8A880046BCD6 /* AvatarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarNode.swift; sourceTree = "<group>"; };
|
||||||
D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = "<group>"; };
|
D0F69DF81D6B8A880046BCD6 /* ChatListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = "<group>"; };
|
||||||
D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListControllerNode.swift; sourceTree = "<group>"; };
|
D0F69DF91D6B8A880046BCD6 /* ChatListControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListControllerNode.swift; sourceTree = "<group>"; };
|
||||||
@@ -886,7 +889,6 @@
|
|||||||
D0F69E711D6B8C340046BCD6 /* ContactsSectionHeaderAccessoryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsSectionHeaderAccessoryItem.swift; sourceTree = "<group>"; };
|
D0F69E711D6B8C340046BCD6 /* ContactsSectionHeaderAccessoryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsSectionHeaderAccessoryItem.swift; sourceTree = "<group>"; };
|
||||||
D0F69E721D6B8C340046BCD6 /* ContactsVCardItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsVCardItem.swift; sourceTree = "<group>"; };
|
D0F69E721D6B8C340046BCD6 /* ContactsVCardItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsVCardItem.swift; sourceTree = "<group>"; };
|
||||||
D0F69E7A1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAccountInfoItem.swift; sourceTree = "<group>"; };
|
D0F69E7A1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsAccountInfoItem.swift; sourceTree = "<group>"; };
|
||||||
D0F69E7B1D6B8C470046BCD6 /* SettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
|
|
||||||
D0F69E7F1D6B8C850046BCD6 /* FastBlur.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FastBlur.h; sourceTree = "<group>"; };
|
D0F69E7F1D6B8C850046BCD6 /* FastBlur.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FastBlur.h; sourceTree = "<group>"; };
|
||||||
D0F69E801D6B8C850046BCD6 /* FastBlur.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FastBlur.m; sourceTree = "<group>"; };
|
D0F69E801D6B8C850046BCD6 /* FastBlur.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FastBlur.m; sourceTree = "<group>"; };
|
||||||
D0F69E811D6B8C850046BCD6 /* FFMpegSwResample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FFMpegSwResample.h; sourceTree = "<group>"; };
|
D0F69E811D6B8C850046BCD6 /* FFMpegSwResample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FFMpegSwResample.h; sourceTree = "<group>"; };
|
||||||
@@ -956,13 +958,6 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
D003702C1DA43006004308D3 /* Components */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
);
|
|
||||||
name = Components;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
D00C7CDA1E3776CA0080C3D5 /* Secret Preview */ = {
|
D00C7CDA1E3776CA0080C3D5 /* Secret Preview */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1479,11 +1474,12 @@
|
|||||||
D0C932391E0B4AC60074F044 /* Settings */ = {
|
D0C932391E0B4AC60074F044 /* Settings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D01B279A1E39386C0022A4C0 /* SettingsControllerEntries.swift */,
|
D01B279A1E39386C0022A4C0 /* SettingsController.swift */,
|
||||||
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
|
D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */,
|
||||||
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
|
D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */,
|
||||||
D0F69E7B1D6B8C470046BCD6 /* SettingsController.swift */,
|
|
||||||
D0F69E7A1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift */,
|
D0F69E7A1D6B8C470046BCD6 /* SettingsAccountInfoItem.swift */,
|
||||||
|
D0CE1BD21E51BC6100404327 /* DebugController.swift */,
|
||||||
|
D03E5E081E55C49C0029569A /* DebugAccountsController.swift */,
|
||||||
);
|
);
|
||||||
name = Settings;
|
name = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1686,9 +1682,12 @@
|
|||||||
D00C7CD61E3664070080C3D5 /* ItemListMultilineInputItem.swift */,
|
D00C7CD61E3664070080C3D5 /* ItemListMultilineInputItem.swift */,
|
||||||
D00B3F9D1E3A4847003872C3 /* ItemListSectionHeaderItem.swift */,
|
D00B3F9D1E3A4847003872C3 /* ItemListSectionHeaderItem.swift */,
|
||||||
D00B3FA11E3A983E003872C3 /* ItemListTextItem.swift */,
|
D00B3FA11E3A983E003872C3 /* ItemListTextItem.swift */,
|
||||||
|
D0561DE01E57153000E6B9E9 /* ItemListActivityTextItem.swift */,
|
||||||
D021E0A81E3AACA200AF709C /* ItemListEditableItem.swift */,
|
D021E0A81E3AACA200AF709C /* ItemListEditableItem.swift */,
|
||||||
D021E0AA1E3B9E2700AF709C /* ItemListRevealOptionsNode.swift */,
|
D021E0AA1E3B9E2700AF709C /* ItemListRevealOptionsNode.swift */,
|
||||||
D08774F71E3DE7BF00A97350 /* ItemListEditableDeleteControlNode.swift */,
|
D08774F71E3DE7BF00A97350 /* ItemListEditableDeleteControlNode.swift */,
|
||||||
|
D0561DDE1E56FE8200E6B9E9 /* ItemListSingleLineInputItem.swift */,
|
||||||
|
D0561DE51E57424700E6B9E9 /* ItemListMultilineTextItem.swift */,
|
||||||
);
|
);
|
||||||
name = Items;
|
name = Items;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1711,7 +1710,6 @@
|
|||||||
D0EE97131D88BB1A006C18E1 /* Peer Info */ = {
|
D0EE97131D88BB1A006C18E1 /* Peer Info */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D003702C1DA43006004308D3 /* Components */,
|
|
||||||
D0EE97141D88BB39006C18E1 /* Controller */,
|
D0EE97141D88BB39006C18E1 /* Controller */,
|
||||||
);
|
);
|
||||||
name = "Peer Info";
|
name = "Peer Info";
|
||||||
@@ -1725,6 +1723,10 @@
|
|||||||
D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */,
|
D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */,
|
||||||
D0B843D21DA922E3005F29E1 /* ChannelInfoEntries.swift */,
|
D0B843D21DA922E3005F29E1 /* ChannelInfoEntries.swift */,
|
||||||
D0B843CC1DA903BB005F29E1 /* PeerInfoController.swift */,
|
D0B843CC1DA903BB005F29E1 /* PeerInfoController.swift */,
|
||||||
|
D0486F091E523C8500091F0C /* GroupInfoController.swift */,
|
||||||
|
D03E5E0E1E55F8B90029569A /* ChannelVisibilityController.swift */,
|
||||||
|
D0561DE71E574C3200E6B9E9 /* ChannelAdminsController.swift */,
|
||||||
|
D0B98E7E1E575D2C008084B1 /* ChannelBlacklistController.swift */,
|
||||||
);
|
);
|
||||||
name = Controller;
|
name = Controller;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1884,13 +1886,6 @@
|
|||||||
D04BB2B71E44E5CB00650E93 /* Phone Entry */,
|
D04BB2B71E44E5CB00650E93 /* Phone Entry */,
|
||||||
D04BB2BC1E44FD1300650E93 /* Code Entry */,
|
D04BB2BC1E44FD1300650E93 /* Code Entry */,
|
||||||
D04BB2C11E45016800650E93 /* Password Entry */,
|
D04BB2C11E45016800650E93 /* Password Entry */,
|
||||||
D0F69DE81D6B8A6C0046BCD6 /* AuthorizationCodeController.swift */,
|
|
||||||
D0F69DE91D6B8A6C0046BCD6 /* AuthorizationCodeControllerNode.swift */,
|
|
||||||
D0F69DEA1D6B8A6C0046BCD6 /* AuthorizationController.swift */,
|
|
||||||
D0F69DEB1D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift */,
|
|
||||||
D0F69DEC1D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift */,
|
|
||||||
D0F69DED1D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift */,
|
|
||||||
D0F69DEE1D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift */,
|
|
||||||
);
|
);
|
||||||
name = Authorization;
|
name = Authorization;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -2395,8 +2390,10 @@
|
|||||||
D0F69EA21D6B8E380046BCD6 /* PhotoResources.swift in Sources */,
|
D0F69EA21D6B8E380046BCD6 /* PhotoResources.swift in Sources */,
|
||||||
D08C367F1DB66A820064C744 /* ChatMediaInputPanelEntries.swift in Sources */,
|
D08C367F1DB66A820064C744 /* ChatMediaInputPanelEntries.swift in Sources */,
|
||||||
D0568AAD1DF198130022E7DA /* AudioWaveformNode.swift in Sources */,
|
D0568AAD1DF198130022E7DA /* AudioWaveformNode.swift in Sources */,
|
||||||
|
D03E5E0F1E55F8B90029569A /* ChannelVisibilityController.swift in Sources */,
|
||||||
D0D03B1E1DECB0FE00220C46 /* opusfile.c in Sources */,
|
D0D03B1E1DECB0FE00220C46 /* opusfile.c in Sources */,
|
||||||
D0DA44561E4E7F43005FDCA7 /* ShakeAnimation.swift in Sources */,
|
D0DA44561E4E7F43005FDCA7 /* ShakeAnimation.swift in Sources */,
|
||||||
|
D0561DE61E57424700E6B9E9 /* ItemListMultilineTextItem.swift in Sources */,
|
||||||
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */,
|
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */,
|
||||||
D0F69DC71D6B89E70046BCD6 /* TransformImageNode.swift in Sources */,
|
D0F69DC71D6B89E70046BCD6 /* TransformImageNode.swift in Sources */,
|
||||||
D039EB031DEAEFEE00886EBC /* ChatTextInputAudioRecordingOverlayButton.swift in Sources */,
|
D039EB031DEAEFEE00886EBC /* ChatTextInputAudioRecordingOverlayButton.swift in Sources */,
|
||||||
@@ -2417,7 +2414,6 @@
|
|||||||
D0F69E4D1D6B8BB20046BCD6 /* ChatMediaActionSheetRollItem.swift in Sources */,
|
D0F69E4D1D6B8BB20046BCD6 /* ChatMediaActionSheetRollItem.swift in Sources */,
|
||||||
D0F69E661D6B8BF90046BCD6 /* ZoomableContentGalleryItemNode.swift in Sources */,
|
D0F69E661D6B8BF90046BCD6 /* ZoomableContentGalleryItemNode.swift in Sources */,
|
||||||
D0DE77001D92F1EB002B8809 /* ChatTitleView.swift in Sources */,
|
D0DE77001D92F1EB002B8809 /* ChatTitleView.swift in Sources */,
|
||||||
D0F69DF01D6B8A6C0046BCD6 /* AuthorizationCodeControllerNode.swift in Sources */,
|
|
||||||
D0B843DB1DAAB138005F29E1 /* ItemListPeerActionItem.swift in Sources */,
|
D0B843DB1DAAB138005F29E1 /* ItemListPeerActionItem.swift in Sources */,
|
||||||
D0F69EA11D6B8E380046BCD6 /* FileResources.swift in Sources */,
|
D0F69EA11D6B8E380046BCD6 /* FileResources.swift in Sources */,
|
||||||
D0F69D271D6B87D30046BCD6 /* FFMpegAudioFrameDecoder.swift in Sources */,
|
D0F69D271D6B87D30046BCD6 /* FFMpegAudioFrameDecoder.swift in Sources */,
|
||||||
@@ -2483,6 +2479,7 @@
|
|||||||
D07CFF791DCA226F00761F81 /* ChatListNode.swift in Sources */,
|
D07CFF791DCA226F00761F81 /* ChatListNode.swift in Sources */,
|
||||||
D0B7F8E81D8A1F5F0045D939 /* PeerMediaCollectionControllerNode.swift in Sources */,
|
D0B7F8E81D8A1F5F0045D939 /* PeerMediaCollectionControllerNode.swift in Sources */,
|
||||||
D0215D561E043020001A0B1E /* InstantPageControllerNode.swift in Sources */,
|
D0215D561E043020001A0B1E /* InstantPageControllerNode.swift in Sources */,
|
||||||
|
D0B98E7F1E575D2C008084B1 /* ChannelBlacklistController.swift in Sources */,
|
||||||
D0F69DD21D6B8A0D0046BCD6 /* SearchDisplayControllerContentNode.swift in Sources */,
|
D0F69DD21D6B8A0D0046BCD6 /* SearchDisplayControllerContentNode.swift in Sources */,
|
||||||
D0105D5A1D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift in Sources */,
|
D0105D5A1D80B957008755D8 /* ChatChannelSubscriberInputPanelNode.swift in Sources */,
|
||||||
D0D03B121DECB0FE00220C46 /* bitwise.c in Sources */,
|
D0D03B121DECB0FE00220C46 /* bitwise.c in Sources */,
|
||||||
@@ -2566,6 +2563,7 @@
|
|||||||
D0F69E581D6B8BDA0046BCD6 /* GalleryItemNode.swift in Sources */,
|
D0F69E581D6B8BDA0046BCD6 /* GalleryItemNode.swift in Sources */,
|
||||||
D0F69E971D6B8C9B0046BCD6 /* WebP.swift in Sources */,
|
D0F69E971D6B8C9B0046BCD6 /* WebP.swift in Sources */,
|
||||||
D0F69E2F1D6B8B030046BCD6 /* ChatMessageBubbleContentCalclulateImageCorners.swift in Sources */,
|
D0F69E2F1D6B8B030046BCD6 /* ChatMessageBubbleContentCalclulateImageCorners.swift in Sources */,
|
||||||
|
D03E5E091E55C49C0029569A /* DebugAccountsController.swift in Sources */,
|
||||||
D075518B1DDA4D7D0073E051 /* LegacyController.swift in Sources */,
|
D075518B1DDA4D7D0073E051 /* LegacyController.swift in Sources */,
|
||||||
D0F69E361D6B8B030046BCD6 /* ChatMessageInteractiveMediaNode.swift in Sources */,
|
D0F69E361D6B8B030046BCD6 /* ChatMessageInteractiveMediaNode.swift in Sources */,
|
||||||
D08775191E3F53FC00A97350 /* ContactMultiselectionController.swift in Sources */,
|
D08775191E3F53FC00A97350 /* ContactMultiselectionController.swift in Sources */,
|
||||||
@@ -2591,15 +2589,14 @@
|
|||||||
D0F917B51E0DA396003687E6 /* GenerateTextEntities.swift in Sources */,
|
D0F917B51E0DA396003687E6 /* GenerateTextEntities.swift in Sources */,
|
||||||
D0736F2E1DF4E54A00F2C02A /* MediaNavigationAccessoryHeaderNode.swift in Sources */,
|
D0736F2E1DF4E54A00F2C02A /* MediaNavigationAccessoryHeaderNode.swift in Sources */,
|
||||||
D04BB33E1E48797500650E93 /* platform_log.c in Sources */,
|
D04BB33E1E48797500650E93 /* platform_log.c in Sources */,
|
||||||
D0F69DF51D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift in Sources */,
|
D01B279B1E39386C0022A4C0 /* SettingsController.swift in Sources */,
|
||||||
D01B279B1E39386C0022A4C0 /* SettingsControllerEntries.swift in Sources */,
|
|
||||||
D08775201E3F595000A97350 /* ContactListActionItem.swift in Sources */,
|
D08775201E3F595000A97350 /* ContactListActionItem.swift in Sources */,
|
||||||
D0F69E751D6B8C340046BCD6 /* ContactsPeerItem.swift in Sources */,
|
D0F69E751D6B8C340046BCD6 /* ContactsPeerItem.swift in Sources */,
|
||||||
D01B27991E39144C0022A4C0 /* ItemListController.swift in Sources */,
|
D01B27991E39144C0022A4C0 /* ItemListController.swift in Sources */,
|
||||||
|
D0561DE11E57153000E6B9E9 /* ItemListActivityTextItem.swift in Sources */,
|
||||||
D0F69DD61D6B8A2D0046BCD6 /* AlertController.swift in Sources */,
|
D0F69DD61D6B8A2D0046BCD6 /* AlertController.swift in Sources */,
|
||||||
D00370301DA43077004308D3 /* ItemListItem.swift in Sources */,
|
D00370301DA43077004308D3 /* ItemListItem.swift in Sources */,
|
||||||
D0215D381E040F53001A0B1E /* InstantPageNode.swift in Sources */,
|
D0215D381E040F53001A0B1E /* InstantPageNode.swift in Sources */,
|
||||||
D0F69E7D1D6B8C470046BCD6 /* SettingsController.swift in Sources */,
|
|
||||||
D0F69E8C1D6B8C850046BCD6 /* FrameworkBundle.swift in Sources */,
|
D0F69E8C1D6B8C850046BCD6 /* FrameworkBundle.swift in Sources */,
|
||||||
D0D03B101DECB0FE00220C46 /* wav_io.c in Sources */,
|
D0D03B101DECB0FE00220C46 /* wav_io.c in Sources */,
|
||||||
D0F69D661D6B87D30046BCD6 /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */,
|
D0F69D661D6B87D30046BCD6 /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */,
|
||||||
@@ -2611,12 +2608,10 @@
|
|||||||
D07CFF7D1DCA273400761F81 /* ChatListViewTransition.swift in Sources */,
|
D07CFF7D1DCA273400761F81 /* ChatListViewTransition.swift in Sources */,
|
||||||
D0DF0C951D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift in Sources */,
|
D0DF0C951D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift in Sources */,
|
||||||
D04BB36F1E48797500650E93 /* RMGeometry.m in Sources */,
|
D04BB36F1E48797500650E93 /* RMGeometry.m in Sources */,
|
||||||
D0F69DF21D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift in Sources */,
|
|
||||||
D0DE772B1D932E16002B8809 /* PeerMediaCollectionModeSelectionNode.swift in Sources */,
|
D0DE772B1D932E16002B8809 /* PeerMediaCollectionModeSelectionNode.swift in Sources */,
|
||||||
D0F69E8F1D6B8C850046BCD6 /* RingBuffer.m in Sources */,
|
D0F69E8F1D6B8C850046BCD6 /* RingBuffer.m in Sources */,
|
||||||
D04BB2BE1E44FD2600650E93 /* AuthorizationSequenceCodeEntryController.swift in Sources */,
|
D04BB2BE1E44FD2600650E93 /* AuthorizationSequenceCodeEntryController.swift in Sources */,
|
||||||
D01B279F1E394BD70022A4C0 /* InAppNotificationSettings.swift in Sources */,
|
D01B279F1E394BD70022A4C0 /* InAppNotificationSettings.swift in Sources */,
|
||||||
D0F69DF31D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift in Sources */,
|
|
||||||
D0F69E131D6B8ACF0046BCD6 /* ChatController.swift in Sources */,
|
D0F69E131D6B8ACF0046BCD6 /* ChatController.swift in Sources */,
|
||||||
D023837E1DDF50FD004018B6 /* ChatToastAlertPanelNode.swift in Sources */,
|
D023837E1DDF50FD004018B6 /* ChatToastAlertPanelNode.swift in Sources */,
|
||||||
D0F69DFF1D6B8A880046BCD6 /* ChatListController.swift in Sources */,
|
D0F69DFF1D6B8A880046BCD6 /* ChatListController.swift in Sources */,
|
||||||
@@ -2625,7 +2620,7 @@
|
|||||||
D04BB3381E48797500650E93 /* rngs.c in Sources */,
|
D04BB3381E48797500650E93 /* rngs.c in Sources */,
|
||||||
D04BB2C01E44FD3100650E93 /* AuthorizationSequenceCodeEntryControllerNode.swift in Sources */,
|
D04BB2C01E44FD3100650E93 /* AuthorizationSequenceCodeEntryControllerNode.swift in Sources */,
|
||||||
D0D03B0E1DECB0FE00220C46 /* picture.c in Sources */,
|
D0D03B0E1DECB0FE00220C46 /* picture.c in Sources */,
|
||||||
D0F69DF11D6B8A6C0046BCD6 /* AuthorizationController.swift in Sources */,
|
D0486F0A1E523C8500091F0C /* GroupInfoController.swift in Sources */,
|
||||||
D04BB33C1E48797500650E93 /* timing.c in Sources */,
|
D04BB33C1E48797500650E93 /* timing.c in Sources */,
|
||||||
D01B27A41E394FC90022A4C0 /* SecuritySettings.swift in Sources */,
|
D01B27A41E394FC90022A4C0 /* SecuritySettings.swift in Sources */,
|
||||||
D050F2181E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift in Sources */,
|
D050F2181E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift in Sources */,
|
||||||
@@ -2643,12 +2638,14 @@
|
|||||||
D0F69E6A1D6B8C160046BCD6 /* MapInputController.swift in Sources */,
|
D0F69E6A1D6B8C160046BCD6 /* MapInputController.swift in Sources */,
|
||||||
D00219041DDCC86400BE708A /* PerformanceSpinner.swift in Sources */,
|
D00219041DDCC86400BE708A /* PerformanceSpinner.swift in Sources */,
|
||||||
D050F2161E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift in Sources */,
|
D050F2161E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift in Sources */,
|
||||||
|
D0561DDF1E56FE8200E6B9E9 /* ItemListSingleLineInputItem.swift in Sources */,
|
||||||
D01749551E1082770057C89A /* StoredMessageFromSearchPeer.swift in Sources */,
|
D01749551E1082770057C89A /* StoredMessageFromSearchPeer.swift in Sources */,
|
||||||
D04BB32C1E48797500650E93 /* animations.c in Sources */,
|
D04BB32C1E48797500650E93 /* animations.c in Sources */,
|
||||||
D049EAEE1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift in Sources */,
|
D049EAEE1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift in Sources */,
|
||||||
D04BB2BB1E44EA2400650E93 /* AuthorizationSequenceSplashControllerNode.swift in Sources */,
|
D04BB2BB1E44EA2400650E93 /* AuthorizationSequenceSplashControllerNode.swift in Sources */,
|
||||||
D0215D581E04302E001A0B1E /* InstantPageTileNode.swift in Sources */,
|
D0215D581E04302E001A0B1E /* InstantPageTileNode.swift in Sources */,
|
||||||
D0215D521E0423EE001A0B1E /* InstantPageShapeItem.swift in Sources */,
|
D0215D521E0423EE001A0B1E /* InstantPageShapeItem.swift in Sources */,
|
||||||
|
D0561DE81E574C3200E6B9E9 /* ChannelAdminsController.swift in Sources */,
|
||||||
D0F69DE51D6B8A420046BCD6 /* ListControllerSpacerItem.swift in Sources */,
|
D0F69DE51D6B8A420046BCD6 /* ListControllerSpacerItem.swift in Sources */,
|
||||||
D0B843D51DA95427005F29E1 /* GroupInfoEntries.swift in Sources */,
|
D0B843D51DA95427005F29E1 /* GroupInfoEntries.swift in Sources */,
|
||||||
D0BA6F851D784ECD0034826E /* ChatInterfaceStateInputPanels.swift in Sources */,
|
D0BA6F851D784ECD0034826E /* ChatInterfaceStateInputPanels.swift in Sources */,
|
||||||
@@ -2667,11 +2664,9 @@
|
|||||||
D0F69E631D6B8BF90046BCD6 /* ChatImageGalleryItem.swift in Sources */,
|
D0F69E631D6B8BF90046BCD6 /* ChatImageGalleryItem.swift in Sources */,
|
||||||
D0BA6F881D784F880034826E /* ChatMessageSelectionInputPanelNode.swift in Sources */,
|
D0BA6F881D784F880034826E /* ChatMessageSelectionInputPanelNode.swift in Sources */,
|
||||||
D0F69E3B1D6B8B030046BCD6 /* ChatMessageStickerItemNode.swift in Sources */,
|
D0F69E3B1D6B8B030046BCD6 /* ChatMessageStickerItemNode.swift in Sources */,
|
||||||
D0F69DEF1D6B8A6C0046BCD6 /* AuthorizationCodeController.swift in Sources */,
|
|
||||||
D099EA2F1DE775BB001AF5A8 /* ChatContextResultManagedMediaId.swift in Sources */,
|
D099EA2F1DE775BB001AF5A8 /* ChatContextResultManagedMediaId.swift in Sources */,
|
||||||
D0E35A091DE4804900BC6096 /* VerticalListContextResultsChatInputPanelItem.swift in Sources */,
|
D0E35A091DE4804900BC6096 /* VerticalListContextResultsChatInputPanelItem.swift in Sources */,
|
||||||
D01AC9181DD5033100E8160F /* ChatMessageActionButtonsNode.swift in Sources */,
|
D01AC9181DD5033100E8160F /* ChatMessageActionButtonsNode.swift in Sources */,
|
||||||
D0F69DF41D6B8A6C0046BCD6 /* AuthorizationPhoneController.swift in Sources */,
|
|
||||||
D017494E1E1059570057C89A /* StringWithAppliedEntities.swift in Sources */,
|
D017494E1E1059570057C89A /* StringWithAppliedEntities.swift in Sources */,
|
||||||
D0DE77231D932043002B8809 /* PeerMediaCollectionInterfaceState.swift in Sources */,
|
D0DE77231D932043002B8809 /* PeerMediaCollectionInterfaceState.swift in Sources */,
|
||||||
D0F69D781D6B87DF0046BCD6 /* MediaTrackFrameBuffer.swift in Sources */,
|
D0F69D781D6B87DF0046BCD6 /* MediaTrackFrameBuffer.swift in Sources */,
|
||||||
@@ -2679,6 +2674,7 @@
|
|||||||
D023EBB21DDA800700BD496D /* LegacyMediaPickers.swift in Sources */,
|
D023EBB21DDA800700BD496D /* LegacyMediaPickers.swift in Sources */,
|
||||||
D0F69DD01D6B8A0D0046BCD6 /* SearchBarPlaceholderNode.swift in Sources */,
|
D0F69DD01D6B8A0D0046BCD6 /* SearchBarPlaceholderNode.swift in Sources */,
|
||||||
D03120F61DA534C1006A2A60 /* ItemListActionItem.swift in Sources */,
|
D03120F61DA534C1006A2A60 /* ItemListActionItem.swift in Sources */,
|
||||||
|
D0CE1BD31E51BC6100404327 /* DebugController.swift in Sources */,
|
||||||
D0F69E781D6B8C340046BCD6 /* ContactsVCardItem.swift in Sources */,
|
D0F69E781D6B8C340046BCD6 /* ContactsVCardItem.swift in Sources */,
|
||||||
D0D268691D78865300C422DA /* ChatAvatarNavigationNode.swift in Sources */,
|
D0D268691D78865300C422DA /* ChatAvatarNavigationNode.swift in Sources */,
|
||||||
D0DC35441DE32230000195EB /* ChatInterfaceStateContextQueries.swift in Sources */,
|
D0DC35441DE32230000195EB /* ChatInterfaceStateContextQueries.swift in Sources */,
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
|
||||||
import MtProtoKitDynamic
|
|
||||||
import TelegramCore
|
|
||||||
|
|
||||||
enum AuthorizationCodeResult {
|
|
||||||
case Authorization(Api.auth.Authorization)
|
|
||||||
case Password(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthorizationCodeController: ViewController {
|
|
||||||
let account: UnauthorizedAccount
|
|
||||||
let phone: String
|
|
||||||
let sentCode: Api.auth.SentCode
|
|
||||||
|
|
||||||
var node: AuthorizationCodeControllerNode {
|
|
||||||
return self.displayNode as! AuthorizationCodeControllerNode
|
|
||||||
}
|
|
||||||
|
|
||||||
let signInDisposable = MetaDisposable()
|
|
||||||
let resultPipe = ValuePipe<AuthorizationCodeResult>()
|
|
||||||
var result: Signal<AuthorizationCodeResult, NoError> {
|
|
||||||
return resultPipe.signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
init(account: UnauthorizedAccount, phone: String, sentCode: Api.auth.SentCode) {
|
|
||||||
self.account = account
|
|
||||||
self.phone = phone
|
|
||||||
self.sentCode = sentCode
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.title = "Code"
|
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(AuthorizationCodeController.nextPressed))
|
|
||||||
}
|
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.signInDisposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func loadDisplayNode() {
|
|
||||||
self.displayNode = AuthorizationCodeControllerNode()
|
|
||||||
self.displayNodeDidLoad()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
|
|
||||||
self.node.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func nextPressed() {
|
|
||||||
var phoneCodeHash: String?
|
|
||||||
switch self.sentCode {
|
|
||||||
case let .sentCode(_, _, apiPhoneCodeHash, _, _):
|
|
||||||
phoneCodeHash = apiPhoneCodeHash
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
|
|
||||||
class AuthorizationCodeControllerNode: ASDisplayNode {
|
|
||||||
let codeNode: ASEditableTextNode
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.codeNode = ASEditableTextNode()
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.codeNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0)]
|
|
||||||
self.codeNode.backgroundColor = UIColor.lightGray
|
|
||||||
self.addSubnode(self.codeNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.codeNode.frame = CGRect(origin: CGPoint(x: 4.0, y: navigationBarHeight + 4.0), size: CGSize(width: layout.size.width - 8.0, height: 32.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
|
||||||
import TelegramCore
|
|
||||||
|
|
||||||
public class AuthorizationController: NavigationController {
|
|
||||||
private var account: UnauthorizedAccount!
|
|
||||||
|
|
||||||
private let authorizedAccountValue = Promise<Account>()
|
|
||||||
public var authorizedAccount: Signal<Account, NoError> {
|
|
||||||
return authorizedAccountValue.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(account: UnauthorizedAccount) {
|
|
||||||
self.account = account
|
|
||||||
let phoneController = AuthorizationPhoneController(account: account)
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.pushViewController(phoneController, animated: false)
|
|
||||||
|
|
||||||
let authorizationSequence = phoneController.result |> mapToSignal { (account, sentCode, phone) -> Signal<(Api.auth.Authorization, UnauthorizedAccount), NoError> in
|
|
||||||
return deferred { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.account = account
|
|
||||||
let codeController = AuthorizationCodeController(account: account, phone: phone, sentCode: sentCode)
|
|
||||||
strongSelf.pushViewController(codeController, animated: true)
|
|
||||||
|
|
||||||
return codeController.result |> mapToSignal { result -> Signal<(Api.auth.Authorization, UnauthorizedAccount), NoError> in
|
|
||||||
switch result {
|
|
||||||
case let .Authorization(authorization):
|
|
||||||
return single((authorization, account), NoError.self)
|
|
||||||
case .Password:
|
|
||||||
return deferred { [weak self] () -> Signal<(Api.auth.Authorization, UnauthorizedAccount), NoError> in
|
|
||||||
if let strongSelf = self {
|
|
||||||
let passwordController = AuthorizationPasswordController(account: account)
|
|
||||||
strongSelf.pushViewController(passwordController, animated: true)
|
|
||||||
|
|
||||||
return passwordController.result |> map { ($0, account) }
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
} |> runOn(Queue.mainQueue())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
} |> runOn(Queue.mainQueue())
|
|
||||||
}
|
|
||||||
|
|
||||||
let accountSignal = authorizationSequence |> mapToSignal { [weak self] authorization, account -> Signal<Account, NoError> in
|
|
||||||
if let strongSelf = self {
|
|
||||||
switch authorization {
|
|
||||||
case let .authorization(_, _, user):
|
|
||||||
let user = TelegramUser(user: user)
|
|
||||||
|
|
||||||
return account.postbox.modify { modifier -> AccountState in
|
|
||||||
let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
|
||||||
modifier.setState(state)
|
|
||||||
return state
|
|
||||||
} |> map { state -> Account in
|
|
||||||
let account = Account(id: account.id, basePath: account.basePath, logger: account.logger, testingEnvironment: account.testingEnvironment, postbox: account.postbox, network: account.network, peerId: user.id)
|
|
||||||
account.shouldBeServiceTaskMaster.set(.single(.always))
|
|
||||||
return account
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.authorizedAccountValue.set(accountSignal)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
|
||||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
|
||||||
import MtProtoKitDynamic
|
|
||||||
import TelegramCore
|
|
||||||
|
|
||||||
class AuthorizationPasswordController: ViewController {
|
|
||||||
private var account: UnauthorizedAccount
|
|
||||||
|
|
||||||
private var node: AuthorizationPasswordControllerNode {
|
|
||||||
return self.displayNode as! AuthorizationPasswordControllerNode
|
|
||||||
}
|
|
||||||
|
|
||||||
private let signInDisposable = MetaDisposable()
|
|
||||||
private let resultPipe = ValuePipe<Api.auth.Authorization>()
|
|
||||||
var result: Signal<Api.auth.Authorization, NoError> {
|
|
||||||
return resultPipe.signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
init(account: UnauthorizedAccount) {
|
|
||||||
self.account = account
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.title = "Password"
|
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(AuthorizationPasswordController.nextPressed))
|
|
||||||
}
|
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
signInDisposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func loadDisplayNode() {
|
|
||||||
self.displayNode = AuthorizationPasswordControllerNode()
|
|
||||||
self.displayNodeDidLoad()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
|
|
||||||
self.node.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func nextPressed() {
|
|
||||||
let password = self.node.passwordNode.attributedText?.string ?? ""
|
|
||||||
|
|
||||||
self.signInDisposable.set(verifyPassword(self.account, password: password).start(next: { [weak self] result in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.resultPipe.putNext(result)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
|
|
||||||
class AuthorizationPasswordControllerNode: ASDisplayNode {
|
|
||||||
let passwordNode: ASEditableTextNode
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.passwordNode = ASEditableTextNode()
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.passwordNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0)]
|
|
||||||
self.passwordNode.backgroundColor = UIColor.lightGray
|
|
||||||
self.addSubnode(self.passwordNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.passwordNode.frame = CGRect(origin: CGPoint(x: 4.0, y: navigationBarHeight + 4.0), size: CGSize(width: layout.size.width - 8.0, height: 32.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
|
||||||
import MtProtoKitDynamic
|
|
||||||
import TelegramCore
|
|
||||||
|
|
||||||
class AuthorizationPhoneController: ViewController {
|
|
||||||
private var account: UnauthorizedAccount
|
|
||||||
|
|
||||||
private var node: AuthorizationPhoneControllerNode {
|
|
||||||
return self.displayNode as! AuthorizationPhoneControllerNode
|
|
||||||
}
|
|
||||||
|
|
||||||
private let codeDisposable = MetaDisposable()
|
|
||||||
private let resultPipe = ValuePipe<(UnauthorizedAccount, Api.auth.SentCode, String)>()
|
|
||||||
var result: Signal<(UnauthorizedAccount, Api.auth.SentCode, String), NoError> {
|
|
||||||
return resultPipe.signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
init(account: UnauthorizedAccount) {
|
|
||||||
self.account = account
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.title = "Telegram"
|
|
||||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(AuthorizationPhoneController.nextPressed))
|
|
||||||
}
|
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
codeDisposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func loadDisplayNode() {
|
|
||||||
self.displayNode = AuthorizationPhoneControllerNode()
|
|
||||||
self.displayNodeDidLoad()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
|
||||||
|
|
||||||
self.node.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func nextPressed() {
|
|
||||||
let phone = self.node.phoneNode.attributedText?.string ?? ""
|
|
||||||
let account = self.account
|
|
||||||
let sendCode = Api.functions.auth.sendCode(flags: 0, phoneNumber: phone, currentNumber: nil, apiId: 10840, apiHash: "33c45224029d59cb3ad0c16134215aeb")
|
|
||||||
|
|
||||||
let signal = account.network.request(sendCode)
|
|
||||||
|> map { result in
|
|
||||||
return (result, account)
|
|
||||||
} |> `catch` { error -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in
|
|
||||||
switch error.errorDescription {
|
|
||||||
case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"):
|
|
||||||
let range = error.errorDescription.range(of: "MIGRATE_")!
|
|
||||||
let updatedMasterDatacenterId = Int32(error.errorDescription.substring(from: range.upperBound))!
|
|
||||||
let updatedAccount = account.changedMasterDatacenterId(updatedMasterDatacenterId)
|
|
||||||
return updatedAccount
|
|
||||||
|> mapToSignalPromotingError { updatedAccount -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in
|
|
||||||
return updatedAccount.network.request(sendCode)
|
|
||||||
|> map { sentCode in
|
|
||||||
return (sentCode, updatedAccount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case _:
|
|
||||||
return .fail(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
codeDisposable.set(signal.start(next: { [weak self] (result, account) in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.account = account
|
|
||||||
strongSelf.resultPipe.putNext((account, result, phone))
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
|
|
||||||
class AuthorizationPhoneControllerNode: ASDisplayNode {
|
|
||||||
let phoneNode: ASEditableTextNode
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
self.phoneNode = ASEditableTextNode()
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.phoneNode.typingAttributes = [NSFontAttributeName: Font.regular(17.0)]
|
|
||||||
self.phoneNode.backgroundColor = UIColor.lightGray
|
|
||||||
self.addSubnode(self.phoneNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
||||||
self.phoneNode.frame = CGRect(origin: CGPoint(x: 4.0, y: navigationBarHeight + 4.0), size: CGSize(width: layout.size.width - 8.0, height: 32.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -77,60 +77,26 @@ public final class AuthorizationSequenceController: NavigationController {
|
|||||||
controller.loginWithNumber = { [weak self, weak controller] number in
|
controller.loginWithNumber = { [weak self, weak controller] number in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
controller?.inProgress = true
|
controller?.inProgress = true
|
||||||
let account = strongSelf.account
|
strongSelf.actionDisposable.set((sendAuthorizationCode(account: strongSelf.account, phoneNumber: number, apiId: 10840, apiHash: "33c45224029d59cb3ad0c16134215aeb") |> deliverOnMainQueue).start(next: { [weak self] account in
|
||||||
let sendCode = Api.functions.auth.sendCode(flags: 0, phoneNumber: number, currentNumber: nil, apiId: 10840, apiHash: "33c45224029d59cb3ad0c16134215aeb")
|
|
||||||
|
|
||||||
let signal = account.network.request(sendCode, automaticFloodWait: false)
|
|
||||||
|> map { result in
|
|
||||||
return (result, account)
|
|
||||||
} |> `catch` { error -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in
|
|
||||||
switch error.errorDescription {
|
|
||||||
case Regex("(PHONE_|USER_|NETWORK_)MIGRATE_(\\d+)"):
|
|
||||||
let range = error.errorDescription.range(of: "MIGRATE_")!
|
|
||||||
let updatedMasterDatacenterId = Int32(error.errorDescription.substring(from: range.upperBound))!
|
|
||||||
let updatedAccount = account.changedMasterDatacenterId(updatedMasterDatacenterId)
|
|
||||||
return updatedAccount
|
|
||||||
|> mapToSignalPromotingError { updatedAccount -> Signal<(Api.auth.SentCode, UnauthorizedAccount), MTRpcError> in
|
|
||||||
return updatedAccount.network.request(sendCode, automaticFloodWait: false)
|
|
||||||
|> map { sentCode in
|
|
||||||
return (sentCode, updatedAccount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case _:
|
|
||||||
return .fail(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.actionDisposable.set(signal.start(next: { [weak self] (result, account) in
|
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
controller?.inProgress = false
|
||||||
strongSelf.account = account
|
strongSelf.account = account
|
||||||
let masterDatacenterId = account.masterDatacenterId
|
|
||||||
let _ = (strongSelf.account.postbox.modify { modifier -> Void in
|
|
||||||
switch result {
|
|
||||||
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
|
|
||||||
var parsedNextType: AuthorizationCodeNextType?
|
|
||||||
if let nextType = nextType {
|
|
||||||
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
|
|
||||||
}
|
|
||||||
modifier.setState(UnauthorizedAccountState(masterDatacenterId: masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType)))
|
|
||||||
}
|
|
||||||
}).start()
|
|
||||||
}
|
}
|
||||||
}, error: { error in
|
}, error: { error in
|
||||||
Queue.mainQueue().async {
|
if let controller = controller {
|
||||||
if let controller = controller {
|
controller.inProgress = false
|
||||||
controller.inProgress = false
|
|
||||||
|
let text: String
|
||||||
var text: String?
|
switch error {
|
||||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
case .limitExceeded:
|
||||||
text = "You have requested authorization code too many times. Please try again later."
|
text = "You have requested authorization code too many times. Please try again later."
|
||||||
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
case .invalidPhoneNumber:
|
||||||
text = "The phone number you entered is not valid. Please enter the correct number along with your area code."
|
text = "The phone number you entered is not valid. Please enter the correct number along with your area code."
|
||||||
}
|
case .generic:
|
||||||
if let text = text {
|
text = "An error occurred. Please try again later."
|
||||||
controller.present(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controller.present(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -157,76 +123,22 @@ public final class AuthorizationSequenceController: NavigationController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
controller?.inProgress = true
|
controller?.inProgress = true
|
||||||
|
|
||||||
let account = strongSelf.account
|
strongSelf.actionDisposable.set((authorizeWithCode(account: strongSelf.account, code: code) |> deliverOnMainQueue).start(error: { error in
|
||||||
let masterDatacenterId = account.masterDatacenterId
|
|
||||||
let signal = account.postbox.modify { modifier -> Signal<Void, String> in
|
|
||||||
if let state = modifier.getState() as? UnauthorizedAccountState {
|
|
||||||
switch state.contents {
|
|
||||||
case let .confirmationCodeEntry(number, _, hash, _, _):
|
|
||||||
return account.network.request(Api.functions.auth.signIn(phoneNumber: number, phoneCodeHash: hash, phoneCode: code), automaticFloodWait: false) |> map { authorization in
|
|
||||||
return AuthorizationCodeResult.Authorization(authorization)
|
|
||||||
} |> `catch` { error -> Signal<AuthorizationCodeResult, String> in
|
|
||||||
switch (error.errorCode, error.errorDescription) {
|
|
||||||
case (401, "SESSION_PASSWORD_NEEDED"):
|
|
||||||
return account.network.request(Api.functions.account.getPassword(), automaticFloodWait: false)
|
|
||||||
|> mapError { error -> String in
|
|
||||||
return error.errorDescription
|
|
||||||
}
|
|
||||||
|> mapToSignal { result -> Signal<AuthorizationCodeResult, String> in
|
|
||||||
switch result {
|
|
||||||
case .noPassword:
|
|
||||||
return .fail("NO_PASSWORD")
|
|
||||||
case let .password(_, _, hint, _, _):
|
|
||||||
return .single(.Password(hint))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case _:
|
|
||||||
return .fail(error.errorDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> mapToSignal { result -> Signal<Void, String> in
|
|
||||||
return account.postbox.modify { modifier -> Void in
|
|
||||||
switch result {
|
|
||||||
case let .Password(hint):
|
|
||||||
modifier.setState(UnauthorizedAccountState(masterDatacenterId: masterDatacenterId, contents: .passwordEntry(hint: hint)))
|
|
||||||
case let .Authorization(authorization):
|
|
||||||
switch authorization {
|
|
||||||
case let .authorization(_, _, user):
|
|
||||||
let user = TelegramUser(user: user)
|
|
||||||
let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
|
||||||
modifier.setState(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} |> mapToSignalPromotingError { result -> Signal<Void, String> in
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
|> mapError { _ -> String in
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|> switchToLatest
|
|
||||||
strongSelf.actionDisposable.set(signal.start(error: { error in
|
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
controller.inProgress = false
|
controller.inProgress = false
|
||||||
|
|
||||||
var text: String?
|
let text: String
|
||||||
if error.hasPrefix("FLOOD_WAIT") {
|
switch error {
|
||||||
text = "You have entered invalid code too many times. Please try again later."
|
case .limitExceeded:
|
||||||
} else if error == "CODE_INVALID" {
|
text = "You have entered invalid code too many times. Please try again later."
|
||||||
text = "Invalid code."
|
case .invalidCode:
|
||||||
} else {
|
text = "Invalid code. Please try again."
|
||||||
text = "An error occured.";
|
case .generic:
|
||||||
}
|
text = "An error occured."
|
||||||
if let text = text {
|
|
||||||
controller.present(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controller.present(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -254,40 +166,22 @@ public final class AuthorizationSequenceController: NavigationController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
controller?.inProgress = true
|
controller?.inProgress = true
|
||||||
|
|
||||||
let account = strongSelf.account
|
strongSelf.actionDisposable.set((authorizeWithPassword(account: strongSelf.account, password: password) |> deliverOnMainQueue).start(error: { error in
|
||||||
let signal = verifyPassword(account, password: password)
|
|
||||||
|> `catch` { error -> Signal<Api.auth.Authorization, String> in
|
|
||||||
return .fail(error.errorDescription)
|
|
||||||
}
|
|
||||||
|> mapToSignal { result -> Signal<Void, String> in
|
|
||||||
return account.postbox.modify { modifier -> Void in
|
|
||||||
switch result {
|
|
||||||
case let .authorization(_, _, user):
|
|
||||||
let user = TelegramUser(user: user)
|
|
||||||
let state = AuthorizedAccountState(masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
|
|
||||||
modifier.setState(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> mapToSignalPromotingError { _ -> Signal<Void, String> in
|
|
||||||
return .complete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.actionDisposable.set(signal.start(error: { error in
|
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
controller.inProgress = false
|
controller.inProgress = false
|
||||||
|
|
||||||
var text: String?
|
let text: String
|
||||||
if error.hasPrefix("FLOOD_WAIT") {
|
switch error {
|
||||||
text = "You have entered invalid password too many times. Please try again later."
|
case .limitExceeded:
|
||||||
} else if error == "PASSWORD_HASH_INVALID" {
|
text = "You have entered invalid password too many times. Please try again later."
|
||||||
text = "Invalid password."
|
case .invalidPassword:
|
||||||
} else {
|
text = "Invalid password. Please try again."
|
||||||
text = "An error occured.";
|
case .generic:
|
||||||
}
|
text = "An error occured. Please try again later."
|
||||||
if let text = text {
|
|
||||||
controller.present(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
controller.present(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@@ -314,7 +208,7 @@ public final class AuthorizationSequenceController: NavigationController {
|
|||||||
self.setViewControllers([self.splashController(), self.passwordEntryController(hint: hint)], animated: !self.viewControllers.isEmpty)
|
self.setViewControllers([self.splashController(), self.passwordEntryController(hint: hint)], animated: !self.viewControllers.isEmpty)
|
||||||
}
|
}
|
||||||
} else if let _ = state as? AuthorizedAccountState {
|
} else if let _ = state as? AuthorizedAccountState {
|
||||||
self._authorizedAccount.set(accountWithId(self.account.id, appGroupPath: self.account.appGroupPath, logger: .instance(self.account.logger), testingEnvironment: self.account.testingEnvironment) |> mapToSignal { account -> Signal<Account, NoError> in
|
self._authorizedAccount.set(accountWithId(self.account.id, appGroupPath: self.account.appGroupPath, testingEnvironment: self.account.testingEnvironment) |> mapToSignal { account -> Signal<Account, NoError> in
|
||||||
if case let .right(authorizedAccount) = account {
|
if case let .right(authorizedAccount) = account {
|
||||||
return .single(authorizedAccount)
|
return .single(authorizedAccount)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ final class AuthorizationSequencePasswordEntryController: ViewController {
|
|||||||
self.displayNode = AuthorizationSequencePasswordEntryControllerNode()
|
self.displayNode = AuthorizationSequencePasswordEntryControllerNode()
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self.controllerNode.loginWithCode = { [weak self] _ in
|
||||||
|
self?.nextPressed()
|
||||||
|
}
|
||||||
|
|
||||||
if let hint = self.hint {
|
if let hint = self.hint {
|
||||||
self.controllerNode.updateData(hint: hint)
|
self.controllerNode.updateData(hint: hint)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Foundation
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
|
||||||
final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode {
|
final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
|
||||||
private let navigationBackgroundNode: ASDisplayNode
|
private let navigationBackgroundNode: ASDisplayNode
|
||||||
private let stripeNode: ASDisplayNode
|
private let stripeNode: ASDisplayNode
|
||||||
private let titleNode: ASTextNode
|
private let titleNode: ASTextNode
|
||||||
@@ -67,6 +67,8 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.backgroundColor = UIColor.white
|
self.backgroundColor = UIColor.white
|
||||||
|
|
||||||
|
self.codeField.textField.delegate = self
|
||||||
|
|
||||||
self.addSubnode(self.navigationBackgroundNode)
|
self.addSubnode(self.navigationBackgroundNode)
|
||||||
self.addSubnode(self.stripeNode)
|
self.addSubnode(self.stripeNode)
|
||||||
self.addSubnode(self.codeSeparatorNode)
|
self.addSubnode(self.codeSeparatorNode)
|
||||||
@@ -171,4 +173,9 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode {
|
|||||||
@objc func passwordFieldTextChanged(_ textField: UITextField) {
|
@objc func passwordFieldTextChanged(_ textField: UITextField) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
self.loginWithCode?(self.currentPassword)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func nextPressed() {
|
@objc func nextPressed() {
|
||||||
let (code, number) = self.controllerNode.codeAndNumber
|
let (_, number) = self.controllerNode.codeAndNumber
|
||||||
if code != nil && !number.isEmpty {
|
if !number.isEmpty {
|
||||||
self.loginWithNumber?(self.controllerNode.currentNumber)
|
self.loginWithNumber?(self.controllerNode.currentNumber)
|
||||||
} else {
|
} else {
|
||||||
hapticFeedback.error()
|
hapticFeedback.error()
|
||||||
|
|||||||
384
TelegramUI/ChannelAdminsController.swift
Normal file
384
TelegramUI/ChannelAdminsController.swift
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
private let addMemberPlusIcon = UIImage(bundleImageName: "Peer Info/PeerItemPlusIcon")?.precomposed()
|
||||||
|
|
||||||
|
private struct ChannelAdminsControllerArguments {
|
||||||
|
let account: Account
|
||||||
|
|
||||||
|
let updateCurrentAdministrationType: () -> Void
|
||||||
|
let addAdmin: () -> Void
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ChannelAdminsSection: Int32 {
|
||||||
|
case administration
|
||||||
|
case admins
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ChannelAdminsEntryStableId: Hashable {
|
||||||
|
case index(Int32)
|
||||||
|
case peer(PeerId)
|
||||||
|
|
||||||
|
var hashValue: Int {
|
||||||
|
switch self {
|
||||||
|
case let .index(index):
|
||||||
|
return index.hashValue
|
||||||
|
case let .peer(peerId):
|
||||||
|
return peerId.hashValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ChannelAdminsEntryStableId, rhs: ChannelAdminsEntryStableId) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .index(index):
|
||||||
|
if case .index(index) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .peer(peerId):
|
||||||
|
if case .peer(peerId) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ChannelAdminsEntry: ItemListNodeEntry {
|
||||||
|
case administrationType(CurrentAdministrationType)
|
||||||
|
case administrationInfo(String)
|
||||||
|
|
||||||
|
case adminsHeader(String)
|
||||||
|
case adminPeerItem(Int32, RenderedChannelParticipant, ItemListPeerItemEditing)
|
||||||
|
case addAdmin(Bool)
|
||||||
|
case adminsInfo(String)
|
||||||
|
|
||||||
|
var section: ItemListSectionId {
|
||||||
|
switch self {
|
||||||
|
case .administrationType, .administrationInfo:
|
||||||
|
return ChannelAdminsSection.administration.rawValue
|
||||||
|
case .adminsHeader, .adminPeerItem, .addAdmin, .adminsInfo:
|
||||||
|
return ChannelAdminsSection.admins.rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stableId: ChannelAdminsEntryStableId {
|
||||||
|
switch self {
|
||||||
|
case .administrationType:
|
||||||
|
return .index(0)
|
||||||
|
case .administrationInfo:
|
||||||
|
return .index(1)
|
||||||
|
case .adminsHeader:
|
||||||
|
return .index(2)
|
||||||
|
case .addAdmin:
|
||||||
|
return .index(3)
|
||||||
|
case .adminsInfo:
|
||||||
|
return .index(4)
|
||||||
|
case let .adminPeerItem(_, participant, _):
|
||||||
|
return .peer(participant.peer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ChannelAdminsEntry, rhs: ChannelAdminsEntry) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .administrationType(type):
|
||||||
|
if case .administrationType(type) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .administrationInfo(text):
|
||||||
|
if case .administrationInfo(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .adminsHeader(title):
|
||||||
|
if case .adminsHeader(title) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .adminPeerItem(lhsIndex, lhsParticipant, lhsEditing):
|
||||||
|
if case let .adminPeerItem(rhsIndex, rhsParticipant, rhsEditing) = rhs {
|
||||||
|
if lhsIndex != rhsIndex {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsParticipant != rhsParticipant {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsEditing != rhsEditing {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .adminsInfo(text):
|
||||||
|
if case .adminsInfo(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .addAdmin(editing):
|
||||||
|
if case .addAdmin(editing) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func <(lhs: ChannelAdminsEntry, rhs: ChannelAdminsEntry) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case .administrationType:
|
||||||
|
return true
|
||||||
|
case .administrationInfo:
|
||||||
|
switch rhs {
|
||||||
|
case .administrationType:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .adminsHeader:
|
||||||
|
switch rhs {
|
||||||
|
case .administrationType, .administrationInfo:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case let .adminPeerItem(index, _, _):
|
||||||
|
switch rhs {
|
||||||
|
case .administrationType, .administrationInfo, .adminsHeader:
|
||||||
|
return false
|
||||||
|
case let .adminPeerItem(rhsIndex, _, _):
|
||||||
|
return index < rhsIndex
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .addAdmin:
|
||||||
|
switch rhs {
|
||||||
|
case .administrationType, .administrationInfo, .adminsHeader, .adminPeerItem:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .adminsInfo:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(_ arguments: ChannelAdminsControllerArguments) -> ListViewItem {
|
||||||
|
switch self {
|
||||||
|
case let .administrationType(type):
|
||||||
|
let label: String
|
||||||
|
switch type {
|
||||||
|
case .adminsCanAddMembers:
|
||||||
|
label = "Only Admins"
|
||||||
|
case .everyoneCanAddMembers:
|
||||||
|
label = "All Members"
|
||||||
|
}
|
||||||
|
return ItemListDisclosureItem(title: "Who can add members", label: label, sectionId: self.section, style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case let .administrationInfo(text):
|
||||||
|
return ItemListTextItem(text: text, sectionId: self.section)
|
||||||
|
case let .adminsHeader(title):
|
||||||
|
return ItemListSectionHeaderItem(text: title, sectionId: self.section)
|
||||||
|
case let .adminPeerItem(_, participant, editing):
|
||||||
|
let peerText: String
|
||||||
|
switch participant.participant {
|
||||||
|
case .creator:
|
||||||
|
peerText = "Creator"
|
||||||
|
default:
|
||||||
|
peerText = "Moderator"
|
||||||
|
}
|
||||||
|
return ItemListPeerItem(account: arguments.account, peer: participant.peer, presence: nil, text: .text(peerText), label: nil, editing: editing, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
|
||||||
|
|
||||||
|
}, removePeer: { _ in
|
||||||
|
|
||||||
|
})
|
||||||
|
case let .addAdmin(editing):
|
||||||
|
return ItemListPeerActionItem(icon: addMemberPlusIcon, title: "Add Admin", sectionId: self.section, editing: editing, action: {
|
||||||
|
arguments.addAdmin()
|
||||||
|
})
|
||||||
|
case let .adminsInfo(text):
|
||||||
|
return ItemListTextItem(text: text, sectionId: self.section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CurrentAdministrationType {
|
||||||
|
case everyoneCanAddMembers
|
||||||
|
case adminsCanAddMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ChannelAdminsControllerState: Equatable {
|
||||||
|
let selectedType: CurrentAdministrationType?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.selectedType = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
init(selectedType: CurrentAdministrationType?) {
|
||||||
|
self.selectedType = selectedType
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ChannelAdminsControllerState, rhs: ChannelAdminsControllerState) -> Bool {
|
||||||
|
if lhs.selectedType != rhs.selectedType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedSelectedType(_ selectedType: CurrentAdministrationType?) -> ChannelAdminsControllerState {
|
||||||
|
return ChannelAdminsControllerState(selectedType: selectedType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func ChannelAdminsControllerEntries(view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelAdminsEntry] {
|
||||||
|
var entries: [ChannelAdminsEntry] = []
|
||||||
|
|
||||||
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
||||||
|
var isGroup = false
|
||||||
|
if case let .group(info) = peer.info {
|
||||||
|
isGroup = true
|
||||||
|
|
||||||
|
let selectedType: CurrentAdministrationType
|
||||||
|
if let current = state.selectedType {
|
||||||
|
selectedType = current
|
||||||
|
} else {
|
||||||
|
if info.flags.contains(.everyMemberCanInviteMembers) {
|
||||||
|
selectedType = .everyoneCanAddMembers
|
||||||
|
} else {
|
||||||
|
selectedType = .adminsCanAddMembers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.administrationType(selectedType))
|
||||||
|
let infoText: String
|
||||||
|
switch selectedType {
|
||||||
|
case .everyoneCanAddMembers:
|
||||||
|
infoText = "Everybody can add new members"
|
||||||
|
case .adminsCanAddMembers:
|
||||||
|
infoText = "Only Admins can add new mebers"
|
||||||
|
}
|
||||||
|
entries.append(.administrationInfo(infoText))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let participants = participants {
|
||||||
|
entries.append(.adminsHeader(isGroup ? "GROUP ADMINS" : "CHANNEL ADMINS"))
|
||||||
|
|
||||||
|
var index: Int32 = 0
|
||||||
|
for participant in participants.sorted(by: { lhs, rhs in
|
||||||
|
let lhsInvitedAt: Int32
|
||||||
|
switch lhs.participant {
|
||||||
|
case .creator:
|
||||||
|
lhsInvitedAt = Int32.min
|
||||||
|
case let .editor(_, _, invitedAt):
|
||||||
|
lhsInvitedAt = invitedAt
|
||||||
|
case let .moderator(_, _, invitedAt):
|
||||||
|
lhsInvitedAt = invitedAt
|
||||||
|
case let .member(_, invitedAt):
|
||||||
|
lhsInvitedAt = invitedAt
|
||||||
|
}
|
||||||
|
let rhsInvitedAt: Int32
|
||||||
|
switch rhs.participant {
|
||||||
|
case .creator:
|
||||||
|
rhsInvitedAt = Int32.min
|
||||||
|
case let .editor(_, _, invitedAt):
|
||||||
|
rhsInvitedAt = invitedAt
|
||||||
|
case let .moderator(_, _, invitedAt):
|
||||||
|
rhsInvitedAt = invitedAt
|
||||||
|
case let .member(_, invitedAt):
|
||||||
|
rhsInvitedAt = invitedAt
|
||||||
|
}
|
||||||
|
return lhsInvitedAt < rhsInvitedAt
|
||||||
|
}) {
|
||||||
|
var editable = true
|
||||||
|
if case .creator = participant.participant {
|
||||||
|
editable = false
|
||||||
|
}
|
||||||
|
entries.append(.adminPeerItem(index, participant, ItemListPeerItemEditing(editable: editable, editing: false, revealed: false)))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.addAdmin(false))
|
||||||
|
entries.append(.adminsInfo(isGroup ? "You can add admins to help you manage your group" : "You can add admins to help you manage your channel"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private func effectiveAdministrationType(state: ChannelAdminsControllerState, peer: TelegramChannel) -> CurrentAdministrationType {
|
||||||
|
let selectedType: CurrentAdministrationType
|
||||||
|
if let current = state.selectedType {
|
||||||
|
selectedType = current
|
||||||
|
} else {
|
||||||
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
|
selectedType = .publicChannel
|
||||||
|
} else {
|
||||||
|
selectedType = .privateChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedType
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public func ChannelAdminsController(account: Account, peerId: PeerId) -> ViewController {
|
||||||
|
let statePromise = ValuePromise(ChannelAdminsControllerState(), ignoreRepeated: true)
|
||||||
|
let stateValue = Atomic(value: ChannelAdminsControllerState())
|
||||||
|
let updateState: ((ChannelAdminsControllerState) -> ChannelAdminsControllerState) -> Void = { f in
|
||||||
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
|
let updateAdministrationDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(updateAdministrationDisposable)
|
||||||
|
|
||||||
|
let addAdminDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(addAdminDisposable)
|
||||||
|
|
||||||
|
let arguments = ChannelAdminsControllerArguments(account: account, updateCurrentAdministrationType: {
|
||||||
|
}, addAdmin: {
|
||||||
|
})
|
||||||
|
|
||||||
|
let peerView = account.viewTracker.peerView(peerId)
|
||||||
|
|
||||||
|
let adminsPromise = Promise<[RenderedChannelParticipant]?>(nil)
|
||||||
|
|
||||||
|
let adminsSignal: Signal<[RenderedChannelParticipant]?, NoError> = .single(nil) |> then(channelAdmins(account: account, peerId: peerId) |> map { Optional($0) })
|
||||||
|
|
||||||
|
adminsPromise.set(adminsSignal)
|
||||||
|
|
||||||
|
let signal = combineLatest(statePromise.get(), peerView, adminsPromise.get())
|
||||||
|
|> map { state, view, admins -> (ItemListControllerState, (ItemListNodeState<ChannelAdminsEntry>, ChannelAdminsEntry.ItemGenerationArguments)) in
|
||||||
|
let peer = peerViewMainPeer(view)
|
||||||
|
|
||||||
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
|
if let admins = admins, admins.count > 1 {
|
||||||
|
rightNavigationButton = ItemListNavigationButton(title: "Edit", style: .regular, enabled: true, action: {
|
||||||
|
updateState { state in
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let controllerState = ItemListControllerState(title: "Admins", leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, animateChanges: true)
|
||||||
|
let listState = ItemListNodeState(entries: ChannelAdminsControllerEntries(view: view, state: state, participants: admins), style: .blocks, animateChanges: false)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
} |> afterDisposed {
|
||||||
|
actionsDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = ItemListController(signal)
|
||||||
|
return controller
|
||||||
|
}
|
||||||
2
TelegramUI/ChannelBlacklistController.swift
Normal file
2
TelegramUI/ChannelBlacklistController.swift
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import Foundation
|
/*import Foundation
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
@@ -234,4 +234,4 @@ func channelBroadcastInfoEntries(view: PeerView) -> PeerInfoEntries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PeerInfoEntries(entries: entries, leftNavigationButton: nil, rightNavigationButton: nil)
|
return PeerInfoEntries(entries: entries, leftNavigationButton: nil, rightNavigationButton: nil)
|
||||||
}
|
}*/
|
||||||
|
|||||||
555
TelegramUI/ChannelVisibilityController.swift
Normal file
555
TelegramUI/ChannelVisibilityController.swift
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
private struct ChannelVisibilityControllerArguments {
|
||||||
|
let account: Account
|
||||||
|
|
||||||
|
let updateCurrentType: (CurrentChannelType) -> Void
|
||||||
|
let updatePublicLinkText: (String) -> Void
|
||||||
|
let displayPrivateLinkMenu: () -> Void
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ChannelVisibilitySection: Int32 {
|
||||||
|
case type
|
||||||
|
case link
|
||||||
|
case existingPublicLinks
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||||
|
case typeHeader(String)
|
||||||
|
case typePublic(Bool)
|
||||||
|
case typePrivate(Bool)
|
||||||
|
case typeInfo(String)
|
||||||
|
|
||||||
|
case privateLink(String?)
|
||||||
|
case editablePublicLink(String)
|
||||||
|
case privateLinkInfo(String)
|
||||||
|
case publicLinkInfo(String)
|
||||||
|
case publicLinkStatus(String, AddressNameStatus)
|
||||||
|
|
||||||
|
case existingLinksInfo(String)
|
||||||
|
case existingLinkPeerItem(Int32, Peer, ItemListPeerItemEditing)
|
||||||
|
|
||||||
|
var section: ItemListSectionId {
|
||||||
|
switch self {
|
||||||
|
case .typeHeader, .typePublic, .typePrivate, .typeInfo:
|
||||||
|
return ChannelVisibilitySection.type.rawValue
|
||||||
|
case .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus:
|
||||||
|
return ChannelVisibilitySection.link.rawValue
|
||||||
|
case .existingLinksInfo, .existingLinkPeerItem:
|
||||||
|
return ChannelVisibilitySection.existingPublicLinks.rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stableId: Int32 {
|
||||||
|
switch self {
|
||||||
|
case .typeHeader:
|
||||||
|
return 0
|
||||||
|
case .typePublic:
|
||||||
|
return 1
|
||||||
|
case .typePrivate:
|
||||||
|
return 2
|
||||||
|
case .typeInfo:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
case .privateLink:
|
||||||
|
return 4
|
||||||
|
case .editablePublicLink:
|
||||||
|
return 5
|
||||||
|
case .privateLinkInfo:
|
||||||
|
return 6
|
||||||
|
case .publicLinkStatus:
|
||||||
|
return 7
|
||||||
|
case .publicLinkInfo:
|
||||||
|
return 8
|
||||||
|
|
||||||
|
case .existingLinksInfo:
|
||||||
|
return 9
|
||||||
|
case let .existingLinkPeerItem(index, _, _):
|
||||||
|
return 10 + index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ChannelVisibilityEntry, rhs: ChannelVisibilityEntry) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .typeHeader(title):
|
||||||
|
if case .typeHeader(title) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .typePublic(selected):
|
||||||
|
if case .typePublic(selected) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .typePrivate(selected):
|
||||||
|
if case .typePrivate(selected) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .typeInfo(text):
|
||||||
|
if case .typeInfo(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .privateLink(lhsLink):
|
||||||
|
if case let .privateLink(rhsLink) = rhs, lhsLink == rhsLink {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .editablePublicLink(text):
|
||||||
|
if case .editablePublicLink(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .privateLinkInfo(text):
|
||||||
|
if case .privateLinkInfo(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .publicLinkInfo(text):
|
||||||
|
if case .publicLinkInfo(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .publicLinkStatus(addressName, status):
|
||||||
|
if case .publicLinkStatus(addressName, status) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .existingLinksInfo(text):
|
||||||
|
if case .existingLinksInfo(text) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .existingLinkPeerItem(lhsIndex, lhsPeer, lhsEditing):
|
||||||
|
if case let .existingLinkPeerItem(rhsIndex, rhsPeer, rhsEditing) = rhs {
|
||||||
|
if lhsIndex != rhsIndex {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !lhsPeer.isEqual(rhsPeer) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsEditing != rhsEditing {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func <(lhs: ChannelVisibilityEntry, rhs: ChannelVisibilityEntry) -> Bool {
|
||||||
|
return lhs.stableId < rhs.stableId
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(_ arguments: ChannelVisibilityControllerArguments) -> ListViewItem {
|
||||||
|
switch self {
|
||||||
|
case let .typeHeader(title):
|
||||||
|
return ItemListSectionHeaderItem(text: title, sectionId: self.section)
|
||||||
|
case let .typePublic(selected):
|
||||||
|
return ItemListCheckboxItem(title: "Public", checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||||
|
arguments.updateCurrentType(.publicChannel)
|
||||||
|
})
|
||||||
|
case let .typePrivate(selected):
|
||||||
|
return ItemListCheckboxItem(title: "Private", checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||||
|
arguments.updateCurrentType(.privateChannel)
|
||||||
|
})
|
||||||
|
case let .typeInfo(text):
|
||||||
|
return ItemListTextItem(text: text, sectionId: self.section)
|
||||||
|
case let .privateLink(link):
|
||||||
|
return ItemListActionItem(title: link ?? "Loading", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case let .editablePublicLink(text):
|
||||||
|
return ItemListSingleLineInputItem(title: NSAttributedString(string: "t.me/", textColor: .black), text: text, placeholder: "", sectionId: self.section, textUpdated: { updatedText in
|
||||||
|
arguments.updatePublicLinkText(updatedText)
|
||||||
|
}, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case let .privateLinkInfo(text):
|
||||||
|
return ItemListTextItem(text: text, sectionId: self.section)
|
||||||
|
case let .publicLinkInfo(text):
|
||||||
|
return ItemListTextItem(text: text, sectionId: self.section)
|
||||||
|
case let .publicLinkStatus(addressName, status):
|
||||||
|
var displayActivity = false
|
||||||
|
let text: NSAttributedString
|
||||||
|
switch status {
|
||||||
|
case .available:
|
||||||
|
text = NSAttributedString(string: "\(addressName) is available.", textColor: UIColor(0x26972c))
|
||||||
|
case .checking:
|
||||||
|
text = NSAttributedString(string: "Checking name...", textColor: .gray)
|
||||||
|
displayActivity = true
|
||||||
|
case let .invalid(reason):
|
||||||
|
switch reason {
|
||||||
|
case .alreadyTaken:
|
||||||
|
text = NSAttributedString(string: "\(addressName) is already taken.", textColor: .red)
|
||||||
|
case .digitStart:
|
||||||
|
text = NSAttributedString(string: "Names can't start with a digit.", textColor: UIColor(0xcf3030))
|
||||||
|
case .invalid, .underscopeEnd, .underscopeStart:
|
||||||
|
text = NSAttributedString(string: "Sorry, this name is invalid.", textColor: UIColor(0xcf3030))
|
||||||
|
case .short:
|
||||||
|
text = NSAttributedString(string: "Names must have at least 5 characters.", textColor: UIColor(0xcf3030))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ItemListActivityTextItem(displayActivity: displayActivity, text: text, sectionId: self.section)
|
||||||
|
case let .existingLinksInfo(text):
|
||||||
|
return ItemListTextItem(text: text, sectionId: self.section)
|
||||||
|
case let .existingLinkPeerItem(_, peer, editing):
|
||||||
|
return ItemListPeerItem(account: arguments.account, peer: peer, presence: nil, text: .activity, label: nil, editing: editing, enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in
|
||||||
|
|
||||||
|
}, removePeer: { _ in
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CurrentChannelType {
|
||||||
|
case publicChannel
|
||||||
|
case privateChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum AddressNameStatus: Equatable {
|
||||||
|
case available
|
||||||
|
case checking
|
||||||
|
case invalid(UsernameAvailabilityError)
|
||||||
|
|
||||||
|
static func ==(lhs: AddressNameStatus, rhs: AddressNameStatus) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case .available:
|
||||||
|
if case .available = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .checking:
|
||||||
|
if case .checking = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .invalid(reason):
|
||||||
|
if case .invalid(reason) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ChannelVisibilityControllerState: Equatable {
|
||||||
|
let selectedType: CurrentChannelType?
|
||||||
|
let editingPublicLinkText: String?
|
||||||
|
let addressNameStatus: AddressNameStatus?
|
||||||
|
let updatingAddressName: Bool
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.selectedType = nil
|
||||||
|
self.editingPublicLinkText = nil
|
||||||
|
self.addressNameStatus = nil
|
||||||
|
self.updatingAddressName = false
|
||||||
|
}
|
||||||
|
|
||||||
|
init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameStatus: AddressNameStatus?, updatingAddressName: Bool) {
|
||||||
|
self.selectedType = selectedType
|
||||||
|
self.editingPublicLinkText = editingPublicLinkText
|
||||||
|
self.addressNameStatus = addressNameStatus
|
||||||
|
self.updatingAddressName = updatingAddressName
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool {
|
||||||
|
if lhs.selectedType != rhs.selectedType {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.editingPublicLinkText != rhs.editingPublicLinkText {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.addressNameStatus != rhs.addressNameStatus {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.updatingAddressName != rhs.updatingAddressName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState {
|
||||||
|
return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameStatus: self.addressNameStatus, updatingAddressName: self.updatingAddressName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedEditingPublicLinkText(_ editingPublicLinkText: String?) -> ChannelVisibilityControllerState {
|
||||||
|
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameStatus: self.addressNameStatus, updatingAddressName: self.updatingAddressName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedAddressNameStatus(_ addressNameStatus: AddressNameStatus?) -> ChannelVisibilityControllerState {
|
||||||
|
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameStatus: addressNameStatus, updatingAddressName: self.updatingAddressName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedUpdatingAddressName(_ updatingAddressName: Bool) -> ChannelVisibilityControllerState {
|
||||||
|
return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameStatus: self.addressNameStatus, updatingAddressName: updatingAddressName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func channelVisibilityControllerEntries(view: PeerView, state: ChannelVisibilityControllerState) -> [ChannelVisibilityEntry] {
|
||||||
|
var entries: [ChannelVisibilityEntry] = []
|
||||||
|
|
||||||
|
if let peer = view.peers[view.peerId] as? TelegramChannel {
|
||||||
|
var isGroup = false
|
||||||
|
if case .group = peer.info {
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedType: CurrentChannelType
|
||||||
|
if let current = state.selectedType {
|
||||||
|
selectedType = current
|
||||||
|
} else {
|
||||||
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
|
selectedType = .publicChannel
|
||||||
|
} else {
|
||||||
|
selectedType = .privateChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentAddressName: String
|
||||||
|
if let current = state.editingPublicLinkText {
|
||||||
|
currentAddressName = current
|
||||||
|
} else {
|
||||||
|
if let addressName = peer.addressName {
|
||||||
|
currentAddressName = addressName
|
||||||
|
} else {
|
||||||
|
currentAddressName = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.typeHeader(isGroup ? "GROUP TYPE" : "CHANNEL TYPE"))
|
||||||
|
entries.append(.typePublic(selectedType == .publicChannel))
|
||||||
|
entries.append(.typePrivate(selectedType == .privateChannel))
|
||||||
|
|
||||||
|
switch selectedType {
|
||||||
|
case .publicChannel:
|
||||||
|
if isGroup {
|
||||||
|
entries.append(.typeInfo("Public groups can be found in search, chat history is available to everyone and anyone can join."))
|
||||||
|
} else {
|
||||||
|
entries.append(.typeInfo("Public channels can be found in search and anyone can join."))
|
||||||
|
}
|
||||||
|
case .privateChannel:
|
||||||
|
if isGroup {
|
||||||
|
entries.append(.typeInfo("Private groups can only be joined if you were invited of have an invite link."))
|
||||||
|
} else {
|
||||||
|
entries.append(.typeInfo("Private channels can only be joined if you were invited of have an invite link."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch selectedType {
|
||||||
|
case .publicChannel:
|
||||||
|
entries.append(.editablePublicLink(currentAddressName))
|
||||||
|
if let status = state.addressNameStatus {
|
||||||
|
entries.append(.publicLinkStatus(currentAddressName, status))
|
||||||
|
}
|
||||||
|
entries.append(.publicLinkInfo("People can share this link with others and find your group using Telegram search."))
|
||||||
|
case .privateChannel:
|
||||||
|
entries.append(.privateLink((view.cachedData as? CachedChannelData)?.exportedInvitation?.link))
|
||||||
|
entries.append(.publicLinkInfo("People can join your group by following this link. You can revoke the link at any time."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
private func effectiveChannelType(state: ChannelVisibilityControllerState, peer: TelegramChannel) -> CurrentChannelType {
|
||||||
|
let selectedType: CurrentChannelType
|
||||||
|
if let current = state.selectedType {
|
||||||
|
selectedType = current
|
||||||
|
} else {
|
||||||
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
|
selectedType = .publicChannel
|
||||||
|
} else {
|
||||||
|
selectedType = .privateChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return selectedType
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updatedAddressName(state: ChannelVisibilityControllerState, peer: TelegramChannel) -> String? {
|
||||||
|
let selectedType = effectiveChannelType(state: state, peer: peer)
|
||||||
|
|
||||||
|
let currentAddressName: String
|
||||||
|
|
||||||
|
switch selectedType {
|
||||||
|
case .privateChannel:
|
||||||
|
currentAddressName = ""
|
||||||
|
case .publicChannel:
|
||||||
|
if let current = state.editingPublicLinkText {
|
||||||
|
currentAddressName = current
|
||||||
|
} else {
|
||||||
|
if let addressName = peer.addressName {
|
||||||
|
currentAddressName = addressName
|
||||||
|
} else {
|
||||||
|
currentAddressName = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !currentAddressName.isEmpty {
|
||||||
|
if currentAddressName != peer.addressName {
|
||||||
|
return currentAddressName
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else if peer.addressName != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func channelVisibilityController(account: Account, peerId: PeerId) -> ViewController {
|
||||||
|
let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true)
|
||||||
|
let stateValue = Atomic(value: ChannelVisibilityControllerState())
|
||||||
|
let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in
|
||||||
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
var dismissImpl: (() -> Void)?
|
||||||
|
|
||||||
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
|
let checkAddressNameDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(checkAddressNameDisposable)
|
||||||
|
|
||||||
|
let updateAddressNameDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(updateAddressNameDisposable)
|
||||||
|
|
||||||
|
let arguments = ChannelVisibilityControllerArguments(account: account, updateCurrentType: { type in
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedSelectedType(type)
|
||||||
|
}
|
||||||
|
}, updatePublicLinkText: { text in
|
||||||
|
if text.isEmpty {
|
||||||
|
checkAddressNameDisposable.set(nil)
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedEditingPublicLinkText(text).withUpdatedAddressNameStatus(nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedEditingPublicLinkText(text)
|
||||||
|
}
|
||||||
|
checkAddressNameDisposable.set((addressNameAvailability(account: account, domain: .peer(peerId), def: nil, current: text)
|
||||||
|
|> deliverOnMainQueue).start(next: { result in
|
||||||
|
updateState { state in
|
||||||
|
let status: AddressNameStatus
|
||||||
|
switch result {
|
||||||
|
case let .fail(_, error):
|
||||||
|
status = .invalid(error)
|
||||||
|
case .none:
|
||||||
|
status = .available
|
||||||
|
case .success:
|
||||||
|
status = .available
|
||||||
|
case .progress:
|
||||||
|
status = .checking
|
||||||
|
}
|
||||||
|
return state.withUpdatedAddressNameStatus(status)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}, displayPrivateLinkMenu: {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
let peerView = account.viewTracker.peerView(peerId)
|
||||||
|
|
||||||
|
let signal = combineLatest(statePromise.get(), peerView)
|
||||||
|
|> map { state, view -> (ItemListControllerState, (ItemListNodeState<ChannelVisibilityEntry>, ChannelVisibilityEntry.ItemGenerationArguments)) in
|
||||||
|
let peer = peerViewMainPeer(view)
|
||||||
|
|
||||||
|
var rightNavigationButton: ItemListNavigationButton?
|
||||||
|
if let peer = peer as? TelegramChannel {
|
||||||
|
var doneEnabled = true
|
||||||
|
if let selectedType = state.selectedType {
|
||||||
|
switch selectedType {
|
||||||
|
case .privateChannel:
|
||||||
|
break
|
||||||
|
case .publicChannel:
|
||||||
|
if let addressNameStatus = state.addressNameStatus {
|
||||||
|
switch addressNameStatus {
|
||||||
|
case .available:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
doneEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rightNavigationButton = ItemListNavigationButton(title: "Done", style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
|
||||||
|
var updatedAddressNameValue: String?
|
||||||
|
updateState { state in
|
||||||
|
updatedAddressNameValue = updatedAddressName(state: state, peer: peer)
|
||||||
|
|
||||||
|
if updatedAddressNameValue != nil {
|
||||||
|
return state.withUpdatedUpdatingAddressName(true)
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let updatedAddressNameValue = updatedAddressNameValue {
|
||||||
|
updateAddressNameDisposable.set((updatePeerAddressName(account: account, peerId: peerId, username: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue)
|
||||||
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedUpdatingAddressName(false)
|
||||||
|
}
|
||||||
|
}, completed: {
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedUpdatingAddressName(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissImpl?()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dismissImpl?()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var isGroup = false
|
||||||
|
if let peer = peer as? TelegramChannel {
|
||||||
|
if case .group = peer.info {
|
||||||
|
isGroup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let leftNavigationButton = ItemListNavigationButton(title: "Cancel", style: .regular, enabled: true, action: {
|
||||||
|
dismissImpl?()
|
||||||
|
})
|
||||||
|
|
||||||
|
let controllerState = ItemListControllerState(title: isGroup ? "Group Type" : "Channel Link", leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, animateChanges: false)
|
||||||
|
let listState = ItemListNodeState(entries: channelVisibilityControllerEntries(view: view, state: state), style: .blocks, animateChanges: false)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
} |> afterDisposed {
|
||||||
|
actionsDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = ItemListController(signal)
|
||||||
|
dismissImpl = { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
@@ -133,6 +133,7 @@ public class ChatController: TelegramController {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
strongSelf.present(gallery, in: .window, with: GalleryControllerPresentationArguments(transitionArguments: { [weak self] messageId, media in
|
strongSelf.present(gallery, in: .window, with: GalleryControllerPresentationArguments(transitionArguments: { [weak self] messageId, media in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var transitionNode: ASDisplayNode?
|
var transitionNode: ASDisplayNode?
|
||||||
@@ -1029,7 +1030,7 @@ public class ChatController: TelegramController {
|
|||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
self.chatDisplayNode.historyNode.preloadPages = true
|
self.chatDisplayNode.historyNode.preloadPages = true
|
||||||
self.chatDisplayNode.historyNode.canReadHistory.set(true)
|
self.chatDisplayNode.historyNode.canReadHistory.set((self.account.applicationContext as! TelegramApplicationContext).applicationInForeground)
|
||||||
|
|
||||||
self.chatDisplayNode.loadInputPanels()
|
self.chatDisplayNode.loadInputPanels()
|
||||||
|
|
||||||
@@ -1041,11 +1042,11 @@ public class ChatController: TelegramController {
|
|||||||
override public func viewWillDisappear(_ animated: Bool) {
|
override public func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
|
|
||||||
self.chatDisplayNode.historyNode.canReadHistory.set(false)
|
self.chatDisplayNode.historyNode.canReadHistory.set(.single(false))
|
||||||
let peerId = self.peerId
|
let peerId = self.peerId
|
||||||
let timestamp = Int32(Date().timeIntervalSince1970)
|
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||||
let interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp)
|
let interfaceState = self.presentationInterfaceState.interfaceState.withUpdatedTimestamp(timestamp)
|
||||||
self.account.postbox.modify({ modifier -> Void in
|
let _ = self.account.postbox.modify({ modifier -> Void in
|
||||||
modifier.updatePeerChatInterfaceState(peerId, update: { _ in
|
modifier.updatePeerChatInterfaceState(peerId, update: { _ in
|
||||||
return interfaceState
|
return interfaceState
|
||||||
})
|
})
|
||||||
@@ -1213,9 +1214,10 @@ public class ChatController: TelegramController {
|
|||||||
self.navigationActionDisposable.set((self.peerView.get()
|
self.navigationActionDisposable.set((self.peerView.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
|> deliverOnMainQueue).start(next: { [weak self] peerView in
|
||||||
if let strongSelf = self, let _ = peerView.peers[peerView.peerId] {
|
if let strongSelf = self, let peer = peerView.peers[peerView.peerId] {
|
||||||
let chatInfoController = PeerInfoController(account: strongSelf.account, peerId: peerView.peerId)
|
if let infoController = peerInfoController(account: strongSelf.account, peer: peer) {
|
||||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(chatInfoController)
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let maxVisibleIncomingMessageIndex = ValuePromise<MessageIndex>(ignoreRepeated: true)
|
private let maxVisibleIncomingMessageIndex = ValuePromise<MessageIndex>(ignoreRepeated: true)
|
||||||
let canReadHistory = ValuePromise<Bool>()
|
let canReadHistory = Promise<Bool>()
|
||||||
|
|
||||||
private let _chatHistoryLocation = ValuePromise<ChatHistoryLocation>()
|
private let _chatHistoryLocation = ValuePromise<ChatHistoryLocation>()
|
||||||
private var chatHistoryLocation: Signal<ChatHistoryLocation, NoError> {
|
private var chatHistoryLocation: Signal<ChatHistoryLocation, NoError> {
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ public protocol ChatHistoryNode: class {
|
|||||||
|
|
||||||
func messageInCurrentHistoryView(_ id: MessageId) -> Message?
|
func messageInCurrentHistoryView(_ id: MessageId) -> Message?
|
||||||
func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets)
|
func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets)
|
||||||
func forEachItemNode(_ f: @noescape(ASDisplayNode) -> Void)
|
func forEachItemNode(_ f: (ASDisplayNode) -> Void)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class ChatMessageActionItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (size, apply) = labelLayout(attributedString, nil, 1, .end, CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (size, apply) = labelLayout(attributedString, nil, 1, .end, CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
let backgroundSize = CGSize(width: size.size.width + 8.0 + 8.0, height: 20.0)
|
let backgroundSize = CGSize(width: size.size.width + 8.0 + 8.0, height: 20.0)
|
||||||
var layoutInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0)
|
var layoutInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import SwiftSignalKit
|
|||||||
|
|
||||||
private let dateFont = UIFont.italicSystemFont(ofSize: 11.0)
|
private let dateFont = UIFont.italicSystemFont(ofSize: 11.0)
|
||||||
|
|
||||||
private func generateCheckImage(partial: Bool) -> UIImage? {
|
private func generateCheckImage(partial: Bool, color: UIColor) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 11.0, height: 9.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 11.0, height: 9.0), contextGenerator: { size, context in
|
||||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
context.scaleBy(x: 1.0, y: -1.0)
|
context.scaleBy(x: 1.0, y: -1.0)
|
||||||
@@ -14,7 +14,7 @@ private func generateCheckImage(partial: Bool) -> UIImage? {
|
|||||||
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.scaleBy(x: 0.5, y: 0.5)
|
context.scaleBy(x: 0.5, y: 0.5)
|
||||||
context.setStrokeColor(UIColor(0x19C700).cgColor)
|
context.setStrokeColor(color.cgColor)
|
||||||
context.setLineWidth(2.5)
|
context.setLineWidth(2.5)
|
||||||
if partial {
|
if partial {
|
||||||
let _ = try? drawSvgPath(context, path: "M1,14.5 L2.5,16 L16.4985125,1 ")
|
let _ = try? drawSvgPath(context, path: "M1,14.5 L2.5,16 L16.4985125,1 ")
|
||||||
@@ -25,11 +25,11 @@ private func generateCheckImage(partial: Bool) -> UIImage? {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateClockFrameImage() -> UIImage? {
|
private func generateClockFrameImage(color: UIColor) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.setStrokeColor(UIColor(0x42b649).cgColor)
|
context.setStrokeColor(color.cgColor)
|
||||||
context.setFillColor(UIColor(0x42b649).cgColor)
|
context.setFillColor(color.cgColor)
|
||||||
let strokeWidth: CGFloat = 1.0
|
let strokeWidth: CGFloat = 1.0
|
||||||
context.setLineWidth(strokeWidth)
|
context.setLineWidth(strokeWidth)
|
||||||
context.strokeEllipse(in: CGRect(x: strokeWidth / 2.0, y: strokeWidth / 2.0, width: size.width - strokeWidth, height: size.height - strokeWidth))
|
context.strokeEllipse(in: CGRect(x: strokeWidth / 2.0, y: strokeWidth / 2.0, width: size.width - strokeWidth, height: size.height - strokeWidth))
|
||||||
@@ -37,10 +37,10 @@ private func generateClockFrameImage() -> UIImage? {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateClockMinImage() -> UIImage? {
|
private func generateClockMinImage(color: UIColor) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.setFillColor(UIColor(0x42b649).cgColor)
|
context.setFillColor(color.cgColor)
|
||||||
let strokeWidth: CGFloat = 1.0
|
let strokeWidth: CGFloat = 1.0
|
||||||
context.fill(CGRect(x: (11.0 - strokeWidth) / 2.0, y: (11.0 - strokeWidth) / 2.0, width: 11.0 / 2.0 - strokeWidth, height: strokeWidth))
|
context.fill(CGRect(x: (11.0 - strokeWidth) / 2.0, y: (11.0 - strokeWidth) / 2.0, width: 11.0 / 2.0 - strokeWidth, height: strokeWidth))
|
||||||
})
|
})
|
||||||
@@ -61,14 +61,21 @@ private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
|
|||||||
layer.add(basicAnimation, forKey: "clockFrameAnimation")
|
layer.add(basicAnimation, forKey: "clockFrameAnimation")
|
||||||
}
|
}
|
||||||
|
|
||||||
private let checkFullImage = generateCheckImage(partial: false)
|
private let checkBubbleFullImage = generateCheckImage(partial: false, color: UIColor(0x19C700))
|
||||||
private let checkPartialImage = generateCheckImage(partial: true)
|
private let checkBubblePartialImage = generateCheckImage(partial: true, color: UIColor(0x19C700))
|
||||||
|
|
||||||
|
private let checkMediaFullImage = generateCheckImage(partial: false, color: .white)
|
||||||
|
private let checkMediaPartialImage = generateCheckImage(partial: true, color: .white)
|
||||||
|
|
||||||
private let incomingDateColor = UIColor(0x525252, 0.6)
|
private let incomingDateColor = UIColor(0x525252, 0.6)
|
||||||
private let outgoingDateColor = UIColor(0x008c09, 0.8)
|
private let outgoingDateColor = UIColor(0x008c09, 0.8)
|
||||||
|
|
||||||
private let clockFrameImage = generateClockFrameImage()
|
private let imageBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: UIColor(white: 0.0, alpha: 0.5))
|
||||||
private let clockMinImage = generateClockMinImage()
|
|
||||||
|
private let clockBubbleFrameImage = generateClockFrameImage(color: UIColor(0x42b649))
|
||||||
|
private let clockBubbleMinImage = generateClockMinImage(color: UIColor(0x42b649))
|
||||||
|
private let clockMediaFrameImage = generateClockFrameImage(color: .white)
|
||||||
|
private let clockMediaMinImage = generateClockMinImage(color: .white)
|
||||||
|
|
||||||
enum ChatMessageDateAndStatusOutgoingType {
|
enum ChatMessageDateAndStatusOutgoingType {
|
||||||
case Sent(read: Bool)
|
case Sent(read: Bool)
|
||||||
@@ -79,9 +86,12 @@ enum ChatMessageDateAndStatusOutgoingType {
|
|||||||
enum ChatMessageDateAndStatusType {
|
enum ChatMessageDateAndStatusType {
|
||||||
case BubbleIncoming
|
case BubbleIncoming
|
||||||
case BubbleOutgoing(ChatMessageDateAndStatusOutgoingType)
|
case BubbleOutgoing(ChatMessageDateAndStatusOutgoingType)
|
||||||
|
case ImageIncoming
|
||||||
|
case ImageOutgoing(ChatMessageDateAndStatusOutgoingType)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
||||||
|
private var backgroundNode: ASImageNode?
|
||||||
private var checkSentNode: ASImageNode?
|
private var checkSentNode: ASImageNode?
|
||||||
private var checkReadNode: ASImageNode?
|
private var checkReadNode: ASImageNode?
|
||||||
private var clockFrameNode: ASImageNode?
|
private var clockFrameNode: ASImageNode?
|
||||||
@@ -106,21 +116,56 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
var clockFrameNode = self.clockFrameNode
|
var clockFrameNode = self.clockFrameNode
|
||||||
var clockMinNode = self.clockMinNode
|
var clockMinNode = self.clockMinNode
|
||||||
|
|
||||||
|
var currentBackgroundNode = self.backgroundNode
|
||||||
|
|
||||||
return { dateText, type, constrainedSize in
|
return { dateText, type, constrainedSize in
|
||||||
let dateColor: UIColor
|
let dateColor: UIColor
|
||||||
|
var backgroundImage: UIImage?
|
||||||
var outgoingStatus: ChatMessageDateAndStatusOutgoingType?
|
var outgoingStatus: ChatMessageDateAndStatusOutgoingType?
|
||||||
|
let leftInset: CGFloat
|
||||||
|
|
||||||
|
let loadedCheckFullImage: UIImage?
|
||||||
|
let loadedCheckPartialImage: UIImage?
|
||||||
|
let clockFrameImage: UIImage?
|
||||||
|
let clockMinImage: UIImage?
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
case .BubbleIncoming:
|
case .BubbleIncoming:
|
||||||
dateColor = incomingDateColor
|
dateColor = incomingDateColor
|
||||||
|
leftInset = 10.0
|
||||||
|
loadedCheckFullImage = checkBubbleFullImage
|
||||||
|
loadedCheckPartialImage = checkBubblePartialImage
|
||||||
|
clockFrameImage = clockBubbleFrameImage
|
||||||
|
clockMinImage = clockBubbleMinImage
|
||||||
case let .BubbleOutgoing(status):
|
case let .BubbleOutgoing(status):
|
||||||
dateColor = outgoingDateColor
|
dateColor = outgoingDateColor
|
||||||
outgoingStatus = status
|
outgoingStatus = status
|
||||||
|
leftInset = 10.0
|
||||||
|
loadedCheckFullImage = checkBubbleFullImage
|
||||||
|
loadedCheckPartialImage = checkBubblePartialImage
|
||||||
|
clockFrameImage = clockBubbleFrameImage
|
||||||
|
clockMinImage = clockBubbleMinImage
|
||||||
|
case .ImageIncoming:
|
||||||
|
dateColor = .white
|
||||||
|
backgroundImage = imageBackground
|
||||||
|
leftInset = 0.0
|
||||||
|
loadedCheckFullImage = checkMediaFullImage
|
||||||
|
loadedCheckPartialImage = checkMediaPartialImage
|
||||||
|
clockFrameImage = clockMediaFrameImage
|
||||||
|
clockMinImage = clockMediaMinImage
|
||||||
|
case let .ImageOutgoing(status):
|
||||||
|
dateColor = .white
|
||||||
|
outgoingStatus = status
|
||||||
|
backgroundImage = imageBackground
|
||||||
|
leftInset = 0.0
|
||||||
|
loadedCheckFullImage = checkMediaFullImage
|
||||||
|
loadedCheckPartialImage = checkMediaPartialImage
|
||||||
|
clockFrameImage = clockMediaFrameImage
|
||||||
|
clockMinImage = clockMediaMinImage
|
||||||
}
|
}
|
||||||
|
|
||||||
let (date, dateApply) = dateLayout(NSAttributedString(string: dateText, font: dateFont, textColor: dateColor), nil, 1, .end, constrainedSize, nil)
|
let (date, dateApply) = dateLayout(NSAttributedString(string: dateText, font: dateFont, textColor: dateColor), nil, 1, .end, constrainedSize, nil)
|
||||||
|
|
||||||
let leftInset: CGFloat = 10.0
|
|
||||||
|
|
||||||
let statusWidth: CGFloat
|
let statusWidth: CGFloat
|
||||||
|
|
||||||
var checkSentFrame: CGRect?
|
var checkSentFrame: CGRect?
|
||||||
@@ -128,9 +173,6 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
|
|
||||||
var clockPosition = CGPoint()
|
var clockPosition = CGPoint()
|
||||||
|
|
||||||
let loadedCheckFullImage = checkFullImage
|
|
||||||
let loadedCheckPartialImage = checkPartialImage
|
|
||||||
|
|
||||||
if let outgoingStatus = outgoingStatus {
|
if let outgoingStatus = outgoingStatus {
|
||||||
switch outgoingStatus {
|
switch outgoingStatus {
|
||||||
case .Sending:
|
case .Sending:
|
||||||
@@ -188,7 +230,7 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
clockFrameNode = nil
|
clockFrameNode = nil
|
||||||
clockMinNode = nil
|
clockMinNode = nil
|
||||||
|
|
||||||
let checkSize = checkFullImage!.size
|
let checkSize = loadedCheckFullImage!.size
|
||||||
|
|
||||||
if read {
|
if read {
|
||||||
checkReadFrame = CGRect(origin: CGPoint(x: leftInset + date.size.width + 5.0 + statusWidth - checkSize.width, y: 3.0), size: checkSize)
|
checkReadFrame = CGRect(origin: CGPoint(x: leftInset + date.size.width + 5.0 + statusWidth - checkSize.width, y: 3.0), size: checkSize)
|
||||||
@@ -211,18 +253,49 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
clockMinNode = nil
|
clockMinNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return (CGSize(width: leftInset + date.size.width + statusWidth, height: date.size.height), { [weak self] animated in
|
var backgroundInsets = UIEdgeInsets()
|
||||||
|
|
||||||
|
if let backgroundImage = backgroundImage {
|
||||||
|
if currentBackgroundNode == nil {
|
||||||
|
let backgroundNode = ASImageNode()
|
||||||
|
backgroundNode.isLayerBacked = true
|
||||||
|
backgroundNode.displayWithoutProcessing = true
|
||||||
|
backgroundNode.displaysAsynchronously = false
|
||||||
|
backgroundNode.image = backgroundImage
|
||||||
|
currentBackgroundNode = backgroundNode
|
||||||
|
}
|
||||||
|
backgroundInsets = UIEdgeInsets(top: 2.0, left: 7.0, bottom: 2.0, right: 7.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let layoutSize = CGSize(width: leftInset + date.size.width + statusWidth + backgroundInsets.left + backgroundInsets.right, height: date.size.height + backgroundInsets.top + backgroundInsets.bottom)
|
||||||
|
|
||||||
|
return (layoutSize, { [weak self] animated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
if backgroundImage != nil {
|
||||||
|
if let currentBackgroundNode = currentBackgroundNode {
|
||||||
|
if currentBackgroundNode.supernode == nil {
|
||||||
|
strongSelf.backgroundNode = currentBackgroundNode
|
||||||
|
strongSelf.insertSubnode(currentBackgroundNode, at: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.backgroundNode?.frame = CGRect(origin: CGPoint(), size: layoutSize)
|
||||||
|
} else {
|
||||||
|
if let backgroundNode = strongSelf.backgroundNode {
|
||||||
|
backgroundNode.removeFromSupernode()
|
||||||
|
strongSelf.backgroundNode = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _ = dateApply()
|
let _ = dateApply()
|
||||||
|
|
||||||
strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: date.size)
|
strongSelf.dateNode.frame = CGRect(origin: CGPoint(x: leftInset + backgroundInsets.left, y: backgroundInsets.top), size: date.size)
|
||||||
|
|
||||||
if let clockFrameNode = clockFrameNode {
|
if let clockFrameNode = clockFrameNode {
|
||||||
if strongSelf.clockFrameNode == nil {
|
if strongSelf.clockFrameNode == nil {
|
||||||
strongSelf.clockFrameNode = clockFrameNode
|
strongSelf.clockFrameNode = clockFrameNode
|
||||||
strongSelf.addSubnode(clockFrameNode)
|
strongSelf.addSubnode(clockFrameNode)
|
||||||
}
|
}
|
||||||
clockFrameNode.position = clockPosition
|
clockFrameNode.position = CGPoint(x: backgroundInsets.left + clockPosition.x, y: backgroundInsets.top + clockPosition.y)
|
||||||
if let clockFrameNode = strongSelf.clockFrameNode {
|
if let clockFrameNode = strongSelf.clockFrameNode {
|
||||||
maybeAddRotationAnimation(clockFrameNode.layer, duration: 6.0)
|
maybeAddRotationAnimation(clockFrameNode.layer, duration: 6.0)
|
||||||
}
|
}
|
||||||
@@ -236,7 +309,7 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
strongSelf.clockMinNode = clockMinNode
|
strongSelf.clockMinNode = clockMinNode
|
||||||
strongSelf.addSubnode(clockMinNode)
|
strongSelf.addSubnode(clockMinNode)
|
||||||
}
|
}
|
||||||
clockMinNode.position = clockPosition
|
clockMinNode.position = CGPoint(x: backgroundInsets.left + clockPosition.x, y: backgroundInsets.top + clockPosition.y)
|
||||||
if let clockMinNode = strongSelf.clockMinNode {
|
if let clockMinNode = strongSelf.clockMinNode {
|
||||||
maybeAddRotationAnimation(clockMinNode.layer, duration: 1.0)
|
maybeAddRotationAnimation(clockMinNode.layer, duration: 1.0)
|
||||||
}
|
}
|
||||||
@@ -259,7 +332,7 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
animateSentNode = animated
|
animateSentNode = animated
|
||||||
}
|
}
|
||||||
checkSentNode.isHidden = false
|
checkSentNode.isHidden = false
|
||||||
checkSentNode.frame = checkSentFrame
|
checkSentNode.frame = checkSentFrame.offsetBy(dx: backgroundInsets.left, dy: backgroundInsets.top)
|
||||||
} else {
|
} else {
|
||||||
checkSentNode.isHidden = true
|
checkSentNode.isHidden = true
|
||||||
}
|
}
|
||||||
@@ -277,7 +350,7 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
animateReadNode = animated
|
animateReadNode = animated
|
||||||
}
|
}
|
||||||
checkReadNode.isHidden = false
|
checkReadNode.isHidden = false
|
||||||
checkReadNode.frame = checkReadFrame
|
checkReadNode.frame = checkReadFrame.offsetBy(dx: backgroundInsets.left, dy: backgroundInsets.top)
|
||||||
} else {
|
} else {
|
||||||
checkReadNode.isHidden = true
|
checkReadNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ struct ChatMessageItemTextLayoutConstants {
|
|||||||
|
|
||||||
struct ChatMessageItemImageLayoutConstants {
|
struct ChatMessageItemImageLayoutConstants {
|
||||||
let bubbleInsets: UIEdgeInsets
|
let bubbleInsets: UIEdgeInsets
|
||||||
|
let statusInsets: UIEdgeInsets
|
||||||
let defaultCornerRadius: CGFloat
|
let defaultCornerRadius: CGFloat
|
||||||
let mergedCornerRadius: CGFloat
|
let mergedCornerRadius: CGFloat
|
||||||
let contentMergedCornerRadius: CGFloat
|
let contentMergedCornerRadius: CGFloat
|
||||||
@@ -43,7 +44,7 @@ struct ChatMessageItemLayoutConstants {
|
|||||||
|
|
||||||
self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFillFactor: 0.85, minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 1.0))
|
self.bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFillFactor: 0.85, minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 1.0))
|
||||||
self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0))
|
self.text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0))
|
||||||
self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 0.5, left: 0.5, bottom: 0.5, right: 0.5), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 260.0, height: 260.0))
|
self.image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 0.5, left: 0.5, bottom: 0.5, right: 0.5), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 17.0, mergedCornerRadius: 5.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 260.0, height: 260.0))
|
||||||
self.file = ChatMessageItemFileLayoutConstants(bubbleInsets: UIEdgeInsets(top: 15.0, left: 9.0, bottom: 15.0, right: 12.0))
|
self.file = ChatMessageItemFileLayoutConstants(bubbleInsets: UIEdgeInsets(top: 15.0, left: 9.0, bottom: 15.0, right: 12.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let interactiveImageNode: ChatMessageInteractiveMediaNode
|
private let interactiveImageNode: ChatMessageInteractiveMediaNode
|
||||||
|
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||||
|
|
||||||
private var item: ChatMessageItem?
|
private var item: ChatMessageItem?
|
||||||
private var media: Media?
|
private var media: Media?
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
self.interactiveImageNode = ChatMessageInteractiveMediaNode()
|
self.interactiveImageNode = ChatMessageInteractiveMediaNode()
|
||||||
|
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
override func asyncLayoutContent() -> (_ item: ChatMessageItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ position: ChatMessageBubbleContentPosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) {
|
override func asyncLayoutContent() -> (_ item: ChatMessageItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ position: ChatMessageBubbleContentPosition, _ constrainedSize: CGSize) -> (CGFloat, (CGSize) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) {
|
||||||
let interactiveImageLayout = self.interactiveImageNode.asyncLayout()
|
let interactiveImageLayout = self.interactiveImageNode.asyncLayout()
|
||||||
|
let statusLayout = self.dateAndStatusNode.asyncLayout()
|
||||||
|
|
||||||
return { item, layoutConstants, position, constrainedSize in
|
return { item, layoutConstants, position, constrainedSize in
|
||||||
var selectedMedia: Media?
|
var selectedMedia: Media?
|
||||||
@@ -58,13 +61,78 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return (refinedWidth + layoutConstants.image.bubbleInsets.left + layoutConstants.image.bubbleInsets.right, { boundingWidth in
|
return (refinedWidth + layoutConstants.image.bubbleInsets.left + layoutConstants.image.bubbleInsets.right, { boundingWidth in
|
||||||
let (imageSize, imageApply) = finishLayout(boundingWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
|
let (imageSize, imageApply) = finishLayout(boundingWidth - layoutConstants.image.bubbleInsets.left - layoutConstants.image.bubbleInsets.right)
|
||||||
|
|
||||||
return (CGSize(width: imageSize.width + layoutConstants.image.bubbleInsets.left + layoutConstants.image.bubbleInsets.right, height: imageSize.height + layoutConstants.image.bubbleInsets.top + layoutConstants.image.bubbleInsets.bottom), { [weak self] _ in
|
var t = Int(item.message.timestamp)
|
||||||
|
var timeinfo = tm()
|
||||||
|
localtime_r(&t, &timeinfo)
|
||||||
|
|
||||||
|
var edited = false
|
||||||
|
var viewCount: Int?
|
||||||
|
for attribute in item.message.attributes {
|
||||||
|
if let _ = attribute as? EditedMessageAttribute {
|
||||||
|
edited = true
|
||||||
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
|
viewCount = attribute.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var dateText = String(format: "%02d:%02d", arguments: [Int(timeinfo.tm_hour), Int(timeinfo.tm_min)])
|
||||||
|
if let viewCount = viewCount {
|
||||||
|
dateText = "\(viewCount) " + dateText
|
||||||
|
}
|
||||||
|
if edited {
|
||||||
|
dateText = "edited " + dateText
|
||||||
|
}
|
||||||
|
|
||||||
|
let statusType: ChatMessageDateAndStatusType?
|
||||||
|
if case .None = position.bottom {
|
||||||
|
if item.message.flags.contains(.Incoming) {
|
||||||
|
statusType = .ImageIncoming
|
||||||
|
} else {
|
||||||
|
if item.message.flags.contains(.Failed) {
|
||||||
|
statusType = .ImageOutgoing(.Failed)
|
||||||
|
} else if item.message.flags.isSending {
|
||||||
|
statusType = .ImageOutgoing(.Sending)
|
||||||
|
} else {
|
||||||
|
statusType = .ImageOutgoing(.Sent(read: item.read))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statusType = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageLayoutSize = CGSize(width: imageSize.width + layoutConstants.image.bubbleInsets.left + layoutConstants.image.bubbleInsets.right, height: imageSize.height + layoutConstants.image.bubbleInsets.top + layoutConstants.image.bubbleInsets.bottom)
|
||||||
|
|
||||||
|
var statusSize = CGSize()
|
||||||
|
var statusApply: ((Bool) -> Void)?
|
||||||
|
|
||||||
|
if let statusType = statusType {
|
||||||
|
let (size, apply) = statusLayout(dateText, statusType, CGSize(width: imageLayoutSize.width, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
statusSize = size
|
||||||
|
statusApply = apply
|
||||||
|
}
|
||||||
|
|
||||||
|
let layoutSize = CGSize(width: max(imageLayoutSize.width, statusSize.width + layoutConstants.image.bubbleInsets.left + layoutConstants.image.bubbleInsets.right + layoutConstants.image.statusInsets.left + layoutConstants.image.statusInsets.right), height: imageLayoutSize.height)
|
||||||
|
|
||||||
|
return (layoutSize, { [weak self] animation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
strongSelf.media = selectedMedia
|
strongSelf.media = selectedMedia
|
||||||
|
|
||||||
strongSelf.interactiveImageNode.frame = CGRect(origin: CGPoint(x: layoutConstants.image.bubbleInsets.left, y: layoutConstants.image.bubbleInsets.top), size: imageSize)
|
strongSelf.interactiveImageNode.frame = CGRect(origin: CGPoint(x: layoutConstants.image.bubbleInsets.left, y: layoutConstants.image.bubbleInsets.top), size: imageSize)
|
||||||
|
|
||||||
|
if let statusApply = statusApply {
|
||||||
|
if strongSelf.dateAndStatusNode.supernode == nil {
|
||||||
|
strongSelf.interactiveImageNode.addSubnode(strongSelf.dateAndStatusNode)
|
||||||
|
}
|
||||||
|
var hasAnimation = true
|
||||||
|
if case .None = animation {
|
||||||
|
hasAnimation = false
|
||||||
|
}
|
||||||
|
statusApply(hasAnimation)
|
||||||
|
strongSelf.dateAndStatusNode.frame = CGRect(origin: CGPoint(x: layoutSize.width - layoutConstants.image.bubbleInsets.right - layoutConstants.image.statusInsets.right - statusSize.width, y: layoutSize.height - layoutConstants.image.bubbleInsets.bottom - layoutConstants.image.statusInsets.bottom - statusSize.height), size: statusSize)
|
||||||
|
} else if strongSelf.dateAndStatusNode.supernode != nil {
|
||||||
|
strongSelf.dateAndStatusNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
imageApply()
|
imageApply()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var edited = false
|
var edited = false
|
||||||
var viewCount: Int?
|
var viewCount: Int?
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? EditedMessageAttribute {
|
if let _ = attribute as? EditedMessageAttribute {
|
||||||
edited = true
|
edited = true
|
||||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||||
viewCount = attribute.count
|
viewCount = attribute.count
|
||||||
|
|||||||
@@ -24,10 +24,14 @@ public class ContactSelectionController: ViewController {
|
|||||||
return self._result.get()
|
return self._result.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
private let createActionDisposable = MetaDisposable()
|
private let confirmation: (PeerId) -> Signal<Bool, NoError>
|
||||||
|
|
||||||
public init(account: Account, title: String) {
|
private let createActionDisposable = MetaDisposable()
|
||||||
|
private let confirmationDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
public init(account: Account, title: String, confirmation: @escaping (PeerId) -> Signal<Bool, NoError> = { _ in .single(true) }) {
|
||||||
self.account = account
|
self.account = account
|
||||||
|
self.confirmation = confirmation
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@@ -142,7 +146,14 @@ public class ContactSelectionController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openPeer(peerId: PeerId) {
|
private func openPeer(peerId: PeerId) {
|
||||||
self._result.set(.single(peerId))
|
self.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||||
self.contactsNode.animateOut()
|
self.confirmationDisposable.set((self.confirmation(peerId) |> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if value {
|
||||||
|
strongSelf._result.set(.single(peerId))
|
||||||
|
strongSelf.contactsNode.animateOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
|||||||
|
|
||||||
})
|
})
|
||||||
case let .member(_, peer, presence):
|
case let .member(_, peer, presence):
|
||||||
return ItemListPeerItem(account: arguments.account, peer: peer, presence: presence, label: nil, sectionId: self.section, action: nil)
|
return ItemListPeerItem(account: arguments.account, peer: peer, presence: presence, text: .activity, label: nil, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), enabled: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _ in }, removePeer: { _ in })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
127
TelegramUI/DebugAccountsController.swift
Normal file
127
TelegramUI/DebugAccountsController.swift
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
private final class DebugAccountsControllerArguments {
|
||||||
|
let account: Account
|
||||||
|
let presentController: (ViewController, ViewControllerPresentationArguments) -> Void
|
||||||
|
|
||||||
|
let switchAccount: (AccountRecordId) -> Void
|
||||||
|
let loginNewAccount: () -> Void
|
||||||
|
|
||||||
|
init(account: Account, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, switchAccount: @escaping (AccountRecordId) -> Void, loginNewAccount: @escaping () -> Void) {
|
||||||
|
self.account = account
|
||||||
|
self.presentController = presentController
|
||||||
|
self.switchAccount = switchAccount
|
||||||
|
self.loginNewAccount = loginNewAccount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DebugAccountsControllerSection: Int32 {
|
||||||
|
case accounts
|
||||||
|
case actions
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DebugAccountsControllerEntry: ItemListNodeEntry {
|
||||||
|
case record(AccountRecord, Bool)
|
||||||
|
case loginNewAccount
|
||||||
|
|
||||||
|
var section: ItemListSectionId {
|
||||||
|
switch self {
|
||||||
|
case .record:
|
||||||
|
return DebugAccountsControllerSection.accounts.rawValue
|
||||||
|
case .loginNewAccount:
|
||||||
|
return DebugAccountsControllerSection.actions.rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stableId: Int64 {
|
||||||
|
switch self {
|
||||||
|
case let .record(record, _):
|
||||||
|
return record.id.int64
|
||||||
|
case .loginNewAccount:
|
||||||
|
return Int64.max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: DebugAccountsControllerEntry, rhs: DebugAccountsControllerEntry) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .record(record, current):
|
||||||
|
if case .record(record, current) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .loginNewAccount:
|
||||||
|
if case .loginNewAccount = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func <(lhs: DebugAccountsControllerEntry, rhs: DebugAccountsControllerEntry) -> Bool {
|
||||||
|
return lhs.stableId < rhs.stableId
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(_ arguments: DebugAccountsControllerArguments) -> ListViewItem {
|
||||||
|
switch self {
|
||||||
|
case let .record(record, current):
|
||||||
|
return ItemListCheckboxItem(title: "\(UInt64(bitPattern: record.id.int64))", checked: current, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||||
|
arguments.switchAccount(record.id)
|
||||||
|
})
|
||||||
|
case .loginNewAccount:
|
||||||
|
return ItemListActionItem(title: "Login to another account", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
|
arguments.loginNewAccount()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func debugAccountsControllerEntries(view: AccountRecordsView) -> [DebugAccountsControllerEntry] {
|
||||||
|
var entries: [DebugAccountsControllerEntry] = []
|
||||||
|
|
||||||
|
for entry in view.records.sorted(by: {
|
||||||
|
$0.id < $1.id
|
||||||
|
}) {
|
||||||
|
entries.append(.record(entry, entry.id == view.currentRecord?.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.append(.loginNewAccount)
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
public func debugAccountsController(account: Account, accountManager: AccountManager) -> ViewController {
|
||||||
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
|
|
||||||
|
let arguments = DebugAccountsControllerArguments(account: account, presentController: { controller, arguments in
|
||||||
|
presentControllerImpl?(controller, arguments)
|
||||||
|
}, switchAccount: { id in
|
||||||
|
let _ = accountManager.modify({ modifier -> Void in
|
||||||
|
modifier.setCurrentId(id)
|
||||||
|
}).start()
|
||||||
|
}, loginNewAccount: {
|
||||||
|
let _ = accountManager.modify({ modifier -> Void in
|
||||||
|
let id = modifier.createRecord([])
|
||||||
|
modifier.setCurrentId(id)
|
||||||
|
}).start()
|
||||||
|
})
|
||||||
|
|
||||||
|
let signal = accountManager.accountRecords()
|
||||||
|
|> map { view -> (ItemListControllerState, (ItemListNodeState<DebugAccountsControllerEntry>, DebugAccountsControllerEntry.ItemGenerationArguments)) in
|
||||||
|
let controllerState = ItemListControllerState(title: "Accounts", leftNavigationButton: nil, rightNavigationButton: nil)
|
||||||
|
let listState = ItemListNodeState(entries: debugAccountsControllerEntries(view: view), style: .blocks)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = ItemListController(signal)
|
||||||
|
presentControllerImpl = { [weak controller] c, a in
|
||||||
|
controller?.present(c, in: .window, with: a)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
123
TelegramUI/DebugController.swift
Normal file
123
TelegramUI/DebugController.swift
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
private final class DebugControllerArguments {
|
||||||
|
let account: Account
|
||||||
|
let accountManager: AccountManager
|
||||||
|
let presentController: (ViewController, ViewControllerPresentationArguments) -> Void
|
||||||
|
let pushController: (ViewController) -> Void
|
||||||
|
|
||||||
|
init(account: Account, accountManager: AccountManager, presentController: @escaping (ViewController, ViewControllerPresentationArguments) -> Void, pushController: @escaping (ViewController) -> Void) {
|
||||||
|
self.account = account
|
||||||
|
self.accountManager = accountManager
|
||||||
|
self.presentController = presentController
|
||||||
|
self.pushController = pushController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DebugControllerSection: Int32 {
|
||||||
|
case logs
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DebugControllerEntry: ItemListNodeEntry {
|
||||||
|
case sendLogs
|
||||||
|
case accounts
|
||||||
|
|
||||||
|
var section: ItemListSectionId {
|
||||||
|
switch self {
|
||||||
|
case .sendLogs:
|
||||||
|
return DebugControllerSection.logs.rawValue
|
||||||
|
case .accounts:
|
||||||
|
return DebugControllerSection.logs.rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stableId: Int32 {
|
||||||
|
switch self {
|
||||||
|
case .sendLogs:
|
||||||
|
return 0
|
||||||
|
case .accounts:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: DebugControllerEntry, rhs: DebugControllerEntry) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case .sendLogs, .accounts:
|
||||||
|
return lhs.stableId == rhs.stableId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func <(lhs: DebugControllerEntry, rhs: DebugControllerEntry) -> Bool {
|
||||||
|
return lhs.stableId < rhs.stableId
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(_ arguments: DebugControllerArguments) -> ListViewItem {
|
||||||
|
switch self {
|
||||||
|
case .sendLogs:
|
||||||
|
return ItemListDisclosureItem(title: "Seng Logs", label: "", sectionId: self.section, style: .blocks, action: {
|
||||||
|
let _ = (Logger.shared.collectLogs()
|
||||||
|
|> deliverOnMainQueue).start(next: { logs in
|
||||||
|
let controller = PeerSelectionController(account: arguments.account)
|
||||||
|
controller.peerSelected = { [weak controller] peerId in
|
||||||
|
if let strongController = controller {
|
||||||
|
strongController.dismiss()
|
||||||
|
|
||||||
|
let messages = logs.map { (name, path) -> EnqueueMessage in
|
||||||
|
let id = arc4random64()
|
||||||
|
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
|
||||||
|
return .message(text: "", attributes: [], media: file, replyToMessageId: nil)
|
||||||
|
}
|
||||||
|
let _ = enqueueMessages(account: arguments.account, peerId: peerId, messages: messages).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: ViewControllerPresentationAnimation.modalSheet))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
case .accounts:
|
||||||
|
return ItemListDisclosureItem(title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: {
|
||||||
|
arguments.pushController(debugAccountsController(account: arguments.account, accountManager: arguments.accountManager))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func debugControllerEntries() -> [DebugControllerEntry] {
|
||||||
|
var entries: [DebugControllerEntry] = []
|
||||||
|
|
||||||
|
entries.append(.sendLogs)
|
||||||
|
entries.append(.accounts)
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
public func debugController(account: Account, accountManager: AccountManager) -> ViewController {
|
||||||
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|
||||||
|
let arguments = DebugControllerArguments(account: account, accountManager: accountManager, presentController: { controller, arguments in
|
||||||
|
presentControllerImpl?(controller, arguments)
|
||||||
|
}, pushController: { controller in
|
||||||
|
pushControllerImpl?(controller)
|
||||||
|
})
|
||||||
|
|
||||||
|
let signal = Signal<Void, NoError>.single(Void())
|
||||||
|
|> map { _ -> (ItemListControllerState, (ItemListNodeState<DebugControllerEntry>, DebugControllerEntry.ItemGenerationArguments)) in
|
||||||
|
let controllerState = ItemListControllerState(title: "Debug", leftNavigationButton: nil, rightNavigationButton: nil)
|
||||||
|
let listState = ItemListNodeState(entries: debugControllerEntries(), style: .blocks)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = ItemListController(signal)
|
||||||
|
presentControllerImpl = { [weak controller] c, a in
|
||||||
|
controller?.present(c, in: .window, with: a)
|
||||||
|
}
|
||||||
|
pushControllerImpl = { [weak controller] c in
|
||||||
|
(controller?.navigationController as? NavigationController)?.pushViewController(c)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
1120
TelegramUI/GroupInfoController.swift
Normal file
1120
TelegramUI/GroupInfoController.swift
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import Foundation
|
/*import Foundation
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
@@ -674,3 +674,4 @@ func groupInfoEntries(view: PeerView, state: PeerInfoState?) -> PeerInfoEntries
|
|||||||
|
|
||||||
return PeerInfoEntries(entries: entries, leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton)
|
return PeerInfoEntries(entries: entries, leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import SwiftSignalKit
|
|||||||
enum ItemListActionKind {
|
enum ItemListActionKind {
|
||||||
case generic
|
case generic
|
||||||
case destructive
|
case destructive
|
||||||
|
case neutral
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ItemListActionAlignment {
|
enum ItemListActionAlignment {
|
||||||
@@ -110,9 +111,17 @@ class ItemListActionItemNode: ListViewItemNode {
|
|||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
|
||||||
return { item, width, neighbors in
|
return { item, width, neighbors in
|
||||||
let sectionInset: CGFloat = 22.0
|
let textColor: UIColor
|
||||||
|
switch item.kind {
|
||||||
|
case .destructive:
|
||||||
|
textColor = UIColor(0xff3b30)
|
||||||
|
case .generic:
|
||||||
|
textColor = UIColor(0x007ee5)
|
||||||
|
case .neutral:
|
||||||
|
textColor = .black
|
||||||
|
}
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: item.kind == .destructive ? UIColor(0xff3b30) : UIColor(0x007ee5)), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: textColor), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
|
|||||||
146
TelegramUI/ItemListActivityTextItem.swift
Normal file
146
TelegramUI/ItemListActivityTextItem.swift
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
class ItemListActivityTextItem: ListViewItem, ItemListItem {
|
||||||
|
let displayActivity: Bool
|
||||||
|
let text: NSAttributedString
|
||||||
|
let sectionId: ItemListSectionId
|
||||||
|
|
||||||
|
let isAlwaysPlain: Bool = true
|
||||||
|
|
||||||
|
init(displayActivity: Bool, text: NSAttributedString, sectionId: ItemListSectionId) {
|
||||||
|
self.displayActivity = displayActivity
|
||||||
|
self.text = text
|
||||||
|
self.sectionId = sectionId
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||||
|
async {
|
||||||
|
let node = ItemListActivityTextItemNode()
|
||||||
|
let (layout, apply) = node.asyncLayout()(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
|
||||||
|
node.contentSize = layout.contentSize
|
||||||
|
node.insets = layout.insets
|
||||||
|
|
||||||
|
completion(node, {
|
||||||
|
return (nil, { apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||||
|
guard let node = node as? ItemListActivityTextItemNode else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
let makeLayout = node.asyncLayout()
|
||||||
|
|
||||||
|
async {
|
||||||
|
let (layout, apply) = makeLayout(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(layout, {
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let titleFont = Font.regular(14.0)
|
||||||
|
|
||||||
|
class ItemListActivityTextItemNode: ListViewItemNode {
|
||||||
|
private let titleNode: TextNode
|
||||||
|
private var activityIndicator: UIActivityIndicatorView?
|
||||||
|
|
||||||
|
private var item: ItemListActivityTextItem?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.titleNode = TextNode()
|
||||||
|
self.titleNode.isLayerBacked = true
|
||||||
|
self.titleNode.contentMode = .left
|
||||||
|
self.titleNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
|
||||||
|
self.activityIndicator = activityIndicator
|
||||||
|
self.view.addSubview(activityIndicator)
|
||||||
|
activityIndicator.frame = CGRect(origin: CGPoint(x: 15.0, y: 6.0), size: activityIndicator.bounds.size)
|
||||||
|
|
||||||
|
if let item = self.item {
|
||||||
|
if item.displayActivity {
|
||||||
|
activityIndicator.isHidden = false
|
||||||
|
activityIndicator.startAnimating()
|
||||||
|
} else {
|
||||||
|
activityIndicator.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncLayout() -> (_ item: ItemListActivityTextItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
|
||||||
|
return { item, width, neighbors in
|
||||||
|
let leftInset: CGFloat = 15.0
|
||||||
|
let verticalInset: CGFloat = 7.0
|
||||||
|
|
||||||
|
var activityWidth: CGFloat = 0.0
|
||||||
|
if item.displayActivity {
|
||||||
|
activityWidth = 25.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let titleString = NSMutableAttributedString(attributedString: item.text)
|
||||||
|
titleString.removeAttribute(NSFontAttributeName, range: NSMakeRange(0, titleString.length))
|
||||||
|
titleString.addAttributes([NSFontAttributeName: titleFont], range: NSMakeRange(0, titleString.length))
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(titleString, nil, 0, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), TextNodeCutout(position: .TopLeft, size: CGSize(width: activityWidth, height: 4.0)))
|
||||||
|
|
||||||
|
let contentSize: CGSize
|
||||||
|
let insets: UIEdgeInsets
|
||||||
|
|
||||||
|
contentSize = CGSize(width: width, height: titleLayout.size.height + verticalInset + verticalInset)
|
||||||
|
insets = itemListNeighborsPlainInsets(neighbors)
|
||||||
|
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
|
|
||||||
|
return (layout, { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
|
||||||
|
let _ = titleApply()
|
||||||
|
|
||||||
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
|
||||||
|
|
||||||
|
if let activityIndicator = strongSelf.activityIndicator, activityIndicator.isHidden != !item.displayActivity {
|
||||||
|
if item.displayActivity {
|
||||||
|
activityIndicator.isHidden = false
|
||||||
|
activityIndicator.startAnimating()
|
||||||
|
} else {
|
||||||
|
activityIndicator.isHidden = true
|
||||||
|
activityIndicator.stopAnimating()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,12 +21,27 @@ enum ItemListAvatarAndNameInfoItemName: Equatable {
|
|||||||
var composedTitle: String {
|
var composedTitle: String {
|
||||||
switch self {
|
switch self {
|
||||||
case let .personName(firstName, lastName):
|
case let .personName(firstName, lastName):
|
||||||
return firstName + " " + lastName
|
if !firstName.isEmpty && !lastName.isEmpty {
|
||||||
|
return firstName + " " + lastName
|
||||||
|
} else if !firstName.isEmpty {
|
||||||
|
return firstName
|
||||||
|
} else {
|
||||||
|
return lastName
|
||||||
|
}
|
||||||
case let .title(title):
|
case let .title(title):
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isEmpty: Bool {
|
||||||
|
switch self {
|
||||||
|
case let .personName(firstName, lastName):
|
||||||
|
return !firstName.isEmpty || !lastName.isEmpty
|
||||||
|
case let .title(title):
|
||||||
|
return title.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func ==(lhs: ItemListAvatarAndNameInfoItemName, rhs: ItemListAvatarAndNameInfoItemName) -> Bool {
|
static func ==(lhs: ItemListAvatarAndNameInfoItemName, rhs: ItemListAvatarAndNameInfoItemName) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case let .personName(firstName, lastName):
|
case let .personName(firstName, lastName):
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import SwiftSignalKit
|
|||||||
enum ItemListNavigationButtonStyle {
|
enum ItemListNavigationButtonStyle {
|
||||||
case regular
|
case regular
|
||||||
case bold
|
case bold
|
||||||
|
case activity
|
||||||
|
|
||||||
var barButtonItemStyle: UIBarButtonItemStyle {
|
var barButtonItemStyle: UIBarButtonItemStyle {
|
||||||
switch self {
|
switch self {
|
||||||
case .regular:
|
case .regular, .activity:
|
||||||
return .plain
|
return .plain
|
||||||
case .bold:
|
case .bold:
|
||||||
return .done
|
return .done
|
||||||
@@ -27,6 +28,14 @@ struct ItemListControllerState {
|
|||||||
let title: String
|
let title: String
|
||||||
let leftNavigationButton: ItemListNavigationButton?
|
let leftNavigationButton: ItemListNavigationButton?
|
||||||
let rightNavigationButton: ItemListNavigationButton?
|
let rightNavigationButton: ItemListNavigationButton?
|
||||||
|
let animateChanges: Bool
|
||||||
|
|
||||||
|
init(title: String, leftNavigationButton: ItemListNavigationButton?, rightNavigationButton: ItemListNavigationButton?, animateChanges: Bool = true) {
|
||||||
|
self.title = title
|
||||||
|
self.leftNavigationButton = leftNavigationButton
|
||||||
|
self.rightNavigationButton = rightNavigationButton
|
||||||
|
self.animateChanges = animateChanges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ItemListController<Entry: ItemListNodeEntry>: ViewController {
|
final class ItemListController<Entry: ItemListNodeEntry>: ViewController {
|
||||||
@@ -73,7 +82,12 @@ final class ItemListController<Entry: ItemListNodeEntry>: ViewController {
|
|||||||
|
|
||||||
if strongSelf.rightNavigationButtonTitleAndStyle?.0 != controllerState.rightNavigationButton?.title || strongSelf.rightNavigationButtonTitleAndStyle?.1 != controllerState.rightNavigationButton?.style {
|
if strongSelf.rightNavigationButtonTitleAndStyle?.0 != controllerState.rightNavigationButton?.title || strongSelf.rightNavigationButtonTitleAndStyle?.1 != controllerState.rightNavigationButton?.style {
|
||||||
if let rightNavigationButton = controllerState.rightNavigationButton {
|
if let rightNavigationButton = controllerState.rightNavigationButton {
|
||||||
let item = UIBarButtonItem(title: rightNavigationButton.title, style: rightNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.rightNavigationButtonPressed))
|
let item: UIBarButtonItem
|
||||||
|
if case .activity = rightNavigationButton.style {
|
||||||
|
item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode())
|
||||||
|
} else {
|
||||||
|
item = UIBarButtonItem(title: rightNavigationButton.title, style: rightNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.rightNavigationButtonPressed))
|
||||||
|
}
|
||||||
strongSelf.rightNavigationButtonTitleAndStyle = (rightNavigationButton.title, rightNavigationButton.style)
|
strongSelf.rightNavigationButtonTitleAndStyle = (rightNavigationButton.title, rightNavigationButton.style)
|
||||||
strongSelf.navigationItem.setRightBarButton(item, animated: false)
|
strongSelf.navigationItem.setRightBarButton(item, animated: false)
|
||||||
item.isEnabled = rightNavigationButton.enabled
|
item.isEnabled = rightNavigationButton.enabled
|
||||||
@@ -112,6 +126,8 @@ final class ItemListController<Entry: ItemListNodeEntry>: ViewController {
|
|||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
(self.displayNode as! ItemListNode<Entry>).listNode.preloadPages = true
|
||||||
|
|
||||||
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments {
|
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments {
|
||||||
if case .modalSheet = presentationArguments.presentationAnimation {
|
if case .modalSheet = presentationArguments.presentationAnimation {
|
||||||
(self.displayNode as! ItemListNode<Entry>).animateIn()
|
(self.displayNode as! ItemListNode<Entry>).animateIn()
|
||||||
@@ -122,4 +138,16 @@ final class ItemListController<Entry: ItemListNodeEntry>: ViewController {
|
|||||||
func dismiss() {
|
func dismiss() {
|
||||||
(self.displayNode as! ItemListNode<Entry>).animateOut()
|
(self.displayNode as! ItemListNode<Entry>).animateOut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func frameForItemNode(_ predicate: (ListViewItemNode) -> Bool) -> CGRect? {
|
||||||
|
var result: CGRect?
|
||||||
|
(self.displayNode as! ItemListNode<Entry>).listNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? ListViewItemNode {
|
||||||
|
if predicate(itemNode) {
|
||||||
|
result = itemNode.convert(itemNode.bounds, to: self.displayNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,19 @@ private struct ItemListNodeTransition {
|
|||||||
let updateStyle: ItemListStyle?
|
let updateStyle: ItemListStyle?
|
||||||
let firstTime: Bool
|
let firstTime: Bool
|
||||||
let animated: Bool
|
let animated: Bool
|
||||||
|
let animateAlpha: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ItemListNodeState<Entry: ItemListNodeEntry> {
|
struct ItemListNodeState<Entry: ItemListNodeEntry> {
|
||||||
let entries: [Entry]
|
let entries: [Entry]
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
|
let animateChanges: Bool
|
||||||
|
|
||||||
|
init(entries: [Entry], style: ItemListStyle, animateChanges: Bool = true) {
|
||||||
|
self.entries = entries
|
||||||
|
self.style = style
|
||||||
|
self.animateChanges = animateChanges
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
|
final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
|
||||||
@@ -53,7 +61,7 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
private var didSetReady = false
|
private var didSetReady = false
|
||||||
|
|
||||||
private let listNode: ListView
|
let listNode: ListView
|
||||||
private let transitionDisposable = MetaDisposable()
|
private let transitionDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var enqueuedTransitions: [ItemListNodeTransition] = []
|
private var enqueuedTransitions: [ItemListNodeTransition] = []
|
||||||
@@ -81,7 +89,7 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
|
|||||||
if previous?.style != state.style {
|
if previous?.style != state.style {
|
||||||
updatedStyle = state.style
|
updatedStyle = state.style
|
||||||
}
|
}
|
||||||
return ItemListNodeTransition(entries: transition, updateStyle: updatedStyle, firstTime: previous == nil, animated: previous != nil)
|
return ItemListNodeTransition(entries: transition, updateStyle: updatedStyle, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && !state.animateChanges)
|
||||||
}) |> deliverOnMainQueue).start(next: { [weak self] transition in
|
}) |> deliverOnMainQueue).start(next: { [weak self] transition in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.enqueueTransition(transition)
|
strongSelf.enqueueTransition(transition)
|
||||||
@@ -167,6 +175,9 @@ final class ItemListNode<Entry: ItemListNodeEntry>: ASDisplayNode {
|
|||||||
options.insert(.LowLatency)
|
options.insert(.LowLatency)
|
||||||
} else if transition.animated {
|
} else if transition.animated {
|
||||||
options.insert(.AnimateInsertion)
|
options.insert(.AnimateInsertion)
|
||||||
|
} else if transition.animateAlpha {
|
||||||
|
options.insert(.PreferSynchronousResourceLoading)
|
||||||
|
options.insert(.AnimateAlpha)
|
||||||
}
|
}
|
||||||
self.listNode.transaction(deleteIndices: transition.entries.deletions, insertIndicesAndItems: transition.entries.insertions, updateIndicesAndItems: transition.entries.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
self.listNode.transaction(deleteIndices: transition.entries.deletions, insertIndicesAndItems: transition.entries.insertions, updateIndicesAndItems: transition.entries.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ class ItemListRevealOptionsItemNode: ListViewItemNode {
|
|||||||
case .changed:
|
case .changed:
|
||||||
var translation = recognizer.translation(in: self.view)
|
var translation = recognizer.translation(in: self.view)
|
||||||
translation.x += self.initialRevealOffset
|
translation.x += self.initialRevealOffset
|
||||||
|
translation.x = min(0.0, translation.x)
|
||||||
if self.revealNode == nil && translation.x.isLess(than: 0.0) {
|
if self.revealNode == nil && translation.x.isLess(than: 0.0) {
|
||||||
self.setupAndAddRevealNode()
|
self.setupAndAddRevealNode()
|
||||||
self.revealOptionsInteractivelyOpened()
|
self.revealOptionsInteractivelyOpened()
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import SwiftSignalKit
|
|||||||
|
|
||||||
class ItemListMultilineInputItem: ListViewItem, ItemListItem {
|
class ItemListMultilineInputItem: ListViewItem, ItemListItem {
|
||||||
let text: String
|
let text: String
|
||||||
|
let placeholder: String
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let textUpdated: (String) -> Void
|
let textUpdated: (String) -> Void
|
||||||
|
|
||||||
init(text: String, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, action: @escaping () -> Void) {
|
init(text: String, placeholder: String, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, action: @escaping () -> Void) {
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.placeholder = placeholder
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.textUpdated = textUpdated
|
self.textUpdated = textUpdated
|
||||||
self.action = action
|
self.action = action
|
||||||
@@ -120,6 +122,8 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
|
|||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
let layoutSize = layout.size
|
let layoutSize = layout.size
|
||||||
|
|
||||||
|
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(17.0), textColor: UIColor(0x878787))
|
||||||
|
|
||||||
return (layout, { [weak self] in
|
return (layout, { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
@@ -159,6 +163,11 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega
|
|||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||||
|
|
||||||
|
|
||||||
|
if strongSelf.textNode.attributedPlaceholderText == nil || !strongSelf.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) {
|
||||||
|
strongSelf.textNode.attributedPlaceholderText = attributedPlaceholderText
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: width - leftInset, height: textLayout.size.height))
|
strongSelf.textClippingNode.frame = CGRect(origin: CGPoint(x: leftInset, y: textTopInset), size: CGSize(width: width - leftInset, height: textLayout.size.height))
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - leftInset - 8.0, height: textLayout.size.height))
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - leftInset - 8.0, height: textLayout.size.height))
|
||||||
}
|
}
|
||||||
|
|||||||
220
TelegramUI/ItemListMultilineTextItem.swift
Normal file
220
TelegramUI/ItemListMultilineTextItem.swift
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
class ItemListMultilineTextItem: ListViewItem, ItemListItem {
|
||||||
|
let text: String
|
||||||
|
let sectionId: ItemListSectionId
|
||||||
|
let style: ItemListStyle
|
||||||
|
|
||||||
|
init(text: String, sectionId: ItemListSectionId, style: ItemListStyle) {
|
||||||
|
self.text = text
|
||||||
|
self.sectionId = sectionId
|
||||||
|
self.style = style
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||||
|
async {
|
||||||
|
let node = ItemListMultilineTextItemNode()
|
||||||
|
let (layout, apply) = node.asyncLayout()(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
|
||||||
|
node.contentSize = layout.contentSize
|
||||||
|
node.insets = layout.insets
|
||||||
|
|
||||||
|
completion(node, {
|
||||||
|
return (nil, { apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||||
|
if let node = node as? ItemListMultilineTextItemNode {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
let makeLayout = node.asyncLayout()
|
||||||
|
|
||||||
|
async {
|
||||||
|
let (layout, apply) = makeLayout(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(layout, {
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let titleFont = Font.regular(17.0)
|
||||||
|
|
||||||
|
class ItemListMultilineTextItemNode: ListViewItemNode {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let topStripeNode: ASDisplayNode
|
||||||
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let textNode: TextNode
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
|
||||||
|
self.topStripeNode = ASDisplayNode()
|
||||||
|
self.topStripeNode.backgroundColor = UIColor(0xc8c7cc)
|
||||||
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.bottomStripeNode = ASDisplayNode()
|
||||||
|
self.bottomStripeNode.backgroundColor = UIColor(0xc8c7cc)
|
||||||
|
self.bottomStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.textNode = TextNode()
|
||||||
|
self.textNode.isLayerBacked = true
|
||||||
|
self.textNode.contentMode = .left
|
||||||
|
self.textNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
|
self.highlightedBackgroundNode.backgroundColor = UIColor(0xd9d9d9)
|
||||||
|
self.highlightedBackgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncLayout() -> (_ item: ItemListMultilineTextItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
|
|
||||||
|
return { item, width, neighbors in
|
||||||
|
let textColor: UIColor = .black
|
||||||
|
|
||||||
|
let leftInset: CGFloat
|
||||||
|
|
||||||
|
switch item.style {
|
||||||
|
case .plain:
|
||||||
|
leftInset = 35.0
|
||||||
|
case .blocks:
|
||||||
|
leftInset = 16.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTextLayout(NSAttributedString(string: item.text, font: titleFont, textColor: textColor), nil, 0, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
|
let contentSize: CGSize
|
||||||
|
let insets: UIEdgeInsets
|
||||||
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
|
switch item.style {
|
||||||
|
case .plain:
|
||||||
|
contentSize = CGSize(width: width, height: titleLayout.size.height + 22.0)
|
||||||
|
insets = itemListNeighborsPlainInsets(neighbors)
|
||||||
|
case .blocks:
|
||||||
|
contentSize = CGSize(width: width, height: titleLayout.size.height + 22.0)
|
||||||
|
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||||
|
}
|
||||||
|
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
|
let layoutSize = layout.size
|
||||||
|
|
||||||
|
return (layout, { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = titleApply()
|
||||||
|
|
||||||
|
switch item.style {
|
||||||
|
case .plain:
|
||||||
|
if strongSelf.backgroundNode.supernode != nil {
|
||||||
|
strongSelf.backgroundNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if strongSelf.topStripeNode.supernode != nil {
|
||||||
|
strongSelf.topStripeNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if strongSelf.bottomStripeNode.supernode == nil {
|
||||||
|
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: width - leftInset, height: separatorHeight))
|
||||||
|
case .blocks:
|
||||||
|
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
|
||||||
|
let bottomStripeOffset: CGFloat
|
||||||
|
switch neighbors.bottom {
|
||||||
|
case .sameSection(false):
|
||||||
|
bottomStripeInset = 16.0
|
||||||
|
bottomStripeOffset = -separatorHeight
|
||||||
|
default:
|
||||||
|
bottomStripeInset = 0.0
|
||||||
|
bottomStripeOffset = 0.0
|
||||||
|
}
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: 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: layoutSize.width, height: separatorHeight))
|
||||||
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size)
|
||||||
|
|
||||||
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: 44.0 + UIScreenPixel + UIScreenPixel))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||||
|
super.setHighlighted(highlighted, animated: animated)
|
||||||
|
|
||||||
|
if highlighted {
|
||||||
|
self.highlightedBackgroundNode.alpha = 1.0
|
||||||
|
if self.highlightedBackgroundNode.supernode == nil {
|
||||||
|
var anchorNode: ASDisplayNode?
|
||||||
|
if self.bottomStripeNode.supernode != nil {
|
||||||
|
anchorNode = self.bottomStripeNode
|
||||||
|
} else if self.topStripeNode.supernode != nil {
|
||||||
|
anchorNode = self.topStripeNode
|
||||||
|
} else if self.backgroundNode.supernode != nil {
|
||||||
|
anchorNode = self.backgroundNode
|
||||||
|
}
|
||||||
|
if let anchorNode = anchorNode {
|
||||||
|
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||||
|
} else {
|
||||||
|
self.addSubnode(self.highlightedBackgroundNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.highlightedBackgroundNode.supernode != nil {
|
||||||
|
if animated {
|
||||||
|
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if completed {
|
||||||
|
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.highlightedBackgroundNode.alpha = 0.0
|
||||||
|
} else {
|
||||||
|
self.highlightedBackgroundNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,14 @@ import SwiftSignalKit
|
|||||||
class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
||||||
let icon: UIImage?
|
let icon: UIImage?
|
||||||
let title: String
|
let title: String
|
||||||
|
let editing: Bool
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
init(icon: UIImage?, title: String, sectionId: ItemListSectionId, action: @escaping () -> Void) {
|
init(icon: UIImage?, title: String, sectionId: ItemListSectionId, editing: Bool, action: @escaping () -> Void) {
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.editing = editing
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
@@ -25,7 +27,7 @@ class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
|||||||
node.insets = layout.insets
|
node.insets = layout.insets
|
||||||
|
|
||||||
completion(node, {
|
completion(node, {
|
||||||
return (nil, { apply() })
|
return (nil, { apply(false) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,11 +37,16 @@ class ItemListPeerActionItem: ListViewItem, ItemListItem {
|
|||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
let makeLayout = node.asyncLayout()
|
let makeLayout = node.asyncLayout()
|
||||||
|
|
||||||
|
var animated = true
|
||||||
|
if case .None = animation {
|
||||||
|
animated = false
|
||||||
|
}
|
||||||
|
|
||||||
async {
|
async {
|
||||||
let (layout, apply) = makeLayout(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
let (layout, apply) = makeLayout(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(layout, {
|
completion(layout, {
|
||||||
apply()
|
apply(animated)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,13 +106,15 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
|||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: ItemListPeerActionItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: ItemListPeerActionItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
|
||||||
return { item, width, neighbors in
|
return { item, width, neighbors in
|
||||||
let leftInset: CGFloat = 65.0
|
let leftInset: CGFloat = 65.0
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: UIColor(0x007ee5)), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
let editingOffset: CGFloat = (item.editing ? 38.0 : 0.0)
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: UIColor(0x007ee5)), nil, 1, .end, CGSize(width: width - leftInset - editingOffset, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
let separatorHeight = UIScreenPixel
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
@@ -115,13 +124,20 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
|||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
let layoutSize = layout.size
|
let layoutSize = layout.size
|
||||||
|
|
||||||
return (layout, { [weak self] in
|
return (layout, { [weak self] animated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
if animated {
|
||||||
|
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
|
} else {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.iconNode.image = item.icon
|
strongSelf.iconNode.image = item.icon
|
||||||
if let image = item.icon {
|
if let image = item.icon {
|
||||||
strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: floor((leftInset - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
|
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: editingOffset + floor((leftInset - image.size.width) / 2.0), y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
@@ -139,11 +155,12 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
|||||||
default:
|
default:
|
||||||
strongSelf.topStripeNode.isHidden = false
|
strongSelf.topStripeNode.isHidden = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let bottomStripeInset: CGFloat
|
let bottomStripeInset: CGFloat
|
||||||
let bottomStripeOffset: CGFloat
|
let bottomStripeOffset: CGFloat
|
||||||
switch neighbors.bottom {
|
switch neighbors.bottom {
|
||||||
case .sameSection(false):
|
case .sameSection(false):
|
||||||
bottomStripeInset = leftInset
|
bottomStripeInset = leftInset + editingOffset
|
||||||
bottomStripeOffset = -separatorHeight
|
bottomStripeOffset = -separatorHeight
|
||||||
default:
|
default:
|
||||||
bottomStripeInset = 0.0
|
bottomStripeInset = 0.0
|
||||||
@@ -151,9 +168,9 @@ class ItemListPeerActionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: 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: layoutSize.width, height: separatorHeight))
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size)
|
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: 12.0), size: titleLayout.size))
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: 44.0 + UIScreenPixel + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: 44.0 + UIScreenPixel + UIScreenPixel))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,55 @@ import SwiftSignalKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
|
struct ItemListPeerItemEditing: Equatable {
|
||||||
|
let editable: Bool
|
||||||
|
let editing: Bool
|
||||||
|
let revealed: Bool
|
||||||
|
|
||||||
|
static func ==(lhs: ItemListPeerItemEditing, rhs: ItemListPeerItemEditing) -> Bool {
|
||||||
|
if lhs.editable != rhs.editable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.editing != rhs.editing {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.revealed != rhs.revealed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ItemListPeerItemText {
|
||||||
|
case activity
|
||||||
|
case text(String)
|
||||||
|
}
|
||||||
|
|
||||||
final class ItemListPeerItem: ListViewItem, ItemListItem {
|
final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||||
let account: Account
|
let account: Account
|
||||||
let peer: Peer?
|
let peer: Peer
|
||||||
let presence: PeerPresence?
|
let presence: PeerPresence?
|
||||||
|
let text: ItemListPeerItemText
|
||||||
let label: String?
|
let label: String?
|
||||||
|
let editing: ItemListPeerItemEditing
|
||||||
|
let enabled: Bool
|
||||||
let sectionId: ItemListSectionId
|
let sectionId: ItemListSectionId
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
|
let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void
|
||||||
|
let removePeer: (PeerId) -> Void
|
||||||
|
|
||||||
init(account: Account, peer: Peer?, presence: PeerPresence?, label: String?, sectionId: ItemListSectionId, action: (() -> Void)?) {
|
init(account: Account, peer: Peer, presence: PeerPresence?, text: ItemListPeerItemText, label: String?, editing: ItemListPeerItemEditing, enabled: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.presence = presence
|
self.presence = presence
|
||||||
|
self.text = text
|
||||||
self.label = label
|
self.label = label
|
||||||
|
self.editing = editing
|
||||||
|
self.enabled = enabled
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
self.action = action
|
||||||
|
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
|
||||||
|
self.removePeer = removePeer
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||||
@@ -31,7 +65,7 @@ final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
node.insets = layout.insets
|
node.insets = layout.insets
|
||||||
|
|
||||||
completion(node, {
|
completion(node, {
|
||||||
return (nil, { apply() })
|
return (nil, { apply(false) })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,11 +75,16 @@ final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
let makeLayout = node.asyncLayout()
|
let makeLayout = node.asyncLayout()
|
||||||
|
|
||||||
|
var animated = true
|
||||||
|
if case .None = animation {
|
||||||
|
animated = false
|
||||||
|
}
|
||||||
|
|
||||||
async {
|
async {
|
||||||
let (layout, apply) = makeLayout(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
let (layout, apply) = makeLayout(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(layout, {
|
completion(layout, {
|
||||||
apply()
|
apply(animated)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,6 +111,7 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
private var disabledOverlayNode: ASDisplayNode?
|
||||||
|
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
@@ -81,7 +121,12 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||||
private var layoutParams: (ItemListPeerItem, CGFloat, ItemListNeighbors)?
|
private var layoutParams: (ItemListPeerItem, CGFloat, ItemListNeighbors)?
|
||||||
|
|
||||||
|
private var editableControlNode: ItemListEditableControlNode?
|
||||||
|
|
||||||
override var canBeSelected: Bool {
|
override var canBeSelected: Bool {
|
||||||
|
if self.editableControlNode != nil || self.disabledOverlayNode != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if let item = self.layoutParams?.0, item.action != nil {
|
if let item = self.layoutParams?.0, item.action != nil {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@@ -134,37 +179,53 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||||
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
|
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
|
||||||
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2)
|
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2)
|
||||||
apply()
|
apply(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: ItemListPeerItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: ItemListPeerItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
|
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||||
|
|
||||||
|
var currentDisabledOverlayNode = self.disabledOverlayNode
|
||||||
|
|
||||||
return { item, width, neighbors in
|
return { item, width, neighbors in
|
||||||
var titleAttributedString: NSAttributedString?
|
var titleAttributedString: NSAttributedString?
|
||||||
var statusAttributedString: NSAttributedString?
|
var statusAttributedString: NSAttributedString?
|
||||||
var labelAttributedString: NSAttributedString?
|
var labelAttributedString: NSAttributedString?
|
||||||
|
|
||||||
if let peer = item.peer {
|
let peerRevealOptions: [ItemListRevealOption]
|
||||||
if let user = peer as? TelegramUser {
|
if item.editing.editable && item.enabled {
|
||||||
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
|
peerRevealOptions = [ItemListRevealOption(key: 0, title: "Remove", icon: nil, color: UIColor(0xff3824))]
|
||||||
let string = NSMutableAttributedString()
|
} else {
|
||||||
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: .black))
|
peerRevealOptions = []
|
||||||
string.append(NSAttributedString(string: " ", font: titleFont, textColor: .black))
|
}
|
||||||
string.append(NSAttributedString(string: lastName, font: titleBoldFont, textColor: .black))
|
|
||||||
titleAttributedString = string
|
if let user = item.peer as? TelegramUser {
|
||||||
} else if let firstName = user.firstName, !firstName.isEmpty {
|
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
|
||||||
titleAttributedString = NSAttributedString(string: firstName, font: titleBoldFont, textColor: UIColor.black)
|
let string = NSMutableAttributedString()
|
||||||
} else if let lastName = user.lastName, !lastName.isEmpty {
|
string.append(NSAttributedString(string: firstName, font: titleFont, textColor: .black))
|
||||||
titleAttributedString = NSAttributedString(string: lastName, font: titleBoldFont, textColor: UIColor.black)
|
string.append(NSAttributedString(string: " ", font: titleFont, textColor: .black))
|
||||||
} else {
|
string.append(NSAttributedString(string: lastName, font: titleBoldFont, textColor: .black))
|
||||||
titleAttributedString = NSAttributedString(string: "Deleted User", font: titleBoldFont, textColor: UIColor(0xa6a6a6))
|
titleAttributedString = string
|
||||||
}
|
} else if let firstName = user.firstName, !firstName.isEmpty {
|
||||||
|
titleAttributedString = NSAttributedString(string: firstName, font: titleBoldFont, textColor: UIColor.black)
|
||||||
|
} else if let lastName = user.lastName, !lastName.isEmpty {
|
||||||
|
titleAttributedString = NSAttributedString(string: lastName, font: titleBoldFont, textColor: UIColor.black)
|
||||||
|
} else {
|
||||||
|
titleAttributedString = NSAttributedString(string: "Deleted User", font: titleBoldFont, textColor: UIColor(0xa6a6a6))
|
||||||
|
}
|
||||||
|
} else if let group = item.peer as? TelegramGroup {
|
||||||
|
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: UIColor.black)
|
||||||
|
} else if let channel = item.peer as? TelegramChannel {
|
||||||
|
titleAttributedString = NSAttributedString(string: channel.title, font: titleBoldFont, textColor: UIColor.black)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item.text {
|
||||||
|
case .activity:
|
||||||
if let presence = item.presence as? TelegramUserPresence {
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
let (string, activity) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp))
|
let (string, activity) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp))
|
||||||
@@ -172,11 +233,8 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else {
|
} else {
|
||||||
statusAttributedString = NSAttributedString(string: "last seen recently", font: statusFont, textColor: UIColor(0xa6a6a6))
|
statusAttributedString = NSAttributedString(string: "last seen recently", font: statusFont, textColor: UIColor(0xa6a6a6))
|
||||||
}
|
}
|
||||||
} else if let group = peer as? TelegramGroup {
|
case let .text(text):
|
||||||
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: UIColor.black)
|
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: UIColor(0xa6a6a6))
|
||||||
} else if let channel = peer as? TelegramChannel {
|
|
||||||
titleAttributedString = NSAttributedString(string: channel.title, font: titleBoldFont, textColor: UIColor.black)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let label = item.label {
|
if let label = item.label {
|
||||||
@@ -185,10 +243,21 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let leftInset: CGFloat = 65.0
|
let leftInset: CGFloat = 65.0
|
||||||
|
|
||||||
let (labelLayout, labelApply) = makeLabelLayout(labelAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
var editableControlSizeAndApply: (CGSize, () -> ItemListEditableControlNode)?
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(titleAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0 - labelLayout.size.width, height: CGFloat.greatestFiniteMagnitude), nil)
|
let editingOffset: CGFloat
|
||||||
let (statusLayout, statusApply) = makeStatusLayout(statusAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0 - (labelLayout.size.width > 0.0 ? (labelLayout.size.width) + 15.0 : 0.0), height: CGFloat.greatestFiniteMagnitude), nil)
|
if item.editing.editing {
|
||||||
|
let sizeAndApply = editableControlLayout(48.0)
|
||||||
|
editableControlSizeAndApply = sizeAndApply
|
||||||
|
editingOffset = sizeAndApply.0.width
|
||||||
|
} else {
|
||||||
|
editingOffset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let (labelLayout, labelApply) = makeLabelLayout(labelAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0 - editingOffset, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(titleAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0 - labelLayout.size.width - editingOffset, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
let (statusLayout, statusApply) = makeStatusLayout(statusAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0 - (labelLayout.size.width > 0.0 ? (labelLayout.size.width) + 15.0 : 0.0) - editingOffset, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
let insets = itemListNeighborsGroupedInsets(neighbors)
|
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||||
let contentSize = CGSize(width: width, height: 48.0)
|
let contentSize = CGSize(width: width, height: 48.0)
|
||||||
@@ -197,12 +266,73 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
let layoutSize = layout.size
|
let layoutSize = layout.size
|
||||||
|
|
||||||
return (layout, { [weak self] in
|
if !item.enabled {
|
||||||
|
if currentDisabledOverlayNode == nil {
|
||||||
|
currentDisabledOverlayNode = ASDisplayNode()
|
||||||
|
currentDisabledOverlayNode?.backgroundColor = UIColor(white: 1.0, alpha: 0.5)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentDisabledOverlayNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return (layout, { [weak self] animated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.layoutParams = (item, width, neighbors)
|
strongSelf.layoutParams = (item, width, neighbors)
|
||||||
|
|
||||||
let revealOffset = strongSelf.revealOffset
|
let revealOffset = strongSelf.revealOffset
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition
|
||||||
|
if animated {
|
||||||
|
transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||||
|
} else {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentDisabledOverlayNode = currentDisabledOverlayNode {
|
||||||
|
if currentDisabledOverlayNode != strongSelf.disabledOverlayNode {
|
||||||
|
strongSelf.disabledOverlayNode = currentDisabledOverlayNode
|
||||||
|
strongSelf.addSubnode(currentDisabledOverlayNode)
|
||||||
|
currentDisabledOverlayNode.alpha = 0.0
|
||||||
|
transition.updateAlpha(node: currentDisabledOverlayNode, alpha: 1.0)
|
||||||
|
currentDisabledOverlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height - separatorHeight))
|
||||||
|
} else {
|
||||||
|
transition.updateFrame(node: currentDisabledOverlayNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height - separatorHeight)))
|
||||||
|
}
|
||||||
|
} else if let disabledOverlayNode = strongSelf.disabledOverlayNode {
|
||||||
|
transition.updateAlpha(node: disabledOverlayNode, alpha: 0.0, completion: { [weak disabledOverlayNode] _ in
|
||||||
|
disabledOverlayNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
strongSelf.disabledOverlayNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let editableControlSizeAndApply = editableControlSizeAndApply {
|
||||||
|
if strongSelf.editableControlNode == nil {
|
||||||
|
let editableControlNode = editableControlSizeAndApply.1()
|
||||||
|
editableControlNode.tapped = {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.setRevealOptionsOpened(true, animated: true)
|
||||||
|
strongSelf.revealOptionsInteractivelyOpened()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.editableControlNode = editableControlNode
|
||||||
|
strongSelf.insertSubnode(editableControlNode, aboveSubnode: strongSelf.avatarNode)
|
||||||
|
let editableControlFrame = CGRect(origin: CGPoint(x: revealOffset, y: 0.0), size: editableControlSizeAndApply.0)
|
||||||
|
editableControlNode.frame = editableControlFrame
|
||||||
|
transition.animatePosition(node: editableControlNode, from: CGPoint(x: editableControlFrame.midX - editableControlFrame.size.width, y: editableControlFrame.midY))
|
||||||
|
editableControlNode.alpha = 0.0
|
||||||
|
transition.updateAlpha(node: editableControlNode, alpha: 1.0)
|
||||||
|
}
|
||||||
|
strongSelf.editableControlNode?.isHidden = !item.editing.editable
|
||||||
|
} else if let editableControlNode = strongSelf.editableControlNode {
|
||||||
|
var editableControlFrame = editableControlNode.frame
|
||||||
|
editableControlFrame.origin.x = -editableControlFrame.size.width
|
||||||
|
strongSelf.editableControlNode = nil
|
||||||
|
transition.updateAlpha(node: editableControlNode, alpha: 0.0)
|
||||||
|
transition.updateFrame(node: editableControlNode, frame: editableControlFrame, completion: { [weak editableControlNode] _ in
|
||||||
|
editableControlNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = statusApply()
|
let _ = statusApply()
|
||||||
let _ = labelApply()
|
let _ = labelApply()
|
||||||
@@ -228,30 +358,31 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let bottomStripeOffset: CGFloat
|
let bottomStripeOffset: CGFloat
|
||||||
switch neighbors.bottom {
|
switch neighbors.bottom {
|
||||||
case .sameSection(false):
|
case .sameSection(false):
|
||||||
bottomStripeInset = leftInset
|
bottomStripeInset = leftInset + editingOffset
|
||||||
bottomStripeOffset = -separatorHeight
|
bottomStripeOffset = -separatorHeight
|
||||||
default:
|
default:
|
||||||
bottomStripeInset = 0.0
|
bottomStripeInset = 0.0
|
||||||
bottomStripeOffset = 0.0
|
bottomStripeOffset = 0.0
|
||||||
}
|
}
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: 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: layoutSize.width, height: separatorHeight))
|
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset, y: 5.0), size: titleLayout.size)
|
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 5.0), size: titleLayout.size))
|
||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset, y: 25.0), size: statusLayout.size)
|
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 25.0), size: statusLayout.size))
|
||||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: revealOffset + width - labelLayout.size.width - 15.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0 - labelLayout.size.height / 10.0)), size: labelLayout.size)
|
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + width - labelLayout.size.width - 15.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0 - labelLayout.size.height / 10.0)), size: labelLayout.size))
|
||||||
|
|
||||||
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: revealOffset + 12.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0))
|
transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + 12.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0)))
|
||||||
if let peer = item.peer {
|
strongSelf.avatarNode.setPeer(account: item.account, peer: item.peer)
|
||||||
strongSelf.avatarNode.setPeer(account: item.account, peer: peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: 48.0 + UIScreenPixel + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: 48.0 + UIScreenPixel + UIScreenPixel))
|
||||||
|
|
||||||
if let presence = item.presence as? TelegramUserPresence {
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
strongSelf.peerPresenceManager?.reset(presence: presence)
|
strongSelf.peerPresenceManager?.reset(presence: presence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.setRevealOptions(peerRevealOptions)
|
||||||
|
strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -306,6 +437,44 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
super.updateRevealOffset(offset: offset, transition: transition)
|
super.updateRevealOffset(offset: offset, transition: transition)
|
||||||
|
|
||||||
|
let leftInset: CGFloat = 65.0
|
||||||
|
let width = self.bounds.size.width
|
||||||
|
|
||||||
|
let editingOffset: CGFloat
|
||||||
|
if let editableControlNode = self.editableControlNode {
|
||||||
|
editingOffset = editableControlNode.bounds.size.width
|
||||||
|
var editableControlFrame = editableControlNode.frame
|
||||||
|
editableControlFrame.origin.x = offset
|
||||||
|
transition.updateFrame(node: editableControlNode, frame: editableControlFrame)
|
||||||
|
} else {
|
||||||
|
editingOffset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 5.0), size: self.titleNode.bounds.size))
|
||||||
|
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 25.0), size: self.statusNode.bounds.size))
|
||||||
|
transition.updateFrame(node: self.labelNode, frame: CGRect(origin: CGPoint(x: revealOffset + width - self.labelNode.bounds.size.width - 15.0, y: floor((self.contentSize.height - self.labelNode.bounds.size.height) / 2.0 - self.labelNode.bounds.size.height / 10.0)), size: self.labelNode.bounds.size))
|
||||||
|
|
||||||
|
transition.updateFrame(node: avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + 12.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func revealOptionsInteractivelyOpened() {
|
||||||
|
if let (item, _, _) = self.layoutParams {
|
||||||
|
item.setPeerIdWithRevealedOptions(item.peer.id, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func revealOptionsInteractivelyClosed() {
|
||||||
|
if let (item, _, _) = self.layoutParams {
|
||||||
|
item.setPeerIdWithRevealedOptions(nil, item.peer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func revealOptionSelected(_ option: ItemListRevealOption) {
|
||||||
|
self.setRevealOptionsOpened(false, animated: true)
|
||||||
|
self.revealOptionsInteractivelyClosed()
|
||||||
|
|
||||||
|
if let (item, _, _) = self.layoutParams {
|
||||||
|
item.removePeer(item.peer.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
191
TelegramUI/ItemListSingleLineInputItem.swift
Normal file
191
TelegramUI/ItemListSingleLineInputItem.swift
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||||
|
let title: NSAttributedString
|
||||||
|
let text: String
|
||||||
|
let placeholder: String
|
||||||
|
let sectionId: ItemListSectionId
|
||||||
|
let action: () -> Void
|
||||||
|
let textUpdated: (String) -> Void
|
||||||
|
|
||||||
|
init(title: NSAttributedString, text: String, placeholder: String, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, action: @escaping () -> Void) {
|
||||||
|
self.title = title
|
||||||
|
self.text = text
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.sectionId = sectionId
|
||||||
|
self.textUpdated = textUpdated
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||||
|
async {
|
||||||
|
let node = ItemListSingleLineInputItemNode()
|
||||||
|
let (layout, apply) = node.asyncLayout()(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
|
||||||
|
node.contentSize = layout.contentSize
|
||||||
|
node.insets = layout.insets
|
||||||
|
|
||||||
|
completion(node, {
|
||||||
|
return (nil, { apply() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||||
|
if let node = node as? ItemListSingleLineInputItemNode {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
let makeLayout = node.asyncLayout()
|
||||||
|
|
||||||
|
async {
|
||||||
|
let (layout, apply) = makeLayout(self, width, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
completion(layout, {
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let titleFont = Font.regular(17.0)
|
||||||
|
|
||||||
|
class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let topStripeNode: ASDisplayNode
|
||||||
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
|
||||||
|
private let titleNode: TextNode
|
||||||
|
private let textNode: TextFieldNode
|
||||||
|
|
||||||
|
private var item: ItemListSingleLineInputItem?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
|
||||||
|
self.topStripeNode = ASDisplayNode()
|
||||||
|
self.topStripeNode.backgroundColor = UIColor(0xc8c7cc)
|
||||||
|
self.topStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.bottomStripeNode = ASDisplayNode()
|
||||||
|
self.bottomStripeNode.backgroundColor = UIColor(0xc8c7cc)
|
||||||
|
self.bottomStripeNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.titleNode = TextNode()
|
||||||
|
self.textNode = TextFieldNode()
|
||||||
|
|
||||||
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.textNode.textField.typingAttributes = [NSFontAttributeName: Font.regular(17.0)]
|
||||||
|
self.textNode.textField.font = Font.regular(17.0)
|
||||||
|
self.textNode.textField.textColor = .black
|
||||||
|
self.textNode.clipsToBounds = true
|
||||||
|
self.textNode.textField.delegate = self
|
||||||
|
self.textNode.textField.addTarget(self, action: #selector(self.textFieldTextChanged(_:)), for: .editingChanged)
|
||||||
|
self.textNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asyncLayout() -> (_ item: ItemListSingleLineInputItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
|
||||||
|
return { item, width, neighbors in
|
||||||
|
let leftInset: CGFloat = 16.0
|
||||||
|
|
||||||
|
let titleString = NSMutableAttributedString(attributedString: item.title)
|
||||||
|
titleString.removeAttribute(NSFontAttributeName, range: NSMakeRange(0, titleString.length))
|
||||||
|
titleString.addAttributes([NSFontAttributeName: Font.regular(17.0)], range: NSMakeRange(0, titleString.length))
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(titleString, nil, 0, .end, CGSize(width: width - 32 - leftInset, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
|
let separatorHeight = UIScreenPixel
|
||||||
|
|
||||||
|
let contentSize = CGSize(width: width, height: 44.0)
|
||||||
|
let insets = itemListNeighborsGroupedInsets(neighbors)
|
||||||
|
|
||||||
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
|
let layoutSize = layout.size
|
||||||
|
|
||||||
|
let attributedPlaceholderText = NSAttributedString(string: item.placeholder, font: Font.regular(17.0), textColor: UIColor(0x878787))
|
||||||
|
|
||||||
|
return (layout, { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.item = item
|
||||||
|
|
||||||
|
let _ = titleApply()
|
||||||
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||||
|
|
||||||
|
if let currentText = strongSelf.textNode.textField.text {
|
||||||
|
if currentText != item.text {
|
||||||
|
strongSelf.textNode.textField.text = item.text
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.textNode.textField.text = item.text
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset + titleLayout.size.width, y: floor((layout.contentSize.height - 40.0) / 2.0)), size: CGSize(width: max(1.0, width - (leftInset + titleLayout.size.width)), height: 40.0))
|
||||||
|
|
||||||
|
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: 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: layoutSize.width, height: separatorHeight))
|
||||||
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||||
|
|
||||||
|
if strongSelf.textNode.textField.attributedPlaceholder == nil || !strongSelf.textNode.textField.attributedPlaceholder!.isEqual(to: attributedPlaceholderText) {
|
||||||
|
strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func textFieldTextChanged(_ textField: UITextField) {
|
||||||
|
if let item = self.item {
|
||||||
|
if let text = self.textNode.textField.text {
|
||||||
|
item.textUpdated(text)
|
||||||
|
} else {
|
||||||
|
item.textUpdated("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,13 +76,11 @@ class ItemListTextItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
let separatorHeight = UIScreenPixel
|
|
||||||
|
|
||||||
contentSize = CGSize(width: width, height: titleLayout.size.height + verticalInset + verticalInset)
|
contentSize = CGSize(width: width, height: titleLayout.size.height + verticalInset + verticalInset)
|
||||||
insets = itemListNeighborsPlainInsets(neighbors)
|
insets = itemListNeighborsPlainInsets(neighbors)
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||||
let layoutSize = layout.size
|
|
||||||
|
|
||||||
return (layout, { [weak self] in
|
return (layout, { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ private func rendererInputProc(refCon: UnsafeMutableRawPointer, ioActionFlags: U
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
free(buffer.mData)
|
free(buffer.mData)
|
||||||
trace("ManagedAudioRecorder", what: "AudioUnitRender returned \(status)")
|
Logger.shared.log("ManagedAudioRecorder", "AudioUnitRender returned \(status)")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -327,17 +327,17 @@ final class ManagedAudioRecorderContext {
|
|||||||
|
|
||||||
status = AudioOutputUnitStop(audioUnit)
|
status = AudioOutputUnitStop(audioUnit)
|
||||||
if status != noErr {
|
if status != noErr {
|
||||||
trace("ManagedAudioRecorder", what: "AudioOutputUnitStop returned \(status)")
|
Logger.shared.log("ManagedAudioRecorder", "AudioOutputUnitStop returned \(status)")
|
||||||
}
|
}
|
||||||
|
|
||||||
status = AudioUnitUninitialize(audioUnit)
|
status = AudioUnitUninitialize(audioUnit)
|
||||||
if status != noErr {
|
if status != noErr {
|
||||||
trace("ManagedAudioRecorder", what: "AudioUnitUninitialize returned \(status)")
|
Logger.shared.log("ManagedAudioRecorder", "AudioUnitUninitialize returned \(status)")
|
||||||
}
|
}
|
||||||
|
|
||||||
status = AudioComponentInstanceDispose(audioUnit)
|
status = AudioComponentInstanceDispose(audioUnit)
|
||||||
if status != noErr {
|
if status != noErr {
|
||||||
trace("ManagedAudioRecorder", what: "AudioComponentInstanceDispose returned \(status)")
|
Logger.shared.log("ManagedAudioRecorder", "AudioComponentInstanceDispose returned \(status)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,12 +322,12 @@ private final class AudioPlayerRendererContext {
|
|||||||
|
|
||||||
status = AudioOutputUnitStop(audioUnit)
|
status = AudioOutputUnitStop(audioUnit)
|
||||||
if status != noErr {
|
if status != noErr {
|
||||||
trace("AudioPlayerRenderer", what: "AudioOutputUnitStop error \(status)")
|
Logger.shared.log("AudioPlayerRenderer", "AudioOutputUnitStop error \(status)")
|
||||||
}
|
}
|
||||||
|
|
||||||
status = AudioComponentInstanceDispose(audioUnit);
|
status = AudioComponentInstanceDispose(audioUnit);
|
||||||
if status != noErr {
|
if status != noErr {
|
||||||
trace("AudioPlayerRenderer", what: "AudioComponentInstanceDispose error \(status)")
|
Logger.shared.log("AudioPlayerRenderer", "AudioComponentInstanceDispose error \(status)")
|
||||||
}
|
}
|
||||||
self.audioUnit = nil
|
self.audioUnit = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
|||||||
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
arguments.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
arguments.soundSelectionDisposable.set(result.start(next: { [weak arguments] value in
|
arguments.soundSelectionDisposable.set(result.start(next: { [weak arguments] value in
|
||||||
if let value = value {
|
if let value = value {
|
||||||
arguments?.updateMessageSound(value)
|
arguments?.updateGroupSound(value)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,21 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
|
func peerInfoController(account: Account, peer: Peer) -> ViewController? {
|
||||||
|
if let _ = peer as? TelegramGroup {
|
||||||
|
return groupInfoController(account: account, peerId: peer.id)
|
||||||
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
if case .group = channel.info {
|
||||||
|
return groupInfoController(account: account, peerId: peer.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
final class PeerInfoControllerInteraction {
|
final class PeerInfoControllerInteraction {
|
||||||
let updateState: ((PeerInfoState?) -> PeerInfoState?) -> Void
|
let updateState: ((PeerInfoState?) -> PeerInfoState?) -> Void
|
||||||
let openSharedMedia: () -> Void
|
let openSharedMedia: () -> Void
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ struct PeerInfoEntries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func peerInfoEntries(view: PeerView, state: PeerInfoState?) -> PeerInfoEntries {
|
func peerInfoEntries(view: PeerView, state: PeerInfoState?) -> PeerInfoEntries {
|
||||||
if let user = view.peers[view.peerId] as? TelegramUser {
|
/*if let user = view.peers[view.peerId] as? TelegramUser {
|
||||||
return userInfoEntries(view: view, state: state)
|
return userInfoEntries(view: view, state: state)
|
||||||
} else if let secretChat = view.peers[view.peerId] as? TelegramSecretChat {
|
} else if let secretChat = view.peers[view.peerId] as? TelegramSecretChat {
|
||||||
return userInfoEntries(view: view, state: state)
|
return userInfoEntries(view: view, state: state)
|
||||||
@@ -61,6 +61,6 @@ func peerInfoEntries(view: PeerView, state: PeerInfoState?) -> PeerInfoEntries {
|
|||||||
}
|
}
|
||||||
} else if let group = view.peers[view.peerId] as? TelegramGroup {
|
} else if let group = view.peers[view.peerId] as? TelegramGroup {
|
||||||
return groupInfoEntries(view: view, state: state)
|
return groupInfoEntries(view: view, state: state)
|
||||||
}
|
}*/
|
||||||
return PeerInfoEntries(entries: [], leftNavigationButton: nil, rightNavigationButton: nil)
|
return PeerInfoEntries(entries: [], leftNavigationButton: nil, rightNavigationButton: nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +1,405 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Display
|
import Display
|
||||||
import Postbox
|
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
public class SettingsController: ListController {
|
private struct SettingsItemArguments {
|
||||||
private let account: Account
|
let account: Account
|
||||||
|
let accountManager: AccountManager
|
||||||
|
|
||||||
private let peer = Promise<Peer>()
|
let pushController: (ViewController) -> Void
|
||||||
private let connectionStatus = Promise<ConnectionStatus>(.Online)
|
let presentController: (ViewController) -> Void
|
||||||
private let peerAndConnectionStatusDisposable = MetaDisposable()
|
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
|
||||||
|
let saveEditingState: () -> Void
|
||||||
public init(account: Account) {
|
let logout: () -> Void
|
||||||
self.account = account
|
}
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.title = "Settings"
|
|
||||||
self.tabBarItem.title = "Settings"
|
|
||||||
self.tabBarItem.image = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")?.precomposed()
|
|
||||||
self.tabBarItem.selectedImage = UIImage(bundleImageName: "Chat List/Tabs/IconSettingsSelected")?.precomposed()
|
|
||||||
|
|
||||||
let deselectAction = { [weak self] () -> Void in
|
|
||||||
self?.listDisplayNode.listView.clearHighlightAnimated(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.items = [
|
|
||||||
SettingsAccountInfoItem(account: account, peer: nil, connectionStatus: .Online),
|
|
||||||
ListControllerButtonItem(title: "Set Profile Photo", action: deselectAction),
|
|
||||||
ListControllerSpacerItem(height: 35.0),
|
|
||||||
ListControllerDisclosureActionItem(title: "Notifications and Sounds", action: deselectAction),
|
|
||||||
ListControllerDisclosureActionItem(title: "Privacy and Security", action: deselectAction),
|
|
||||||
ListControllerDisclosureActionItem(title: "Data and Storage", action: { [weak self] in
|
|
||||||
deselectAction()
|
|
||||||
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.navigationController?.pushViewController(DataAndStorageSettingsController(account: strongSelf.account), animated: true)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
//SettingsWallpaperListItem(),
|
|
||||||
ListControllerSpacerItem(height: 35.0),
|
|
||||||
ListControllerDisclosureActionItem(title: "Phone Number", action: deselectAction),
|
|
||||||
ListControllerDisclosureActionItem(title: "Username", action: deselectAction),
|
|
||||||
ListControllerSpacerItem(height: 35.0),
|
|
||||||
ListControllerDisclosureActionItem(title: "Ask a Question", action: deselectAction),
|
|
||||||
ListControllerDisclosureActionItem(title: "Telegram FAQ", action: deselectAction),
|
|
||||||
ListControllerSpacerItem(height: 35.0),
|
|
||||||
ListControllerButtonItem(title: "Logout", action: { }, color: UIColor.red),
|
|
||||||
ListControllerSpacerItem(height: 35.0)
|
|
||||||
]
|
|
||||||
|
|
||||||
let peerAndConnectionStatus = combineLatest(peer.get(), connectionStatus.get()) |> deliverOn(Queue.mainQueue()) |> afterNext { [weak self] peer, connectionStatus in
|
|
||||||
if let strongSelf = self {
|
|
||||||
let item = SettingsAccountInfoItem(account: account, peer: peer, connectionStatus: connectionStatus)
|
|
||||||
strongSelf.items[0] = item
|
|
||||||
if strongSelf.isNodeLoaded {
|
|
||||||
strongSelf.listDisplayNode.listView.transaction(deleteIndices: [ListViewDeleteItem(index: 0, directionHint: nil)], insertIndicesAndItems: [ListViewInsertItem(index: 0, previousIndex: 0, item: item, directionHint: .Down)], updateIndicesAndItems: [], options: [.AnimateInsertion], updateOpaqueState: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
peerAndConnectionStatusDisposable.set(peerAndConnectionStatus.start())
|
|
||||||
|
|
||||||
peer.set(account.postbox.loadedPeerWithId(account.peerId))
|
|
||||||
connectionStatus.set(account.network.connectionStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
private enum SettingsSection: Int32 {
|
||||||
fatalError("init(coder:) has not been implemented")
|
case info
|
||||||
|
case generalSettings
|
||||||
|
case accountSettings
|
||||||
|
case help
|
||||||
|
case logOut
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum SettingsEntry: ItemListNodeEntry {
|
||||||
|
case userInfo(Peer?, CachedPeerData?, ItemListAvatarAndNameInfoItemState)
|
||||||
|
case setProfilePhoto
|
||||||
|
|
||||||
|
case notificationsAndSounds
|
||||||
|
case privacyAndSecurity
|
||||||
|
case dataAndStorage
|
||||||
|
case stickers
|
||||||
|
case phoneNumber(String)
|
||||||
|
case username(String)
|
||||||
|
case askAQuestion
|
||||||
|
case faq
|
||||||
|
case debug
|
||||||
|
case logOut
|
||||||
|
|
||||||
|
var section: ItemListSectionId {
|
||||||
|
switch self {
|
||||||
|
case .userInfo, .setProfilePhoto:
|
||||||
|
return SettingsSection.info.rawValue
|
||||||
|
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .stickers:
|
||||||
|
return SettingsSection.generalSettings.rawValue
|
||||||
|
case .phoneNumber, .username:
|
||||||
|
return SettingsSection.accountSettings.rawValue
|
||||||
|
case .askAQuestion, .faq, .debug:
|
||||||
|
return SettingsSection.help.rawValue
|
||||||
|
case .logOut:
|
||||||
|
return SettingsSection.logOut.rawValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
var stableId: Int32 {
|
||||||
peerAndConnectionStatusDisposable.dispose()
|
switch self {
|
||||||
|
case .userInfo:
|
||||||
|
return 0
|
||||||
|
case .setProfilePhoto:
|
||||||
|
return 1
|
||||||
|
case .notificationsAndSounds:
|
||||||
|
return 2
|
||||||
|
case .privacyAndSecurity:
|
||||||
|
return 3
|
||||||
|
case .dataAndStorage:
|
||||||
|
return 4
|
||||||
|
case .stickers:
|
||||||
|
return 5
|
||||||
|
case .phoneNumber:
|
||||||
|
return 6
|
||||||
|
case .username:
|
||||||
|
return 7
|
||||||
|
case .askAQuestion:
|
||||||
|
return 8
|
||||||
|
case .faq:
|
||||||
|
return 9
|
||||||
|
case .debug:
|
||||||
|
return 10
|
||||||
|
case .logOut:
|
||||||
|
return 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case let .userInfo(lhsPeer, lhsCachedData, lhsEditingState):
|
||||||
|
if case let .userInfo(rhsPeer, rhsCachedData, rhsEditingState) = rhs {
|
||||||
|
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
|
||||||
|
if !lhsPeer.isEqual(rhsPeer) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (lhsPeer != nil) != (rhsPeer != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData {
|
||||||
|
if !lhsCachedData.isEqual(to: rhsCachedData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (lhsCachedData != nil) != (rhsCachedData != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsEditingState != rhsEditingState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .setProfilePhoto:
|
||||||
|
if case .setProfilePhoto = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .notificationsAndSounds:
|
||||||
|
if case .notificationsAndSounds = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .privacyAndSecurity:
|
||||||
|
if case .privacyAndSecurity = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .dataAndStorage:
|
||||||
|
if case .dataAndStorage = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .stickers:
|
||||||
|
if case .stickers = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .phoneNumber(number):
|
||||||
|
if case .phoneNumber(number) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .username(address):
|
||||||
|
if case .username(address) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .askAQuestion:
|
||||||
|
if case .askAQuestion = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .faq:
|
||||||
|
if case .faq = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .debug:
|
||||||
|
if case .debug = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .logOut:
|
||||||
|
if case .logOut = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func <(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool {
|
||||||
|
return lhs.stableId < rhs.stableId
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(_ arguments: SettingsItemArguments) -> ListViewItem {
|
||||||
|
switch self {
|
||||||
|
case let .userInfo(peer, cachedData, state):
|
||||||
|
return ItemListAvatarAndNameInfoItem(account: arguments.account, peer: peer, presence: TelegramUserPresence(status: .present(until: Int32.max)), cachedData: cachedData, state: state, sectionId: ItemListSectionId(self.section), style: .blocks, editingNameUpdated: { editingName in
|
||||||
|
arguments.updateEditingName(editingName)
|
||||||
|
})
|
||||||
|
case .setProfilePhoto:
|
||||||
|
return ItemListActionItem(title: "Set Profile Photo", kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
arguments.presentController(standardTextAlertController(title: "Verification Failed", text: "Your Apple ID or password is incorrect.", actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: "Cancel", action: {
|
||||||
|
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .defaultAction, title: "OK", action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
]))
|
||||||
|
})
|
||||||
|
case .notificationsAndSounds:
|
||||||
|
return ItemListDisclosureItem(title: "Notifications and Sounds", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
arguments.pushController(notificationsAndSoundsController(account: arguments.account))
|
||||||
|
})
|
||||||
|
case .privacyAndSecurity:
|
||||||
|
return ItemListDisclosureItem(title: "Privacy and Security", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case .dataAndStorage:
|
||||||
|
return ItemListDisclosureItem(title: "Data and Storage", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case .stickers:
|
||||||
|
return ItemListDisclosureItem(title: "Stickers", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case let .phoneNumber(number):
|
||||||
|
return ItemListDisclosureItem(title: "Phone Number", label: number, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case let .username(address):
|
||||||
|
return ItemListDisclosureItem(title: "Username", label: address, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case .askAQuestion:
|
||||||
|
return ItemListDisclosureItem(title: "Ask a Question", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case .faq:
|
||||||
|
return ItemListDisclosureItem(title: "Telegram FAQ", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
|
||||||
|
})
|
||||||
|
case .debug:
|
||||||
|
return ItemListDisclosureItem(title: "Debug", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
arguments.pushController(debugController(account: arguments.account, accountManager: arguments.accountManager))
|
||||||
|
})
|
||||||
|
case .logOut:
|
||||||
|
return ItemListActionItem(title: "Log Out", kind: .destructive, alignment: .center, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||||
|
arguments.logout()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct SettingsEditingState: Equatable {
|
||||||
|
let editingName: ItemListAvatarAndNameInfoItemName
|
||||||
|
|
||||||
|
static func ==(lhs: SettingsEditingState, rhs: SettingsEditingState) -> Bool {
|
||||||
|
if lhs.editingName != rhs.editingName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct SettingsState: Equatable {
|
||||||
|
let editingState: SettingsEditingState?
|
||||||
|
let updatingName: ItemListAvatarAndNameInfoItemName?
|
||||||
|
|
||||||
|
func withUpdatedEditingState(_ editingState: SettingsEditingState?) -> SettingsState {
|
||||||
|
return SettingsState(editingState: editingState, updatingName: self.updatingName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withUpdatedUpdatingName(_ updatingName: ItemListAvatarAndNameInfoItemName?) -> SettingsState {
|
||||||
|
return SettingsState(editingState: self.editingState, updatingName: updatingName)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: SettingsState, rhs: SettingsState) -> Bool {
|
||||||
|
if lhs.editingState != rhs.editingState {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.updatingName != rhs.updatingName {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func settingsEntries(state: SettingsState, view: PeerView) -> [SettingsEntry] {
|
||||||
|
var entries: [SettingsEntry] = []
|
||||||
|
|
||||||
|
if let peer = peerViewMainPeer(view) as? TelegramUser {
|
||||||
|
let userInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingState?.editingName, updatingName: state.updatingName)
|
||||||
|
entries.append(.userInfo(peer, view.cachedData, userInfoState))
|
||||||
|
entries.append(.setProfilePhoto)
|
||||||
|
|
||||||
|
entries.append(.notificationsAndSounds)
|
||||||
|
entries.append(.privacyAndSecurity)
|
||||||
|
entries.append(.dataAndStorage)
|
||||||
|
entries.append(.stickers)
|
||||||
|
|
||||||
|
if let phone = peer.phone {
|
||||||
|
entries.append(.phoneNumber(formatPhoneNumber(phone)))
|
||||||
|
}
|
||||||
|
entries.append(.username(peer.addressName == nil ? "" : ("@" + peer.addressName!)))
|
||||||
|
|
||||||
|
entries.append(.askAQuestion)
|
||||||
|
entries.append(.faq)
|
||||||
|
entries.append(.debug)
|
||||||
|
|
||||||
|
if let _ = state.editingState {
|
||||||
|
entries.append(.logOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
public func settingsController(account: Account, accountManager: AccountManager) -> ViewController {
|
||||||
|
let statePromise = ValuePromise(SettingsState(editingState: nil, updatingName: nil), ignoreRepeated: true)
|
||||||
|
let stateValue = Atomic(value: SettingsState(editingState: nil, updatingName: nil))
|
||||||
|
let updateState: ((SettingsState) -> SettingsState) -> Void = { f in
|
||||||
|
statePromise.set(stateValue.modify { f($0) })
|
||||||
|
}
|
||||||
|
|
||||||
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
|
var presentControllerImpl: ((ViewController) -> Void)?
|
||||||
|
|
||||||
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
|
let updatePeerNameDisposable = MetaDisposable()
|
||||||
|
actionsDisposable.add(updatePeerNameDisposable)
|
||||||
|
|
||||||
|
let arguments = SettingsItemArguments(account: account, accountManager: accountManager, pushController: { controller in
|
||||||
|
pushControllerImpl?(controller)
|
||||||
|
}, presentController: { controller in
|
||||||
|
presentControllerImpl?(controller)
|
||||||
|
}, updateEditingName: { editingName in
|
||||||
|
updateState { state in
|
||||||
|
if let _ = state.editingState {
|
||||||
|
return state.withUpdatedEditingState(SettingsEditingState(editingName: editingName))
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, saveEditingState: {
|
||||||
|
var updateName: ItemListAvatarAndNameInfoItemName?
|
||||||
|
updateState { state in
|
||||||
|
if let editingState = state.editingState {
|
||||||
|
updateName = editingState.editingName
|
||||||
|
return state.withUpdatedEditingState(nil).withUpdatedUpdatingName(editingState.editingName)
|
||||||
|
} else {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let updateName = updateName, case let .personName(firstName, lastName) = updateName {
|
||||||
|
updatePeerNameDisposable.set((updateAccountPeerName(account: account, firstName: firstName, lastName: lastName) |> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedUpdatingName(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start())
|
||||||
|
}
|
||||||
|
}, logout: {
|
||||||
|
let alertController = standardTextAlertController(title: NSLocalizedString("Settings.LogoutConfirmationTitle", comment: ""), text: NSLocalizedString("Settings.LogoutConfirmationText", comment: ""), actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: "Cancel", action: {
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .defaultAction, title: "OK", action: {
|
||||||
|
let _ = logoutFromAccount(id: account.id, accountManager: accountManager).start()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
presentControllerImpl?(alertController)
|
||||||
|
})
|
||||||
|
|
||||||
|
let peerView = account.viewTracker.peerView(account.peerId)
|
||||||
|
|
||||||
|
let signal = combineLatest(statePromise.get(), peerView)
|
||||||
|
|> map { state, view -> (ItemListControllerState, (ItemListNodeState<SettingsEntry>, SettingsEntry.ItemGenerationArguments)) in
|
||||||
|
let peer = peerViewMainPeer(view)
|
||||||
|
let rightNavigationButton: ItemListNavigationButton
|
||||||
|
if let _ = state.editingState {
|
||||||
|
rightNavigationButton = ItemListNavigationButton(title: "Done", style: .bold, enabled: true, action: {
|
||||||
|
arguments.saveEditingState()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
rightNavigationButton = ItemListNavigationButton(title: "Edit", style: .regular, enabled: true, action: {
|
||||||
|
if let peer = peer as? TelegramUser {
|
||||||
|
updateState { state in
|
||||||
|
return state.withUpdatedEditingState(SettingsEditingState(editingName: ItemListAvatarAndNameInfoItemName(peer.indexName)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let controllerState = ItemListControllerState(title: "Settings", leftNavigationButton: nil, rightNavigationButton: rightNavigationButton)
|
||||||
|
let listState = ItemListNodeState(entries: settingsEntries(state: state, view: view), style: .blocks)
|
||||||
|
|
||||||
|
return (controllerState, (listState, arguments))
|
||||||
|
} |> afterDisposed {
|
||||||
|
actionsDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = ItemListController(signal)
|
||||||
|
controller.tabBarItem.title = "Settings"
|
||||||
|
controller.tabBarItem.image = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")?.precomposed()
|
||||||
|
controller.tabBarItem.selectedImage = UIImage(bundleImageName: "Chat List/Tabs/IconSettingsSelected")?.precomposed()
|
||||||
|
pushControllerImpl = { [weak controller] value in
|
||||||
|
(controller?.navigationController as? NavigationController)?.pushViewController(value)
|
||||||
|
}
|
||||||
|
presentControllerImpl = { [weak controller] value in
|
||||||
|
controller?.present(value, in: .window)
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,380 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
|
||||||
import Postbox
|
|
||||||
import TelegramCore
|
|
||||||
|
|
||||||
private struct SettingsItemArguments {
|
|
||||||
let account: Account
|
|
||||||
|
|
||||||
let pushController: (ViewController) -> Void
|
|
||||||
let presentController: (ViewController) -> Void
|
|
||||||
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
|
|
||||||
let saveEditingState: () -> Void
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum SettingsSection: Int32 {
|
|
||||||
case info
|
|
||||||
case generalSettings
|
|
||||||
case accountSettings
|
|
||||||
case help
|
|
||||||
case logOut
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum SettingsEntry: ItemListNodeEntry {
|
|
||||||
case userInfo(Peer?, CachedPeerData?, ItemListAvatarAndNameInfoItemState)
|
|
||||||
case setProfilePhoto
|
|
||||||
|
|
||||||
case notificationsAndSounds
|
|
||||||
case privacyAndSecurity
|
|
||||||
case dataAndStorage
|
|
||||||
case stickers
|
|
||||||
case phoneNumber(String)
|
|
||||||
case username(String)
|
|
||||||
case askAQuestion
|
|
||||||
case faq
|
|
||||||
case logOut
|
|
||||||
|
|
||||||
var section: ItemListSectionId {
|
|
||||||
switch self {
|
|
||||||
case .userInfo, .setProfilePhoto:
|
|
||||||
return SettingsSection.info.rawValue
|
|
||||||
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .stickers:
|
|
||||||
return SettingsSection.generalSettings.rawValue
|
|
||||||
case .phoneNumber, .username:
|
|
||||||
return SettingsSection.accountSettings.rawValue
|
|
||||||
case .askAQuestion, .faq:
|
|
||||||
return SettingsSection.help.rawValue
|
|
||||||
case .logOut:
|
|
||||||
return SettingsSection.logOut.rawValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var stableId: Int32 {
|
|
||||||
switch self {
|
|
||||||
case .userInfo:
|
|
||||||
return 0
|
|
||||||
case .setProfilePhoto:
|
|
||||||
return 1
|
|
||||||
case .notificationsAndSounds:
|
|
||||||
return 2
|
|
||||||
case .privacyAndSecurity:
|
|
||||||
return 3
|
|
||||||
case .dataAndStorage:
|
|
||||||
return 4
|
|
||||||
case .stickers:
|
|
||||||
return 5
|
|
||||||
case .phoneNumber:
|
|
||||||
return 6
|
|
||||||
case .username:
|
|
||||||
return 7
|
|
||||||
case .askAQuestion:
|
|
||||||
return 8
|
|
||||||
case .faq:
|
|
||||||
return 9
|
|
||||||
case .logOut:
|
|
||||||
return 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool {
|
|
||||||
switch lhs {
|
|
||||||
case let .userInfo(lhsPeer, lhsCachedData, lhsEditingState):
|
|
||||||
if case let .userInfo(rhsPeer, rhsCachedData, rhsEditingState) = rhs {
|
|
||||||
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
|
|
||||||
if !lhsPeer.isEqual(rhsPeer) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if (lhsPeer != nil) != (rhsPeer != nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData {
|
|
||||||
if !lhsCachedData.isEqual(to: rhsCachedData) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if (lhsCachedData != nil) != (rhsCachedData != nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhsEditingState != rhsEditingState {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .setProfilePhoto:
|
|
||||||
if case .setProfilePhoto = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .notificationsAndSounds:
|
|
||||||
if case .notificationsAndSounds = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .privacyAndSecurity:
|
|
||||||
if case .privacyAndSecurity = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .dataAndStorage:
|
|
||||||
if case .dataAndStorage = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .stickers:
|
|
||||||
if case .stickers = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case let .phoneNumber(number):
|
|
||||||
if case .phoneNumber(number) = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case let .username(address):
|
|
||||||
if case .username(address) = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .askAQuestion:
|
|
||||||
if case .askAQuestion = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .faq:
|
|
||||||
if case .faq = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case .logOut:
|
|
||||||
if case .logOut = rhs {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func <(lhs: SettingsEntry, rhs: SettingsEntry) -> Bool {
|
|
||||||
return lhs.stableId < rhs.stableId
|
|
||||||
}
|
|
||||||
|
|
||||||
func item(_ arguments: SettingsItemArguments) -> ListViewItem {
|
|
||||||
switch self {
|
|
||||||
case let .userInfo(peer, cachedData, state):
|
|
||||||
return ItemListAvatarAndNameInfoItem(account: arguments.account, peer: peer, presence: TelegramUserPresence(status: .present(until: Int32.max)), cachedData: cachedData, state: state, sectionId: ItemListSectionId(self.section), style: .blocks, editingNameUpdated: { editingName in
|
|
||||||
arguments.updateEditingName(editingName)
|
|
||||||
})
|
|
||||||
case .setProfilePhoto:
|
|
||||||
return ItemListActionItem(title: "Set Profile Photo", kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
arguments.presentController(standardTextAlertController(title: "Verification Failed", text: "Your Apple ID or password is incorrect.", actions: [
|
|
||||||
TextAlertAction(type: .genericAction, title: "Cancel", action: {
|
|
||||||
|
|
||||||
}),
|
|
||||||
TextAlertAction(type: .defaultAction, title: "OK", action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
]))
|
|
||||||
})
|
|
||||||
case .notificationsAndSounds:
|
|
||||||
return ItemListDisclosureItem(title: "Notifications and Sounds", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
arguments.pushController(notificationsAndSoundsController(account: arguments.account))
|
|
||||||
})
|
|
||||||
case .privacyAndSecurity:
|
|
||||||
return ItemListDisclosureItem(title: "Privacy and Security", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
case .dataAndStorage:
|
|
||||||
return ItemListDisclosureItem(title: "Data and Storage", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
case .stickers:
|
|
||||||
return ItemListDisclosureItem(title: "Stickers", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
case let .phoneNumber(number):
|
|
||||||
return ItemListDisclosureItem(title: "Phone Number", label: number, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
case let .username(address):
|
|
||||||
return ItemListDisclosureItem(title: "Username", label: address, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
case .askAQuestion:
|
|
||||||
return ItemListDisclosureItem(title: "Ask a Question", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
case .faq:
|
|
||||||
return ItemListDisclosureItem(title: "Telegram FAQ", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
case .logOut:
|
|
||||||
return ItemListActionItem(title: "Log Out", kind: .destructive, alignment: .center, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct SettingsEditingState: Equatable {
|
|
||||||
let editingName: ItemListAvatarAndNameInfoItemName
|
|
||||||
|
|
||||||
static func ==(lhs: SettingsEditingState, rhs: SettingsEditingState) -> Bool {
|
|
||||||
if lhs.editingName != rhs.editingName {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct SettingsState: Equatable {
|
|
||||||
let editingState: SettingsEditingState?
|
|
||||||
let updatingName: ItemListAvatarAndNameInfoItemName?
|
|
||||||
|
|
||||||
func withUpdatedEditingState(_ editingState: SettingsEditingState?) -> SettingsState {
|
|
||||||
return SettingsState(editingState: editingState, updatingName: self.updatingName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func withUpdatedUpdatingName(_ updatingName: ItemListAvatarAndNameInfoItemName?) -> SettingsState {
|
|
||||||
return SettingsState(editingState: self.editingState, updatingName: updatingName)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: SettingsState, rhs: SettingsState) -> Bool {
|
|
||||||
if lhs.editingState != rhs.editingState {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.updatingName != rhs.updatingName {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func settingsEntries(state: SettingsState, view: PeerView) -> [SettingsEntry] {
|
|
||||||
var entries: [SettingsEntry] = []
|
|
||||||
|
|
||||||
if let peer = peerViewMainPeer(view) as? TelegramUser {
|
|
||||||
let userInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingState?.editingName, updatingName: state.updatingName)
|
|
||||||
entries.append(.userInfo(peer, view.cachedData, userInfoState))
|
|
||||||
entries.append(.setProfilePhoto)
|
|
||||||
|
|
||||||
entries.append(.notificationsAndSounds)
|
|
||||||
entries.append(.privacyAndSecurity)
|
|
||||||
entries.append(.dataAndStorage)
|
|
||||||
entries.append(.stickers)
|
|
||||||
|
|
||||||
if let phone = peer.phone {
|
|
||||||
entries.append(.phoneNumber(formatPhoneNumber(phone)))
|
|
||||||
}
|
|
||||||
entries.append(.username(peer.addressName == nil ? "" : ("@" + peer.addressName!)))
|
|
||||||
|
|
||||||
entries.append(.askAQuestion)
|
|
||||||
entries.append(.faq)
|
|
||||||
|
|
||||||
if let _ = state.editingState {
|
|
||||||
entries.append(.logOut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
public func settingsController(account: Account) -> ViewController {
|
|
||||||
let statePromise = ValuePromise(SettingsState(editingState: nil, updatingName: nil), ignoreRepeated: true)
|
|
||||||
let stateValue = Atomic(value: SettingsState(editingState: nil, updatingName: nil))
|
|
||||||
let updateState: ((SettingsState) -> SettingsState) -> Void = { f in
|
|
||||||
statePromise.set(stateValue.modify { f($0) })
|
|
||||||
}
|
|
||||||
|
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
|
||||||
var presentControllerImpl: ((ViewController) -> Void)?
|
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
|
||||||
|
|
||||||
let updatePeerNameDisposable = MetaDisposable()
|
|
||||||
actionsDisposable.add(updatePeerNameDisposable)
|
|
||||||
|
|
||||||
let arguments = SettingsItemArguments(account: account, pushController: { controller in
|
|
||||||
pushControllerImpl?(controller)
|
|
||||||
}, presentController: { controller in
|
|
||||||
presentControllerImpl?(controller)
|
|
||||||
}, updateEditingName: { editingName in
|
|
||||||
updateState { state in
|
|
||||||
if let _ = state.editingState {
|
|
||||||
return state.withUpdatedEditingState(SettingsEditingState(editingName: editingName))
|
|
||||||
} else {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, saveEditingState: {
|
|
||||||
var updateName: ItemListAvatarAndNameInfoItemName?
|
|
||||||
updateState { state in
|
|
||||||
if let editingState = state.editingState {
|
|
||||||
updateName = editingState.editingName
|
|
||||||
return state.withUpdatedEditingState(nil).withUpdatedUpdatingName(editingState.editingName)
|
|
||||||
} else {
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let updateName = updateName, case let .personName(firstName, lastName) = updateName {
|
|
||||||
updatePeerNameDisposable.set((updateAccountPeerName(account: account, firstName: firstName, lastName: lastName) |> afterDisposed {
|
|
||||||
Queue.mainQueue().async {
|
|
||||||
updateState { state in
|
|
||||||
return state.withUpdatedUpdatingName(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
let peerView = account.viewTracker.peerView(account.peerId)
|
|
||||||
|
|
||||||
let signal = combineLatest(statePromise.get(), peerView)
|
|
||||||
|> map { state, view -> (ItemListControllerState, (ItemListNodeState<SettingsEntry>, SettingsEntry.ItemGenerationArguments)) in
|
|
||||||
let peer = peerViewMainPeer(view)
|
|
||||||
let rightNavigationButton: ItemListNavigationButton
|
|
||||||
if let _ = state.editingState {
|
|
||||||
rightNavigationButton = ItemListNavigationButton(title: "Done", style: .bold, enabled: true, action: {
|
|
||||||
arguments.saveEditingState()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
rightNavigationButton = ItemListNavigationButton(title: "Edit", style: .bold, enabled: true, action: {
|
|
||||||
if let peer = peer as? TelegramUser {
|
|
||||||
updateState { state in
|
|
||||||
return state.withUpdatedEditingState(SettingsEditingState(editingName: ItemListAvatarAndNameInfoItemName(peer.indexName)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(title: "Settings", leftNavigationButton: nil, rightNavigationButton: rightNavigationButton)
|
|
||||||
let listState = ItemListNodeState(entries: settingsEntries(state: state, view: view), style: .blocks)
|
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
|
||||||
} |> afterDisposed {
|
|
||||||
actionsDisposable.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
let controller = ItemListController(signal)
|
|
||||||
controller.tabBarItem.title = "Settings"
|
|
||||||
controller.tabBarItem.image = UIImage(bundleImageName: "Chat List/Tabs/IconSettings")?.precomposed()
|
|
||||||
controller.tabBarItem.selectedImage = UIImage(bundleImageName: "Chat List/Tabs/IconSettingsSelected")?.precomposed()
|
|
||||||
pushControllerImpl = { [weak controller] value in
|
|
||||||
(controller?.navigationController as? NavigationController)?.pushViewController(value)
|
|
||||||
}
|
|
||||||
presentControllerImpl = { [weak controller] value in
|
|
||||||
controller?.present(value, in: .window)
|
|
||||||
}
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,12 @@ public final class TelegramApplicationContext {
|
|||||||
let sharedChatMediaInputNode = Atomic<ChatMediaInputNode?>(value: nil)
|
let sharedChatMediaInputNode = Atomic<ChatMediaInputNode?>(value: nil)
|
||||||
let mediaManager = MediaManager()
|
let mediaManager = MediaManager()
|
||||||
|
|
||||||
public init(openUrl: @escaping (String) -> Void, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void) {
|
public let applicationInForeground: Signal<Bool, NoError>
|
||||||
|
|
||||||
|
public init(openUrl: @escaping (String) -> Void, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>) {
|
||||||
self.openUrl = openUrl
|
self.openUrl = openUrl
|
||||||
self.getTopWindow = getTopWindow
|
self.getTopWindow = getTopWindow
|
||||||
self.displayNotification = displayNotification
|
self.displayNotification = displayNotification
|
||||||
|
self.applicationInForeground = applicationInForeground
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ final class TextNode: ASDisplayNode {
|
|||||||
var lineAdditionalWidth: CGFloat = 0.0
|
var lineAdditionalWidth: CGFloat = 0.0
|
||||||
|
|
||||||
if cutoutEnabled {
|
if cutoutEnabled {
|
||||||
if lineOriginY < cutoutMaxY && lineOriginY + fontLineHeight > cutoutMinY {
|
if lineOriginY - fontLineHeight < cutoutMaxY && lineOriginY + fontLineHeight > cutoutMinY {
|
||||||
lineConstrainedWidth = max(1.0, lineConstrainedWidth - cutoutWidth)
|
lineConstrainedWidth = max(1.0, lineConstrainedWidth - cutoutWidth)
|
||||||
lineCutoutOffset = cutoutOffset
|
lineCutoutOffset = cutoutOffset
|
||||||
lineAdditionalWidth = cutoutWidth
|
lineAdditionalWidth = cutoutWidth
|
||||||
|
|||||||
Reference in New Issue
Block a user