mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 05:03:45 +00:00
no message
This commit is contained in:
parent
d0a9b00844
commit
32efb5962d
@ -42,6 +42,7 @@
|
||||
D01B279D1E394A500022A4C0 /* NotificationsAndSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279C1E394A500022A4C0 /* NotificationsAndSounds.swift */; };
|
||||
D01B279F1E394BD70022A4C0 /* InAppNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B279E1E394BD70022A4C0 /* InAppNotificationSettings.swift */; };
|
||||
D01B27A41E394FC90022A4C0 /* SecuritySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01B27A31E394FC90022A4C0 /* SecuritySettings.swift */; };
|
||||
D01D6BFC1E42AB3C006151C6 /* EmojiUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01D6BFB1E42AB3C006151C6 /* EmojiUtils.swift */; };
|
||||
D01F66131DE8903300345CBE /* ChatTextInputAudioRecordingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F66121DE8903300345CBE /* ChatTextInputAudioRecordingButton.swift */; };
|
||||
D0215D381E040F53001A0B1E /* InstantPageNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0215D371E040F53001A0B1E /* InstantPageNode.swift */; };
|
||||
D0215D3A1E041003001A0B1E /* InstantPageLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0215D391E041003001A0B1E /* InstantPageLayout.swift */; };
|
||||
@ -91,7 +92,84 @@
|
||||
D03ADB4B1D70443F005A521C /* ReplyAccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */; };
|
||||
D03ADB4D1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4C1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift */; };
|
||||
D03ADB4F1D70546B005A521C /* AccessoryPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */; };
|
||||
D049EAE21E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */; };
|
||||
D049EAE41E44949F00A2CD3A /* HorizontalStickerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */; };
|
||||
D049EAE61E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE51E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift */; };
|
||||
D049EAEE1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAED1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift */; };
|
||||
D049EAF31E44DE2500A2CD3A /* AuthorizationSequenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAF21E44DE2500A2CD3A /* AuthorizationSequenceController.swift */; };
|
||||
D04B66B81DD672D00049C3D2 /* GeoLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04B66B71DD672D00049C3D2 /* GeoLocation.swift */; };
|
||||
D04BB2B31E44E56200650E93 /* AuthorizationSequenceSplashController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2B21E44E56200650E93 /* AuthorizationSequenceSplashController.swift */; };
|
||||
D04BB2B51E44E58E00650E93 /* AuthorizationSequencePhoneEntryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2B41E44E58E00650E93 /* AuthorizationSequencePhoneEntryController.swift */; };
|
||||
D04BB2B91E44E5E400650E93 /* AuthorizationSequencePhoneEntryControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2B81E44E5E400650E93 /* AuthorizationSequencePhoneEntryControllerNode.swift */; };
|
||||
D04BB2BB1E44EA2400650E93 /* AuthorizationSequenceSplashControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2BA1E44EA2400650E93 /* AuthorizationSequenceSplashControllerNode.swift */; };
|
||||
D04BB2BE1E44FD2600650E93 /* AuthorizationSequenceCodeEntryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2BD1E44FD2600650E93 /* AuthorizationSequenceCodeEntryController.swift */; };
|
||||
D04BB2C01E44FD3100650E93 /* AuthorizationSequenceCodeEntryControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2BF1E44FD3100650E93 /* AuthorizationSequenceCodeEntryControllerNode.swift */; };
|
||||
D04BB2C31E45020A00650E93 /* AuthorizationSequencePasswordEntryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2C21E45020A00650E93 /* AuthorizationSequencePasswordEntryController.swift */; };
|
||||
D04BB2C51E45022C00650E93 /* AuthorizationSequencePasswordEntryControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2C41E45022C00650E93 /* AuthorizationSequencePasswordEntryControllerNode.swift */; };
|
||||
D04BB32B1E48797500650E93 /* linmath.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2CA1E48797500650E93 /* linmath.h */; };
|
||||
D04BB32C1E48797500650E93 /* animations.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2CC1E48797500650E93 /* animations.c */; };
|
||||
D04BB32D1E48797500650E93 /* animations.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2CD1E48797500650E93 /* animations.h */; };
|
||||
D04BB32E1E48797500650E93 /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2CE1E48797500650E93 /* buffer.c */; };
|
||||
D04BB32F1E48797500650E93 /* buffer.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2CF1E48797500650E93 /* buffer.h */; };
|
||||
D04BB3301E48797500650E93 /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2D01E48797500650E93 /* config.h */; };
|
||||
D04BB3311E48797500650E93 /* macros.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2D11E48797500650E93 /* macros.h */; };
|
||||
D04BB3321E48797500650E93 /* math_helper.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2D21E48797500650E93 /* math_helper.h */; };
|
||||
D04BB3331E48797500650E93 /* matrix.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2D31E48797500650E93 /* matrix.h */; };
|
||||
D04BB3341E48797500650E93 /* objects.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2D41E48797500650E93 /* objects.c */; };
|
||||
D04BB3351E48797500650E93 /* objects.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2D51E48797500650E93 /* objects.h */; };
|
||||
D04BB3361E48797500650E93 /* program.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2D61E48797500650E93 /* program.c */; };
|
||||
D04BB3371E48797500650E93 /* program.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2D71E48797500650E93 /* program.h */; };
|
||||
D04BB3381E48797500650E93 /* rngs.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2D81E48797500650E93 /* rngs.c */; };
|
||||
D04BB3391E48797500650E93 /* rngs.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2D91E48797500650E93 /* rngs.h */; };
|
||||
D04BB33A1E48797500650E93 /* shader.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2DA1E48797500650E93 /* shader.c */; };
|
||||
D04BB33B1E48797500650E93 /* shader.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2DB1E48797500650E93 /* shader.h */; };
|
||||
D04BB33C1E48797500650E93 /* timing.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2DC1E48797500650E93 /* timing.c */; };
|
||||
D04BB33D1E48797500650E93 /* timing.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2DD1E48797500650E93 /* timing.h */; };
|
||||
D04BB33E1E48797500650E93 /* platform_log.c in Sources */ = {isa = PBXBuildFile; fileRef = D04BB2E01E48797500650E93 /* platform_log.c */; };
|
||||
D04BB33F1E48797500650E93 /* platform_log.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2E11E48797500650E93 /* platform_log.h */; };
|
||||
D04BB3401E48797500650E93 /* platform_macros.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2E21E48797500650E93 /* platform_macros.h */; };
|
||||
D04BB3511E48797500650E93 /* platform_gl.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB2FE1E48797500650E93 /* platform_gl.h */; };
|
||||
D04BB3521E48797500650E93 /* fast_arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3001E48797500650E93 /* fast_arrow@2x.png */; };
|
||||
D04BB3531E48797500650E93 /* fast_arrow_shadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3011E48797500650E93 /* fast_arrow_shadow@2x.png */; };
|
||||
D04BB3541E48797500650E93 /* fast_body@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3021E48797500650E93 /* fast_body@2x.png */; };
|
||||
D04BB3551E48797500650E93 /* fast_spiral@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3031E48797500650E93 /* fast_spiral@2x.png */; };
|
||||
D04BB3561E48797500650E93 /* ic_bubble@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3041E48797500650E93 /* ic_bubble@2x.png */; };
|
||||
D04BB3571E48797500650E93 /* ic_bubble_dot@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3051E48797500650E93 /* ic_bubble_dot@2x.png */; };
|
||||
D04BB3581E48797500650E93 /* ic_cam@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3061E48797500650E93 /* ic_cam@2x.png */; };
|
||||
D04BB3591E48797500650E93 /* ic_cam_lens@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3071E48797500650E93 /* ic_cam_lens@2x.png */; };
|
||||
D04BB35A1E48797500650E93 /* ic_pencil@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3081E48797500650E93 /* ic_pencil@2x.png */; };
|
||||
D04BB35B1E48797500650E93 /* ic_pin@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3091E48797500650E93 /* ic_pin@2x.png */; };
|
||||
D04BB35C1E48797500650E93 /* ic_smile@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB30A1E48797500650E93 /* ic_smile@2x.png */; };
|
||||
D04BB35D1E48797500650E93 /* ic_smile_eye@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB30B1E48797500650E93 /* ic_smile_eye@2x.png */; };
|
||||
D04BB35E1E48797500650E93 /* ic_videocam@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB30C1E48797500650E93 /* ic_videocam@2x.png */; };
|
||||
D04BB35F1E48797500650E93 /* knot_down@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB30D1E48797500650E93 /* knot_down@2x.png */; };
|
||||
D04BB3601E48797500650E93 /* knot_up@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB30E1E48797500650E93 /* knot_up@2x.png */; };
|
||||
D04BB3611E48797500650E93 /* powerful_infinity@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB30F1E48797500650E93 /* powerful_infinity@2x.png */; };
|
||||
D04BB3621E48797500650E93 /* powerful_infinity_white@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3101E48797500650E93 /* powerful_infinity_white@2x.png */; };
|
||||
D04BB3631E48797500650E93 /* powerful_mask@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3111E48797500650E93 /* powerful_mask@2x.png */; };
|
||||
D04BB3641E48797500650E93 /* powerful_star@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3121E48797500650E93 /* powerful_star@2x.png */; };
|
||||
D04BB3651E48797500650E93 /* private_door@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3131E48797500650E93 /* private_door@2x.png */; };
|
||||
D04BB3661E48797500650E93 /* private_screw@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3141E48797500650E93 /* private_screw@2x.png */; };
|
||||
D04BB3671E48797500650E93 /* start_arrow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3151E48797500650E93 /* start_arrow@2x.png */; };
|
||||
D04BB3681E48797500650E93 /* start_arrow_ipad.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3161E48797500650E93 /* start_arrow_ipad.png */; };
|
||||
D04BB3691E48797500650E93 /* start_arrow_ipad@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3171E48797500650E93 /* start_arrow_ipad@2x.png */; };
|
||||
D04BB36A1E48797500650E93 /* telegram_plane@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3181E48797500650E93 /* telegram_plane@2x.png */; };
|
||||
D04BB36B1E48797500650E93 /* telegram_sphere@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D04BB3191E48797500650E93 /* telegram_sphere@2x.png */; };
|
||||
D04BB36E1E48797500650E93 /* RMGeometry.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB31C1E48797500650E93 /* RMGeometry.h */; };
|
||||
D04BB36F1E48797500650E93 /* RMGeometry.m in Sources */ = {isa = PBXBuildFile; fileRef = D04BB31D1E48797500650E93 /* RMGeometry.m */; };
|
||||
D04BB3721E48797500650E93 /* RMIntroPageView.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB3201E48797500650E93 /* RMIntroPageView.h */; };
|
||||
D04BB3731E48797500650E93 /* RMIntroPageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D04BB3211E48797500650E93 /* RMIntroPageView.m */; };
|
||||
D04BB3741E48797500650E93 /* RMIntroViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB3221E48797500650E93 /* RMIntroViewController.h */; };
|
||||
D04BB3751E48797500650E93 /* RMIntroViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D04BB3231E48797500650E93 /* RMIntroViewController.m */; };
|
||||
D04BB3761E48797500650E93 /* RMLoginViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB3241E48797500650E93 /* RMLoginViewController.h */; };
|
||||
D04BB3771E48797500650E93 /* RMLoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D04BB3251E48797500650E93 /* RMLoginViewController.m */; };
|
||||
D04BB3781E48797500650E93 /* RMRootViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB3261E48797500650E93 /* RMRootViewController.h */; };
|
||||
D04BB3791E48797500650E93 /* RMRootViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D04BB3271E48797500650E93 /* RMRootViewController.m */; };
|
||||
D04BB37A1E48797500650E93 /* texture_helper.h in Headers */ = {isa = PBXBuildFile; fileRef = D04BB3281E48797500650E93 /* texture_helper.h */; };
|
||||
D04BB37B1E48797500650E93 /* texture_helper.m in Sources */ = {isa = PBXBuildFile; fileRef = D04BB3291E48797500650E93 /* texture_helper.m */; };
|
||||
D050F2131E48B61500988324 /* PhoneInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2121E48B61500988324 /* PhoneInputNode.swift */; };
|
||||
D050F2161E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2151E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift */; };
|
||||
D050F2181E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D050F2171E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift */; };
|
||||
D0568AAD1DF198130022E7DA /* AudioWaveformNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0568AAC1DF198130022E7DA /* AudioWaveformNode.swift */; };
|
||||
D0568AAF1DF1B3920022E7DA /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0568AAE1DF1B3920022E7DA /* HapticFeedback.swift */; };
|
||||
D05811941DD5F9380057C769 /* TelegramApplicationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */; };
|
||||
@ -170,6 +248,8 @@
|
||||
D0BA6F881D784F880034826E /* ChatMessageSelectionInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */; };
|
||||
D0BC38631E3F9EFA0044D6FE /* EditableTokenListNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38621E3F9EFA0044D6FE /* EditableTokenListNode.swift */; };
|
||||
D0BC386A1E3FB94D0044D6FE /* CreateGroupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38691E3FB94D0044D6FE /* CreateGroupController.swift */; };
|
||||
D0BC387F1E40F1CF0044D6FE /* ContactSelectionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC387E1E40F1CF0044D6FE /* ContactSelectionController.swift */; };
|
||||
D0BC38811E40F1D80044D6FE /* ContactSelectionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BC38801E40F1D80044D6FE /* ContactSelectionControllerNode.swift */; };
|
||||
D0C932361E0988C60074F044 /* ChatButtonKeyboardInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */; };
|
||||
D0C932381E09E0EA0074F044 /* ChatBotInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */; };
|
||||
D0C9323C1E0B4AE90074F044 /* DataAndStorageSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */; };
|
||||
@ -209,6 +289,8 @@
|
||||
D0D2686E1D7898A900C422DA /* ChatMessageSelectionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2686D1D7898A900C422DA /* ChatMessageSelectionNode.swift */; };
|
||||
D0D2689A1D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D268991D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift */; };
|
||||
D0D2689D1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */; };
|
||||
D0DA44541E4E7302005FDCA7 /* ProgressNavigationButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44531E4E7302005FDCA7 /* ProgressNavigationButtonNode.swift */; };
|
||||
D0DA44561E4E7F43005FDCA7 /* ShakeAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44551E4E7F43005FDCA7 /* ShakeAnimation.swift */; };
|
||||
D0DC35441DE32230000195EB /* ChatInterfaceStateContextQueries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC35431DE32230000195EB /* ChatInterfaceStateContextQueries.swift */; };
|
||||
D0DC35461DE35805000195EB /* MentionChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC35451DE35805000195EB /* MentionChatInputPanelItem.swift */; };
|
||||
D0DC354A1DE366CD000195EB /* CommandChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC35491DE366CD000195EB /* CommandChatInputContextPanelNode.swift */; };
|
||||
@ -419,6 +501,7 @@
|
||||
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>"; };
|
||||
D01B27A31E394FC90022A4C0 /* SecuritySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecuritySettings.swift; sourceTree = "<group>"; };
|
||||
D01D6BFB1E42AB3C006151C6 /* EmojiUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiUtils.swift; sourceTree = "<group>"; };
|
||||
D01F66121DE8903300345CBE /* ChatTextInputAudioRecordingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatTextInputAudioRecordingButton.swift; sourceTree = "<group>"; };
|
||||
D0215D371E040F53001A0B1E /* InstantPageNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageNode.swift; sourceTree = "<group>"; };
|
||||
D0215D391E041003001A0B1E /* InstantPageLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPageLayout.swift; sourceTree = "<group>"; };
|
||||
@ -468,7 +551,84 @@
|
||||
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>"; };
|
||||
D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPanelNode.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>"; };
|
||||
D049EAE51E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputRecentStickerPacksItem.swift; sourceTree = "<group>"; };
|
||||
D049EAED1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListRecentPeersListItem.swift; sourceTree = "<group>"; };
|
||||
D049EAF21E44DE2500A2CD3A /* AuthorizationSequenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceController.swift; sourceTree = "<group>"; };
|
||||
D04B66B71DD672D00049C3D2 /* GeoLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoLocation.swift; sourceTree = "<group>"; };
|
||||
D04BB2B21E44E56200650E93 /* AuthorizationSequenceSplashController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceSplashController.swift; sourceTree = "<group>"; };
|
||||
D04BB2B41E44E58E00650E93 /* AuthorizationSequencePhoneEntryController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequencePhoneEntryController.swift; sourceTree = "<group>"; };
|
||||
D04BB2B81E44E5E400650E93 /* AuthorizationSequencePhoneEntryControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequencePhoneEntryControllerNode.swift; sourceTree = "<group>"; };
|
||||
D04BB2BA1E44EA2400650E93 /* AuthorizationSequenceSplashControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceSplashControllerNode.swift; sourceTree = "<group>"; };
|
||||
D04BB2BD1E44FD2600650E93 /* AuthorizationSequenceCodeEntryController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceCodeEntryController.swift; sourceTree = "<group>"; };
|
||||
D04BB2BF1E44FD3100650E93 /* AuthorizationSequenceCodeEntryControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceCodeEntryControllerNode.swift; sourceTree = "<group>"; };
|
||||
D04BB2C21E45020A00650E93 /* AuthorizationSequencePasswordEntryController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequencePasswordEntryController.swift; sourceTree = "<group>"; };
|
||||
D04BB2C41E45022C00650E93 /* AuthorizationSequencePasswordEntryControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequencePasswordEntryControllerNode.swift; sourceTree = "<group>"; };
|
||||
D04BB2CA1E48797500650E93 /* linmath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = linmath.h; sourceTree = "<group>"; };
|
||||
D04BB2CC1E48797500650E93 /* animations.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = animations.c; sourceTree = "<group>"; };
|
||||
D04BB2CD1E48797500650E93 /* animations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = animations.h; sourceTree = "<group>"; };
|
||||
D04BB2CE1E48797500650E93 /* buffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = buffer.c; sourceTree = "<group>"; };
|
||||
D04BB2CF1E48797500650E93 /* buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = buffer.h; sourceTree = "<group>"; };
|
||||
D04BB2D01E48797500650E93 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = "<group>"; };
|
||||
D04BB2D11E48797500650E93 /* macros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macros.h; sourceTree = "<group>"; };
|
||||
D04BB2D21E48797500650E93 /* math_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = math_helper.h; sourceTree = "<group>"; };
|
||||
D04BB2D31E48797500650E93 /* matrix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = matrix.h; sourceTree = "<group>"; };
|
||||
D04BB2D41E48797500650E93 /* objects.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = objects.c; sourceTree = "<group>"; };
|
||||
D04BB2D51E48797500650E93 /* objects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = objects.h; sourceTree = "<group>"; };
|
||||
D04BB2D61E48797500650E93 /* program.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = program.c; sourceTree = "<group>"; };
|
||||
D04BB2D71E48797500650E93 /* program.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = program.h; sourceTree = "<group>"; };
|
||||
D04BB2D81E48797500650E93 /* rngs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = rngs.c; sourceTree = "<group>"; };
|
||||
D04BB2D91E48797500650E93 /* rngs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rngs.h; sourceTree = "<group>"; };
|
||||
D04BB2DA1E48797500650E93 /* shader.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = shader.c; sourceTree = "<group>"; };
|
||||
D04BB2DB1E48797500650E93 /* shader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shader.h; sourceTree = "<group>"; };
|
||||
D04BB2DC1E48797500650E93 /* timing.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = timing.c; sourceTree = "<group>"; };
|
||||
D04BB2DD1E48797500650E93 /* timing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = timing.h; sourceTree = "<group>"; };
|
||||
D04BB2E01E48797500650E93 /* platform_log.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = platform_log.c; sourceTree = "<group>"; };
|
||||
D04BB2E11E48797500650E93 /* platform_log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform_log.h; sourceTree = "<group>"; };
|
||||
D04BB2E21E48797500650E93 /* platform_macros.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform_macros.h; sourceTree = "<group>"; };
|
||||
D04BB2FE1E48797500650E93 /* platform_gl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = platform_gl.h; sourceTree = "<group>"; };
|
||||
D04BB3001E48797500650E93 /* fast_arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "fast_arrow@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3011E48797500650E93 /* fast_arrow_shadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "fast_arrow_shadow@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3021E48797500650E93 /* fast_body@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "fast_body@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3031E48797500650E93 /* fast_spiral@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "fast_spiral@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3041E48797500650E93 /* ic_bubble@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_bubble@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3051E48797500650E93 /* ic_bubble_dot@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_bubble_dot@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3061E48797500650E93 /* ic_cam@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_cam@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3071E48797500650E93 /* ic_cam_lens@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_cam_lens@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3081E48797500650E93 /* ic_pencil@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_pencil@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3091E48797500650E93 /* ic_pin@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_pin@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB30A1E48797500650E93 /* ic_smile@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_smile@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB30B1E48797500650E93 /* ic_smile_eye@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_smile_eye@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB30C1E48797500650E93 /* ic_videocam@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ic_videocam@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB30D1E48797500650E93 /* knot_down@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "knot_down@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB30E1E48797500650E93 /* knot_up@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "knot_up@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB30F1E48797500650E93 /* powerful_infinity@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "powerful_infinity@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3101E48797500650E93 /* powerful_infinity_white@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "powerful_infinity_white@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3111E48797500650E93 /* powerful_mask@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "powerful_mask@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3121E48797500650E93 /* powerful_star@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "powerful_star@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3131E48797500650E93 /* private_door@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "private_door@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3141E48797500650E93 /* private_screw@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "private_screw@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3151E48797500650E93 /* start_arrow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "start_arrow@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3161E48797500650E93 /* start_arrow_ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = start_arrow_ipad.png; sourceTree = "<group>"; };
|
||||
D04BB3171E48797500650E93 /* start_arrow_ipad@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "start_arrow_ipad@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3181E48797500650E93 /* telegram_plane@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "telegram_plane@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB3191E48797500650E93 /* telegram_sphere@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "telegram_sphere@2x.png"; sourceTree = "<group>"; };
|
||||
D04BB31C1E48797500650E93 /* RMGeometry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMGeometry.h; sourceTree = "<group>"; };
|
||||
D04BB31D1E48797500650E93 /* RMGeometry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMGeometry.m; sourceTree = "<group>"; };
|
||||
D04BB3201E48797500650E93 /* RMIntroPageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMIntroPageView.h; sourceTree = "<group>"; };
|
||||
D04BB3211E48797500650E93 /* RMIntroPageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMIntroPageView.m; sourceTree = "<group>"; };
|
||||
D04BB3221E48797500650E93 /* RMIntroViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMIntroViewController.h; sourceTree = "<group>"; };
|
||||
D04BB3231E48797500650E93 /* RMIntroViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMIntroViewController.m; sourceTree = "<group>"; };
|
||||
D04BB3241E48797500650E93 /* RMLoginViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMLoginViewController.h; sourceTree = "<group>"; };
|
||||
D04BB3251E48797500650E93 /* RMLoginViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMLoginViewController.m; sourceTree = "<group>"; };
|
||||
D04BB3261E48797500650E93 /* RMRootViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RMRootViewController.h; sourceTree = "<group>"; };
|
||||
D04BB3271E48797500650E93 /* RMRootViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RMRootViewController.m; sourceTree = "<group>"; };
|
||||
D04BB3281E48797500650E93 /* texture_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = texture_helper.h; sourceTree = "<group>"; };
|
||||
D04BB3291E48797500650E93 /* texture_helper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = texture_helper.m; 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>"; };
|
||||
D050F2171E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceCountrySelectionControllerNode.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>"; };
|
||||
D05811931DD5F9380057C769 /* TelegramApplicationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramApplicationContext.swift; sourceTree = "<group>"; };
|
||||
@ -550,6 +710,8 @@
|
||||
D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionInputPanelNode.swift; sourceTree = "<group>"; };
|
||||
D0BC38621E3F9EFA0044D6FE /* EditableTokenListNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditableTokenListNode.swift; sourceTree = "<group>"; };
|
||||
D0BC38691E3FB94D0044D6FE /* CreateGroupController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateGroupController.swift; sourceTree = "<group>"; };
|
||||
D0BC387E1E40F1CF0044D6FE /* ContactSelectionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactSelectionController.swift; sourceTree = "<group>"; };
|
||||
D0BC38801E40F1D80044D6FE /* ContactSelectionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactSelectionControllerNode.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>"; };
|
||||
D0C9323B1E0B4AE90074F044 /* DataAndStorageSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataAndStorageSettingsController.swift; sourceTree = "<group>"; };
|
||||
@ -589,6 +751,8 @@
|
||||
D0D2686D1D7898A900C422DA /* ChatMessageSelectionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionNode.swift; sourceTree = "<group>"; };
|
||||
D0D268991D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatPanelInterfaceInteraction.swift; sourceTree = "<group>"; };
|
||||
D0D2689C1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareRecipientsActionSheetController.swift; sourceTree = "<group>"; };
|
||||
D0DA44531E4E7302005FDCA7 /* ProgressNavigationButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressNavigationButtonNode.swift; sourceTree = "<group>"; };
|
||||
D0DA44551E4E7F43005FDCA7 /* ShakeAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShakeAnimation.swift; sourceTree = "<group>"; };
|
||||
D0DC35431DE32230000195EB /* ChatInterfaceStateContextQueries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateContextQueries.swift; sourceTree = "<group>"; };
|
||||
D0DC35451DE35805000195EB /* MentionChatInputPanelItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MentionChatInputPanelItem.swift; sourceTree = "<group>"; };
|
||||
D0DC35491DE366CD000195EB /* CommandChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
||||
@ -843,6 +1007,7 @@
|
||||
D021E0CD1DB4135500C6B04F /* ChatMediaInputNode.swift */,
|
||||
D08C367E1DB66A820064C744 /* ChatMediaInputPanelEntries.swift */,
|
||||
D08C36801DB66AAC0064C744 /* ChatMediaInputGridEntries.swift */,
|
||||
D049EAE51E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift */,
|
||||
D021E0E41DB55D0A00C6B04F /* ChatMediaInputStickerPackItem.swift */,
|
||||
D08C36821DB66AD40064C744 /* ChatMediaInputStickerGridItem.swift */,
|
||||
);
|
||||
@ -889,6 +1054,185 @@
|
||||
name = "Accessory Panels";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D049EAE01E447AB700A2CD3A /* Stickers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */,
|
||||
D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */,
|
||||
);
|
||||
name = Stickers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2B61E44E5BB00650E93 /* Splash */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2B21E44E56200650E93 /* AuthorizationSequenceSplashController.swift */,
|
||||
D04BB2BA1E44EA2400650E93 /* AuthorizationSequenceSplashControllerNode.swift */,
|
||||
);
|
||||
name = Splash;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2B71E44E5CB00650E93 /* Phone Entry */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2B41E44E58E00650E93 /* AuthorizationSequencePhoneEntryController.swift */,
|
||||
D04BB2B81E44E5E400650E93 /* AuthorizationSequencePhoneEntryControllerNode.swift */,
|
||||
);
|
||||
name = "Phone Entry";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2BC1E44FD1300650E93 /* Code Entry */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2BD1E44FD2600650E93 /* AuthorizationSequenceCodeEntryController.swift */,
|
||||
D04BB2BF1E44FD3100650E93 /* AuthorizationSequenceCodeEntryControllerNode.swift */,
|
||||
);
|
||||
name = "Code Entry";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2C11E45016800650E93 /* Password Entry */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2C21E45020A00650E93 /* AuthorizationSequencePasswordEntryController.swift */,
|
||||
D04BB2C41E45022C00650E93 /* AuthorizationSequencePasswordEntryControllerNode.swift */,
|
||||
);
|
||||
name = "Password Entry";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2C61E48797500650E93 /* RMIntro */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2C71E48797500650E93 /* 3rdparty */,
|
||||
D04BB2CB1E48797500650E93 /* core */,
|
||||
D04BB2DE1E48797500650E93 /* platform */,
|
||||
);
|
||||
name = RMIntro;
|
||||
path = "third-party/RMIntro";
|
||||
sourceTree = SOURCE_ROOT;
|
||||
};
|
||||
D04BB2C71E48797500650E93 /* 3rdparty */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2C91E48797500650E93 /* linmath */,
|
||||
);
|
||||
path = 3rdparty;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2C91E48797500650E93 /* linmath */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2CA1E48797500650E93 /* linmath.h */,
|
||||
);
|
||||
path = linmath;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2CB1E48797500650E93 /* core */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2CC1E48797500650E93 /* animations.c */,
|
||||
D04BB2CD1E48797500650E93 /* animations.h */,
|
||||
D04BB2CE1E48797500650E93 /* buffer.c */,
|
||||
D04BB2CF1E48797500650E93 /* buffer.h */,
|
||||
D04BB2D01E48797500650E93 /* config.h */,
|
||||
D04BB2D11E48797500650E93 /* macros.h */,
|
||||
D04BB2D21E48797500650E93 /* math_helper.h */,
|
||||
D04BB2D31E48797500650E93 /* matrix.h */,
|
||||
D04BB2D41E48797500650E93 /* objects.c */,
|
||||
D04BB2D51E48797500650E93 /* objects.h */,
|
||||
D04BB2D61E48797500650E93 /* program.c */,
|
||||
D04BB2D71E48797500650E93 /* program.h */,
|
||||
D04BB2D81E48797500650E93 /* rngs.c */,
|
||||
D04BB2D91E48797500650E93 /* rngs.h */,
|
||||
D04BB2DA1E48797500650E93 /* shader.c */,
|
||||
D04BB2DB1E48797500650E93 /* shader.h */,
|
||||
D04BB2DC1E48797500650E93 /* timing.c */,
|
||||
D04BB2DD1E48797500650E93 /* timing.h */,
|
||||
);
|
||||
path = core;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2DE1E48797500650E93 /* platform */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2DF1E48797500650E93 /* common */,
|
||||
D04BB2E31E48797500650E93 /* ios */,
|
||||
);
|
||||
path = platform;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2DF1E48797500650E93 /* common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2E01E48797500650E93 /* platform_log.c */,
|
||||
D04BB2E11E48797500650E93 /* platform_log.h */,
|
||||
D04BB2E21E48797500650E93 /* platform_macros.h */,
|
||||
);
|
||||
path = common;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2E31E48797500650E93 /* ios */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2FE1E48797500650E93 /* platform_gl.h */,
|
||||
D04BB2FF1E48797500650E93 /* Resources */,
|
||||
D04BB31C1E48797500650E93 /* RMGeometry.h */,
|
||||
D04BB31D1E48797500650E93 /* RMGeometry.m */,
|
||||
D04BB3201E48797500650E93 /* RMIntroPageView.h */,
|
||||
D04BB3211E48797500650E93 /* RMIntroPageView.m */,
|
||||
D04BB3221E48797500650E93 /* RMIntroViewController.h */,
|
||||
D04BB3231E48797500650E93 /* RMIntroViewController.m */,
|
||||
D04BB3241E48797500650E93 /* RMLoginViewController.h */,
|
||||
D04BB3251E48797500650E93 /* RMLoginViewController.m */,
|
||||
D04BB3261E48797500650E93 /* RMRootViewController.h */,
|
||||
D04BB3271E48797500650E93 /* RMRootViewController.m */,
|
||||
D04BB3281E48797500650E93 /* texture_helper.h */,
|
||||
D04BB3291E48797500650E93 /* texture_helper.m */,
|
||||
);
|
||||
path = ios;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D04BB2FF1E48797500650E93 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB3001E48797500650E93 /* fast_arrow@2x.png */,
|
||||
D04BB3011E48797500650E93 /* fast_arrow_shadow@2x.png */,
|
||||
D04BB3021E48797500650E93 /* fast_body@2x.png */,
|
||||
D04BB3031E48797500650E93 /* fast_spiral@2x.png */,
|
||||
D04BB3041E48797500650E93 /* ic_bubble@2x.png */,
|
||||
D04BB3051E48797500650E93 /* ic_bubble_dot@2x.png */,
|
||||
D04BB3061E48797500650E93 /* ic_cam@2x.png */,
|
||||
D04BB3071E48797500650E93 /* ic_cam_lens@2x.png */,
|
||||
D04BB3081E48797500650E93 /* ic_pencil@2x.png */,
|
||||
D04BB3091E48797500650E93 /* ic_pin@2x.png */,
|
||||
D04BB30A1E48797500650E93 /* ic_smile@2x.png */,
|
||||
D04BB30B1E48797500650E93 /* ic_smile_eye@2x.png */,
|
||||
D04BB30C1E48797500650E93 /* ic_videocam@2x.png */,
|
||||
D04BB30D1E48797500650E93 /* knot_down@2x.png */,
|
||||
D04BB30E1E48797500650E93 /* knot_up@2x.png */,
|
||||
D04BB30F1E48797500650E93 /* powerful_infinity@2x.png */,
|
||||
D04BB3101E48797500650E93 /* powerful_infinity_white@2x.png */,
|
||||
D04BB3111E48797500650E93 /* powerful_mask@2x.png */,
|
||||
D04BB3121E48797500650E93 /* powerful_star@2x.png */,
|
||||
D04BB3131E48797500650E93 /* private_door@2x.png */,
|
||||
D04BB3141E48797500650E93 /* private_screw@2x.png */,
|
||||
D04BB3151E48797500650E93 /* start_arrow@2x.png */,
|
||||
D04BB3161E48797500650E93 /* start_arrow_ipad.png */,
|
||||
D04BB3171E48797500650E93 /* start_arrow_ipad@2x.png */,
|
||||
D04BB3181E48797500650E93 /* telegram_plane@2x.png */,
|
||||
D04BB3191E48797500650E93 /* telegram_sphere@2x.png */,
|
||||
);
|
||||
path = Resources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D050F2141E48D9C200988324 /* Country Selection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D050F2151E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift */,
|
||||
D050F2171E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift */,
|
||||
);
|
||||
name = "Country Selection";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0736F261DF4D2F300F2C02A /* Telegram Controller */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -912,6 +1256,7 @@
|
||||
D07551891DDA4C7C0073E051 /* Legacy Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D04BB2C61E48797500650E93 /* RMIntro */,
|
||||
D075518A1DDA4D7D0073E051 /* LegacyController.swift */,
|
||||
D075518C1DDA4E0B0073E051 /* LegacyControllerNode.swift */,
|
||||
D07551921DDA540F0073E051 /* TelegramInitializeLegacyComponents.swift */,
|
||||
@ -1108,11 +1453,21 @@
|
||||
children = (
|
||||
D087750E1E3F469700A97350 /* Compose */,
|
||||
D087751A1E3F540900A97350 /* Contact Multiselection */,
|
||||
D0BC387D1E40F1B90044D6FE /* Contact Selection */,
|
||||
D0BC38671E3FB9190044D6FE /* Create Group */,
|
||||
);
|
||||
name = Compose;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0BC387D1E40F1B90044D6FE /* Contact Selection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0BC387E1E40F1CF0044D6FE /* ContactSelectionController.swift */,
|
||||
D0BC38801E40F1D80044D6FE /* ContactSelectionControllerNode.swift */,
|
||||
);
|
||||
name = "Contact Selection";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0C932341E0988AD0074F044 /* Button Keyboard */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1270,6 +1625,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0DF0C991D81FF3F008AEB01 /* ChatInputContextPanelNode.swift */,
|
||||
D049EAE01E447AB700A2CD3A /* Stickers */,
|
||||
D0DF0C9F1D8219C7008AEB01 /* Hashtags */,
|
||||
D0DF0CA21D82BCBC008AEB01 /* Mentions */,
|
||||
D0DC35481DE366B4000195EB /* Commands */,
|
||||
@ -1452,6 +1808,8 @@
|
||||
D00219051DDD1C9E00BE708A /* ImageContainingNode.swift */,
|
||||
D0568AAC1DF198130022E7DA /* AudioWaveformNode.swift */,
|
||||
D0BC38621E3F9EFA0044D6FE /* EditableTokenListNode.swift */,
|
||||
D050F2121E48B61500988324 /* PhoneInputNode.swift */,
|
||||
D0DA44531E4E7302005FDCA7 /* ProgressNavigationButtonNode.swift */,
|
||||
);
|
||||
name = Nodes;
|
||||
sourceTree = "<group>";
|
||||
@ -1520,6 +1878,12 @@
|
||||
D0F69DE71D6B8A590046BCD6 /* Authorization */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D049EAF21E44DE2500A2CD3A /* AuthorizationSequenceController.swift */,
|
||||
D04BB2B61E44E5BB00650E93 /* Splash */,
|
||||
D050F2141E48D9C200988324 /* Country Selection */,
|
||||
D04BB2B71E44E5CB00650E93 /* Phone Entry */,
|
||||
D04BB2BC1E44FD1300650E93 /* Code Entry */,
|
||||
D04BB2C11E45016800650E93 /* Password Entry */,
|
||||
D0F69DE81D6B8A6C0046BCD6 /* AuthorizationCodeController.swift */,
|
||||
D0F69DE91D6B8A6C0046BCD6 /* AuthorizationCodeControllerNode.swift */,
|
||||
D0F69DEA1D6B8A6C0046BCD6 /* AuthorizationController.swift */,
|
||||
@ -1555,6 +1919,7 @@
|
||||
D0F69E061D6B8A930046BCD6 /* Recent Peers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D049EAED1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift */,
|
||||
D0F69E0B1D6B8AB10046BCD6 /* HorizontalPeerItem.swift */,
|
||||
D0F69E091D6B8AA60046BCD6 /* ChatListSearchRecentPeersNode.swift */,
|
||||
);
|
||||
@ -1756,6 +2121,8 @@
|
||||
D017494D1E1059570057C89A /* StringWithAppliedEntities.swift */,
|
||||
D01749541E1082770057C89A /* StoredMessageFromSearchPeer.swift */,
|
||||
D087750B1E3E7B7600A97350 /* PreferencesKeys.swift */,
|
||||
D01D6BFB1E42AB3C006151C6 /* EmojiUtils.swift */,
|
||||
D0DA44551E4E7F43005FDCA7 /* ShakeAnimation.swift */,
|
||||
);
|
||||
name = Utils;
|
||||
sourceTree = "<group>";
|
||||
@ -1827,25 +2194,46 @@
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D04BB3401E48797500650E93 /* platform_macros.h in Headers */,
|
||||
D04BB3391E48797500650E93 /* rngs.h in Headers */,
|
||||
D04BB37A1E48797500650E93 /* texture_helper.h in Headers */,
|
||||
D0D03B161DECB0FE00220C46 /* opus.h in Headers */,
|
||||
D04BB36E1E48797500650E93 /* RMGeometry.h in Headers */,
|
||||
D04BB3311E48797500650E93 /* macros.h in Headers */,
|
||||
D0D03B141DECB0FE00220C46 /* ogg.h in Headers */,
|
||||
D04BB3331E48797500650E93 /* matrix.h in Headers */,
|
||||
D04BB3511E48797500650E93 /* platform_gl.h in Headers */,
|
||||
D0F69E8E1D6B8C850046BCD6 /* RingBuffer.h in Headers */,
|
||||
D04BB33D1E48797500650E93 /* timing.h in Headers */,
|
||||
D04BB3351E48797500650E93 /* objects.h in Headers */,
|
||||
D0D03B231DECB1AD00220C46 /* TGDataItem.h in Headers */,
|
||||
D0D03B1F1DECB0FE00220C46 /* opusfile.h in Headers */,
|
||||
D04BB32D1E48797500650E93 /* animations.h in Headers */,
|
||||
D04BB3781E48797500650E93 /* RMRootViewController.h in Headers */,
|
||||
D04BB32F1E48797500650E93 /* buffer.h in Headers */,
|
||||
D04BB3371E48797500650E93 /* program.h in Headers */,
|
||||
D0D03B1D1DECB0FE00220C46 /* internal.h in Headers */,
|
||||
D0FC40901D5B8E7500261D9D /* TelegramUI.h in Headers */,
|
||||
D04BB3741E48797500650E93 /* RMIntroViewController.h in Headers */,
|
||||
D0D03B0B1DECB0FE00220C46 /* opus_header.h in Headers */,
|
||||
D0D03B181DECB0FE00220C46 /* opus_multistream.h in Headers */,
|
||||
D04BB33F1E48797500650E93 /* platform_log.h in Headers */,
|
||||
D0D03B111DECB0FE00220C46 /* wav_io.h in Headers */,
|
||||
D0F69E9A1D6B8D200046BCD6 /* UIImage+WebP.h in Headers */,
|
||||
D04BB32B1E48797500650E93 /* linmath.h in Headers */,
|
||||
D04BB33B1E48797500650E93 /* shader.h in Headers */,
|
||||
D0D03B0C1DECB0FE00220C46 /* opusenc.h in Headers */,
|
||||
D0D03B0F1DECB0FE00220C46 /* picture.h in Headers */,
|
||||
D0F69E8A1D6B8C850046BCD6 /* FFMpegSwResample.h in Headers */,
|
||||
D0F69E881D6B8C850046BCD6 /* FastBlur.h in Headers */,
|
||||
D0D03B151DECB0FE00220C46 /* os_types.h in Headers */,
|
||||
D0D03B191DECB0FE00220C46 /* opus_types.h in Headers */,
|
||||
D04BB3301E48797500650E93 /* config.h in Headers */,
|
||||
D04BB3721E48797500650E93 /* RMIntroPageView.h in Headers */,
|
||||
D0D03B171DECB0FE00220C46 /* opus_defines.h in Headers */,
|
||||
D0D03B091DECB0FE00220C46 /* diag_range.h in Headers */,
|
||||
D04BB3321E48797500650E93 /* math_helper.h in Headers */,
|
||||
D04BB3761E48797500650E93 /* RMLoginViewController.h in Headers */,
|
||||
D00C7CF71E37BF680080C3D5 /* SecretChatKeyVisualization.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -1918,6 +2306,7 @@
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = D0FC40751D5B8E7400261D9D;
|
||||
productRefGroup = D0FC40801D5B8E7400261D9D /* Products */;
|
||||
@ -1935,9 +2324,35 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D04BB3541E48797500650E93 /* fast_body@2x.png in Resources */,
|
||||
D04BB35A1E48797500650E93 /* ic_pencil@2x.png in Resources */,
|
||||
D04BB3611E48797500650E93 /* powerful_infinity@2x.png in Resources */,
|
||||
D04BB3681E48797500650E93 /* start_arrow_ipad.png in Resources */,
|
||||
D04BB3521E48797500650E93 /* fast_arrow@2x.png in Resources */,
|
||||
D04BB3561E48797500650E93 /* ic_bubble@2x.png in Resources */,
|
||||
D04BB35C1E48797500650E93 /* ic_smile@2x.png in Resources */,
|
||||
D04BB3661E48797500650E93 /* private_screw@2x.png in Resources */,
|
||||
D0AB0BBB1D6719B5002C78E7 /* Images.xcassets in Resources */,
|
||||
D04BB3601E48797500650E93 /* knot_up@2x.png in Resources */,
|
||||
D04BB3651E48797500650E93 /* private_door@2x.png in Resources */,
|
||||
D04BB3671E48797500650E93 /* start_arrow@2x.png in Resources */,
|
||||
D04BB36A1E48797500650E93 /* telegram_plane@2x.png in Resources */,
|
||||
D04BB3641E48797500650E93 /* powerful_star@2x.png in Resources */,
|
||||
D04BB35D1E48797500650E93 /* ic_smile_eye@2x.png in Resources */,
|
||||
D04BB35F1E48797500650E93 /* knot_down@2x.png in Resources */,
|
||||
D04BB36B1E48797500650E93 /* telegram_sphere@2x.png in Resources */,
|
||||
D04BB3551E48797500650E93 /* fast_spiral@2x.png in Resources */,
|
||||
D0F69DBA1D6B88190046BCD6 /* TelegramUI.xcconfig in Resources */,
|
||||
D04BB35E1E48797500650E93 /* ic_videocam@2x.png in Resources */,
|
||||
D04BB35B1E48797500650E93 /* ic_pin@2x.png in Resources */,
|
||||
D04BB3571E48797500650E93 /* ic_bubble_dot@2x.png in Resources */,
|
||||
D04BB3581E48797500650E93 /* ic_cam@2x.png in Resources */,
|
||||
D04BB3621E48797500650E93 /* powerful_infinity_white@2x.png in Resources */,
|
||||
D073CE631DCBBE5D007511FD /* MessageSent.caf in Resources */,
|
||||
D04BB3591E48797500650E93 /* ic_cam_lens@2x.png in Resources */,
|
||||
D04BB3631E48797500650E93 /* powerful_mask@2x.png in Resources */,
|
||||
D04BB3531E48797500650E93 /* fast_arrow_shadow@2x.png in Resources */,
|
||||
D04BB3691E48797500650E93 /* start_arrow_ipad@2x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1966,10 +2381,12 @@
|
||||
D0D03B1B1DECB0FE00220C46 /* info.c in Sources */,
|
||||
D0215D4A1E041CAF001A0B1E /* InstantPageMediaItem.swift in Sources */,
|
||||
D087751E1E3F579300A97350 /* CounterContollerTitleView.swift in Sources */,
|
||||
D050F2131E48B61500988324 /* PhoneInputNode.swift in Sources */,
|
||||
D08775141E3F4A7700A97350 /* ContactListNameIndexHeader.swift in Sources */,
|
||||
D0215D461E041851001A0B1E /* InstantPageTextItem.swift in Sources */,
|
||||
D021E0D21DB4147500C6B04F /* ChatInterfaceInputNodes.swift in Sources */,
|
||||
D0D2689D1D79D33E00C422DA /* ShareRecipientsActionSheetController.swift in Sources */,
|
||||
D0BC38811E40F1D80044D6FE /* ContactSelectionControllerNode.swift in Sources */,
|
||||
D0F69E171D6B8ACF0046BCD6 /* ChatHistoryLocation.swift in Sources */,
|
||||
D0F69E741D6B8C340046BCD6 /* ContactsControllerNode.swift in Sources */,
|
||||
D07827C71E01CD5900071108 /* VerticalListContextResultsChatInputPanelButtonItem.swift in Sources */,
|
||||
@ -1979,6 +2396,7 @@
|
||||
D08C367F1DB66A820064C744 /* ChatMediaInputPanelEntries.swift in Sources */,
|
||||
D0568AAD1DF198130022E7DA /* AudioWaveformNode.swift in Sources */,
|
||||
D0D03B1E1DECB0FE00220C46 /* opusfile.c in Sources */,
|
||||
D0DA44561E4E7F43005FDCA7 /* ShakeAnimation.swift in Sources */,
|
||||
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */,
|
||||
D0F69DC71D6B89E70046BCD6 /* TransformImageNode.swift in Sources */,
|
||||
D039EB031DEAEFEE00886EBC /* ChatTextInputAudioRecordingOverlayButton.swift in Sources */,
|
||||
@ -1986,8 +2404,10 @@
|
||||
D0D2686C1D788F8200C422DA /* ChatTitleAccessoryPanelNode.swift in Sources */,
|
||||
D0215D541E043018001A0B1E /* InstantPageController.swift in Sources */,
|
||||
D00C7CE91E379B820080C3D5 /* ChatSecretAutoremoveTimerActionSheet.swift in Sources */,
|
||||
D04BB3361E48797500650E93 /* program.c in Sources */,
|
||||
D07A7DA51D95783C005BCD27 /* ListMessageNode.swift in Sources */,
|
||||
D0C9323C1E0B4AE90074F044 /* DataAndStorageSettingsController.swift in Sources */,
|
||||
D04BB2C51E45022C00650E93 /* AuthorizationSequencePasswordEntryControllerNode.swift in Sources */,
|
||||
D01F66131DE8903300345CBE /* ChatTextInputAudioRecordingButton.swift in Sources */,
|
||||
D0F69E341D6B8B030046BCD6 /* ChatMessageForwardInfoNode.swift in Sources */,
|
||||
D00C7CF81E37BF680080C3D5 /* SecretChatKeyVisualization.m in Sources */,
|
||||
@ -2025,11 +2445,13 @@
|
||||
D0F69DE01D6B8A420046BCD6 /* ListControllerButtonItem.swift in Sources */,
|
||||
D0F69E0C1D6B8AB10046BCD6 /* HorizontalPeerItem.swift in Sources */,
|
||||
D0F69E551D6B8BDA0046BCD6 /* GalleryController.swift in Sources */,
|
||||
D04BB2B91E44E5E400650E93 /* AuthorizationSequencePhoneEntryControllerNode.swift in Sources */,
|
||||
D0F69E571D6B8BDA0046BCD6 /* GalleryItem.swift in Sources */,
|
||||
D003702E1DA43052004308D3 /* ItemListAvatarAndNameItem.swift in Sources */,
|
||||
D0D03B1C1DECB0FE00220C46 /* internal.c in Sources */,
|
||||
D0F69D231D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift in Sources */,
|
||||
D0F69E8D1D6B8C850046BCD6 /* Localizable.swift in Sources */,
|
||||
D049EAE41E44949F00A2CD3A /* HorizontalStickerGridItem.swift in Sources */,
|
||||
D0736F231DF496D000F2C02A /* PeerMediaAudioPlaylist.swift in Sources */,
|
||||
D02958021D6F0D5F00360E5E /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */,
|
||||
D0D03B131DECB0FE00220C46 /* framing.c in Sources */,
|
||||
@ -2047,12 +2469,15 @@
|
||||
D0F69D4B1D6B87D30046BCD6 /* TouchDownGestureRecognizer.swift in Sources */,
|
||||
D01B279D1E394A500022A4C0 /* NotificationsAndSounds.swift in Sources */,
|
||||
D0F69E3D1D6B8B030046BCD6 /* ChatMessageWebpageBubbleContentNode.swift in Sources */,
|
||||
D04BB3731E48797500650E93 /* RMIntroPageView.m in Sources */,
|
||||
D08C36811DB66AAC0064C744 /* ChatMediaInputGridEntries.swift in Sources */,
|
||||
D099EA1F1DE7450B001AF5A8 /* HorizontalListContextResultsChatInputContextPanelNode.swift in Sources */,
|
||||
D0DF0C9E1D82141F008AEB01 /* ChatInterfaceInputContexts.swift in Sources */,
|
||||
D04BB3341E48797500650E93 /* objects.c in Sources */,
|
||||
D0B843CD1DA903BB005F29E1 /* PeerInfoController.swift in Sources */,
|
||||
D0D2686E1D7898A900C422DA /* ChatMessageSelectionNode.swift in Sources */,
|
||||
D0EFD8961DDE8249009E508A /* LegacyLocationPicker.swift in Sources */,
|
||||
D04BB3771E48797500650E93 /* RMLoginViewController.m in Sources */,
|
||||
D0F69E8B1D6B8C850046BCD6 /* FFMpegSwResample.m in Sources */,
|
||||
D0B843921DA7F13E005F29E1 /* ItemListDisclosureItem.swift in Sources */,
|
||||
D07CFF791DCA226F00761F81 /* ChatListNode.swift in Sources */,
|
||||
@ -2074,16 +2499,19 @@
|
||||
D0215D3E1E041048001A0B1E /* InstantPageMedia.swift in Sources */,
|
||||
D00C7CD91E36B2DB0080C3D5 /* ContactListNode.swift in Sources */,
|
||||
D0C932361E0988C60074F044 /* ChatButtonKeyboardInputNode.swift in Sources */,
|
||||
D04BB32E1E48797500650E93 /* buffer.c in Sources */,
|
||||
D0F69E591D6B8BDA0046BCD6 /* GalleryPagerNode.swift in Sources */,
|
||||
D0F69E391D6B8B030046BCD6 /* ChatMessageMediaBubbleContentNode.swift in Sources */,
|
||||
D0F69D351D6B87D30046BCD6 /* MediaFrameSource.swift in Sources */,
|
||||
D0736F2A1DF4D5FF00F2C02A /* MediaNavigationAccessoryPanel.swift in Sources */,
|
||||
D087751C1E3F542500A97350 /* ContactMultiselectionControllerNode.swift in Sources */,
|
||||
D0E7A1BD1D8C246D00C37A6F /* ChatHistoryListNode.swift in Sources */,
|
||||
D049EAE21E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift in Sources */,
|
||||
D0F69E371D6B8B030046BCD6 /* ChatMessageItem.swift in Sources */,
|
||||
D099EA291DE76655001AF5A8 /* ManagedVideoNode.swift in Sources */,
|
||||
D0215D501E0422C7001A0B1E /* InstantPageWebEmbedItem.swift in Sources */,
|
||||
D0BC38631E3F9EFA0044D6FE /* EditableTokenListNode.swift in Sources */,
|
||||
D049EAE61E44AD5600A2CD3A /* ChatMediaInputRecentStickerPacksItem.swift in Sources */,
|
||||
D023ED2E1DDB5BEC00BD496D /* LegacyAttachmentMenu.swift in Sources */,
|
||||
D02383771DDF16B2004018B6 /* ChatControllerTitlePanelNodeContainer.swift in Sources */,
|
||||
D00370321DA46C06004308D3 /* ItemListTextWithLabelItem.swift in Sources */,
|
||||
@ -2106,6 +2534,7 @@
|
||||
D02383731DDF0D8A004018B6 /* ChatInfoTitlePanelNode.swift in Sources */,
|
||||
D0568AAF1DF1B3920022E7DA /* HapticFeedback.swift in Sources */,
|
||||
D0F69E001D6B8A880046BCD6 /* ChatListControllerNode.swift in Sources */,
|
||||
D04BB37B1E48797500650E93 /* texture_helper.m in Sources */,
|
||||
D0F69EA31D6B8E380046BCD6 /* StickerResources.swift in Sources */,
|
||||
D0DE77321D940295002B8809 /* ListMessageFileItemNode.swift in Sources */,
|
||||
D06879571DB8F22200424BBD /* FetchCachedRepresentations.swift in Sources */,
|
||||
@ -2114,6 +2543,7 @@
|
||||
D0F69E961D6B8C9B0046BCD6 /* ProgressiveImage.swift in Sources */,
|
||||
D0F69E621D6B8BF90046BCD6 /* ChatHoleGalleryItem.swift in Sources */,
|
||||
D0F69E331D6B8B030046BCD6 /* ChatMessageFileBubbleContentNode.swift in Sources */,
|
||||
D04BB33A1E48797500650E93 /* shader.c in Sources */,
|
||||
D0F69E461D6B8B950046BCD6 /* ChatHistoryNavigationButtonNode.swift in Sources */,
|
||||
D03ADB481D703268005A521C /* ChatInterfaceState.swift in Sources */,
|
||||
D0F69D671D6B87D30046BCD6 /* FFMpegPacket.swift in Sources */,
|
||||
@ -2139,6 +2569,7 @@
|
||||
D075518B1DDA4D7D0073E051 /* LegacyController.swift in Sources */,
|
||||
D0F69E361D6B8B030046BCD6 /* ChatMessageInteractiveMediaNode.swift in Sources */,
|
||||
D08775191E3F53FC00A97350 /* ContactMultiselectionController.swift in Sources */,
|
||||
D04BB2B51E44E58E00650E93 /* AuthorizationSequencePhoneEntryController.swift in Sources */,
|
||||
D087750C1E3E7B7600A97350 /* PreferencesKeys.swift in Sources */,
|
||||
D0F69E381D6B8B030046BCD6 /* ChatMessageItemView.swift in Sources */,
|
||||
D0D268671D78793B00C422DA /* ChatInterfaceStateNavigationButtons.swift in Sources */,
|
||||
@ -2149,6 +2580,7 @@
|
||||
D0DF0C981D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift in Sources */,
|
||||
D0F69E731D6B8C340046BCD6 /* ContactsController.swift in Sources */,
|
||||
D0F69D261D6B87D30046BCD6 /* MediaManager.swift in Sources */,
|
||||
D01D6BFC1E42AB3C006151C6 /* EmojiUtils.swift in Sources */,
|
||||
D02383841DDFA22C004018B6 /* ListMessageHoleItem.swift in Sources */,
|
||||
D0F69D2C1D6B87D30046BCD6 /* MediaPlayerNode.swift in Sources */,
|
||||
D0DE76F71D91BA3D002B8809 /* GridHoleItem.swift in Sources */,
|
||||
@ -2158,6 +2590,7 @@
|
||||
D0F69E021D6B8A880046BCD6 /* ChatListHoleItem.swift in Sources */,
|
||||
D0F917B51E0DA396003687E6 /* GenerateTextEntities.swift in Sources */,
|
||||
D0736F2E1DF4E54A00F2C02A /* MediaNavigationAccessoryHeaderNode.swift in Sources */,
|
||||
D04BB33E1E48797500650E93 /* platform_log.c in Sources */,
|
||||
D0F69DF51D6B8A6C0046BCD6 /* AuthorizationPhoneControllerNode.swift in Sources */,
|
||||
D01B279B1E39386C0022A4C0 /* SettingsControllerEntries.swift in Sources */,
|
||||
D08775201E3F595000A97350 /* ContactListActionItem.swift in Sources */,
|
||||
@ -2177,9 +2610,11 @@
|
||||
D0DE77271D932627002B8809 /* ChatHistoryNode.swift in Sources */,
|
||||
D07CFF7D1DCA273400761F81 /* ChatListViewTransition.swift in Sources */,
|
||||
D0DF0C951D81B063008AEB01 /* ChatInterfaceStateContextMenus.swift in Sources */,
|
||||
D04BB36F1E48797500650E93 /* RMGeometry.m in Sources */,
|
||||
D0F69DF21D6B8A6C0046BCD6 /* AuthorizationPasswordController.swift in Sources */,
|
||||
D0DE772B1D932E16002B8809 /* PeerMediaCollectionModeSelectionNode.swift in Sources */,
|
||||
D0F69E8F1D6B8C850046BCD6 /* RingBuffer.m in Sources */,
|
||||
D04BB2BE1E44FD2600650E93 /* AuthorizationSequenceCodeEntryController.swift in Sources */,
|
||||
D01B279F1E394BD70022A4C0 /* InAppNotificationSettings.swift in Sources */,
|
||||
D0F69DF31D6B8A6C0046BCD6 /* AuthorizationPasswordControllerNode.swift in Sources */,
|
||||
D0F69E131D6B8ACF0046BCD6 /* ChatController.swift in Sources */,
|
||||
@ -2187,9 +2622,14 @@
|
||||
D0F69DFF1D6B8A880046BCD6 /* ChatListController.swift in Sources */,
|
||||
D0E7A1C11D8C258D00C37A6F /* ChatHistoryEntriesForView.swift in Sources */,
|
||||
D01749511E1067E40057C89A /* HashtagSearchController.swift in Sources */,
|
||||
D04BB3381E48797500650E93 /* rngs.c in Sources */,
|
||||
D04BB2C01E44FD3100650E93 /* AuthorizationSequenceCodeEntryControllerNode.swift in Sources */,
|
||||
D0D03B0E1DECB0FE00220C46 /* picture.c in Sources */,
|
||||
D0F69DF11D6B8A6C0046BCD6 /* AuthorizationController.swift in Sources */,
|
||||
D04BB33C1E48797500650E93 /* timing.c in Sources */,
|
||||
D01B27A41E394FC90022A4C0 /* SecuritySettings.swift in Sources */,
|
||||
D050F2181E48D9EA00988324 /* AuthorizationSequenceCountrySelectionControllerNode.swift in Sources */,
|
||||
D049EAF31E44DE2500A2CD3A /* AuthorizationSequenceController.swift in Sources */,
|
||||
D08774FA1E3E2A5600A97350 /* ItemListCheckboxItem.swift in Sources */,
|
||||
D073CE711DCBF23F007511FD /* DeclareEncodables.swift in Sources */,
|
||||
D0E7A1BF1D8C24B900C37A6F /* ChatHistoryViewForLocation.swift in Sources */,
|
||||
@ -2202,7 +2642,11 @@
|
||||
D0B843D11DA922D7005F29E1 /* UserInfoEntries.swift in Sources */,
|
||||
D0F69E6A1D6B8C160046BCD6 /* MapInputController.swift in Sources */,
|
||||
D00219041DDCC86400BE708A /* PerformanceSpinner.swift in Sources */,
|
||||
D050F2161E48D9E000988324 /* AuthorizationSequenceCountrySelectionController.swift in Sources */,
|
||||
D01749551E1082770057C89A /* StoredMessageFromSearchPeer.swift in Sources */,
|
||||
D04BB32C1E48797500650E93 /* animations.c in Sources */,
|
||||
D049EAEE1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift in Sources */,
|
||||
D04BB2BB1E44EA2400650E93 /* AuthorizationSequenceSplashControllerNode.swift in Sources */,
|
||||
D0215D581E04302E001A0B1E /* InstantPageTileNode.swift in Sources */,
|
||||
D0215D521E0423EE001A0B1E /* InstantPageShapeItem.swift in Sources */,
|
||||
D0F69DE51D6B8A420046BCD6 /* ListControllerSpacerItem.swift in Sources */,
|
||||
@ -2211,7 +2655,10 @@
|
||||
D0F69DE11D6B8A420046BCD6 /* ListControllerDisclosureActionItem.swift in Sources */,
|
||||
D0F69E301D6B8B030046BCD6 /* ChatMessageBubbleContentNode.swift in Sources */,
|
||||
D0F69DC31D6B89DA0046BCD6 /* TextNode.swift in Sources */,
|
||||
D04BB3791E48797500650E93 /* RMRootViewController.m in Sources */,
|
||||
D0F69DC11D6B89D30046BCD6 /* ListSectionHeaderNode.swift in Sources */,
|
||||
D04BB2B31E44E56200650E93 /* AuthorizationSequenceSplashController.swift in Sources */,
|
||||
D04BB3751E48797500650E93 /* RMIntroViewController.m in Sources */,
|
||||
D0D03AE31DECACB700220C46 /* ManagedAudioSession.swift in Sources */,
|
||||
D0D2689A1D79CF9F00C422DA /* ChatPanelInterfaceInteraction.swift in Sources */,
|
||||
D0177B821DFAEA5400A5083A /* MediaNavigationAccessoryItemListNode.swift in Sources */,
|
||||
@ -2236,10 +2683,13 @@
|
||||
D0D268691D78865300C422DA /* ChatAvatarNavigationNode.swift in Sources */,
|
||||
D0DC35441DE32230000195EB /* ChatInterfaceStateContextQueries.swift in Sources */,
|
||||
D0215D4C1E041D5E001A0B1E /* InstantPageMediaNode.swift in Sources */,
|
||||
D0DA44541E4E7302005FDCA7 /* ProgressNavigationButtonNode.swift in Sources */,
|
||||
D04BB2C31E45020A00650E93 /* AuthorizationSequencePasswordEntryController.swift in Sources */,
|
||||
D0F69DA51D6B87EC0046BCD6 /* MediaTrackFrameDecoder.swift in Sources */,
|
||||
D023ED321DDB60CF00BD496D /* LegacyNavigationController.swift in Sources */,
|
||||
D0F69E2D1D6B8B030046BCD6 /* ChatMessageActionItemNode.swift in Sources */,
|
||||
D0D03B081DECB0FE00220C46 /* diag_range.c in Sources */,
|
||||
D0BC387F1E40F1CF0044D6FE /* ContactSelectionController.swift in Sources */,
|
||||
D0F69DE21D6B8A420046BCD6 /* ListControllerGroupableItem.swift in Sources */,
|
||||
D0F69D791D6B87DF0046BCD6 /* MediaTrackFrame.swift in Sources */,
|
||||
D0215D3A1E041003001A0B1E /* InstantPageLayout.swift in Sources */,
|
||||
@ -2360,6 +2810,10 @@
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/third-party/RMIntro/platform/ios/HockeySDK-iOS/HockeySDK.embeddedframework",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "third-party/ogg";
|
||||
INFOPLIST_FILE = TelegramUI/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
@ -2509,6 +2963,10 @@
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/third-party/RMIntro/platform/ios/HockeySDK-iOS/HockeySDK.embeddedframework",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "third-party/ogg";
|
||||
INFOPLIST_FILE = TelegramUI/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
@ -2545,6 +3003,10 @@
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/third-party/RMIntro/platform/ios/HockeySDK-iOS/HockeySDK.embeddedframework",
|
||||
);
|
||||
HEADER_SEARCH_PATHS = "third-party/ogg";
|
||||
INFOPLIST_FILE = TelegramUI/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<key>TelegramUI.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>19</integer>
|
||||
<integer>12</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
||||
@ -6,7 +6,7 @@ import TelegramCore
|
||||
|
||||
enum AuthorizationCodeResult {
|
||||
case Authorization(Api.auth.Authorization)
|
||||
case Password
|
||||
case Password(String)
|
||||
}
|
||||
|
||||
class AuthorizationCodeController: ViewController {
|
||||
@ -62,23 +62,5 @@ class AuthorizationCodeController: ViewController {
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let phoneCodeHash = phoneCodeHash {
|
||||
let signal = self.account.network.request(Api.functions.auth.signIn(phoneNumber: self.phone, phoneCodeHash: phoneCodeHash, phoneCode: node.codeNode.attributedText?.string ?? "")) |> map { authorization in
|
||||
return AuthorizationCodeResult.Authorization(authorization)
|
||||
} |> `catch` { error -> Signal<AuthorizationCodeResult, MTRpcError> in
|
||||
switch (error.errorCode, error.errorDescription) {
|
||||
case (401, "SESSION_PASSWORD_NEEDED"):
|
||||
return .single(.Password)
|
||||
case _:
|
||||
return .fail(error)
|
||||
}
|
||||
}
|
||||
|
||||
self.signInDisposable.set(signal.start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
strongSelf.resultPipe.putNext(result)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ public class AuthorizationController: NavigationController {
|
||||
modifier.setState(state)
|
||||
return state
|
||||
} |> map { state -> Account in
|
||||
let account = Account(id: account.id, basePath: account.basePath, testingEnvironment: account.testingEnvironment, postbox: account.postbox, network: account.network, peerId: user.id)
|
||||
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
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ class AuthorizationPhoneController: ViewController {
|
||||
@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", langCode: "en")
|
||||
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
|
||||
|
||||
91
TelegramUI/AuthorizationSequenceCodeEntryController.swift
Normal file
91
TelegramUI/AuthorizationSequenceCodeEntryController.swift
Normal file
@ -0,0 +1,91 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
|
||||
final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
private var controllerNode: AuthorizationSequenceCodeEntryControllerNode {
|
||||
return self.displayNode as! AuthorizationSequenceCodeEntryControllerNode
|
||||
}
|
||||
|
||||
var loginWithCode: ((String) -> Void)?
|
||||
var requestNextOption: (() -> Void)?
|
||||
|
||||
var data: (String, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
if self.inProgress {
|
||||
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode())
|
||||
self.navigationItem.rightBarButtonItem = item
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
self.controllerNode.inProgress = self.inProgress
|
||||
}
|
||||
}
|
||||
|
||||
override init(navigationBar: NavigationBar = NavigationBar()) {
|
||||
super.init(navigationBar: navigationBar)
|
||||
|
||||
self.navigationBar.backgroundColor = nil
|
||||
self.navigationBar.isOpaque = false
|
||||
self.navigationBar.stripeColor = UIColor.clear
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = AuthorizationSequenceCodeEntryControllerNode()
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.controllerNode.loginWithCode = { [weak self] code in
|
||||
self?.loginWithCode?(code)
|
||||
}
|
||||
|
||||
self.controllerNode.requestNextOption = { [weak self] in
|
||||
self?.requestNextOption?()
|
||||
}
|
||||
|
||||
if let (number, codeType, nextType, timeout) = self.data {
|
||||
self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.controllerNode.activateInput()
|
||||
}
|
||||
|
||||
func updateData(number: String, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?) {
|
||||
if self.data?.0 != number || self.data?.1 != codeType || self.data?.2 != nextType || self.data?.3 != timeout {
|
||||
self.data = (number, codeType, nextType, timeout)
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout)
|
||||
self.requestLayout(transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition)
|
||||
}
|
||||
|
||||
@objc func nextPressed() {
|
||||
if self.controllerNode.currentCode.isEmpty {
|
||||
hapticFeedback.error()
|
||||
self.controllerNode.animateError()
|
||||
} else {
|
||||
self.loginWithCode?(self.controllerNode.currentCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
278
TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift
Normal file
278
TelegramUI/AuthorizationSequenceCodeEntryControllerNode.swift
Normal file
@ -0,0 +1,278 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
|
||||
private func currentOptionText(_ type: SentAuthorizationCodeType) -> NSAttributedString {
|
||||
switch type {
|
||||
case .sms:
|
||||
return NSAttributedString(string: "We have sent you an SMS with a code to the number", font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center)
|
||||
case .otherSession:
|
||||
let string = NSMutableAttributedString()
|
||||
string.append(NSAttributedString(string: "We've sent the code to the ", font: Font.regular(16.0), textColor: UIColor.black))
|
||||
string.append(NSAttributedString(string: "Telegram", font: Font.medium(16.0), textColor: UIColor.black))
|
||||
string.append(NSAttributedString(string: " app on your other device.", font: Font.regular(16.0), textColor: UIColor.black))
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.alignment = .center
|
||||
string.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, string.length))
|
||||
return string
|
||||
case .call, .flashCall:
|
||||
return NSAttributedString(string: "Telegram dialed your number", font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
private func nextOptionText(_ type: AuthorizationCodeNextType?, timeout: Int32?) -> NSAttributedString {
|
||||
if let type = type, let timeout = timeout {
|
||||
let minutes = timeout / 60
|
||||
let seconds = timeout % 60
|
||||
switch type {
|
||||
case .sms:
|
||||
if timeout <= 0 {
|
||||
return NSAttributedString(string: "Telegram sent you an SMS", font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center)
|
||||
} else {
|
||||
return NSAttributedString(string: String(format: "Telegram will send you an SMS in %d:%.2d", minutes, seconds), font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center)
|
||||
}
|
||||
case .call, .flashCall:
|
||||
if timeout <= 0 {
|
||||
return NSAttributedString(string: "Telegram dialed your number", font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center)
|
||||
} else {
|
||||
return NSAttributedString(string: String(format: "Telegram will call you in %d:%.2d", minutes, seconds), font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return NSAttributedString(string: "Haven't received the code?", font: Font.regular(16.0), textColor: UIColor(0x007ee5), paragraphAlignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let navigationBackgroundNode: ASDisplayNode
|
||||
private let stripeNode: ASDisplayNode
|
||||
private let titleNode: ASTextNode
|
||||
private let currentOptionNode: ASTextNode
|
||||
private let nextOptionNode: ASTextNode
|
||||
|
||||
private let codeField: TextFieldNode
|
||||
private let codeSeparatorNode: ASDisplayNode
|
||||
|
||||
private var codeType: SentAuthorizationCodeType?
|
||||
|
||||
private let countdownDisposable = MetaDisposable()
|
||||
private var currentTimeoutTime: Int32?
|
||||
|
||||
private var layoutArguments: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
var phoneNumber: String = "" {
|
||||
didSet {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.light(30.0), textColor: UIColor.black)
|
||||
}
|
||||
}
|
||||
|
||||
var currentCode: String {
|
||||
return self.codeField.textField.text ?? ""
|
||||
}
|
||||
|
||||
var loginWithCode: ((String) -> Void)?
|
||||
var requestNextOption: (() -> Void)?
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
self.codeField.alpha = self.inProgress ? 0.6 : 1.0
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.navigationBackgroundNode = ASDisplayNode()
|
||||
self.navigationBackgroundNode.isLayerBacked = true
|
||||
self.navigationBackgroundNode.backgroundColor = UIColor(0xefefef)
|
||||
|
||||
self.stripeNode = ASDisplayNode()
|
||||
self.stripeNode.isLayerBacked = true
|
||||
self.stripeNode.backgroundColor = UIColor(0xbcbbc1)
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.isLayerBacked = true
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.currentOptionNode = ASTextNode()
|
||||
self.currentOptionNode.isLayerBacked = true
|
||||
self.currentOptionNode.displaysAsynchronously = false
|
||||
|
||||
self.nextOptionNode = ASTextNode()
|
||||
self.nextOptionNode.isLayerBacked = true
|
||||
self.nextOptionNode.displaysAsynchronously = false
|
||||
self.nextOptionNode.attributedText = nextOptionText(AuthorizationCodeNextType.call, timeout: 60)
|
||||
|
||||
self.codeSeparatorNode = ASDisplayNode()
|
||||
self.codeSeparatorNode.isLayerBacked = true
|
||||
self.codeSeparatorNode.backgroundColor = UIColor(0xbcbbc1)
|
||||
|
||||
self.codeField = TextFieldNode()
|
||||
self.codeField.textField.font = Font.regular(24.0)
|
||||
self.codeField.textField.textAlignment = .center
|
||||
self.codeField.textField.keyboardType = .numberPad
|
||||
self.codeField.textField.returnKeyType = .done
|
||||
|
||||
super.init(viewBlock: {
|
||||
return UITracingLayerView()
|
||||
}, didLoad: nil)
|
||||
|
||||
self.backgroundColor = UIColor.white
|
||||
|
||||
self.addSubnode(self.navigationBackgroundNode)
|
||||
self.addSubnode(self.stripeNode)
|
||||
self.addSubnode(self.codeSeparatorNode)
|
||||
self.addSubnode(self.codeField)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.currentOptionNode)
|
||||
self.addSubnode(self.nextOptionNode)
|
||||
|
||||
self.codeField.textField.addTarget(self, action: #selector(self.codeFieldTextChanged(_:)), for: .editingChanged)
|
||||
|
||||
self.codeField.textField.attributedPlaceholder = NSAttributedString(string: "Code", font: Font.regular(24.0), textColor: UIColor(0xbcbcc3))
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.countdownDisposable.dispose()
|
||||
}
|
||||
|
||||
func updateData(number: String, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?) {
|
||||
self.codeType = codeType
|
||||
self.phoneNumber = number
|
||||
|
||||
self.currentOptionNode.attributedText = currentOptionText(codeType)
|
||||
if let timeout = timeout {
|
||||
self.currentTimeoutTime = timeout
|
||||
let disposable = ((Signal<Int, NoError>.single(1) |> delay(1.0, queue: Queue.mainQueue())) |> restart).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if let currentTimeoutTime = strongSelf.currentTimeoutTime, currentTimeoutTime > 0 {
|
||||
strongSelf.currentTimeoutTime = currentTimeoutTime - 1
|
||||
strongSelf.nextOptionNode.attributedText = nextOptionText(nextType, timeout:strongSelf.currentTimeoutTime)
|
||||
if let layoutArguments = strongSelf.layoutArguments {
|
||||
strongSelf.containerLayoutUpdated(layoutArguments.0, navigationBarHeight: layoutArguments.1, transition: .immediate)
|
||||
}
|
||||
if currentTimeoutTime == 1 {
|
||||
strongSelf.requestNextOption?()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
self.countdownDisposable.set(disposable)
|
||||
} else {
|
||||
self.currentTimeoutTime = nil
|
||||
self.countdownDisposable.set(nil)
|
||||
}
|
||||
self.nextOptionNode.attributedText = nextOptionText(nextType, timeout: self.currentTimeoutTime)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.layoutArguments = (layout, navigationBarHeight)
|
||||
|
||||
let insets = layout.insets(options: [.statusBar, .input])
|
||||
let availableHeight = max(1.0, layout.size.height - insets.top - insets.bottom)
|
||||
|
||||
if max(layout.size.width, layout.size.height) > 1023.0 {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.light(30.0), textColor: UIColor.black)
|
||||
} else {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.phoneNumber, font: Font.regular(20.0), textColor: UIColor.black)
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
let additionalTitleSpacing: CGFloat
|
||||
if titleSize.width > layout.size.width - 160.0 {
|
||||
additionalTitleSpacing = 44.0
|
||||
} else {
|
||||
additionalTitleSpacing = 0.0
|
||||
}
|
||||
|
||||
let minimalTitleSpacing: CGFloat = 10.0
|
||||
let maxTitleSpacing: CGFloat = 22.0
|
||||
let inputFieldsHeight: CGFloat = 60.0
|
||||
|
||||
let minimalNoticeSpacing: CGFloat = 11.0
|
||||
let maxNoticeSpacing: CGFloat = 35.0
|
||||
let noticeSize = self.currentOptionNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let minimalTermsOfServiceSpacing: CGFloat = 6.0
|
||||
let maxTermsOfServiceSpacing: CGFloat = 20.0
|
||||
let termsOfServiceSize = self.nextOptionNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
let minTrailingSpacing: CGFloat = 10.0
|
||||
|
||||
let inputHeight = inputFieldsHeight
|
||||
let essentialHeight = additionalTitleSpacing + titleSize.height + minimalTitleSpacing + inputHeight + minimalNoticeSpacing + noticeSize.height
|
||||
let additionalHeight = minimalTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing
|
||||
|
||||
let navigationHeight: CGFloat
|
||||
if essentialHeight + additionalHeight > availableHeight || availableHeight * 0.66 - inputHeight < additionalHeight {
|
||||
navigationHeight = min(floor(availableHeight * 0.3), availableHeight - inputFieldsHeight)
|
||||
} else {
|
||||
navigationHeight = floor(availableHeight * 0.3)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.navigationBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: navigationHeight)))
|
||||
transition.updateFrame(node: self.stripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
|
||||
let titleOffset: CGFloat
|
||||
if navigationHeight * 0.5 < titleSize.height + minimalTitleSpacing {
|
||||
titleOffset = floor((navigationHeight - titleSize.height) / 2.0)
|
||||
} else {
|
||||
titleOffset = max(navigationHeight * 0.5, navigationHeight - maxTitleSpacing - titleSize.height)
|
||||
}
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: titleOffset), size: titleSize))
|
||||
|
||||
let codeFieldFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight + 3.0), size: CGSize(width: layout.size.width, height: 60.0))
|
||||
transition.updateFrame(node: self.codeField, frame: codeFieldFrame)
|
||||
transition.updateFrame(node: self.codeSeparatorNode, frame: CGRect(origin: CGPoint(x: 22.0, y: navigationHeight + 60.0), size: CGSize(width: layout.size.width - 44.0, height: UIScreenPixel)))
|
||||
|
||||
let additionalAvailableHeight = max(1.0, availableHeight - codeFieldFrame.maxY)
|
||||
let additionalAvailableSpacing = max(1.0, additionalAvailableHeight - noticeSize.height - termsOfServiceSize.height)
|
||||
let noticeSpacingFactor = maxNoticeSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
|
||||
let termsOfServiceSpacingFactor = maxTermsOfServiceSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
|
||||
|
||||
let noticeSpacing: CGFloat
|
||||
let termsOfServiceSpacing: CGFloat
|
||||
if additionalAvailableHeight <= maxNoticeSpacing + noticeSize.height + maxTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing {
|
||||
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
|
||||
noticeSpacing = floor((additionalAvailableHeight - termsOfServiceSpacing - noticeSize.height - termsOfServiceSize.height) / 2.0)
|
||||
} else {
|
||||
noticeSpacing = min(floor(noticeSpacingFactor * additionalAvailableSpacing), maxNoticeSpacing)
|
||||
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
|
||||
}
|
||||
|
||||
let currentOptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - noticeSize.width) / 2.0), y: codeFieldFrame.maxY + noticeSpacing), size: noticeSize)
|
||||
let nextOptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsOfServiceSize.width) / 2.0), y: currentOptionFrame.maxY + termsOfServiceSpacing), size: termsOfServiceSize)
|
||||
|
||||
transition.updateFrame(node: self.currentOptionNode, frame: currentOptionFrame)
|
||||
transition.updateFrame(node: self.nextOptionNode, frame: nextOptionFrame)
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.codeField.textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
self.codeField.layer.addShakeAnimation()
|
||||
}
|
||||
|
||||
@objc func codeFieldTextChanged(_ textField: UITextField) {
|
||||
if let codeType = self.codeType {
|
||||
var codeLength: Int32?
|
||||
switch codeType {
|
||||
case let .call(length):
|
||||
codeLength = length
|
||||
case let .otherSession(length):
|
||||
codeLength = length
|
||||
case let .sms(length):
|
||||
codeLength = length
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let codeLength = codeLength, let text = textField.text, text.characters.count == Int(codeLength) {
|
||||
self.loginWithCode?(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
return !self.inProgress
|
||||
}
|
||||
}
|
||||
326
TelegramUI/AuthorizationSequenceController.swift
Normal file
326
TelegramUI/AuthorizationSequenceController.swift
Normal file
@ -0,0 +1,326 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import MtProtoKitDynamic
|
||||
|
||||
public final class AuthorizationSequenceController: NavigationController {
|
||||
private var account: UnauthorizedAccount
|
||||
|
||||
private var stateDisposable: Disposable?
|
||||
private let actionDisposable = MetaDisposable()
|
||||
|
||||
let _authorizedAccount = Promise<Account>()
|
||||
public var authorizedAccount: Signal<Account, NoError> {
|
||||
return self._authorizedAccount.get()
|
||||
}
|
||||
|
||||
public init(account: UnauthorizedAccount) {
|
||||
self.account = account
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.stateDisposable = (account.postbox.stateView() |> deliverOnMainQueue).start(next: { [weak self] view in
|
||||
self?.updateState(state: view.state ?? UnauthorizedAccountState(masterDatacenterId: account.masterDatacenterId, contents: .empty))
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stateDisposable?.dispose()
|
||||
self.actionDisposable.dispose()
|
||||
}
|
||||
|
||||
private func splashController() -> AuthorizationSequenceSplashController {
|
||||
var currentController: AuthorizationSequenceSplashController?
|
||||
for c in self.viewControllers {
|
||||
if let c = c as? AuthorizationSequenceSplashController {
|
||||
currentController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
let controller: AuthorizationSequenceSplashController
|
||||
if let currentController = currentController {
|
||||
controller = currentController
|
||||
} else {
|
||||
controller = AuthorizationSequenceSplashController()
|
||||
controller.nextPressed = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let masterDatacenterId = strongSelf.account.masterDatacenterId
|
||||
let _ = (strongSelf.account.postbox.modify { modifier -> Void in
|
||||
modifier.setState(UnauthorizedAccountState(masterDatacenterId: masterDatacenterId, contents: .phoneEntry(countryCode: 1, number: "")))
|
||||
}).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
private func phoneEntryController(countryCode: Int32, number: String) -> AuthorizationSequencePhoneEntryController {
|
||||
var currentController: AuthorizationSequencePhoneEntryController?
|
||||
for c in self.viewControllers {
|
||||
if let c = c as? AuthorizationSequencePhoneEntryController {
|
||||
currentController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
let controller: AuthorizationSequencePhoneEntryController
|
||||
if let currentController = currentController {
|
||||
controller = currentController
|
||||
} else {
|
||||
controller = AuthorizationSequencePhoneEntryController()
|
||||
controller.loginWithNumber = { [weak self, weak controller] number in
|
||||
if let strongSelf = self {
|
||||
controller?.inProgress = true
|
||||
let account = strongSelf.account
|
||||
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 {
|
||||
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
|
||||
Queue.mainQueue().async {
|
||||
if let controller = controller {
|
||||
controller.inProgress = false
|
||||
|
||||
var text: String?
|
||||
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
|
||||
text = "You have requested authorization code too many times. Please try again later."
|
||||
} else if error.errorDescription == "PHONE_NUMBER_INVALID" {
|
||||
text = "The phone number you entered is not valid. Please enter the correct number along with your area code."
|
||||
}
|
||||
if let text = text {
|
||||
controller.present(standardTextAlertController(title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
controller.updateData(countryCode: countryCode, number: number)
|
||||
return controller
|
||||
}
|
||||
|
||||
private func codeEntryController(number: String, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?) -> AuthorizationSequenceCodeEntryController {
|
||||
var currentController: AuthorizationSequenceCodeEntryController?
|
||||
for c in self.viewControllers {
|
||||
if let c = c as? AuthorizationSequenceCodeEntryController {
|
||||
currentController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
let controller: AuthorizationSequenceCodeEntryController
|
||||
if let currentController = currentController {
|
||||
controller = currentController
|
||||
} else {
|
||||
controller = AuthorizationSequenceCodeEntryController()
|
||||
controller.loginWithCode = { [weak self, weak controller] code in
|
||||
if let strongSelf = self {
|
||||
controller?.inProgress = true
|
||||
|
||||
let account = strongSelf.account
|
||||
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 {
|
||||
if let controller = controller {
|
||||
controller.inProgress = false
|
||||
|
||||
var text: String?
|
||||
if error.hasPrefix("FLOOD_WAIT") {
|
||||
text = "You have entered invalid code too many times. Please try again later."
|
||||
} else if error == "CODE_INVALID" {
|
||||
text = "Invalid code."
|
||||
} else {
|
||||
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.updateData(number: formatPhoneNumber(number), codeType: type, nextType: nextType, timeout: timeout)
|
||||
return controller
|
||||
}
|
||||
|
||||
private func passwordEntryController(hint: String) -> AuthorizationSequencePasswordEntryController {
|
||||
var currentController: AuthorizationSequencePasswordEntryController?
|
||||
for c in self.viewControllers {
|
||||
if let c = c as? AuthorizationSequencePasswordEntryController {
|
||||
currentController = c
|
||||
break
|
||||
}
|
||||
}
|
||||
let controller: AuthorizationSequencePasswordEntryController
|
||||
if let currentController = currentController {
|
||||
controller = currentController
|
||||
} else {
|
||||
controller = AuthorizationSequencePasswordEntryController()
|
||||
controller.loginWithPassword = { [weak self, weak controller] password in
|
||||
if let strongSelf = self {
|
||||
controller?.inProgress = true
|
||||
|
||||
let account = strongSelf.account
|
||||
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 {
|
||||
if let controller = controller {
|
||||
controller.inProgress = false
|
||||
|
||||
var text: String?
|
||||
if error.hasPrefix("FLOOD_WAIT") {
|
||||
text = "You have entered invalid password too many times. Please try again later."
|
||||
} else if error == "PASSWORD_HASH_INVALID" {
|
||||
text = "Invalid password."
|
||||
} else {
|
||||
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.updateData(hint: hint)
|
||||
return controller
|
||||
}
|
||||
|
||||
private func updateState(state: Coding?) {
|
||||
if let state = state as? UnauthorizedAccountState {
|
||||
switch state.contents {
|
||||
case .empty:
|
||||
if let _ = self.viewControllers.last as? AuthorizationSequenceSplashController {
|
||||
} else {
|
||||
self.setViewControllers([self.splashController()], animated: !self.viewControllers.isEmpty)
|
||||
}
|
||||
case let .phoneEntry(countryCode, number):
|
||||
self.setViewControllers([self.splashController(), self.phoneEntryController(countryCode: countryCode, number: number)], animated: !self.viewControllers.isEmpty)
|
||||
case let .confirmationCodeEntry(number, type, _, timeout, nextType):
|
||||
self.setViewControllers([self.splashController(), self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout)], animated: !self.viewControllers.isEmpty)
|
||||
case let .passwordEntry(hint):
|
||||
self.setViewControllers([self.splashController(), self.passwordEntryController(hint: hint)], animated: !self.viewControllers.isEmpty)
|
||||
}
|
||||
} 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
|
||||
if case let .right(authorizedAccount) = account {
|
||||
return .single(authorizedAccount)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
499
TelegramUI/AuthorizationSequenceCountrySelectionController.swift
Normal file
499
TelegramUI/AuthorizationSequenceCountrySelectionController.swift
Normal file
@ -0,0 +1,499 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
private let countryNamesAndCodes: [(String, Int)] = [
|
||||
("Jamaica", 1876),
|
||||
("Saint Kitts & Nevis", 1869),
|
||||
("Trinidad & Tobago", 1868),
|
||||
("Saint Vincent & the Grenadines", 1784),
|
||||
("Dominica", 1767),
|
||||
("Saint Lucia", 1758),
|
||||
("Sint Maarten", 1721),
|
||||
("American Samoa", 1684),
|
||||
("Guam", 1671),
|
||||
("Northern Mariana Islands", 1670),
|
||||
("Montserrat", 1664),
|
||||
("Turks & Caicos Islands", 1649),
|
||||
("Grenada", 1473),
|
||||
("Bermuda", 1441),
|
||||
("Cayman Islands", 1345),
|
||||
("US Virgin Islands", 1340),
|
||||
("British Virgin Islands", 1284),
|
||||
("Antigua & Barbuda", 1268),
|
||||
("Anguilla", 1264),
|
||||
("Barbados", 1246),
|
||||
("Bahamas", 1242),
|
||||
("Uzbekistan", 998),
|
||||
("Kyrgyzstan", 996),
|
||||
("Georgia", 995),
|
||||
("Azerbaijan", 994),
|
||||
("Turkmenistan", 993),
|
||||
("Tajikistan", 992),
|
||||
("Nepal", 977),
|
||||
("Mongolia", 976),
|
||||
("Bhutan", 975),
|
||||
("Qatar", 974),
|
||||
("Bahrain", 973),
|
||||
("Israel", 972),
|
||||
("United Arab Emirates", 971),
|
||||
("Palestine", 970),
|
||||
("Oman", 968),
|
||||
("Yemen", 967),
|
||||
("Saudi Arabia", 966),
|
||||
("Kuwait", 965),
|
||||
("Iraq", 964),
|
||||
("Syrian Arab Republic", 963),
|
||||
("Jordan", 962),
|
||||
("Lebanon", 961),
|
||||
("Maldives", 960),
|
||||
("Taiwan", 886),
|
||||
("Bangladesh", 880),
|
||||
("Laos", 856),
|
||||
("Cambodia", 855),
|
||||
("Macau", 853),
|
||||
("Hong Kong", 852),
|
||||
("North Korea", 850),
|
||||
("Marshall Islands", 692),
|
||||
("Micronesia", 691),
|
||||
("Tokelau", 690),
|
||||
("French Polynesia", 689),
|
||||
("Tuvalu", 688),
|
||||
("New Caledonia", 687),
|
||||
("Kiribati", 686),
|
||||
("Samoa", 685),
|
||||
("Niue", 683),
|
||||
("Cook Islands", 682),
|
||||
("Wallis & Futuna", 681),
|
||||
("Palau", 680),
|
||||
("Fiji", 679),
|
||||
("Vanuatu", 678),
|
||||
("Solomon Islands", 677),
|
||||
("Tonga", 676),
|
||||
("Papua New Guinea", 675),
|
||||
("Nauru", 674),
|
||||
("Brunei Darussalam", 673),
|
||||
("Norfolk Island", 672),
|
||||
("Timor-Leste", 670),
|
||||
("Bonaire, Sint Eustatius & Saba", 599),
|
||||
("Curaçao", 599),
|
||||
("Uruguay", 598),
|
||||
("Suriname", 597),
|
||||
("Martinique", 596),
|
||||
("Paraguay", 595),
|
||||
("French Guiana", 594),
|
||||
("Ecuador", 593),
|
||||
("Guyana", 592),
|
||||
("Bolivia", 591),
|
||||
("Guadeloupe", 590),
|
||||
("Haiti", 509),
|
||||
("Saint Pierre & Miquelon", 508),
|
||||
("Panama", 507),
|
||||
("Costa Rica", 506),
|
||||
("Nicaragua", 505),
|
||||
("Honduras", 504),
|
||||
("El Salvador", 503),
|
||||
("Guatemala", 502),
|
||||
("Belize", 501),
|
||||
("Falkland Islands", 500),
|
||||
("Liechtenstein", 423),
|
||||
("Slovakia", 421),
|
||||
("Czech Republic", 420),
|
||||
("Macedonia", 389),
|
||||
("Bosnia & Herzegovina", 387),
|
||||
("Slovenia", 386),
|
||||
("Croatia", 385),
|
||||
("Montenegro", 382),
|
||||
("Serbia", 381),
|
||||
("Ukraine", 380),
|
||||
("San Marino", 378),
|
||||
("Monaco", 377),
|
||||
("Andorra", 376),
|
||||
("Belarus", 375),
|
||||
("Armenia", 374),
|
||||
("Moldova", 373),
|
||||
("Estonia", 372),
|
||||
("Latvia", 371),
|
||||
("Lithuania", 370),
|
||||
("Bulgaria", 359),
|
||||
("Finland", 358),
|
||||
("Cyprus", 357),
|
||||
("Malta", 356),
|
||||
("Albania", 355),
|
||||
("Iceland", 354),
|
||||
("Ireland", 353),
|
||||
("Luxembourg", 352),
|
||||
("Portugal", 351),
|
||||
("Gibraltar", 350),
|
||||
("Greenland", 299),
|
||||
("Faroe Islands", 298),
|
||||
("Aruba", 297),
|
||||
("Eritrea", 291),
|
||||
("Saint Helena", 290),
|
||||
("Comoros", 269),
|
||||
("Swaziland", 268),
|
||||
("Botswana", 267),
|
||||
("Lesotho", 266),
|
||||
("Malawi", 265),
|
||||
("Namibia", 264),
|
||||
("Zimbabwe", 263),
|
||||
("Réunion", 262),
|
||||
("Madagascar", 261),
|
||||
("Zambia", 260),
|
||||
("Mozambique", 258),
|
||||
("Burundi", 257),
|
||||
("Uganda", 256),
|
||||
("Tanzania", 255),
|
||||
("Kenya", 254),
|
||||
("Djibouti", 253),
|
||||
("Somalia", 252),
|
||||
("Ethiopia", 251),
|
||||
("Rwanda", 250),
|
||||
("Sudan", 249),
|
||||
("Seychelles", 248),
|
||||
("Saint Helena", 247),
|
||||
("Diego Garcia", 246),
|
||||
("Guinea-Bissau", 245),
|
||||
("Angola", 244),
|
||||
("Congo (Dem. Rep.)", 243),
|
||||
("Congo (Rep.)", 242),
|
||||
("Gabon", 241),
|
||||
("Equatorial Guinea", 240),
|
||||
("São Tomé & Príncipe", 239),
|
||||
("Cape Verde", 238),
|
||||
("Cameroon", 237),
|
||||
("Central African Rep.", 236),
|
||||
("Chad", 235),
|
||||
("Nigeria", 234),
|
||||
("Ghana", 233),
|
||||
("Sierra Leone", 232),
|
||||
("Liberia", 231),
|
||||
("Mauritius", 230),
|
||||
("Benin", 229),
|
||||
("Togo", 228),
|
||||
("Niger", 227),
|
||||
("Burkina Faso", 226),
|
||||
("Côte d`Ivoire", 225),
|
||||
("Guinea", 224),
|
||||
("Mali", 223),
|
||||
("Mauritania", 222),
|
||||
("Senegal", 221),
|
||||
("Gambia", 220),
|
||||
("Libya", 218),
|
||||
("Tunisia", 216),
|
||||
("Algeria", 213),
|
||||
("Morocco", 212),
|
||||
("South Sudan", 211),
|
||||
("Iran", 98),
|
||||
("Myanmar", 95),
|
||||
("Sri Lanka", 94),
|
||||
("Afghanistan", 93),
|
||||
("Pakistan", 92),
|
||||
("India", 91),
|
||||
("Turkey", 90),
|
||||
("China", 86),
|
||||
("Vietnam", 84),
|
||||
("South Korea", 82),
|
||||
("Japan", 81),
|
||||
("Thailand", 66),
|
||||
("Singapore", 65),
|
||||
("New Zealand", 64),
|
||||
("Philippines", 63),
|
||||
("Indonesia", 62),
|
||||
("Australia", 61),
|
||||
("Malaysia", 60),
|
||||
("Venezuela", 58),
|
||||
("Colombia", 57),
|
||||
("Chile", 56),
|
||||
("Brazil", 55),
|
||||
("Argentina", 54),
|
||||
("Cuba", 53),
|
||||
("Mexico", 52),
|
||||
("Peru", 51),
|
||||
("Germany", 49),
|
||||
("Poland", 48),
|
||||
("Norway", 47),
|
||||
("Sweden", 46),
|
||||
("Denmark", 45),
|
||||
("United Kingdom", 44),
|
||||
("Austria", 43),
|
||||
("Switzerland", 41),
|
||||
("Romania", 40),
|
||||
("Italy", 39),
|
||||
("Hungary", 36),
|
||||
("Spain", 34),
|
||||
("France", 33),
|
||||
("Belgium", 32),
|
||||
("Netherlands", 31),
|
||||
("Greece", 30),
|
||||
("South Africa", 27),
|
||||
("Egypt", 20),
|
||||
("Russian Federation", 7),
|
||||
("Kazakhstan", 7),
|
||||
("USA", 1),
|
||||
("Puerto Rico", 1),
|
||||
("Dominican Rep.", 1),
|
||||
("Canada", 1)
|
||||
]
|
||||
|
||||
private final class InnerCoutrySearchResultsController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
||||
private let tableView: UITableView
|
||||
|
||||
var searchResults: [(String, Int)] = [] {
|
||||
didSet {
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
var itemSelected: (((String, Int)) -> Void)?
|
||||
|
||||
init() {
|
||||
self.tableView = UITableView(frame: CGRect(), style: .plain)
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = .white
|
||||
|
||||
self.view.addSubview(self.tableView)
|
||||
self.tableView.frame = self.view.bounds
|
||||
self.tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
self.tableView.dataSource = self
|
||||
self.tableView.delegate = self
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return self.searchResults.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: UITableViewCell
|
||||
if let currentCell = tableView.dequeueReusableCell(withIdentifier: "CountryCell") {
|
||||
cell = currentCell
|
||||
} else {
|
||||
cell = UITableViewCell()
|
||||
let label = UILabel()
|
||||
label.font = Font.medium(17.0)
|
||||
cell.accessoryView = label
|
||||
}
|
||||
cell.textLabel?.text = self.searchResults[indexPath.row].0
|
||||
if let label = cell.accessoryView as? UILabel {
|
||||
label.text = "+\(self.searchResults[indexPath.row].1)"
|
||||
label.sizeToFit()
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
self.itemSelected?(self.searchResults[indexPath.row])
|
||||
}
|
||||
}
|
||||
|
||||
private final class InnerCountrySelectionController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate {
|
||||
private let tableView: UITableView
|
||||
|
||||
private let sections: [(String, [(String, Int)])]
|
||||
private let sectionTitles: [String]
|
||||
|
||||
private var searchController: UISearchController!
|
||||
private var searchResultsController: InnerCoutrySearchResultsController!
|
||||
|
||||
var dismiss: (() -> Void)?
|
||||
var itemSelected: (((String, Int)) -> Void)?
|
||||
|
||||
init() {
|
||||
self.tableView = UITableView(frame: CGRect(), style: .plain)
|
||||
|
||||
var sections: [(String, [(String, Int)])] = []
|
||||
for (name, code) in countryNamesAndCodes.sorted(by: { lhs, rhs in
|
||||
return lhs.0 < rhs.0
|
||||
}) {
|
||||
let title = name.substring(to: name.index(after: name.startIndex)).uppercased()
|
||||
if sections.isEmpty || sections[sections.count - 1].0 != title {
|
||||
sections.append((title, []))
|
||||
}
|
||||
sections[sections.count - 1].1.append((name, code))
|
||||
}
|
||||
self.sections = sections
|
||||
var sectionTitles = sections.map { $0.0 }
|
||||
sectionTitles.insert(UITableViewIndexSearch, at: 0)
|
||||
self.sectionTitles = sectionTitles
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
self.title = "Select Country"
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
|
||||
self.definesPresentationContext = true
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.backgroundColor = .white
|
||||
|
||||
self.searchResultsController = InnerCoutrySearchResultsController()
|
||||
self.searchResultsController.itemSelected = { [weak self] item in
|
||||
self?.itemSelected?(item)
|
||||
}
|
||||
|
||||
self.searchController = UISearchController(searchResultsController: self.searchResultsController)
|
||||
self.searchController.searchResultsUpdater = self
|
||||
self.searchController.dimsBackgroundDuringPresentation = true
|
||||
self.searchController.searchBar.delegate = self
|
||||
|
||||
self.view.addSubview(self.tableView)
|
||||
self.tableView.tableHeaderView = self.searchController.searchBar
|
||||
self.tableView.dataSource = self
|
||||
self.tableView.delegate = self
|
||||
|
||||
self.tableView.frame = self.view.bounds
|
||||
self.view.addSubview(self.tableView)
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return self.sections.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return self.sections[section].1.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
return self.sections[section].0
|
||||
}
|
||||
|
||||
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
|
||||
return self.sectionTitles
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
||||
if index == 0 {
|
||||
return 0
|
||||
} else {
|
||||
return max(0, index - 1)
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: UITableViewCell
|
||||
if let currentCell = tableView.dequeueReusableCell(withIdentifier: "CountryCell") {
|
||||
cell = currentCell
|
||||
} else {
|
||||
cell = UITableViewCell()
|
||||
let label = UILabel()
|
||||
label.font = Font.medium(17.0)
|
||||
cell.accessoryView = label
|
||||
}
|
||||
cell.textLabel?.text = self.sections[indexPath.section].1[indexPath.row].0
|
||||
if let label = cell.accessoryView as? UILabel {
|
||||
label.text = "+\(self.sections[indexPath.section].1[indexPath.row].1)"
|
||||
label.sizeToFit()
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
self.itemSelected?(self.sections[indexPath.section].1[indexPath.row])
|
||||
}
|
||||
|
||||
func updateSearchResults(for searchController: UISearchController) {
|
||||
guard let normalizedQuery = searchController.searchBar.text?.lowercased() else {
|
||||
self.searchResultsController.searchResults = []
|
||||
return
|
||||
}
|
||||
|
||||
var results: [(String, Int)] = []
|
||||
for (_, items) in self.sections {
|
||||
for item in items {
|
||||
if item.0.lowercased().hasPrefix(normalizedQuery) {
|
||||
results.append(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.searchResultsController.searchResults = results
|
||||
}
|
||||
|
||||
@objc func cancelPressed() {
|
||||
self.dismiss?()
|
||||
}
|
||||
}
|
||||
|
||||
final class AuthorizationSequenceCountrySelectionController: ViewController {
|
||||
private var controllerNode: AuthorizationSequenceCountrySelectionControllerNode {
|
||||
return self.displayNode as! AuthorizationSequenceCountrySelectionControllerNode
|
||||
}
|
||||
|
||||
private let innerNavigationController: UINavigationController
|
||||
private let innerController: InnerCountrySelectionController
|
||||
|
||||
var completeWithCountryCode: ((Int) -> Void)?
|
||||
|
||||
override init(navigationBar: NavigationBar = NavigationBar()) {
|
||||
self.innerController = InnerCountrySelectionController()
|
||||
self.innerNavigationController = UINavigationController(rootViewController: self.innerController)
|
||||
|
||||
super.init(navigationBar: navigationBar)
|
||||
|
||||
self.navigationBar.isHidden = true
|
||||
|
||||
self.innerController.dismiss = { [weak self] in
|
||||
self?.cancelPressed()
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = AuthorizationSequenceCountrySelectionControllerNode()
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.innerNavigationController.willMove(toParentViewController: self)
|
||||
self.addChildViewController(self.innerNavigationController)
|
||||
self.displayNode.view.addSubview(self.innerNavigationController.view)
|
||||
self.innerNavigationController.didMove(toParentViewController: self)
|
||||
|
||||
self.innerController.itemSelected = { [weak self] _, code in
|
||||
self?.completeWithCountryCode?(code)
|
||||
self?.controllerNode.animateOut()
|
||||
}
|
||||
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.innerNavigationController.viewWillAppear(false)
|
||||
self.innerNavigationController.viewDidAppear(false)
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.innerNavigationController.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.innerController.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
//self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
private func cancelPressed() {
|
||||
self.controllerNode.animateOut()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
|
||||
final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode {
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
override init() {
|
||||
super.init(viewBlock: {
|
||||
return UITracingLayerView()
|
||||
}, didLoad: nil)
|
||||
|
||||
self.backgroundColor = UIColor.white
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
func animateOut() {
|
||||
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss?()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class AuthorizationSequencePasswordEntryController: ViewController {
|
||||
private var controllerNode: AuthorizationSequencePasswordEntryControllerNode {
|
||||
return self.displayNode as! AuthorizationSequencePasswordEntryControllerNode
|
||||
}
|
||||
|
||||
var loginWithPassword: ((String) -> Void)?
|
||||
var hint: String?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
if self.inProgress {
|
||||
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode())
|
||||
self.navigationItem.rightBarButtonItem = item
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
self.controllerNode.inProgress = self.inProgress
|
||||
}
|
||||
}
|
||||
|
||||
override init(navigationBar: NavigationBar = NavigationBar()) {
|
||||
super.init(navigationBar: navigationBar)
|
||||
|
||||
self.navigationBar.backgroundColor = nil
|
||||
self.navigationBar.isOpaque = false
|
||||
self.navigationBar.stripeColor = UIColor.clear
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = AuthorizationSequencePasswordEntryControllerNode()
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
if let hint = self.hint {
|
||||
self.controllerNode.updateData(hint: hint)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.controllerNode.activateInput()
|
||||
}
|
||||
|
||||
func updateData(hint: String) {
|
||||
if self.hint != hint {
|
||||
self.hint = hint
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.updateData(hint: hint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition)
|
||||
}
|
||||
|
||||
@objc func nextPressed() {
|
||||
if self.controllerNode.currentPassword.isEmpty {
|
||||
hapticFeedback.error()
|
||||
self.controllerNode.animateError()
|
||||
} else {
|
||||
self.loginWithPassword?(self.controllerNode.currentPassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,174 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode {
|
||||
private let navigationBackgroundNode: ASDisplayNode
|
||||
private let stripeNode: ASDisplayNode
|
||||
private let titleNode: ASTextNode
|
||||
private let currentOptionNode: ASTextNode
|
||||
private let nextOptionNode: ASTextNode
|
||||
|
||||
private let codeField: TextFieldNode
|
||||
private let codeSeparatorNode: ASDisplayNode
|
||||
|
||||
private var layoutArguments: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
var currentPassword: String {
|
||||
return self.codeField.textField.text ?? ""
|
||||
}
|
||||
|
||||
var loginWithCode: ((String) -> Void)?
|
||||
var requestNextOption: (() -> Void)?
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
self.codeField.alpha = self.inProgress ? 0.6 : 1.0
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.navigationBackgroundNode = ASDisplayNode()
|
||||
self.navigationBackgroundNode.isLayerBacked = true
|
||||
self.navigationBackgroundNode.backgroundColor = UIColor(0xefefef)
|
||||
|
||||
self.stripeNode = ASDisplayNode()
|
||||
self.stripeNode.isLayerBacked = true
|
||||
self.stripeNode.backgroundColor = UIColor(0xbcbbc1)
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.isLayerBacked = true
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Your Password", font: Font.light(30.0), textColor: UIColor.black)
|
||||
|
||||
self.currentOptionNode = ASTextNode()
|
||||
self.currentOptionNode.isLayerBacked = true
|
||||
self.currentOptionNode.displaysAsynchronously = false
|
||||
self.currentOptionNode.attributedText = NSAttributedString(string: "Two-step verification enabled.\nYour account is protected with an\nadditional password.", font: Font.regular(16.0), textColor: UIColor.black, paragraphAlignment: .center)
|
||||
|
||||
self.nextOptionNode = ASTextNode()
|
||||
self.nextOptionNode.isLayerBacked = true
|
||||
self.nextOptionNode.displaysAsynchronously = false
|
||||
self.nextOptionNode.attributedText = NSAttributedString(string: "Forgot password?", font: Font.regular(16.0), textColor: UIColor(0x007ee5), paragraphAlignment: .center)
|
||||
|
||||
self.codeSeparatorNode = ASDisplayNode()
|
||||
self.codeSeparatorNode.isLayerBacked = true
|
||||
self.codeSeparatorNode.backgroundColor = UIColor(0xbcbbc1)
|
||||
|
||||
self.codeField = TextFieldNode()
|
||||
self.codeField.textField.font = Font.regular(20.0)
|
||||
self.codeField.textField.textAlignment = .natural
|
||||
self.codeField.textField.isSecureTextEntry = true
|
||||
self.codeField.textField.returnKeyType = .done
|
||||
|
||||
super.init(viewBlock: {
|
||||
return UITracingLayerView()
|
||||
}, didLoad: nil)
|
||||
|
||||
self.backgroundColor = UIColor.white
|
||||
|
||||
self.addSubnode(self.navigationBackgroundNode)
|
||||
self.addSubnode(self.stripeNode)
|
||||
self.addSubnode(self.codeSeparatorNode)
|
||||
self.addSubnode(self.codeField)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.currentOptionNode)
|
||||
self.addSubnode(self.nextOptionNode)
|
||||
}
|
||||
|
||||
func updateData(hint: String) {
|
||||
self.codeField.textField.attributedPlaceholder = NSAttributedString(string: hint, font: Font.regular(20.0), textColor: UIColor(0xbcbcc3))
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.layoutArguments = (layout, navigationBarHeight)
|
||||
|
||||
let insets = layout.insets(options: [.statusBar, .input])
|
||||
let availableHeight = max(1.0, layout.size.height - insets.top - insets.bottom)
|
||||
|
||||
if max(layout.size.width, layout.size.height) > 1023.0 {
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Your Password", font: Font.light(30.0), textColor: UIColor.black)
|
||||
} else {
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Your Password", font: Font.regular(20.0), textColor: UIColor.black)
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
let additionalTitleSpacing: CGFloat
|
||||
if titleSize.width > layout.size.width - 160.0 {
|
||||
additionalTitleSpacing = 44.0
|
||||
} else {
|
||||
additionalTitleSpacing = 0.0
|
||||
}
|
||||
|
||||
let minimalTitleSpacing: CGFloat = 10.0
|
||||
let maxTitleSpacing: CGFloat = 22.0
|
||||
let inputFieldsHeight: CGFloat = 60.0
|
||||
|
||||
let minimalNoticeSpacing: CGFloat = 11.0
|
||||
let maxNoticeSpacing: CGFloat = 35.0
|
||||
let noticeSize = self.currentOptionNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let minimalTermsOfServiceSpacing: CGFloat = 6.0
|
||||
let maxTermsOfServiceSpacing: CGFloat = 20.0
|
||||
let termsOfServiceSize = self.nextOptionNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
let minTrailingSpacing: CGFloat = 10.0
|
||||
|
||||
let inputHeight = inputFieldsHeight
|
||||
let essentialHeight = additionalTitleSpacing + titleSize.height + minimalTitleSpacing + inputHeight + minimalNoticeSpacing + noticeSize.height
|
||||
let additionalHeight = minimalTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing
|
||||
|
||||
let navigationHeight: CGFloat
|
||||
if essentialHeight + additionalHeight > availableHeight || availableHeight * 0.66 - inputHeight < additionalHeight {
|
||||
navigationHeight = min(floor(availableHeight * 0.3), availableHeight - inputFieldsHeight)
|
||||
} else {
|
||||
navigationHeight = floor(availableHeight * 0.3)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.navigationBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: navigationHeight)))
|
||||
transition.updateFrame(node: self.stripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
|
||||
let titleOffset: CGFloat
|
||||
if navigationHeight * 0.5 < titleSize.height + minimalTitleSpacing {
|
||||
titleOffset = floor((navigationHeight - titleSize.height) / 2.0)
|
||||
} else {
|
||||
titleOffset = max(navigationHeight * 0.5, navigationHeight - maxTitleSpacing - titleSize.height)
|
||||
}
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: titleOffset), size: titleSize))
|
||||
|
||||
let codeFieldFrame = CGRect(origin: CGPoint(x: 22.0, y: navigationHeight + 3.0), size: CGSize(width: layout.size.width - 44.0, height: 60.0))
|
||||
transition.updateFrame(node: self.codeField, frame: codeFieldFrame)
|
||||
transition.updateFrame(node: self.codeSeparatorNode, frame: CGRect(origin: CGPoint(x: 22.0, y: navigationHeight + 60.0), size: CGSize(width: layout.size.width - 44.0, height: UIScreenPixel)))
|
||||
|
||||
let additionalAvailableHeight = max(1.0, availableHeight - codeFieldFrame.maxY)
|
||||
let additionalAvailableSpacing = max(1.0, additionalAvailableHeight - noticeSize.height - termsOfServiceSize.height)
|
||||
let noticeSpacingFactor = maxNoticeSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
|
||||
let termsOfServiceSpacingFactor = maxTermsOfServiceSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
|
||||
|
||||
let noticeSpacing: CGFloat
|
||||
let termsOfServiceSpacing: CGFloat
|
||||
if additionalAvailableHeight <= maxNoticeSpacing + noticeSize.height + maxTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing {
|
||||
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
|
||||
noticeSpacing = floor((additionalAvailableHeight - termsOfServiceSpacing - noticeSize.height - termsOfServiceSize.height) / 2.0)
|
||||
} else {
|
||||
noticeSpacing = min(floor(noticeSpacingFactor * additionalAvailableSpacing), maxNoticeSpacing)
|
||||
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
|
||||
}
|
||||
|
||||
let currentOptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - noticeSize.width) / 2.0), y: codeFieldFrame.maxY + noticeSpacing), size: noticeSize)
|
||||
let nextOptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsOfServiceSize.width) / 2.0), y: currentOptionFrame.maxY + termsOfServiceSpacing), size: termsOfServiceSize)
|
||||
|
||||
transition.updateFrame(node: self.currentOptionNode, frame: currentOptionFrame)
|
||||
transition.updateFrame(node: self.nextOptionNode, frame: nextOptionFrame)
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.codeField.textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
self.codeField.layer.addShakeAnimation()
|
||||
}
|
||||
|
||||
@objc func passwordFieldTextChanged(_ textField: UITextField) {
|
||||
|
||||
}
|
||||
}
|
||||
95
TelegramUI/AuthorizationSequencePhoneEntryController.swift
Normal file
95
TelegramUI/AuthorizationSequencePhoneEntryController.swift
Normal file
@ -0,0 +1,95 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class AuthorizationSequencePhoneEntryController: ViewController {
|
||||
private var controllerNode: AuthorizationSequencePhoneEntryControllerNode {
|
||||
return self.displayNode as! AuthorizationSequencePhoneEntryControllerNode
|
||||
}
|
||||
|
||||
private var currentData: (Int32, String)?
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
if self.inProgress {
|
||||
let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode())
|
||||
self.navigationItem.rightBarButtonItem = item
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
self.controllerNode.inProgress = self.inProgress
|
||||
}
|
||||
}
|
||||
var loginWithNumber: ((String) -> Void)?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
override init(navigationBar: NavigationBar = NavigationBar()) {
|
||||
super.init(navigationBar: navigationBar)
|
||||
|
||||
self.navigationBar.backgroundColor = nil
|
||||
self.navigationBar.isOpaque = false
|
||||
self.navigationBar.stripeColor = UIColor.clear
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(self.nextPressed))
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateData(countryCode: Int32, number: String) {
|
||||
if self.currentData == nil || self.currentData! != (countryCode, number) {
|
||||
self.currentData = (countryCode, number)
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.codeAndNumber = (countryCode, number)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = AuthorizationSequencePhoneEntryControllerNode()
|
||||
self.displayNodeDidLoad()
|
||||
self.controllerNode.selectCountryCode = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let controller = AuthorizationSequenceCountrySelectionController()
|
||||
controller.completeWithCountryCode = { code in
|
||||
if let strongSelf = self, let currentData = strongSelf.currentData {
|
||||
strongSelf.updateData(countryCode: Int32(code), number: currentData.1)
|
||||
strongSelf.controllerNode.activateInput()
|
||||
}
|
||||
}
|
||||
strongSelf.controllerNode.view.endEditing(true)
|
||||
strongSelf.present(controller, in: .window)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
self.controllerNode.activateInput()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.controllerNode.activateInput()
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition)
|
||||
}
|
||||
|
||||
@objc func nextPressed() {
|
||||
let (code, number) = self.controllerNode.codeAndNumber
|
||||
if code != nil && !number.isEmpty {
|
||||
self.loginWithNumber?(self.controllerNode.currentNumber)
|
||||
} else {
|
||||
hapticFeedback.error()
|
||||
self.controllerNode.animateError()
|
||||
}
|
||||
}
|
||||
}
|
||||
488
TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift
Normal file
488
TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift
Normal file
@ -0,0 +1,488 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
|
||||
let countryCodeToName: [Int: String] = [
|
||||
1876: "Jamaica",
|
||||
1869: "Saint Kitts & Nevis",
|
||||
1868: "Trinidad & Tobago",
|
||||
1784: "Saint Vincent & the Grenadines",
|
||||
1767: "Dominica",
|
||||
1758: "Saint Lucia",
|
||||
1721: "Sint Maarten",
|
||||
1684: "American Samoa",
|
||||
1671: "Guam",
|
||||
1670: "Northern Mariana Islands",
|
||||
1664: "Montserrat",
|
||||
1649: "Turks & Caicos Islands",
|
||||
1473: "Grenada",
|
||||
1441: "Bermuda",
|
||||
1345: "Cayman Islands",
|
||||
1340: "US Virgin Islands",
|
||||
1284: "British Virgin Islands",
|
||||
1268: "Antigua & Barbuda",
|
||||
1264: "Anguilla",
|
||||
1246: "Barbados",
|
||||
1242: "Bahamas",
|
||||
998: "Uzbekistan",
|
||||
996: "Kyrgyzstan",
|
||||
995: "Georgia",
|
||||
994: "Azerbaijan",
|
||||
993: "Turkmenistan",
|
||||
992: "Tajikistan",
|
||||
977: "Nepal",
|
||||
976: "Mongolia",
|
||||
975: "Bhutan",
|
||||
974: "Qatar",
|
||||
973: "Bahrain",
|
||||
972: "Israel",
|
||||
971: "United Arab Emirates",
|
||||
970: "Palestine",
|
||||
968: "Oman",
|
||||
967: "Yemen",
|
||||
966: "Saudi Arabia",
|
||||
965: "Kuwait",
|
||||
964: "Iraq",
|
||||
963: "Syrian Arab Republic",
|
||||
962: "Jordan",
|
||||
961: "Lebanon",
|
||||
960: "Maldives",
|
||||
886: "Taiwan",
|
||||
880: "Bangladesh",
|
||||
856: "Laos",
|
||||
855: "Cambodia",
|
||||
853: "Macau",
|
||||
852: "Hong Kong",
|
||||
850: "North Korea",
|
||||
692: "Marshall Islands",
|
||||
691: "Micronesia",
|
||||
690: "Tokelau",
|
||||
689: "French Polynesia",
|
||||
688: "Tuvalu",
|
||||
687: "New Caledonia",
|
||||
686: "Kiribati",
|
||||
685: "Samoa",
|
||||
683: "Niue",
|
||||
682: "Cook Islands",
|
||||
681: "Wallis & Futuna",
|
||||
680: "Palau",
|
||||
679: "Fiji",
|
||||
678: "Vanuatu",
|
||||
677: "Solomon Islands",
|
||||
676: "Tonga",
|
||||
675: "Papua New Guinea",
|
||||
674: "Nauru",
|
||||
673: "Brunei Darussalam",
|
||||
672: "Norfolk Island",
|
||||
670: "Timor-Leste",
|
||||
599: "Bonaire, Sint Eustatius & Saba",
|
||||
//599: "Curaçao",
|
||||
598: "Uruguay",
|
||||
597: "Suriname",
|
||||
596: "Martinique",
|
||||
595: "Paraguay",
|
||||
594: "French Guiana",
|
||||
593: "Ecuador",
|
||||
592: "Guyana",
|
||||
591: "Bolivia",
|
||||
590: "Guadeloupe",
|
||||
509: "Haiti",
|
||||
508: "Saint Pierre & Miquelon",
|
||||
507: "Panama",
|
||||
506: "Costa Rica",
|
||||
505: "Nicaragua",
|
||||
504: "Honduras",
|
||||
503: "El Salvador",
|
||||
502: "Guatemala",
|
||||
501: "Belize",
|
||||
500: "Falkland Islands",
|
||||
423: "Liechtenstein",
|
||||
421: "Slovakia",
|
||||
420: "Czech Republic",
|
||||
389: "Macedonia",
|
||||
387: "Bosnia & Herzegovina",
|
||||
386: "Slovenia",
|
||||
385: "Croatia",
|
||||
382: "Montenegro",
|
||||
381: "Serbia",
|
||||
380: "Ukraine",
|
||||
378: "San Marino",
|
||||
377: "Monaco",
|
||||
376: "Andorra",
|
||||
375: "Belarus",
|
||||
374: "Armenia",
|
||||
373: "Moldova",
|
||||
372: "Estonia",
|
||||
371: "Latvia",
|
||||
370: "Lithuania",
|
||||
359: "Bulgaria",
|
||||
358: "Finland",
|
||||
357: "Cyprus",
|
||||
356: "Malta",
|
||||
355: "Albania",
|
||||
354: "Iceland",
|
||||
353: "Ireland",
|
||||
352: "Luxembourg",
|
||||
351: "Portugal",
|
||||
350: "Gibraltar",
|
||||
299: "Greenland",
|
||||
298: "Faroe Islands",
|
||||
297: "Aruba",
|
||||
291: "Eritrea",
|
||||
290: "Saint Helena",
|
||||
269: "Comoros",
|
||||
268: "Swaziland",
|
||||
267: "Botswana",
|
||||
266: "Lesotho",
|
||||
265: "Malawi",
|
||||
264: "Namibia",
|
||||
263: "Zimbabwe",
|
||||
262: "Réunion",
|
||||
261: "Madagascar",
|
||||
260: "Zambia",
|
||||
258: "Mozambique",
|
||||
257: "Burundi",
|
||||
256: "Uganda",
|
||||
255: "Tanzania",
|
||||
254: "Kenya",
|
||||
253: "Djibouti",
|
||||
252: "Somalia",
|
||||
251: "Ethiopia",
|
||||
250: "Rwanda",
|
||||
249: "Sudan",
|
||||
248: "Seychelles",
|
||||
247: "Saint Helena",
|
||||
246: "Diego Garcia",
|
||||
245: "Guinea-Bissau",
|
||||
244: "Angola",
|
||||
243: "Congo (Dem. Rep.)",
|
||||
242: "Congo (Rep.)",
|
||||
241: "Gabon",
|
||||
240: "Equatorial Guinea",
|
||||
239: "São Tomé & Príncipe",
|
||||
238: "Cape Verde",
|
||||
237: "Cameroon",
|
||||
236: "Central African Rep.",
|
||||
235: "Chad",
|
||||
234: "Nigeria",
|
||||
233: "Ghana",
|
||||
232: "Sierra Leone",
|
||||
231: "Liberia",
|
||||
230: "Mauritius",
|
||||
229: "Benin",
|
||||
228: "Togo",
|
||||
227: "Niger",
|
||||
226: "Burkina Faso",
|
||||
225: "Côte d`Ivoire",
|
||||
224: "Guinea",
|
||||
223: "Mali",
|
||||
222: "Mauritania",
|
||||
221: "Senegal",
|
||||
220: "Gambia",
|
||||
218: "Libya",
|
||||
216: "Tunisia",
|
||||
213: "Algeria",
|
||||
212: "Morocco",
|
||||
211: "South Sudan",
|
||||
98: "Iran",
|
||||
95: "Myanmar",
|
||||
94: "Sri Lanka",
|
||||
93: "Afghanistan",
|
||||
92: "Pakistan",
|
||||
91: "India",
|
||||
90: "Turkey",
|
||||
86: "China",
|
||||
84: "Vietnam",
|
||||
82: "South Korea",
|
||||
81: "Japan",
|
||||
66: "Thailand",
|
||||
65: "Singapore",
|
||||
64: "New Zealand",
|
||||
63: "Philippines",
|
||||
62: "Indonesia",
|
||||
61: "Australia",
|
||||
60: "Malaysia",
|
||||
58: "Venezuela",
|
||||
57: "Colombia",
|
||||
56: "Chile",
|
||||
55: "Brazil",
|
||||
54: "Argentina",
|
||||
53: "Cuba",
|
||||
52: "Mexico",
|
||||
51: "Peru",
|
||||
49: "Germany",
|
||||
48: "Poland",
|
||||
47: "Norway",
|
||||
46: "Sweden",
|
||||
45: "Denmark",
|
||||
44: "United Kingdom",
|
||||
43: "Austria",
|
||||
41: "Switzerland",
|
||||
40: "Romania",
|
||||
39: "Italy",
|
||||
36: "Hungary",
|
||||
34: "Spain",
|
||||
33: "France",
|
||||
32: "Belgium",
|
||||
31: "Netherlands",
|
||||
30: "Greece",
|
||||
27: "South Africa",
|
||||
20: "Egypt",
|
||||
7: "Russian Federation",
|
||||
// 7: "Kazakhstan",
|
||||
1: "USA",
|
||||
// 1: "Puerto Rico",
|
||||
// 1: "Dominican Rep.",
|
||||
// 1: "Canada"
|
||||
]
|
||||
|
||||
private let countryButtonBackground = generateImage(CGSize(width: 61.0, height: 67.0), rotatedContext: { size, context in
|
||||
let arrowSize: CGFloat = 10.0
|
||||
let lineWidth = UIScreenPixel
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(UIColor(0xbcbbc1).cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: 15.0, y: size.height - arrowSize - lineWidth / 2.0))
|
||||
context.strokePath()
|
||||
})?.stretchableImage(withLeftCapWidth: 61, topCapHeight: 1)
|
||||
|
||||
private let countryButtonHighlightedBackground = generateImage(CGSize(width: 60.0, height: 67.0), rotatedContext: { size, context in
|
||||
let arrowSize: CGFloat = 10.0
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor(0xbcbbc1).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height - arrowSize)))
|
||||
context.move(to: CGPoint(x: size.width, y: size.height - arrowSize))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - arrowSize))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize, y: size.height))
|
||||
context.addLine(to: CGPoint(x: size.width - 1.0 - arrowSize - arrowSize, y: size.height - arrowSize))
|
||||
context.closePath()
|
||||
context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 61, topCapHeight: 2)
|
||||
|
||||
private let phoneInputBackground = generateImage(CGSize(width: 85.0, height: 57.0), rotatedContext: { size, context in
|
||||
let arrowSize: CGFloat = 10.0
|
||||
let lineWidth = UIScreenPixel
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(UIColor(0xbcbbc1).cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.move(to: CGPoint(x: 15.0, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width, y: size.height - lineWidth / 2.0))
|
||||
context.strokePath()
|
||||
context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 0.0))
|
||||
context.strokePath()
|
||||
})?.stretchableImage(withLeftCapWidth: 84, topCapHeight: 2)
|
||||
|
||||
final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
private let navigationBackgroundNode: ASDisplayNode
|
||||
private let stripeNode: ASDisplayNode
|
||||
private let titleNode: ASTextNode
|
||||
private let noticeNode: ASTextNode
|
||||
private let termsOfServiceNode: ASTextNode
|
||||
private let countryButton: ASButtonNode
|
||||
private let phoneBackground: ASImageNode
|
||||
private let phoneInputNode: PhoneInputNode
|
||||
|
||||
var currentNumber: String {
|
||||
return self.phoneInputNode.number
|
||||
}
|
||||
|
||||
var codeAndNumber: (Int32?, String) {
|
||||
get {
|
||||
return self.phoneInputNode.codeAndNumber
|
||||
} set(value) {
|
||||
self.phoneInputNode.codeAndNumber = value
|
||||
}
|
||||
}
|
||||
|
||||
var selectCountryCode: (() -> Void)?
|
||||
|
||||
var inProgress: Bool = false {
|
||||
didSet {
|
||||
self.phoneInputNode.enableEditing = !self.inProgress
|
||||
self.phoneInputNode.alpha = self.inProgress ? 0.6 : 1.0
|
||||
self.countryButton.isEnabled = !self.inProgress
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.navigationBackgroundNode = ASDisplayNode()
|
||||
self.navigationBackgroundNode.isLayerBacked = true
|
||||
self.navigationBackgroundNode.backgroundColor = UIColor(0xefefef)
|
||||
|
||||
self.stripeNode = ASDisplayNode()
|
||||
self.stripeNode.isLayerBacked = true
|
||||
self.stripeNode.backgroundColor = UIColor(0xbcbbc1)
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.isLayerBacked = true
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Your Phone", font: Font.light(30.0), textColor: UIColor.black)
|
||||
|
||||
self.noticeNode = ASTextNode()
|
||||
self.noticeNode.isLayerBacked = true
|
||||
self.noticeNode.displaysAsynchronously = false
|
||||
self.noticeNode.attributedText = NSAttributedString(string: "Please confirm your country code and enter your phone number.", font: Font.regular(16.0), textColor: UIColor(0x878787), paragraphAlignment: .center)
|
||||
|
||||
self.termsOfServiceNode = ASTextNode()
|
||||
self.termsOfServiceNode.isLayerBacked = true
|
||||
self.termsOfServiceNode.displaysAsynchronously = false
|
||||
let termsString = NSMutableAttributedString()
|
||||
termsString.append(NSAttributedString(string: "By signing up,\nyou agree to the ", font: Font.regular(16.0), textColor: UIColor.black))
|
||||
termsString.append(NSAttributedString(string: "Terms of Service", font: Font.regular(16.0), textColor: UIColor(0x007ee5)))
|
||||
termsString.append(NSAttributedString(string: ".", font: Font.regular(16.0), textColor: UIColor.black))
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.alignment = .center
|
||||
termsString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSMakeRange(0, termsString.length))
|
||||
self.termsOfServiceNode.attributedText = termsString
|
||||
|
||||
self.countryButton = ASButtonNode()
|
||||
self.countryButton.setBackgroundImage(countryButtonBackground, for: [])
|
||||
self.countryButton.setBackgroundImage(countryButtonHighlightedBackground, for: .highlighted)
|
||||
|
||||
self.phoneBackground = ASImageNode()
|
||||
self.phoneBackground.image = phoneInputBackground
|
||||
self.phoneBackground.displaysAsynchronously = false
|
||||
self.phoneBackground.displayWithoutProcessing = true
|
||||
self.phoneBackground.isLayerBacked = true
|
||||
|
||||
self.phoneInputNode = PhoneInputNode()
|
||||
|
||||
super.init(viewBlock: {
|
||||
return UITracingLayerView()
|
||||
}, didLoad: nil)
|
||||
|
||||
self.backgroundColor = UIColor.white
|
||||
|
||||
self.addSubnode(self.navigationBackgroundNode)
|
||||
self.addSubnode(self.stripeNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.termsOfServiceNode)
|
||||
self.addSubnode(self.noticeNode)
|
||||
self.addSubnode(self.phoneBackground)
|
||||
self.addSubnode(self.countryButton)
|
||||
self.addSubnode(self.phoneInputNode)
|
||||
|
||||
self.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 10.0, right: 0.0)
|
||||
self.countryButton.contentHorizontalAlignment = .left
|
||||
|
||||
self.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: "Your phone number", font: Font.regular(20.0), textColor: UIColor(0xbcbcc3))
|
||||
|
||||
self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.phoneInputNode.countryCodeUpdated = { [weak self] code in
|
||||
if let strongSelf = self {
|
||||
if let code = Int(code), let countryName = countryCodeToName[code] {
|
||||
strongSelf.countryButton.setTitle(countryName, with: Font.regular(20.0), with: .black, for: [])
|
||||
} else {
|
||||
strongSelf.countryButton.setTitle("Select Country", with: Font.regular(20.0), with: .black, for: [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.phoneInputNode.number = "+1"
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
let insets = layout.insets(options: [.statusBar, .input])
|
||||
let availableHeight = max(1.0, layout.size.height - insets.top - insets.bottom)
|
||||
|
||||
if max(layout.size.width, layout.size.height) > 1023.0 {
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Your Phone", font: Font.light(40.0), textColor: UIColor.black)
|
||||
} else {
|
||||
self.titleNode.attributedText = NSAttributedString(string: "Your Phone", font: Font.light(30.0), textColor: UIColor.black)
|
||||
}
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
let minimalTitleSpacing: CGFloat = 10.0
|
||||
let maxTitleSpacing: CGFloat = 28.0
|
||||
let countryButtonHeight: CGFloat = 57.0
|
||||
let inputFieldsHeight: CGFloat = 57.0
|
||||
|
||||
let minimalNoticeSpacing: CGFloat = 11.0
|
||||
let maxNoticeSpacing: CGFloat = 35.0
|
||||
let noticeSize = self.noticeNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let minimalTermsOfServiceSpacing: CGFloat = 6.0
|
||||
let maxTermsOfServiceSpacing: CGFloat = 20.0
|
||||
let termsOfServiceSize = self.termsOfServiceNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude))
|
||||
let minTrailingSpacing: CGFloat = 10.0
|
||||
|
||||
let inputHeight = countryButtonHeight + inputFieldsHeight
|
||||
let essentialHeight = titleSize.height + minimalTitleSpacing + inputHeight
|
||||
let additionalHeight = minimalNoticeSpacing + noticeSize.height + minimalTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing
|
||||
|
||||
let navigationHeight: CGFloat
|
||||
if essentialHeight + additionalHeight > availableHeight || availableHeight * 0.66 - inputHeight < additionalHeight {
|
||||
transition.updateAlpha(node: self.noticeNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.termsOfServiceNode, alpha: 0.0)
|
||||
navigationHeight = min(floor(availableHeight * 0.3), availableHeight - countryButtonHeight - inputFieldsHeight)
|
||||
} else {
|
||||
transition.updateAlpha(node: self.noticeNode, alpha: 1.0)
|
||||
transition.updateAlpha(node: self.termsOfServiceNode, alpha: 1.0)
|
||||
navigationHeight = floor(availableHeight * 0.3)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.navigationBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: navigationHeight)))
|
||||
transition.updateFrame(node: self.stripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
|
||||
let titleOffset: CGFloat
|
||||
if navigationHeight * 0.5 < titleSize.height + minimalTitleSpacing {
|
||||
titleOffset = floor((navigationHeight - titleSize.height) / 2.0)
|
||||
} else {
|
||||
titleOffset = max(navigationHeight * 0.5, navigationHeight - maxTitleSpacing - titleSize.height)
|
||||
}
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: titleOffset), size: titleSize))
|
||||
|
||||
transition.updateFrame(node: self.countryButton, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: 67.0)))
|
||||
transition.updateFrame(node: self.phoneBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight + 57.0), size: CGSize(width: layout.size.width, height: 57.0)))
|
||||
|
||||
let countryCodeFrame = CGRect(origin: CGPoint(x: 18.0, y: navigationHeight + 58.0), size: CGSize(width: 60.0, height: 57.0))
|
||||
let numberFrame = CGRect(origin: CGPoint(x: 96.0, y: navigationHeight + 58.0), size: CGSize(width: layout.size.width - 96.0 - 8.0, height: 57.0))
|
||||
|
||||
let phoneInputFrame = countryCodeFrame.union(numberFrame)
|
||||
|
||||
transition.updateFrame(node: self.phoneInputNode, frame: phoneInputFrame)
|
||||
transition.updateFrame(node: self.phoneInputNode.countryCodeField, frame: countryCodeFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY))
|
||||
transition.updateFrame(node: self.phoneInputNode.numberField, frame: numberFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY))
|
||||
|
||||
let additionalAvailableHeight = max(1.0, availableHeight - phoneInputFrame.maxY)
|
||||
let additionalAvailableSpacing = max(1.0, additionalAvailableHeight - noticeSize.height - termsOfServiceSize.height)
|
||||
let noticeSpacingFactor = maxNoticeSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
|
||||
let termsOfServiceSpacingFactor = maxTermsOfServiceSpacing / (maxNoticeSpacing + maxTermsOfServiceSpacing + minTrailingSpacing)
|
||||
|
||||
let noticeSpacing: CGFloat
|
||||
let termsOfServiceSpacing: CGFloat
|
||||
if additionalAvailableHeight <= maxNoticeSpacing + noticeSize.height + maxTermsOfServiceSpacing + termsOfServiceSize.height + minTrailingSpacing {
|
||||
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
|
||||
noticeSpacing = floor((additionalAvailableHeight - termsOfServiceSpacing - noticeSize.height - termsOfServiceSize.height) / 2.0)
|
||||
} else {
|
||||
noticeSpacing = min(floor(noticeSpacingFactor * additionalAvailableSpacing), maxNoticeSpacing)
|
||||
termsOfServiceSpacing = min(floor(termsOfServiceSpacingFactor * additionalAvailableSpacing), maxTermsOfServiceSpacing)
|
||||
}
|
||||
|
||||
let noticeFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - noticeSize.width) / 2.0), y: phoneInputFrame.maxY + noticeSpacing), size: noticeSize)
|
||||
let termsOfServiceFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - termsOfServiceSize.width) / 2.0), y: noticeFrame.maxY + termsOfServiceSpacing), size: termsOfServiceSize)
|
||||
|
||||
transition.updateFrame(node: self.noticeNode, frame: noticeFrame)
|
||||
transition.updateFrame(node: self.termsOfServiceNode, frame: termsOfServiceFrame)
|
||||
}
|
||||
|
||||
func activateInput() {
|
||||
self.phoneInputNode.numberField.textField.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func animateError() {
|
||||
self.phoneInputNode.countryCodeField.layer.addShakeAnimation()
|
||||
self.phoneInputNode.numberField.layer.addShakeAnimation()
|
||||
}
|
||||
|
||||
@objc func countryPressed() {
|
||||
self.selectCountryCode?()
|
||||
}
|
||||
|
||||
}
|
||||
80
TelegramUI/AuthorizationSequenceSplashController.swift
Normal file
80
TelegramUI/AuthorizationSequenceSplashController.swift
Normal file
@ -0,0 +1,80 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
import TelegramUIPrivateModule
|
||||
|
||||
final class AuthorizationSequenceSplashController: ViewController {
|
||||
private var controllerNode: AuthorizationSequenceSplashControllerNode {
|
||||
return self.displayNode as! AuthorizationSequenceSplashControllerNode
|
||||
}
|
||||
|
||||
private let controller = RMIntroViewController()
|
||||
|
||||
var nextPressed: (() -> Void)?
|
||||
|
||||
override init(navigationBar: NavigationBar = NavigationBar()) {
|
||||
super.init(navigationBar: navigationBar)
|
||||
|
||||
self.navigationBar.isHidden = true
|
||||
self.controller.startMessaging = { [weak self] in
|
||||
self?.nextPressed?()
|
||||
}
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = AuthorizationSequenceSplashControllerNode()
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
private func addControllerIfNeeded() {
|
||||
if !controller.isViewLoaded {
|
||||
self.displayNode.view.addSubview(controller.view)
|
||||
controller.view.frame = self.displayNode.bounds;
|
||||
controller.viewDidAppear(false)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated);
|
||||
controller.viewWillAppear(false)
|
||||
self.addControllerIfNeeded()
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
controller.viewDidAppear(animated)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
|
||||
controller.viewWillDisappear(animated)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
controller.viewDidDisappear(animated)
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: 0.0, transition: transition)
|
||||
|
||||
self.addControllerIfNeeded()
|
||||
if case .immediate = transition {
|
||||
self.controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
self.controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
17
TelegramUI/AuthorizationSequenceSplashControllerNode.swift
Normal file
17
TelegramUI/AuthorizationSequenceSplashControllerNode.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
final class AuthorizationSequenceSplashControllerNode: ASDisplayNode {
|
||||
override init() {
|
||||
super.init(viewBlock: {
|
||||
return UITracingLayerView()
|
||||
}, didLoad: nil)
|
||||
|
||||
self.backgroundColor = UIColor.white
|
||||
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
}
|
||||
@ -165,7 +165,7 @@ enum ChannelInfoEntry: PeerInfoEntry {
|
||||
func item(account: Account, interaction: PeerInfoControllerInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .info(peer, cachedData):
|
||||
return ItemListAvatarAndNameInfoItem(account: account, peer: peer, cachedData: cachedData, state: ItemListAvatarAndNameInfoItemState(editingName: nil, updatingName: nil), sectionId: self.section, style: .plain, editingNameUpdated: { editingName in
|
||||
return ItemListAvatarAndNameInfoItem(account: account, peer: peer, presence: nil, cachedData: cachedData, state: ItemListAvatarAndNameInfoItemState(editingName: nil, updatingName: nil), sectionId: self.section, style: .plain, editingNameUpdated: { editingName in
|
||||
|
||||
})
|
||||
case let .about(text):
|
||||
|
||||
@ -64,6 +64,9 @@ public class ChatController: TelegramController {
|
||||
private var chatUnreadCountDisposable: Disposable?
|
||||
private var peerInputActivitiesDisposable: Disposable?
|
||||
|
||||
private var recentlyUsedInlineBotsValue: [Peer] = []
|
||||
private var recentlyUsedInlineBotsDisposable: Disposable?
|
||||
|
||||
public init(account: Account, peerId: PeerId, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil) {
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
@ -264,7 +267,7 @@ public class ChatController: TelegramController {
|
||||
})
|
||||
}
|
||||
})
|
||||
enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: "", attributes: [], media: file, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId)]).start()
|
||||
let _ = enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: "", attributes: [], media: file, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId)]).start()
|
||||
}
|
||||
}, requestMessageActionCallback: { [weak self] messageId, data, isGame in
|
||||
if let strongSelf = self {
|
||||
@ -334,6 +337,7 @@ public class ChatController: TelegramController {
|
||||
}
|
||||
}, shareAccountContact: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
|
||||
}
|
||||
}, sendBotCommand: { [weak self] messageId, command in
|
||||
if let strongSelf = self {
|
||||
@ -560,6 +564,7 @@ public class ChatController: TelegramController {
|
||||
self.resolveUrlDisposable?.dispose()
|
||||
self.chatUnreadCountDisposable?.dispose()
|
||||
self.peerInputActivitiesDisposable?.dispose()
|
||||
self.recentlyUsedInlineBotsDisposable?.dispose()
|
||||
}
|
||||
|
||||
var chatDisplayNode: ChatControllerNode {
|
||||
@ -698,7 +703,7 @@ public class ChatController: TelegramController {
|
||||
let legacyController = LegacyController(legacyController: navigationController, presentation: .custom)
|
||||
|
||||
var presentOverlayController: ((UIViewController) -> (() -> Void))?
|
||||
let controller = legacyAttachmentMenu(parentController: legacyController, presentOverlayController: { controller in
|
||||
let controller = legacyAttachmentMenu(parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, presentOverlayController: { controller in
|
||||
if let presentOverlayController = presentOverlayController {
|
||||
return presentOverlayController(controller)
|
||||
} else {
|
||||
@ -715,8 +720,44 @@ public class ChatController: TelegramController {
|
||||
}
|
||||
}, openFileGallery: {
|
||||
self?.presentMediaPicker(fileMode: true)
|
||||
}, openMap: {
|
||||
|
||||
}, openContacts: {
|
||||
if let strongSelf = self {
|
||||
let contactsController = ContactSelectionController(account: strongSelf.account, title: "Select Contact")
|
||||
strongSelf.present(contactsController, in: .window, with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
strongSelf.controllerNavigationDisposable.set((contactsController.result |> deliverOnMainQueue).start(next: { peerId in
|
||||
if let strongSelf = self, let peerId = peerId {
|
||||
let peer = strongSelf.account.postbox.loadedPeerWithId(peerId)
|
||||
|> take(1)
|
||||
strongSelf.controllerNavigationDisposable.set((peer |> deliverOnMainQueue).start(next: { peer in
|
||||
if let strongSelf = self, let user = peer as? TelegramUser, let phone = user.phone, !phone.isEmpty {
|
||||
let media = TelegramMediaContact(firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: phone, peerId: user.id)
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
|
||||
})
|
||||
}
|
||||
})
|
||||
let message = EnqueueMessage.message(text: "", attributes: [], media: media, replyToMessageId: replyMessageId)
|
||||
let _ = enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [message]).start()
|
||||
}
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}, sendMessagesWithSignals: { [weak self] signals in
|
||||
self?.enqueueMediaMessages(signals: signals)
|
||||
}, selectRecentlyUsedInlineBot: { [weak self] peer in
|
||||
if let strongSelf = self, let addressName = peer.addressName {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState({ $0.withUpdatedComposeInputState(ChatTextInputState(inputText: "@" + addressName + " ")) }).updatedInputMode({ _ in
|
||||
return .text
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
controller.applicationInterface = legacyController.applicationInterface
|
||||
controller.didDismiss = { [weak legacyController] _ in
|
||||
@ -727,7 +768,7 @@ public class ChatController: TelegramController {
|
||||
controller.present(in: emptyController, sourceView: nil, animated: true)
|
||||
|
||||
presentOverlayController = { [weak legacyController] controller in
|
||||
if let strongSelf = self, let legacyController = legacyController {
|
||||
if let legacyController = legacyController {
|
||||
let childController = LegacyController(legacyController: controller, presentation: .custom)
|
||||
legacyController.present(childController, in: .window)
|
||||
return { [weak childController] in
|
||||
@ -741,42 +782,6 @@ public class ChatController: TelegramController {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let controller = ChatMediaActionSheetController()
|
||||
controller.photo = { [weak strongSelf] asset in
|
||||
if let strongSelf = strongSelf {
|
||||
var randomId: Int64 = 0
|
||||
arc4random_buf(&randomId, 8)
|
||||
let size = CGSize(width: CGFloat(asset.pixelWidth), height: CGFloat(asset.pixelHeight))
|
||||
let scaledSize = size.aspectFitted(CGSize(width: 1280.0, height: 1280.0))
|
||||
let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier)
|
||||
|
||||
if false {
|
||||
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)])
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({})
|
||||
enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: "", attributes: [], media: media, replyToMessageId: nil)]).start()
|
||||
} else {
|
||||
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), resource: resource, previewRepresentations: [], mimeType: "image/jpeg", size: 0, attributes: [.FileName(fileName: "image.jpeg"), .ImageSize(size: scaledSize)])
|
||||
enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: "", attributes: [], media: media, replyToMessageId: nil)]).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
controller.files = { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf {
|
||||
|
||||
}
|
||||
}
|
||||
controller.location = { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf {
|
||||
let mapInputController = MapInputController()
|
||||
strongSelf.present(mapInputController, in: .window)
|
||||
}
|
||||
}
|
||||
controller.contacts = { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf {
|
||||
}
|
||||
}
|
||||
strongSelf.present(controller, in: .window)
|
||||
}
|
||||
}
|
||||
|
||||
@ -809,7 +814,7 @@ public class ChatController: TelegramController {
|
||||
}, deleteSelectedMessages: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let messageIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||
deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: Array(messageIds), type: .forLocalPeer).start()
|
||||
let _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: Array(messageIds), type: .forLocalPeer).start()
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
|
||||
}
|
||||
@ -828,14 +833,13 @@ public class ChatController: TelegramController {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedForwardMessageIds(forwardMessageIds).withoutSelectionState() }) })
|
||||
strongController.dismiss()
|
||||
} else {
|
||||
(strongSelf.account.postbox.modify({ modifier -> Void in
|
||||
let _ = (strongSelf.account.postbox.modify({ modifier -> Void in
|
||||
modifier.updatePeerChatInterfaceState(peerId, update: { currentState in
|
||||
if let currentState = currentState as? ChatInterfaceState {
|
||||
return currentState.withUpdatedForwardMessageIds(forwardMessageIds)
|
||||
} else {
|
||||
return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds)
|
||||
}
|
||||
return currentState
|
||||
})
|
||||
}) |> deliverOnMainQueue).start(completed: {
|
||||
if let strongSelf = self {
|
||||
@ -913,7 +917,7 @@ public class ChatController: TelegramController {
|
||||
if !entities.isEmpty {
|
||||
attributes.append(TextEntitiesMessageAttribute(entities: entities))
|
||||
}
|
||||
enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: messageText, attributes: attributes, media: nil, replyToMessageId: replyMessageId)]).start()
|
||||
let _ = enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: messageText, attributes: attributes, media: nil, replyToMessageId: replyMessageId)]).start()
|
||||
}
|
||||
}
|
||||
}, sendBotStart: { [weak self] payload in
|
||||
@ -935,12 +939,23 @@ public class ChatController: TelegramController {
|
||||
if let peer = strongSelf.presentationInterfaceState.peer as? TelegramSecretChat {
|
||||
let controller = ChatSecretAutoremoveTimerActionSheetController(currentValue: peer.messageAutoremoveTimeout == nil ? 0 : peer.messageAutoremoveTimeout!, applyValue: { value in
|
||||
if let strongSelf = self {
|
||||
setSecretChatMessageAutoremoveTimeoutInteractively(account: strongSelf.account, peerId: strongSelf.peerId, timeout: value == 0 ? nil : value).start()
|
||||
let _ = setSecretChatMessageAutoremoveTimeoutInteractively(account: strongSelf.account, peerId: strongSelf.peerId, timeout: value == 0 ? nil : value).start()
|
||||
}
|
||||
})
|
||||
strongSelf.present(controller, in: .window)
|
||||
}
|
||||
}
|
||||
}, sendSticker: { [weak self] file in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: "")) }
|
||||
})
|
||||
}
|
||||
})
|
||||
let _ = enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: [.message(text: "", attributes: [], media: file, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId)]).start()
|
||||
}
|
||||
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get()))
|
||||
|
||||
self.chatUnreadCountDisposable = (self.account.postbox.unreadMessageCountsView(items: [.peer(self.peerId)]) |> deliverOnMainQueue).start(next: { [weak self] items in
|
||||
@ -1017,6 +1032,10 @@ public class ChatController: TelegramController {
|
||||
self.chatDisplayNode.historyNode.canReadHistory.set(true)
|
||||
|
||||
self.chatDisplayNode.loadInputPanels()
|
||||
|
||||
self.recentlyUsedInlineBotsDisposable = (recentlyUsedInlineBots(postbox: self.account.postbox) |> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
self?.recentlyUsedInlineBotsValue = peers
|
||||
})
|
||||
}
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
@ -1254,7 +1273,7 @@ public class ChatController: TelegramController {
|
||||
})
|
||||
}
|
||||
})
|
||||
enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }).start()
|
||||
let _ = enqueueMessages(account: strongSelf.account, peerId: strongSelf.peerId, messages: messages.map { $0.withUpdatedReplyToMessageId(replyMessageId) }).start()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@ -318,7 +318,7 @@ class ChatControllerNode: ASDisplayNode {
|
||||
immediatelyLayoutAccessoryPanelAndAnimateAppearance = true
|
||||
}
|
||||
} else if let accessoryPanelNode = self.accessoryPanelNode {
|
||||
dismissedAccessoryPanelNode = self.accessoryPanelNode
|
||||
dismissedAccessoryPanelNode = accessoryPanelNode
|
||||
self.accessoryPanelNode = nil
|
||||
}
|
||||
|
||||
@ -328,7 +328,7 @@ class ChatControllerNode: ASDisplayNode {
|
||||
dismissedInputContextPanelNode = self.inputContextPanelNode
|
||||
self.inputContextPanelNode = inputContextPanelNode
|
||||
|
||||
self.insertSubnode(inputContextPanelNode, aboveSubnode: self.navigateToLatestButton)
|
||||
self.addSubnode(inputContextPanelNode)
|
||||
immediatelyLayoutInputContextPanelAndAnimateAppearance = true
|
||||
|
||||
}
|
||||
|
||||
@ -23,6 +23,11 @@ class ChatDocumentGalleryItem: GalleryItem {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
node.setFile(account: account, file: file)
|
||||
break
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
if let file = content.file {
|
||||
node.setFile(account: account, file: file)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -321,7 +321,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
return dict
|
||||
}
|
||||
if apply {
|
||||
applyMaxReadIndexInteractively(postbox: account.postbox, network: account.network, index: messageIndex).start()
|
||||
let _ = applyMaxReadIndexInteractively(postbox: account.postbox, network: account.network, index: messageIndex).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,14 @@ class ChatImageGalleryItem: GalleryItem {
|
||||
} else if let file = media as? TelegramMediaFile, file.mimeType.hasPrefix("image/") {
|
||||
node.setFile(account: account, file: file)
|
||||
break
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
if let image = content.image {
|
||||
node.setImage(account: account, image: image)
|
||||
break
|
||||
} else if let file = content.file, file.mimeType.hasPrefix("image/") {
|
||||
node.setFile(account: account, file: file)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,18 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
|
||||
}
|
||||
|
||||
switch inputQueryResult {
|
||||
case let .stickers(results):
|
||||
if !results.isEmpty {
|
||||
if let currentPanel = currentPanel as? HorizontalStickersChatContextPanelNode {
|
||||
currentPanel.updateResults(results.map({ $0.file }))
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = HorizontalStickersChatContextPanelNode(account: account)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
panel.updateResults(results.map({ $0.file }))
|
||||
return panel
|
||||
}
|
||||
}
|
||||
case let .hashtags(results):
|
||||
if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode {
|
||||
currentPanel.updateResults(results)
|
||||
|
||||
@ -14,10 +14,11 @@ struct PossibleContextQueryTypes: OptionSet {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let hashtag = PossibleContextQueryTypes(rawValue: (1 << 0))
|
||||
static let mention = PossibleContextQueryTypes(rawValue: (1 << 1))
|
||||
static let command = PossibleContextQueryTypes(rawValue: (1 << 2))
|
||||
static let contextRequest = PossibleContextQueryTypes(rawValue: (1 << 3))
|
||||
static let emoji = PossibleContextQueryTypes(rawValue: (1 << 0))
|
||||
static let hashtag = PossibleContextQueryTypes(rawValue: (1 << 1))
|
||||
static let mention = PossibleContextQueryTypes(rawValue: (1 << 2))
|
||||
static let command = PossibleContextQueryTypes(rawValue: (1 << 3))
|
||||
static let contextRequest = PossibleContextQueryTypes(rawValue: (1 << 4))
|
||||
}
|
||||
|
||||
private func makeScalar(_ c: Character) -> Character {
|
||||
@ -81,6 +82,10 @@ func textInputStateContextQueryRangeAndType(_ inputState: ChatTextInputState) ->
|
||||
|
||||
var possibleQueryRange: Range<String.Index>?
|
||||
|
||||
if inputText.isSingleEmoji {
|
||||
return (inputText.startIndex ..< inputText.endIndex, [.emoji], nil)
|
||||
}
|
||||
|
||||
var possibleTypes = PossibleContextQueryTypes([.command, .mention])
|
||||
var definedType = false
|
||||
|
||||
@ -128,7 +133,9 @@ func inputContextQueryForChatPresentationIntefaceState(_ chatPresentationInterfa
|
||||
let inputState = chatPresentationInterfaceState.interfaceState.effectiveInputState
|
||||
if let (possibleQueryRange, possibleTypes, additionalStringRange) = textInputStateContextQueryRangeAndType(inputState) {
|
||||
let query = inputState.inputText.substring(with: possibleQueryRange)
|
||||
if possibleTypes == [.hashtag] {
|
||||
if possibleTypes == [.emoji] {
|
||||
return .emoji(query)
|
||||
} else if possibleTypes == [.hashtag] {
|
||||
return .hashtag(query)
|
||||
} else if possibleTypes == [.mention] {
|
||||
return .mention(query)
|
||||
|
||||
@ -9,6 +9,23 @@ func contextQueryResultStateForChatInterfacePresentationState(_ chatPresentation
|
||||
return nil
|
||||
} else {
|
||||
switch inputQuery {
|
||||
case let .emoji(query):
|
||||
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
|
||||
if let currentQuery = currentQuery {
|
||||
switch currentQuery {
|
||||
case .emoji:
|
||||
break
|
||||
default:
|
||||
signal = .single({ _ in return nil })
|
||||
}
|
||||
}
|
||||
let stickers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = searchStickers(postbox: account.postbox, query: query)
|
||||
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||
return { _ in
|
||||
return .stickers(stickers)
|
||||
}
|
||||
}
|
||||
return (inputQuery, signal |> then(stickers))
|
||||
case let .hashtag(query):
|
||||
/*var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
|
||||
if let currentQuery = currentQuery {
|
||||
|
||||
@ -113,6 +113,26 @@ public class ChatListController: TelegramController {
|
||||
self?.activateSearch()
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.deletePeerChat = { [weak self] peerId in
|
||||
if let strongSelf = self {
|
||||
let actionSheet = ActionSheetController()
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: "Delete", color: .destructive, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
if let strongSelf = self {
|
||||
let _ = removePeerChat(postbox: strongSelf.account.postbox, peerId: peerId).start()
|
||||
}
|
||||
})
|
||||
]), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: "Cancel", color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])])
|
||||
strongSelf.present(actionSheet, in: .window)
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListDisplayNode.chatListNode.peerSelected = { [weak self] peerId in
|
||||
if let strongSelf = self {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peerId))
|
||||
|
||||
@ -40,10 +40,10 @@ class ChatListItem: ListViewItem {
|
||||
async {
|
||||
let node = ChatListItemNode()
|
||||
node.setupItem(item: self)
|
||||
let (first, last, firstWithHeader) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
node.insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader)
|
||||
|
||||
let (nodeLayout, apply) = node.asyncLayout()(self, width, first, last, firstWithHeader)
|
||||
let (nodeLayout, apply) = node.asyncLayout()(self, width, first, last, firstWithHeader, nextIsPinned)
|
||||
|
||||
node.insets = nodeLayout.insets
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
@ -63,13 +63,13 @@ class ChatListItem: ListViewItem {
|
||||
node.setupItem(item: self)
|
||||
let layout = node.asyncLayout()
|
||||
async {
|
||||
let (first, last, firstWithHeader) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
var animated = true
|
||||
if case .None = animation {
|
||||
animated = false
|
||||
}
|
||||
|
||||
let (nodeLayout, apply) = layout(self, width, first, last, firstWithHeader)
|
||||
let (nodeLayout, apply) = layout(self, width, first, last, firstWithHeader, nextIsPinned)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, {
|
||||
apply(animated)
|
||||
@ -88,7 +88,7 @@ class ChatListItem: ListViewItem {
|
||||
}
|
||||
}
|
||||
|
||||
static func mergeType(item: ChatListItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) {
|
||||
static func mergeType(item: ChatListItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool) {
|
||||
var first = false
|
||||
var last = false
|
||||
var firstWithHeader = false
|
||||
@ -104,11 +104,15 @@ class ChatListItem: ListViewItem {
|
||||
first = true
|
||||
firstWithHeader = item.header != nil
|
||||
}
|
||||
if let _ = nextItem {
|
||||
var nextIsPinned = false
|
||||
if let nextItem = nextItem as? ChatListItem {
|
||||
if nextItem.index.pinningIndex != nil {
|
||||
nextIsPinned = true
|
||||
}
|
||||
} else {
|
||||
last = true
|
||||
}
|
||||
return (first, last, firstWithHeader)
|
||||
return (first, last, firstWithHeader, nextIsPinned)
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,6 +203,8 @@ private let peerMutedIcon = UIImage(bundleImageName: "Chat List/PeerMutedIcon")?
|
||||
|
||||
private let separatorHeight = 1.0 / UIScreen.main.scale
|
||||
|
||||
private let pinnedBackgroundColor = UIColor(0xf7f7f7)
|
||||
|
||||
class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var item: ChatListItem?
|
||||
|
||||
@ -218,7 +224,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var editableControlNode: ItemListEditableControlNode?
|
||||
|
||||
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool)?
|
||||
var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool)?
|
||||
|
||||
override var canBeSelected: Bool {
|
||||
if self.editableControlNode != nil {
|
||||
@ -318,8 +324,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
override func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||
let layout = self.asyncLayout()
|
||||
let (first, last, firstWithHeader) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem)
|
||||
let (nodeLayout, apply) = layout(item as! ChatListItem, width, first, last, firstWithHeader)
|
||||
let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem)
|
||||
let (nodeLayout, apply) = layout(item as! ChatListItem, width, first, last, firstWithHeader, nextIsPinned)
|
||||
apply(false)
|
||||
self.contentSize = nodeLayout.contentSize
|
||||
self.insets = nodeLayout.insets
|
||||
@ -348,7 +354,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let strongSelf = self {
|
||||
if completed {
|
||||
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||
strongSelf.contentNode.backgroundColor = UIColor.white
|
||||
if let item = strongSelf.layoutParams?.0, item.index.pinningIndex != nil {
|
||||
strongSelf.contentNode.backgroundColor = pinnedBackgroundColor
|
||||
} else {
|
||||
strongSelf.contentNode.backgroundColor = UIColor.white
|
||||
}
|
||||
|
||||
strongSelf.contentNode.isOpaque = true
|
||||
strongSelf.contentNode.displaysAsynchronously = true
|
||||
}
|
||||
@ -357,7 +368,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
} else {
|
||||
self.highlightedBackgroundNode.removeFromSupernode()
|
||||
self.contentNode.backgroundColor = UIColor.white
|
||||
if let item = self.layoutParams?.0, item.index.pinningIndex != nil {
|
||||
self.contentNode.backgroundColor = pinnedBackgroundColor
|
||||
} else {
|
||||
self.contentNode.backgroundColor = UIColor.white
|
||||
}
|
||||
self.contentNode.isOpaque = true
|
||||
self.contentNode.displaysAsynchronously = true
|
||||
}
|
||||
@ -365,14 +380,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ChatListItem, _ width: CGFloat, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
func asyncLayout() -> (_ item: ChatListItem, _ width: CGFloat, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let dateLayout = TextNode.asyncLayout(self.dateNode)
|
||||
let textLayout = TextNode.asyncLayout(self.textNode)
|
||||
let titleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let badgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
|
||||
return { item, width, first, last, firstWithHeader in
|
||||
return { item, width, first, last, firstWithHeader, nextIsPinned in
|
||||
let account = item.account
|
||||
let message = item.message
|
||||
let combinedReadState = item.combinedReadState
|
||||
@ -536,7 +551,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
return (layout, { [weak self] animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.layoutParams = (item, first, last, firstWithHeader)
|
||||
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned)
|
||||
|
||||
let revealOffset = strongSelf.revealOffset
|
||||
|
||||
@ -633,9 +648,29 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.maxY - textLayout.size.height - 1.0), size: textLayout.size)
|
||||
|
||||
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: editingOffset + 78.0 + contentRect.origin.x, y: 68.0 - separatorHeight), size: CGSize(width: width - 78.0 - editingOffset, height: separatorHeight)))
|
||||
let separatorInset: CGFloat
|
||||
if !nextIsPinned && item.index.pinningIndex != nil {
|
||||
separatorInset = 0.0
|
||||
} else {
|
||||
separatorInset = editingOffset + 78.0 + contentRect.origin.x
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: 68.0 - separatorHeight), size: CGSize(width: width - separatorInset, height: separatorHeight)))
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
if item.index.pinningIndex != nil {
|
||||
strongSelf.backgroundNode.backgroundColor = pinnedBackgroundColor
|
||||
if strongSelf.contentNode.backgroundColor == nil || !strongSelf.contentNode.backgroundColor!.isEqual(pinnedBackgroundColor) {
|
||||
strongSelf.contentNode.backgroundColor = pinnedBackgroundColor
|
||||
updateContentNode = true
|
||||
}
|
||||
} else {
|
||||
strongSelf.backgroundNode.backgroundColor = UIColor.white
|
||||
if strongSelf.contentNode.backgroundColor == nil || !strongSelf.contentNode.backgroundColor!.isEqual(UIColor.white) {
|
||||
strongSelf.contentNode.backgroundColor = UIColor.white
|
||||
updateContentNode = true
|
||||
}
|
||||
}
|
||||
let topNegativeInset: CGFloat = 0.0
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -separatorHeight - topNegativeInset), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height + separatorHeight + topNegativeInset))
|
||||
|
||||
@ -672,7 +707,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
if let (item, _, _, _) = self.layoutParams {
|
||||
if let (item, _, _, _, _) = self.layoutParams {
|
||||
return item.header
|
||||
} else {
|
||||
return nil
|
||||
@ -721,15 +756,15 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
if let item = self.item {
|
||||
switch option.key {
|
||||
case RevealOptionKey.pin.rawValue:
|
||||
break
|
||||
item.interaction.setPeerPinned(item.index.messageIndex.id.peerId, true)
|
||||
case RevealOptionKey.unpin.rawValue:
|
||||
break
|
||||
item.interaction.setPeerPinned(item.index.messageIndex.id.peerId, false)
|
||||
case RevealOptionKey.mute.rawValue:
|
||||
break
|
||||
item.interaction.setPeerMuted(item.index.messageIndex.id.peerId, true)
|
||||
case RevealOptionKey.unmute.rawValue:
|
||||
break
|
||||
item.interaction.setPeerMuted(item.index.messageIndex.id.peerId, false)
|
||||
case RevealOptionKey.delete.rawValue:
|
||||
item.interaction
|
||||
item.interaction.deletePeer(item.index.messageIndex.id.peerId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@ -148,6 +148,7 @@ final class ChatListNode: ListView {
|
||||
|
||||
var peerSelected: ((PeerId) -> Void)?
|
||||
var activateSearch: (() -> Void)?
|
||||
var deletePeerChat: ((PeerId) -> Void)?
|
||||
|
||||
private let viewProcessingQueue = Queue()
|
||||
private var chatListView: ChatListNodeView?
|
||||
@ -187,10 +188,12 @@ final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, setPeerPinned: { _ in
|
||||
}, setPeerMuted: { _ in
|
||||
}, deletePeer: { peerId in
|
||||
let _ = removePeerChat(postbox: account.postbox, peerId: peerId).start()
|
||||
}, setPeerPinned: { peerId, _ in
|
||||
let _ = togglePeerChatPinned(postbox: account.postbox, peerId: peerId).start()
|
||||
}, setPeerMuted: { peerId, _ in
|
||||
let _ = togglePeerMuted(account: account, peerId: peerId).start()
|
||||
}, deletePeer: { [weak self] peerId in
|
||||
self?.deletePeerChat?(peerId)
|
||||
})
|
||||
|
||||
let viewProcessingQueue = self.viewProcessingQueue
|
||||
|
||||
134
TelegramUI/ChatListRecentPeersListItem.swift
Normal file
134
TelegramUI/ChatListRecentPeersListItem.swift
Normal file
@ -0,0 +1,134 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
|
||||
class ChatListRecentPeersListItem: ListViewItem {
|
||||
let account: Account
|
||||
let peers: [Peer]
|
||||
let peerSelected: (Peer) -> Void
|
||||
|
||||
let header: ListViewItemHeader?
|
||||
|
||||
init(account: Account, peers: [Peer], peerSelected: @escaping (Peer) -> Void) {
|
||||
self.account = account
|
||||
self.peers = peers
|
||||
self.peerSelected = peerSelected
|
||||
self.header = nil
|
||||
}
|
||||
|
||||
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 = ChatListRecentPeersListItemNode()
|
||||
let makeLayout = node.asyncLayout()
|
||||
let (nodeLayout, nodeApply) = makeLayout(self, width, nextItem != nil)
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
node.insets = nodeLayout.insets
|
||||
|
||||
completion(node, nodeApply)
|
||||
}
|
||||
}
|
||||
|
||||
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? ChatListRecentPeersListItemNode {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
async {
|
||||
let (nodeLayout, apply) = layout(self, width, nextItem != nil)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, {
|
||||
apply().1()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let separatorHeight = 1.0 / UIScreen.main.scale
|
||||
|
||||
class ChatListRecentPeersListItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private var peersNode: ChatListSearchRecentPeersNode?
|
||||
|
||||
private var item: ChatListRecentPeersListItem?
|
||||
|
||||
required init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.backgroundColor = UIColor(0xc8c7cc)
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
}
|
||||
|
||||
override func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||
if let item = self.item {
|
||||
let makeLayout = self.asyncLayout()
|
||||
let (nodeLayout, nodeApply) = makeLayout(item, width, nextItem == nil)
|
||||
self.contentSize = nodeLayout.contentSize
|
||||
self.insets = nodeLayout.insets
|
||||
let _ = nodeApply()
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ChatListRecentPeersListItem, _ width: CGFloat, _ last: Bool) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, () -> Void)) {
|
||||
return { [weak self] item, width, last in
|
||||
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: width, height: 130.0), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0))
|
||||
|
||||
return (nodeLayout, { [weak self] in
|
||||
return (nil, {
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
let peersNode: ChatListSearchRecentPeersNode
|
||||
if let currentPeersNode = strongSelf.peersNode {
|
||||
peersNode = currentPeersNode
|
||||
} else {
|
||||
peersNode = ChatListSearchRecentPeersNode(account: item.account, peerSelected: { peer in
|
||||
self?.item?.peerSelected(peer)
|
||||
})
|
||||
strongSelf.peersNode = peersNode
|
||||
strongSelf.addSubnode(peersNode)
|
||||
}
|
||||
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
peersNode.frame = CGRect(origin: CGPoint(), size: nodeLayout.contentSize)
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||
strongSelf.separatorNode.isHidden = true
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
if let item = self.item {
|
||||
return item.header
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,104 @@ import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
enum ChatListRecentEntryStableId: Hashable {
|
||||
case topPeers
|
||||
case peerId(PeerId)
|
||||
|
||||
static func ==(lhs: ChatListRecentEntryStableId, rhs: ChatListRecentEntryStableId) -> Bool {
|
||||
switch lhs {
|
||||
case .topPeers:
|
||||
if case .topPeers = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerId(peerId):
|
||||
if case .peerId(peerId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hashValue: Int {
|
||||
switch self {
|
||||
case .topPeers:
|
||||
return 0
|
||||
case let .peerId(peerId):
|
||||
return peerId.hashValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
case topPeers([Peer])
|
||||
case peer(index: Int, peer: Peer)
|
||||
|
||||
var stableId: ChatListRecentEntryStableId {
|
||||
switch self {
|
||||
case .topPeers:
|
||||
return .topPeers
|
||||
case let .peer(_, peer):
|
||||
return .peerId(peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: ChatListRecentEntry, rhs: ChatListRecentEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .topPeers(lhsPeers):
|
||||
if case let .topPeers(rhsPeers) = rhs {
|
||||
if lhsPeers.count != rhsPeers.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhsPeers.count {
|
||||
if !lhsPeers[i].isEqual(rhsPeers[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(lhsIndex, lhsPeer):
|
||||
if case let .peer(rhsIndex, rhsPeer) = rhs, lhsPeer.isEqual(rhsPeer) && lhsIndex == rhsIndex {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: ChatListRecentEntry, rhs: ChatListRecentEntry) -> Bool {
|
||||
switch lhs {
|
||||
case .topPeers:
|
||||
return true
|
||||
case let .peer(lhsIndex, _):
|
||||
switch rhs {
|
||||
case .topPeers:
|
||||
return false
|
||||
case let .peer(rhsIndex, _):
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(account: Account, peerSelected: @escaping (Peer) -> Void) -> ListViewItem {
|
||||
switch self {
|
||||
case let .topPeers(peers):
|
||||
return ChatListRecentPeersListItem(account: account, peers: peers, peerSelected: { peer in
|
||||
peerSelected(peer)
|
||||
})
|
||||
case let .peer(_, peer):
|
||||
return ContactsPeerItem(account: account, peer: peer, chatPeer: peer, status: .none, selection: .none, index: nil, header: ChatListSearchItemHeader(type: .recentPeers), action: { _ in
|
||||
peerSelected(peer)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ChatListSearchEntryStableId: Hashable {
|
||||
case localPeerId(PeerId)
|
||||
case globalPeerId(PeerId)
|
||||
@ -133,6 +231,12 @@ enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListSearchContainerRecentTransition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
}
|
||||
|
||||
struct ChatListSearchContainerTransition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
@ -140,6 +244,16 @@ struct ChatListSearchContainerTransition {
|
||||
let displayingResults: Bool
|
||||
}
|
||||
|
||||
func chatListSearchContainerPreparedRecentTransition(from fromEntries: [ChatListRecentEntry], to toEntries: [ChatListRecentEntry], account: Account, peerSelected: @escaping (Peer) -> Void) -> ChatListSearchContainerRecentTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, peerSelected: peerSelected), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, peerSelected: peerSelected), directionHint: nil) }
|
||||
|
||||
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
|
||||
func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, account: Account, enableHeaders: Bool, interaction: ChatListNodeInteraction) -> ChatListSearchContainerTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
@ -154,11 +268,16 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
private let account: Account
|
||||
private let openMessage: (Peer, MessageId) -> Void
|
||||
|
||||
private let recentPeersNode: ChatListSearchRecentPeersNode
|
||||
//private let recentPeersNode: ChatListSearchRecentPeersNode
|
||||
private let recentListNode: ListView
|
||||
private let listNode: ListView
|
||||
|
||||
private var enqueuedRecentTransitions: [(ChatListSearchContainerRecentTransition, Bool)] = []
|
||||
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
|
||||
private var hasValidLayout = false
|
||||
|
||||
private let recentDisposable = MetaDisposable()
|
||||
|
||||
private let searchQuery = Promise<String?>()
|
||||
private let searchDisposable = MetaDisposable()
|
||||
|
||||
@ -166,18 +285,21 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
self.account = account
|
||||
self.openMessage = openMessage
|
||||
|
||||
self.recentPeersNode = ChatListSearchRecentPeersNode(account: account, peerSelected: openPeer)
|
||||
//self.recentPeersNode = ChatListSearchRecentPeersNode(account: account, peerSelected: openPeer)
|
||||
self.recentListNode = ListView()
|
||||
|
||||
self.listNode = ListView()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = UIColor.white
|
||||
|
||||
self.addSubnode(self.recentPeersNode)
|
||||
//self.addSubnode(self.recentPeersNode)
|
||||
self.addSubnode(self.recentListNode)
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.listNode.isHidden = true
|
||||
|
||||
|
||||
let foundItems = searchQuery.get()
|
||||
|> mapToSignal { query -> Signal<[ChatListSearchEntry]?, NoError> in
|
||||
if let query = query, !query.isEmpty {
|
||||
@ -224,6 +346,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {
|
||||
}, peerSelected: { [weak self] peer in
|
||||
openPeer(peer)
|
||||
let _ = addRecentlySearchedPeer(postbox: account.postbox, peerId: peer.id).start()
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
}, messageSelected: { [weak self] message in
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
@ -236,6 +359,29 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
}, deletePeer: { _ in
|
||||
})
|
||||
|
||||
let previousRecentItems = Atomic<[ChatListRecentEntry]?>(value: nil)
|
||||
let recentItemsTransition = recentlySearchedPeers(postbox: account.postbox)
|
||||
|> mapToSignal { [weak self] peers -> Signal<(ChatListSearchContainerRecentTransition, Bool), NoError> in
|
||||
var entries: [ChatListRecentEntry] = []
|
||||
entries.append(.topPeers([]))
|
||||
for i in 0 ..< peers.count {
|
||||
entries.append(.peer(index: i, peer: peers[i]))
|
||||
}
|
||||
let previousEntries = previousRecentItems.swap(entries)
|
||||
|
||||
let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, account: account, peerSelected: { peer in
|
||||
self?.recentListNode.clearHighlightAnimated(true)
|
||||
openPeer(peer)
|
||||
})
|
||||
return .single((transition, previousEntries == nil))
|
||||
}
|
||||
|
||||
self.recentDisposable.set((recentItemsTransition |> deliverOnMainQueue).start(next: { [weak self] (transition, firstTime) in
|
||||
if let strongSelf = self {
|
||||
strongSelf.enqueueRecentTransition(transition, firstTime: firstTime)
|
||||
}
|
||||
}))
|
||||
|
||||
self.searchDisposable.set((foundItems
|
||||
|> deliverOnMainQueue).start(next: { [weak self] entries in
|
||||
if let strongSelf = self {
|
||||
@ -249,6 +395,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.recentDisposable.dispose()
|
||||
self.searchDisposable.dispose()
|
||||
}
|
||||
|
||||
@ -260,6 +407,31 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
private func enqueueRecentTransition(_ transition: ChatListSearchContainerRecentTransition, firstTime: Bool) {
|
||||
enqueuedRecentTransitions.append((transition, firstTime))
|
||||
|
||||
if self.hasValidLayout {
|
||||
while !self.enqueuedRecentTransitions.isEmpty {
|
||||
self.dequeueRecentTransition()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueRecentTransition() {
|
||||
if let (transition, firstTime) = self.enqueuedRecentTransitions.first {
|
||||
self.enqueuedRecentTransitions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
if firstTime {
|
||||
} else {
|
||||
}
|
||||
|
||||
self.recentListNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: ChatListSearchContainerTransition, firstTime: Bool) {
|
||||
enqueuedTransitions.append((transition, firstTime))
|
||||
|
||||
@ -286,7 +458,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
if let strongSelf = self {
|
||||
if displayingResults != !strongSelf.listNode.isHidden {
|
||||
strongSelf.listNode.isHidden = !displayingResults
|
||||
strongSelf.recentPeersNode.isHidden = displayingResults
|
||||
strongSelf.recentListNode.isHidden = displayingResults
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -296,9 +468,9 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
let recentPeersSize = self.recentPeersNode.measure(CGSize(width: layout.size.width, height: CGFloat.infinity))
|
||||
self.recentPeersNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: recentPeersSize)
|
||||
self.recentPeersNode.layout()
|
||||
//let recentPeersSize = self.recentPeersNode.measure(CGSize(width: layout.size.width, height: CGFloat.infinity))
|
||||
//self.recentPeersNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: recentPeersSize)
|
||||
//self.recentPeersNode.layout()
|
||||
|
||||
var duration: Double = 0.0
|
||||
var curve: UInt = 0
|
||||
@ -323,11 +495,17 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
|
||||
listViewCurve = .Default
|
||||
}
|
||||
|
||||
self.recentListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.insets(options: [.input]).bottom, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.insets(options: [.input]).bottom, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
if !hasValidLayout {
|
||||
hasValidLayout = true
|
||||
while !self.enqueuedRecentTransitions.isEmpty {
|
||||
self.dequeueRecentTransition()
|
||||
}
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
self.dequeueTransition()
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@ import Display
|
||||
import SwiftSignalKit
|
||||
|
||||
private let searchBarFont = Font.regular(15.0)
|
||||
private let pinnedBackgroundColor = UIColor(0xf7f7f7)
|
||||
private let regularSearchBackgroundColor = UIColor(0xededed)
|
||||
private let pinnedSearchBackgroundColor = UIColor(0xe5e5e5)
|
||||
|
||||
class ChatListSearchItem: ListViewItem {
|
||||
let selectable: Bool = false
|
||||
@ -24,14 +27,20 @@ class ChatListSearchItem: ListViewItem {
|
||||
node.placeholder = self.placeholder
|
||||
|
||||
let makeLayout = node.asyncLayout()
|
||||
let (layout, apply) = makeLayout(width)
|
||||
var nextIsPinned = false
|
||||
if let nextItem = nextItem as? ChatListItem, nextItem.index.pinningIndex != nil {
|
||||
nextIsPinned = true
|
||||
}
|
||||
let (layout, apply) = makeLayout(width, nextIsPinned)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
node.activate = self.activate
|
||||
completion(node, {
|
||||
return (nil, apply)
|
||||
return (nil, {
|
||||
apply(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -41,10 +50,14 @@ class ChatListSearchItem: ListViewItem {
|
||||
Queue.mainQueue().async {
|
||||
let layout = node.asyncLayout()
|
||||
async {
|
||||
let (nodeLayout, apply) = layout(width)
|
||||
var nextIsPinned = false
|
||||
if let nextItem = nextItem as? ChatListItem, nextItem.index.pinningIndex != nil {
|
||||
nextIsPinned = true
|
||||
}
|
||||
let (nodeLayout, apply) = layout(width, nextIsPinned)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, {
|
||||
apply()
|
||||
apply(animation.isAnimated)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -73,27 +86,40 @@ class ChatListSearchItemNode: ListViewItemNode {
|
||||
|
||||
override func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||
let makeLayout = self.asyncLayout()
|
||||
let (layout, apply) = makeLayout(width)
|
||||
apply()
|
||||
var nextIsPinned = false
|
||||
if let nextItem = nextItem as? ChatListItem, nextItem.index.pinningIndex != nil {
|
||||
nextIsPinned = true
|
||||
}
|
||||
let (layout, apply) = makeLayout(width, nextIsPinned)
|
||||
apply(false)
|
||||
self.contentSize = layout.contentSize
|
||||
self.insets = layout.insets
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ width: CGFloat) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
func asyncLayout() -> (_ width: CGFloat, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let searchBarNodeLayout = self.searchBarNode.asyncLayout()
|
||||
let placeholder = self.placeholder
|
||||
|
||||
return { width in
|
||||
let searchBarApply = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "Search", font: searchBarFont, textColor: UIColor(0x8e8e93)), CGSize(width: width - 16.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
return { width, nextIsPinned in
|
||||
let searchBarApply = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "Search", font: searchBarFont, textColor: UIColor(0x8e8e93)), CGSize(width: width - 16.0, height: CGFloat.greatestFiniteMagnitude), nextIsPinned ? pinnedSearchBackgroundColor : regularSearchBackgroundColor)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: width, height: 44.0 + 4.0), insets: UIEdgeInsets())
|
||||
|
||||
return (layout, { [weak self] in
|
||||
return (layout, { [weak self] animated in
|
||||
if let strongSelf = self {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if animated {
|
||||
transition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
strongSelf.searchBarNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: width - 16.0, height: 28.0))
|
||||
searchBarApply()
|
||||
|
||||
strongSelf.searchBarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: width - 16.0, height: 28.0))
|
||||
|
||||
transition.updateBackgroundColor(node: strongSelf, color: nextIsPinned ? pinnedBackgroundColor : UIColor.white)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import Display
|
||||
enum ChatListSearchItemHeaderType: Int32 {
|
||||
case localPeers
|
||||
case globalPeers
|
||||
case recentPeers
|
||||
case messages
|
||||
}
|
||||
|
||||
@ -43,6 +44,8 @@ final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
|
||||
self.sectionHeaderNode.title = "GLOBAL SEARCH"
|
||||
case .messages:
|
||||
self.sectionHeaderNode.title = "MESSAGES"
|
||||
case .recentPeers:
|
||||
self.sectionHeaderNode.title = "RECENT"
|
||||
}
|
||||
|
||||
self.addSubnode(self.sectionHeaderNode)
|
||||
|
||||
@ -51,15 +51,19 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, from fr
|
||||
}
|
||||
stationaryItems = .indices(indices)
|
||||
case let .navigate(index):
|
||||
for i in 0 ..< toEntries.count {
|
||||
if toEntries[i].index >= index {
|
||||
var directionHint: GridNodePreviousItemsTransitionDirectionHint = .up
|
||||
if !fromEntries.isEmpty && fromEntries[0].index < toEntries[i].index {
|
||||
directionHint = .down
|
||||
if let index = index {
|
||||
for i in 0 ..< toEntries.count {
|
||||
if toEntries[i].index >= index {
|
||||
var directionHint: GridNodePreviousItemsTransitionDirectionHint = .up
|
||||
if !fromEntries.isEmpty && fromEntries[0].index < toEntries[i].index {
|
||||
directionHint = .down
|
||||
}
|
||||
scrollToItem = GridNodeScrollToItem(index: i, position: .top, transition: .animated(duration: 0.45, curve: .spring), directionHint: directionHint, adjustForSection: true)
|
||||
break
|
||||
}
|
||||
scrollToItem = GridNodeScrollToItem(index: i, position: .top, transition: .animated(duration: 0.45, curve: .spring), directionHint: directionHint, adjustForSection: true)
|
||||
break
|
||||
}
|
||||
} else if !toEntries.isEmpty {
|
||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top, transition: .animated(duration: 0.45, curve: .spring), directionHint: .up, adjustForSection: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,8 +81,11 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, from fr
|
||||
return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem)
|
||||
}
|
||||
|
||||
private func chatMediaInputPanelEntries(view: ItemCollectionsView) -> [ChatMediaInputPanelEntry] {
|
||||
private func chatMediaInputPanelEntries(view: ItemCollectionsView, recentStickers: OrderedItemListView?) -> [ChatMediaInputPanelEntry] {
|
||||
var entries: [ChatMediaInputPanelEntry] = []
|
||||
if let recentStickers = recentStickers, !recentStickers.items.isEmpty {
|
||||
entries.append(.recentPacks)
|
||||
}
|
||||
var index = 0
|
||||
for (_, info, item) in view.collectionInfos {
|
||||
if let info = info as? StickerPackCollectionInfo {
|
||||
@ -89,7 +96,7 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView) -> [ChatMedia
|
||||
return entries
|
||||
}
|
||||
|
||||
private func chatMediaInputGridEntries(view: ItemCollectionsView) -> [ChatMediaInputGridEntry] {
|
||||
private func chatMediaInputGridEntries(view: ItemCollectionsView, recentStickers: OrderedItemListView?) -> [ChatMediaInputGridEntry] {
|
||||
var entries: [ChatMediaInputGridEntry] = []
|
||||
|
||||
var stickerPackInfos: [ItemCollectionId: StickerPackCollectionInfo] = [:]
|
||||
@ -99,6 +106,17 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView) -> [ChatMediaI
|
||||
}
|
||||
}
|
||||
|
||||
if let recentStickers = recentStickers, !recentStickers.items.isEmpty {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: Namespaces.ItemCollection.CloudRecentStickers, id: 0), accessHash: 0, title: "FREQUENTLY USED", shortName: "", hash: 0)
|
||||
for i in 0 ..< min(20, recentStickers.items.count) {
|
||||
if let item = recentStickers.items[i].contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile, let mediaId = item.media.id {
|
||||
let index = ItemCollectionItemIndex(index: Int32(i), id: mediaId.id)
|
||||
let stickerItem = StickerPackItem(index: index, file: file, indexKeys: [])
|
||||
entries.append(ChatMediaInputGridEntry(index: ItemCollectionViewEntryIndex(collectionIndex: -1, collectionId: packInfo.id, itemIndex: index), stickerItem: stickerItem, stickerPackInfo: packInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for entry in view.entries {
|
||||
if let item = entry.item as? StickerPackItem {
|
||||
entries.append(ChatMediaInputGridEntry(index: entry.index, stickerItem: item, stickerPackInfo: stickerPackInfos[entry.index.collectionId]))
|
||||
@ -109,8 +127,8 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView) -> [ChatMediaI
|
||||
|
||||
private enum StickerPacksCollectionPosition: Equatable {
|
||||
case initial
|
||||
case scroll(aroundIndex: ItemCollectionViewEntryIndex)
|
||||
case navigate(index: ItemCollectionViewEntryIndex)
|
||||
case scroll(aroundIndex: ItemCollectionViewEntryIndex?)
|
||||
case navigate(index: ItemCollectionViewEntryIndex?)
|
||||
|
||||
static func ==(lhs: StickerPacksCollectionPosition, rhs: StickerPacksCollectionPosition) -> Bool {
|
||||
switch lhs {
|
||||
@ -120,8 +138,8 @@ private enum StickerPacksCollectionPosition: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .scroll(aroundIndex):
|
||||
if case .scroll(aroundIndex) = rhs {
|
||||
case let .scroll(lhsAroundIndex):
|
||||
if case let .scroll(rhsAroundIndex) = rhs, lhsAroundIndex == rhsAroundIndex {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -135,7 +153,7 @@ private enum StickerPacksCollectionPosition: Equatable {
|
||||
private enum StickerPacksCollectionUpdate {
|
||||
case generic
|
||||
case scroll
|
||||
case navigate(ItemCollectionViewEntryIndex)
|
||||
case navigate(ItemCollectionViewEntryIndex?)
|
||||
}
|
||||
|
||||
final class ChatMediaInputNodeInteraction {
|
||||
@ -148,6 +166,18 @@ final class ChatMediaInputNodeInteraction {
|
||||
}
|
||||
}
|
||||
|
||||
private func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> StickerPacksCollectionPosition {
|
||||
switch position {
|
||||
case let .scroll(index):
|
||||
if let index = index, index.collectionId.namespace == Namespaces.ItemCollection.CloudRecentStickers {
|
||||
return .scroll(aroundIndex: nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
private let defaultPortraitPanelHeight: CGFloat = UIScreenScale.isEqual(to: 3.0) ? 271.0 : 258.0
|
||||
private let defaultLandscapePanelHeight: CGFloat = UIScreenScale.isEqual(to: 3.0) ? 194.0 : 194.0
|
||||
|
||||
@ -190,14 +220,18 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
self.inputNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in
|
||||
if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) {
|
||||
var index: Int32 = 0
|
||||
for (id, _, _) in currentView.collectionInfos {
|
||||
if id.namespace == collectionId.namespace {
|
||||
if id == collectionId {
|
||||
let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id)
|
||||
strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: itemIndex)))
|
||||
break
|
||||
if collectionId.namespace == Namespaces.ItemCollection.CloudRecentStickers {
|
||||
strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil)))
|
||||
} else {
|
||||
for (id, _, _) in currentView.collectionInfos {
|
||||
if id.namespace == collectionId.namespace {
|
||||
if id == collectionId {
|
||||
let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id)
|
||||
strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: itemIndex)))
|
||||
break
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,13 +250,13 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|> mapToSignal { position -> Signal<(ItemCollectionsView, StickerPacksCollectionUpdate), NoError> in
|
||||
switch position {
|
||||
case .initial:
|
||||
return account.postbox.itemCollectionsView(namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50)
|
||||
return account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50)
|
||||
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
||||
return (view, .generic)
|
||||
}
|
||||
case let .scroll(aroundIndex):
|
||||
var firstTime = true
|
||||
return account.postbox.itemCollectionsView(namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: aroundIndex, count: 140)
|
||||
return account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: aroundIndex, count: 140)
|
||||
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
||||
let update: StickerPacksCollectionUpdate
|
||||
if firstTime {
|
||||
@ -235,7 +269,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
case let .navigate(index):
|
||||
var firstTime = true
|
||||
return account.postbox.itemCollectionsView(namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index, count: 140)
|
||||
return account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index, count: 140)
|
||||
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
||||
let update: StickerPacksCollectionUpdate
|
||||
if firstTime {
|
||||
@ -255,8 +289,15 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|
||||
let transitions = itemCollectionsView
|
||||
|> map { (view, update) -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view)
|
||||
var recentStickers: OrderedItemListView?
|
||||
for orderedView in view.orderedItemListsViews {
|
||||
if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers {
|
||||
recentStickers = orderedView
|
||||
break
|
||||
}
|
||||
}
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, recentStickers: recentStickers)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view, recentStickers: recentStickers)
|
||||
let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries))
|
||||
return (view, preparedChatMediaInputPanelEntryTransition(account: account, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: account, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction), previousGridEntries.isEmpty)
|
||||
}
|
||||
@ -281,7 +322,6 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
if let collectionId = topVisibleCollectionId {
|
||||
if strongSelf.inputNodeInteraction.highlightedItemCollectionId != collectionId {
|
||||
strongSelf.inputNodeInteraction.highlightedItemCollectionId = collectionId
|
||||
var selectedItemNode: ChatMediaInputStickerPackItemNode?
|
||||
var ensuredNodeVisible = false
|
||||
var firstVisibleCollectionId: ItemCollectionId?
|
||||
strongSelf.listView.forEachItemNode { itemNode in
|
||||
@ -294,6 +334,12 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
strongSelf.listView.ensureItemNodeVisible(itemNode)
|
||||
ensuredNodeVisible = true
|
||||
}
|
||||
} else if let itemNode = itemNode as? ChatMediaInputRecentStickerPacksItemNode {
|
||||
itemNode.updateIsHighlighted()
|
||||
if itemNode.currentCollectionId == collectionId {
|
||||
strongSelf.listView.ensureItemNodeVisible(itemNode)
|
||||
ensuredNodeVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if let currentView = strongSelf.currentView, let firstVisibleCollectionId = firstVisibleCollectionId, !ensuredNodeVisible {
|
||||
@ -309,13 +355,13 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|
||||
if let currentView = strongSelf.currentView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom {
|
||||
if topIndex <= 10 && currentView.lower != nil {
|
||||
let position: StickerPacksCollectionPosition = .scroll(aroundIndex: (topItem as! ChatMediaInputStickerGridItem).index)
|
||||
let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (topItem as! ChatMediaInputStickerGridItem).index))
|
||||
if strongSelf.currentStickerPacksCollectionPosition != position {
|
||||
strongSelf.currentStickerPacksCollectionPosition = position
|
||||
strongSelf.itemCollectionsViewPosition.set(.single(position))
|
||||
}
|
||||
} else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil {
|
||||
let position: StickerPacksCollectionPosition = .scroll(aroundIndex: (bottomItem as! ChatMediaInputStickerGridItem).index)
|
||||
let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (bottomItem as! ChatMediaInputStickerGridItem).index))
|
||||
if strongSelf.currentStickerPacksCollectionPosition != position {
|
||||
strongSelf.currentStickerPacksCollectionPosition = position
|
||||
strongSelf.itemCollectionsViewPosition.set(.single(position))
|
||||
|
||||
@ -4,68 +4,95 @@ import SwiftSignalKit
|
||||
import Display
|
||||
|
||||
enum ChatMediaInputPanelEntryStableId: Hashable {
|
||||
case recentPacks
|
||||
case stickerPack(Int64)
|
||||
|
||||
static func ==(lhs: ChatMediaInputPanelEntryStableId, rhs: ChatMediaInputPanelEntryStableId) -> Bool {
|
||||
switch lhs {
|
||||
case let .stickerPack(id):
|
||||
if case .stickerPack(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .recentPacks:
|
||||
if case .recentPacks = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .stickerPack(id):
|
||||
if case .stickerPack(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hashValue: Int {
|
||||
switch self {
|
||||
case let .stickerPack(id):
|
||||
return id.hashValue
|
||||
case .recentPacks:
|
||||
return 0
|
||||
case let .stickerPack(id):
|
||||
return id.hashValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatMediaInputPanelEntry: Comparable, Identifiable {
|
||||
case recentPacks
|
||||
case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?)
|
||||
|
||||
var stableId: ChatMediaInputPanelEntryStableId {
|
||||
switch self {
|
||||
case let .stickerPack(_, info, _):
|
||||
return .stickerPack(info.id.id)
|
||||
case .recentPacks:
|
||||
return .recentPacks
|
||||
case let .stickerPack(_, info, _):
|
||||
return .stickerPack(info.id.id)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .stickerPack(index, info, topItem):
|
||||
if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .recentPacks:
|
||||
if case .recentPacks = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .stickerPack(index, info, topItem):
|
||||
if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: ChatMediaInputPanelEntry, rhs: ChatMediaInputPanelEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .stickerPack(lhsIndex, lhsInfo, _):
|
||||
switch rhs {
|
||||
case let .stickerPack(rhsIndex, rhsInfo, _):
|
||||
if lhsIndex == rhsIndex {
|
||||
return lhsInfo.id.id < rhsInfo.id.id
|
||||
} else {
|
||||
return lhsIndex < rhsIndex
|
||||
case .recentPacks:
|
||||
return true
|
||||
case let .stickerPack(lhsIndex, lhsInfo, _):
|
||||
switch rhs {
|
||||
case .recentPacks:
|
||||
return false
|
||||
case let .stickerPack(rhsIndex, rhsInfo, _):
|
||||
if lhsIndex == rhsIndex {
|
||||
return lhsInfo.id.id < rhsInfo.id.id
|
||||
} else {
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .stickerPack(index, info, topItem):
|
||||
return ChatMediaInputStickerPackItem(account: account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, stickerPackItem: topItem, index: index, selected: {
|
||||
inputNodeInteraction.navigateToCollectionId(info.id)
|
||||
})
|
||||
case .recentPacks:
|
||||
return ChatMediaInputRecentStickerPacksItem(inputNodeInteraction: inputNodeInteraction, selected: {
|
||||
let collectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudRecentStickers, id: 0)
|
||||
inputNodeInteraction.navigateToCollectionId(collectionId)
|
||||
})
|
||||
case let .stickerPack(index, info, topItem):
|
||||
return ChatMediaInputStickerPackItem(account: account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, stickerPackItem: topItem, index: index, selected: {
|
||||
inputNodeInteraction.navigateToCollectionId(info.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
109
TelegramUI/ChatMediaInputRecentStickerPacksItem.swift
Normal file
109
TelegramUI/ChatMediaInputRecentStickerPacksItem.swift
Normal file
@ -0,0 +1,109 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
||||
private let iconImage = generateImage(CGSize(width: 26.0, height: 26.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(UIColor(0x9099A2).cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
let diameter: CGFloat = 22.0
|
||||
context.strokeEllipse(in: CGRect(origin: CGPoint(x: floor((size.width - diameter) / 2.0), y: floor((size.width - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter)))
|
||||
context.translateBy(x: 1.5, y: 2.5)
|
||||
context.move(to: CGPoint(x: 11.0, y: 5.5))
|
||||
context.addLine(to: CGPoint(x: 11.0, y: 11.0))
|
||||
context.addLine(to: CGPoint(x: 14.5, y: 14.5))
|
||||
context.strokePath()
|
||||
})
|
||||
|
||||
final class ChatMediaInputRecentStickerPacksItem: ListViewItem {
|
||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||
let selectedItem: () -> Void
|
||||
|
||||
var selectable: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
init(inputNodeInteraction: ChatMediaInputNodeInteraction, selected: @escaping () -> Void) {
|
||||
self.inputNodeInteraction = inputNodeInteraction
|
||||
self.selectedItem = selected
|
||||
}
|
||||
|
||||
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 = ChatMediaInputRecentStickerPacksItemNode()
|
||||
node.contentSize = CGSize(width: 41.0, height: 41.0)
|
||||
node.insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
completion(node, {
|
||||
return (nil, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {
|
||||
})
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
self.selectedItem()
|
||||
}
|
||||
}
|
||||
|
||||
private let boundingSize = CGSize(width: 41.0, height: 41.0)
|
||||
private let boundingImageSize = CGSize(width: 30.0, height: 30.0)
|
||||
private let highlightSize = CGSize(width: 35.0, height: 35.0)
|
||||
private let verticalOffset: CGFloat = 3.0 + UIScreenPixel
|
||||
|
||||
private let highlightBackground = generateStretchableFilledCircleImage(radius: 9.0, color: UIColor(0x9099A2, 0.2))
|
||||
|
||||
final class ChatMediaInputRecentStickerPacksItemNode: ListViewItemNode {
|
||||
private let imageNode: ASImageNode
|
||||
private let highlightNode: ASImageNode
|
||||
|
||||
var currentCollectionId: ItemCollectionId?
|
||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
|
||||
init() {
|
||||
self.highlightNode = ASImageNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
self.highlightNode.image = highlightBackground
|
||||
self.highlightNode.isHidden = true
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
|
||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)
|
||||
|
||||
self.imageNode.image = iconImage
|
||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat(M_PI / 2.0), 0.0, 0.0, 1.0)
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.highlightNode)
|
||||
self.addSubnode(self.imageNode)
|
||||
|
||||
self.currentCollectionId = ItemCollectionId(namespace: Namespaces.ItemCollection.CloudRecentStickers, id: 0)
|
||||
|
||||
let imageSize = CGSize(width: 26.0, height: 26.0)
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
func updateStickerPackItem(account: Account, item: StickerPackItem?, collectionId: ItemCollectionId) {
|
||||
self.currentCollectionId = collectionId
|
||||
self.updateIsHighlighted()
|
||||
}
|
||||
|
||||
func updateIsHighlighted() {
|
||||
if let currentCollectionId = self.currentCollectionId, let inputNodeInteraction = self.inputNodeInteraction {
|
||||
self.highlightNode.isHidden = inputNodeInteraction.highlightedItemCollectionId != currentCollectionId
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,6 +87,17 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
||||
node.selected = self.selected
|
||||
return node
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
guard let node = node as? ChatMediaInputStickerGridItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.interfaceInteraction = self.interfaceInteraction
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
node.setup(account: self.account, stickerItem: self.stickerItem)
|
||||
node.selected = self.selected
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
||||
|
||||
@ -74,15 +74,15 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode {
|
||||
|
||||
@objc func imageTap(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if let file = self.media as? TelegramMediaFile, let message = self.message, (file.isVideo || file.isAnimated || file.mimeType.hasPrefix("video/")) && !message.containsSecretMedia {
|
||||
/*if let file = self.media as? TelegramMediaFile, let message = self.message, (file.isVideo || file.isAnimated || file.mimeType.hasPrefix("video/")) && !message.containsSecretMedia {
|
||||
self.activateLocalContent()
|
||||
} else {
|
||||
} else {*/
|
||||
if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
|
||||
self.activateLocalContent()
|
||||
} else {
|
||||
self.progressPressed()
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
private let statusNode: ChatMessageDateAndStatusNode
|
||||
|
||||
private var item: ChatMessageItem?
|
||||
private var webPage: TelegramMediaWebpage?
|
||||
private var image: TelegramMediaImage?
|
||||
|
||||
@ -319,6 +320,8 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
return (adjustedBoundingSize, { [weak self] animation in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
var hasAnimation = true
|
||||
if case .None = animation {
|
||||
hasAnimation = false
|
||||
@ -366,7 +369,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.contentImageNode = contentImageNode
|
||||
strongSelf.addSubnode(contentImageNode)
|
||||
contentImageNode.activateLocalContent = { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf {
|
||||
if let strongSelf = strongSelf, let item = strongSelf.item {
|
||||
strongSelf.controllerInteraction?.openMessage(item.message.id)
|
||||
}
|
||||
}
|
||||
@ -384,7 +387,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.contentFileNode = contentFileNode
|
||||
strongSelf.addSubnode(contentFileNode)
|
||||
contentFileNode.activateLocalContent = { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf {
|
||||
if let strongSelf = strongSelf, let item = strongSelf.item {
|
||||
strongSelf.controllerInteraction?.openMessage(item.message.id)
|
||||
}
|
||||
}
|
||||
@ -440,4 +443,46 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
return .none
|
||||
}
|
||||
|
||||
override func updateHiddenMedia(_ media: [Media]?) {
|
||||
var currentMedia: Media?
|
||||
if let webPage = self.webPage {
|
||||
if case let .Loaded(content) = webPage.content {
|
||||
if let image = content.image {
|
||||
currentMedia = image
|
||||
} else if let file = content.file {
|
||||
currentMedia = file
|
||||
}
|
||||
}
|
||||
}
|
||||
if let currentMedia = currentMedia {
|
||||
if let media = media {
|
||||
var found = false
|
||||
for m in media {
|
||||
if currentMedia.isEqual(m) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if let contentImageNode = self.contentImageNode {
|
||||
contentImageNode.isHidden = found
|
||||
}
|
||||
} else if let contentImageNode = self.contentImageNode {
|
||||
contentImageNode.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func transitionNode(media: Media) -> ASDisplayNode? {
|
||||
if let webPage = self.webPage {
|
||||
if case let .Loaded(content) = webPage.content {
|
||||
if let image = content.image, image.isEqual(media) {
|
||||
return self.contentImageNode
|
||||
} else if let file = content.file, file.isEqual(media) {
|
||||
return self.contentImageNode
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,9 +32,10 @@ final class ChatPanelInterfaceInteraction {
|
||||
let beginAudioRecording: () -> Void
|
||||
let finishAudioRecording: (Bool) -> Void
|
||||
let setupMessageAutoremoveTimeout: () -> Void
|
||||
let sendSticker: (TelegramMediaFile) -> Void
|
||||
let statuses: ChatPanelInterfaceInteractionStatuses?
|
||||
|
||||
init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId) -> Void, beginMessageSelection: @escaping (MessageId) -> Void, deleteSelectedMessages: @escaping () -> Void, forwardSelectedMessages: @escaping () -> Void, updateTextInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, editMessage: @escaping (MessageId, String) -> Void, beginMessageSearch: @escaping () -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginAudioRecording: @escaping () -> Void, finishAudioRecording: @escaping (Bool) -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
|
||||
init(setupReplyMessage: @escaping (MessageId) -> Void, setupEditMessage: @escaping (MessageId) -> Void, beginMessageSelection: @escaping (MessageId) -> Void, deleteSelectedMessages: @escaping () -> Void, forwardSelectedMessages: @escaping () -> Void, updateTextInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, editMessage: @escaping (MessageId, String) -> Void, beginMessageSearch: @escaping () -> Void, openPeerInfo: @escaping () -> Void, togglePeerNotifications: @escaping () -> Void, sendContextResult: @escaping (ChatContextResultCollection, ChatContextResult) -> Void, sendBotCommand: @escaping (Peer, String) -> Void, sendBotStart: @escaping (String?) -> Void, botSwitchChatWithPayload: @escaping (PeerId, String) -> Void, beginAudioRecording: @escaping () -> Void, finishAudioRecording: @escaping (Bool) -> Void, setupMessageAutoremoveTimeout: @escaping () -> Void, sendSticker: @escaping (TelegramMediaFile) -> Void, statuses: ChatPanelInterfaceInteractionStatuses?) {
|
||||
self.setupReplyMessage = setupReplyMessage
|
||||
self.setupEditMessage = setupEditMessage
|
||||
self.beginMessageSelection = beginMessageSelection
|
||||
@ -53,6 +54,7 @@ final class ChatPanelInterfaceInteraction {
|
||||
self.beginAudioRecording = beginAudioRecording
|
||||
self.finishAudioRecording = finishAudioRecording
|
||||
self.setupMessageAutoremoveTimeout = setupMessageAutoremoveTimeout
|
||||
self.sendSticker = sendSticker
|
||||
self.statuses = statuses
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import Postbox
|
||||
import TelegramCore
|
||||
|
||||
enum ChatPresentationInputQuery: Equatable {
|
||||
case emoji(String)
|
||||
case hashtag(String)
|
||||
case mention(String)
|
||||
case command(String)
|
||||
@ -10,6 +11,12 @@ enum ChatPresentationInputQuery: Equatable {
|
||||
|
||||
static func ==(lhs: ChatPresentationInputQuery, rhs: ChatPresentationInputQuery) -> Bool {
|
||||
switch lhs {
|
||||
case let .emoji(query):
|
||||
if case .emoji(query) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .hashtag(query):
|
||||
if case .hashtag(query) = rhs {
|
||||
return true
|
||||
@ -39,6 +46,7 @@ enum ChatPresentationInputQuery: Equatable {
|
||||
}
|
||||
|
||||
enum ChatPresentationInputQueryResult: Equatable {
|
||||
case stickers([StickerPackItem])
|
||||
case hashtags([String])
|
||||
case mentions([Peer])
|
||||
case commands([PeerCommand])
|
||||
@ -46,6 +54,12 @@ enum ChatPresentationInputQueryResult: Equatable {
|
||||
|
||||
static func ==(lhs: ChatPresentationInputQueryResult, rhs: ChatPresentationInputQueryResult) -> Bool {
|
||||
switch lhs {
|
||||
case let .stickers(lhsItems):
|
||||
if case let .stickers(rhsItems) = rhs, lhsItems == rhsItems {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .hashtags(lhsResults):
|
||||
if case let .hashtags(rhsResults) = rhs {
|
||||
return lhsResults == rhsResults
|
||||
|
||||
@ -23,6 +23,11 @@ class ChatVideoGalleryItem: GalleryItem {
|
||||
if let file = media as? TelegramMediaFile, (file.isVideo || file.mimeType.hasPrefix("video/")) {
|
||||
node.setFile(account: account, file: file, loopVideo: file.isAnimated || self.message.containsSecretMedia)
|
||||
break
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
if let file = content.file, (file.isVideo || file.mimeType.hasPrefix("video/")) {
|
||||
node.setFile(account: account, file: file, loopVideo: file.isAnimated || self.message.containsSecretMedia)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -81,6 +81,13 @@ public class ComposeController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
self.contactsNode.openCreateNewSecretChat = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let controller = ContactSelectionController(account: strongSelf.account, title: "New Secret Chat")
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
|
||||
@ -233,7 +233,7 @@ private extension PeerIndexNameRepresentation {
|
||||
}
|
||||
}
|
||||
|
||||
private func contactListNodeEntries(view: ContactPeersView, presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?) -> [ContactListNodeEntry] {
|
||||
private func contactListNodeEntries(accountPeer: Peer?, peers: [Peer], presences: [PeerId: PeerPresence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?) -> [ContactListNodeEntry] {
|
||||
var entries: [ContactListNodeEntry] = []
|
||||
|
||||
var orderedPeers: [Peer]
|
||||
@ -241,14 +241,15 @@ private func contactListNodeEntries(view: ContactPeersView, presentation: Contac
|
||||
|
||||
switch presentation {
|
||||
case let .orderedByPresence(displayVCard):
|
||||
entries.append(.search)
|
||||
if displayVCard {
|
||||
if let peer = view.accountPeer {
|
||||
if let peer = accountPeer {
|
||||
entries.append(.vcard(peer))
|
||||
}
|
||||
}
|
||||
orderedPeers = view.peers.sorted(by: { lhs, rhs in
|
||||
let lhsPresence = view.peerPresences[lhs.id]
|
||||
let rhsPresence = view.peerPresences[rhs.id]
|
||||
orderedPeers = peers.sorted(by: { lhs, rhs in
|
||||
let lhsPresence = presences[lhs.id]
|
||||
let rhsPresence = presences[rhs.id]
|
||||
if let lhsPresence = lhsPresence as? TelegramUserPresence, let rhsPresence = rhsPresence as? TelegramUserPresence {
|
||||
if lhsPresence.status < rhsPresence.status {
|
||||
return false
|
||||
@ -262,9 +263,8 @@ private func contactListNodeEntries(view: ContactPeersView, presentation: Contac
|
||||
}
|
||||
return lhs.id < rhs.id
|
||||
})
|
||||
entries.append(.search)
|
||||
case let .natural(displaySearch, options):
|
||||
orderedPeers = view.peers.sorted(by: { lhs, rhs in
|
||||
orderedPeers = peers.sorted(by: { lhs, rhs in
|
||||
let result = lhs.indexName.isLessThan(other: rhs.indexName)
|
||||
if result == .orderedSame {
|
||||
return lhs.id < rhs.id
|
||||
@ -302,6 +302,8 @@ private func contactListNodeEntries(view: ContactPeersView, presentation: Contac
|
||||
for i in 0 ..< options.count {
|
||||
entries.append(.option(i, options[i]))
|
||||
}
|
||||
case .search:
|
||||
orderedPeers = peers
|
||||
}
|
||||
|
||||
var removeIndices: [Int] = []
|
||||
@ -330,7 +332,7 @@ private func contactListNodeEntries(view: ContactPeersView, presentation: Contac
|
||||
} else {
|
||||
selection = .none
|
||||
}
|
||||
entries.append(.peer(i, orderedPeers[i], view.peerPresences[orderedPeers[i].id], headers[orderedPeers[i].id], selection))
|
||||
entries.append(.peer(i, orderedPeers[i], presences[orderedPeers[i].id], headers[orderedPeers[i].id], selection))
|
||||
}
|
||||
return entries
|
||||
}
|
||||
@ -366,6 +368,7 @@ struct ContactListAdditionalOption: Equatable {
|
||||
enum ContactListPresentation {
|
||||
case orderedByPresence(displayVCard: Bool)
|
||||
case natural(displaySearch: Bool, options: [ContactListAdditionalOption])
|
||||
case search(Signal<String, NoError>)
|
||||
}
|
||||
|
||||
struct ContactListNodeGroupSelectionState: Equatable {
|
||||
@ -400,7 +403,6 @@ struct ContactListNodeGroupSelectionState: Equatable {
|
||||
|
||||
final class ContactListNode: ASDisplayNode {
|
||||
private let account: Account
|
||||
private let presentation: ContactListPresentation
|
||||
|
||||
let listNode: ListView
|
||||
|
||||
@ -440,7 +442,6 @@ final class ContactListNode: ASDisplayNode {
|
||||
|
||||
init(account: Account, presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState? = nil) {
|
||||
self.account = account
|
||||
self.presentation = presentation
|
||||
|
||||
self.listNode = ListView()
|
||||
|
||||
@ -463,33 +464,54 @@ final class ContactListNode: ASDisplayNode {
|
||||
let account = self.account
|
||||
var firstTime: Int32 = 1
|
||||
let selectionStateSignal = self.selectionStatePromise.get()
|
||||
let transition = self.enableUpdatesPromise.get()
|
||||
|> mapToSignal { enableUpdates -> Signal<ContactsListNodeTransition, NoError> in
|
||||
if enableUpdates {
|
||||
return combineLatest(account.postbox.contactPeersView(accountPeerId: account.peerId), selectionStateSignal)
|
||||
|> mapToQueue { view, selectionState -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let entries = contactListNodeEntries(view: view, presentation: presentation, selectionState: selectionState)
|
||||
let previous = previousEntries.swap(entries)
|
||||
let animated: Bool
|
||||
if let previous = previous {
|
||||
animated = (entries.count - previous.count) < 20
|
||||
} else {
|
||||
animated = false
|
||||
let transition: Signal<ContactsListNodeTransition, NoError>
|
||||
if case let .search(query) = presentation {
|
||||
transition = query
|
||||
|> mapToSignal { query in
|
||||
return combineLatest(account.postbox.searchContacts(query: query), selectionStateSignal)
|
||||
|> mapToQueue { peers, selectionState -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: [:], presentation: presentation, selectionState: selectionState)
|
||||
let previous = previousEntries.swap(entries)
|
||||
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: false))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
return signal |> runOn(Queue.mainQueue())
|
||||
} else {
|
||||
return signal |> runOn(processingQueue)
|
||||
}
|
||||
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: animated))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
return signal |> runOn(Queue.mainQueue())
|
||||
} else {
|
||||
return signal |> runOn(processingQueue)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .never()
|
||||
}
|
||||
} |> deliverOnMainQueue
|
||||
} else {
|
||||
transition = self.enableUpdatesPromise.get()
|
||||
|> mapToSignal { enableUpdates -> Signal<ContactsListNodeTransition, NoError> in
|
||||
if enableUpdates {
|
||||
return combineLatest(account.postbox.contactPeersView(accountPeerId: account.peerId), selectionStateSignal)
|
||||
|> mapToQueue { view, selectionState -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let entries = contactListNodeEntries(accountPeer: view.accountPeer, peers: view.peers, presences: view.peerPresences, presentation: presentation, selectionState: selectionState)
|
||||
let previous = previousEntries.swap(entries)
|
||||
let animated: Bool
|
||||
if let previous = previous {
|
||||
animated = (entries.count - previous.count) < 20
|
||||
} else {
|
||||
animated = false
|
||||
}
|
||||
return .single(preparedContactListNodeTransition(account: account, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, animated: animated))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
return signal |> runOn(Queue.mainQueue())
|
||||
} else {
|
||||
return signal |> runOn(processingQueue)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .never()
|
||||
}
|
||||
} |> deliverOnMainQueue
|
||||
}
|
||||
self.disposable.set(transition.start(next: { [weak self] transition in
|
||||
self?.enqueueTransition(transition)
|
||||
}))
|
||||
|
||||
@ -68,12 +68,13 @@ public class ContactMultiselectionController: ViewController {
|
||||
self.displayNode = ContactMultiselectionControllerNode(account: self.account)
|
||||
self._ready.set(self.contactsNode.contactListNode.ready)
|
||||
|
||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer in
|
||||
self.contactsNode.openPeer = { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
var updatedCount: Int?
|
||||
var addedToken: EditableTokenListToken?
|
||||
var removedTokenId: AnyHashable?
|
||||
|
||||
var selectionState: ContactListNodeGroupSelectionState?
|
||||
strongSelf.contactsNode.contactListNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
let updatedState = state.withToggledPeerId(peer.id)
|
||||
@ -83,11 +84,17 @@ public class ContactMultiselectionController: ViewController {
|
||||
addedToken = EditableTokenListToken(id: peer.id, title: peer.displayTitle)
|
||||
}
|
||||
updatedCount = updatedState.selectedPeerIndices.count
|
||||
selectionState = updatedState
|
||||
return updatedState
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if let searchResultsNode = strongSelf.contactsNode.searchResultsNode {
|
||||
searchResultsNode.updateSelectionState { _ in
|
||||
return selectionState
|
||||
}
|
||||
}
|
||||
|
||||
if let updatedCount = updatedCount {
|
||||
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0
|
||||
@ -105,6 +112,45 @@ public class ContactMultiselectionController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
self.contactsNode.removeSelectedPeer = { [weak self] peerId in
|
||||
if let strongSelf = self {
|
||||
var updatedCount: Int?
|
||||
var removedTokenId: AnyHashable?
|
||||
|
||||
var selectionState: ContactListNodeGroupSelectionState?
|
||||
strongSelf.contactsNode.contactListNode.updateSelectionState { state in
|
||||
if let state = state {
|
||||
let updatedState = state.withToggledPeerId(peerId)
|
||||
if updatedState.selectedPeerIndices[peerId] == nil {
|
||||
removedTokenId = peerId
|
||||
}
|
||||
updatedCount = updatedState.selectedPeerIndices.count
|
||||
selectionState = updatedState
|
||||
return updatedState
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if let searchResultsNode = strongSelf.contactsNode.searchResultsNode {
|
||||
searchResultsNode.updateSelectionState { _ in
|
||||
return selectionState
|
||||
}
|
||||
}
|
||||
|
||||
if let updatedCount = updatedCount {
|
||||
strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0
|
||||
strongSelf.titleView.title = CounterContollerTitle(title: "New Group", counter: "\(updatedCount)/5000")
|
||||
}
|
||||
|
||||
if let removedTokenId = removedTokenId {
|
||||
strongSelf.contactsNode.editableTokens = strongSelf.contactsNode.editableTokens.filter { token in
|
||||
return token.id != removedTokenId
|
||||
}
|
||||
}
|
||||
strongSelf.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
|
||||
@ -3,21 +3,43 @@ import AsyncDisplayKit
|
||||
import UIKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
|
||||
private struct SearchResultEntry: Identifiable {
|
||||
let index: Int
|
||||
let peer: Peer
|
||||
|
||||
var stableId: Int64 {
|
||||
return self.peer.id.toInt64()
|
||||
}
|
||||
|
||||
static func ==(lhs: SearchResultEntry, rhs: SearchResultEntry) -> Bool {
|
||||
return lhs.index == rhs.index && lhs.peer.isEqual(rhs.peer)
|
||||
}
|
||||
|
||||
static func <(lhs: SearchResultEntry, rhs: SearchResultEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
}
|
||||
|
||||
final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
let contactListNode: ContactListNode
|
||||
let tokenListNode: EditableTokenListNode
|
||||
var searchResultsNode: ContactListNode?
|
||||
|
||||
private let account: Account
|
||||
private var searchDisplayController: SearchDisplayController?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestOpenPeerFromSearch: ((PeerId) -> Void)?
|
||||
var openPeer: ((Peer) -> Void)?
|
||||
var removeSelectedPeer: ((PeerId) -> Void)?
|
||||
|
||||
var editableTokens: [EditableTokenListToken] = []
|
||||
|
||||
private let searchResultsReadyDisposable = MetaDisposable()
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: false, options: []), selectionState: ContactListNodeGroupSelectionState())
|
||||
@ -31,6 +53,61 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
|
||||
self.addSubnode(self.contactListNode)
|
||||
self.addSubnode(self.tokenListNode)
|
||||
|
||||
self.contactListNode.openPeer = { [weak self] peer in
|
||||
self?.openPeer?(peer)
|
||||
}
|
||||
|
||||
let searchText = ValuePromise<String>()
|
||||
|
||||
self.tokenListNode.deleteToken = { [weak self] id in
|
||||
self?.removeSelectedPeer?(id as! PeerId)
|
||||
}
|
||||
|
||||
self.tokenListNode.textUpdated = { [weak self] text in
|
||||
if let strongSelf = self {
|
||||
searchText.set(text)
|
||||
if text.isEmpty {
|
||||
if let searchResultsNode = strongSelf.searchResultsNode {
|
||||
searchResultsNode.removeFromSupernode()
|
||||
strongSelf.searchResultsNode = nil
|
||||
}
|
||||
} else {
|
||||
if strongSelf.searchResultsNode == nil {
|
||||
var selectionState: ContactListNodeGroupSelectionState?
|
||||
strongSelf.contactListNode.updateSelectionState { state in
|
||||
selectionState = state
|
||||
return state
|
||||
}
|
||||
let searchResultsNode = ContactListNode(account: account, presentation: ContactListPresentation.search(searchText.get()), selectionState: selectionState)
|
||||
searchResultsNode.openPeer = { peer in
|
||||
self?.tokenListNode.setText("")
|
||||
self?.openPeer?(peer)
|
||||
}
|
||||
strongSelf.searchResultsNode = searchResultsNode
|
||||
searchResultsNode.enableUpdates = true
|
||||
searchResultsNode.backgroundColor = .white
|
||||
if let (layout, navigationBarHeight) = strongSelf.containerLayout {
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
insets.top += strongSelf.tokenListNode.bounds.size.height
|
||||
searchResultsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, intrinsicInsets: insets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight), transition: .immediate)
|
||||
searchResultsNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
}
|
||||
|
||||
strongSelf.searchResultsReadyDisposable.set((searchResultsNode.ready |> deliverOnMainQueue).start(next: { _ in
|
||||
if let strongSelf = self, let searchResultsNode = strongSelf.searchResultsNode {
|
||||
strongSelf.insertSubnode(searchResultsNode, aboveSubnode: strongSelf.contactListNode)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.searchResultsReadyDisposable.dispose()
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
@ -40,16 +117,17 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
insets.top += navigationBarHeight
|
||||
|
||||
let tokenListHeight = self.tokenListNode.updateLayout(tokens: self.editableTokens, width: layout.size.width, transition: transition)
|
||||
|
||||
transition.updateFrame(node: self.tokenListNode, frame: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: tokenListHeight)))
|
||||
|
||||
insets.top += tokenListHeight
|
||||
|
||||
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, intrinsicInsets: insets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight), transition: transition)
|
||||
|
||||
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
if let searchResultsNode = self.searchResultsNode {
|
||||
searchResultsNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, intrinsicInsets: insets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight), transition: transition)
|
||||
searchResultsNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
148
TelegramUI/ContactSelectionController.swift
Normal file
148
TelegramUI/ContactSelectionController.swift
Normal file
@ -0,0 +1,148 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
|
||||
public class ContactSelectionController: ViewController {
|
||||
private let account: Account
|
||||
|
||||
private var contactsNode: ContactSelectionControllerNode {
|
||||
return self.displayNode as! ContactSelectionControllerNode
|
||||
}
|
||||
|
||||
private let index: PeerNameIndex = .lastNameFirst
|
||||
|
||||
private var _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
private let _result = Promise<PeerId?>()
|
||||
var result: Signal<PeerId?, NoError> {
|
||||
return self._result.get()
|
||||
}
|
||||
|
||||
private let createActionDisposable = MetaDisposable()
|
||||
|
||||
public init(account: Account, title: String) {
|
||||
self.account = account
|
||||
|
||||
super.init()
|
||||
|
||||
self.title = title
|
||||
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
|
||||
|
||||
self.scrollToTop = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.contactsNode.contactListNode.scrollToTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.createActionDisposable.dispose()
|
||||
}
|
||||
|
||||
@objc func cancelPressed() {
|
||||
self._result.set(.single(nil))
|
||||
self.contactsNode.animateOut()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ContactSelectionControllerNode(account: self.account)
|
||||
self._ready.set(self.contactsNode.contactListNode.ready)
|
||||
|
||||
self.contactsNode.navigationBar = self.navigationBar
|
||||
|
||||
self.contactsNode.requestDeactivateSearch = { [weak self] in
|
||||
self?.deactivateSearch()
|
||||
}
|
||||
|
||||
self.contactsNode.requestOpenPeerFromSearch = { [weak self] peerId in
|
||||
self?.openPeer(peerId: peerId)
|
||||
}
|
||||
|
||||
self.contactsNode.contactListNode.activateSearch = { [weak self] in
|
||||
self?.activateSearch()
|
||||
}
|
||||
|
||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer in
|
||||
self?.openPeer(peerId: peer.id)
|
||||
}
|
||||
|
||||
self.contactsNode.dismiss = { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments {
|
||||
switch presentationArguments.presentationAnimation {
|
||||
case .modalSheet:
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(cancelPressed))
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.contactsNode.contactListNode.enableUpdates = true
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let presentationArguments = self.presentationArguments as? ViewControllerPresentationArguments {
|
||||
switch presentationArguments.presentationAnimation {
|
||||
case .modalSheet:
|
||||
self.contactsNode.animateIn()
|
||||
case .none:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
self.contactsNode.contactListNode.enableUpdates = false
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
private func activateSearch() {
|
||||
if self.displayNavigationBar {
|
||||
if let scrollToTop = self.scrollToTop {
|
||||
scrollToTop()
|
||||
}
|
||||
self.contactsNode.activateSearch()
|
||||
self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
private func deactivateSearch() {
|
||||
if !self.displayNavigationBar {
|
||||
self.contactsNode.deactivateSearch()
|
||||
self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
private func openPeer(peerId: PeerId) {
|
||||
self._result.set(.single(peerId))
|
||||
self.contactsNode.animateOut()
|
||||
}
|
||||
}
|
||||
109
TelegramUI/ContactSelectionControllerNode.swift
Normal file
109
TelegramUI/ContactSelectionControllerNode.swift
Normal file
@ -0,0 +1,109 @@
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import UIKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
let contactListNode: ContactListNode
|
||||
|
||||
private let account: Account
|
||||
private var searchDisplayController: SearchDisplayController?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
var navigationBar: NavigationBar?
|
||||
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestOpenPeerFromSearch: ((PeerId) -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
|
||||
self.contactListNode = ContactListNode(account: account, presentation: .natural(displaySearch: true, options: []))
|
||||
|
||||
super.init(viewBlock: {
|
||||
return UITracingLayerView()
|
||||
}, didLoad: nil)
|
||||
|
||||
self.backgroundColor = UIColor.white
|
||||
|
||||
self.addSubnode(self.contactListNode)
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight)
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
|
||||
self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, intrinsicInsets: insets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight), transition: transition)
|
||||
|
||||
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func activateSearch() {
|
||||
guard let (containerLayout, navigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar else {
|
||||
return
|
||||
}
|
||||
|
||||
var maybePlaceholderNode: SearchBarPlaceholderNode?
|
||||
self.contactListNode.listNode.forEachItemNode { node in
|
||||
if let node = node as? ChatListSearchItemNode {
|
||||
maybePlaceholderNode = node.searchBarNode
|
||||
}
|
||||
}
|
||||
|
||||
if let _ = self.searchDisplayController {
|
||||
return
|
||||
}
|
||||
|
||||
if let placeholderNode = maybePlaceholderNode {
|
||||
self.searchDisplayController = SearchDisplayController(contentNode: ContactsSearchContainerNode(account: self.account, openPeer: { [weak self] peerId in
|
||||
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch {
|
||||
requestOpenPeerFromSearch(peerId)
|
||||
}
|
||||
}), cancel: { [weak self] in
|
||||
if let requestDeactivateSearch = self?.requestDeactivateSearch {
|
||||
requestDeactivateSearch()
|
||||
}
|
||||
})
|
||||
|
||||
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
self.searchDisplayController?.activate(insertSubnode: { subnode in
|
||||
self.insertSubnode(subnode, belowSubnode: navigationBar)
|
||||
}, placeholder: placeholderNode)
|
||||
}
|
||||
}
|
||||
|
||||
func deactivateSearch() {
|
||||
if let searchDisplayController = self.searchDisplayController {
|
||||
var maybePlaceholderNode: SearchBarPlaceholderNode?
|
||||
self.contactListNode.listNode.forEachItemNode { node in
|
||||
if let node = node as? ChatListSearchItemNode {
|
||||
maybePlaceholderNode = node.searchBarNode
|
||||
}
|
||||
}
|
||||
|
||||
searchDisplayController.deactivate(placeholder: maybePlaceholderNode)
|
||||
self.searchDisplayController = nil
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
func animateOut() {
|
||||
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.dismiss?()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -96,7 +96,7 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: CreateGroupArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .groupInfo(peer, state):
|
||||
return ItemListAvatarAndNameInfoItem(account: arguments.account, peer: peer, cachedData: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks, editingNameUpdated: { editingName in
|
||||
return ItemListAvatarAndNameInfoItem(account: arguments.account, peer: peer, presence: nil, cachedData: nil, state: state, sectionId: ItemListSectionId(self.section), style: .blocks, editingNameUpdated: { editingName in
|
||||
arguments.updateEditingName(editingName)
|
||||
})
|
||||
case .setProfilePhoto:
|
||||
@ -126,7 +126,7 @@ private func createGroupEntries(state: CreateGroupState, peerIds: [PeerId], view
|
||||
|
||||
let groupInfoState = ItemListAvatarAndNameInfoItemState(editingName: state.editingName, updatingName: nil)
|
||||
|
||||
let peer = TelegramGroup(id: PeerId(namespace: 100, id: 0), title: "", photo: [], participantCount: 0, role: .creator, membership: .Member, flags: [], migrationReference: nil, creationDate: 0, version: 0)
|
||||
let peer = TelegramGroup(id: PeerId(namespace: 100, id: 0), title: state.editingName.composedTitle, photo: [], participantCount: 0, role: .creator, membership: .Member, flags: [], migrationReference: nil, creationDate: 0, version: 0)
|
||||
|
||||
entries.append(.groupInfo(peer, groupInfoState))
|
||||
entries.append(.setProfilePhoto)
|
||||
|
||||
@ -26,13 +26,13 @@ public class DataAndStorageSettingsController: ListController {
|
||||
ListControllerDisclosureActionItem(title: "Bytes Received", action: deselectAction),
|
||||
]
|
||||
|
||||
self.currentStatsDisposable = (((account.currentNetworkStats() |> then(Signal<MTNetworkUsageManagerStats, NoError>.complete() |> delay(1.0, queue: Queue.concurrentDefaultQueue()))) |> restart) |> deliverOnMainQueue).start(next: { [weak self] stats in
|
||||
/*self.currentStatsDisposable = (((account.currentNetworkStats() |> then(Signal<MTNetworkUsageManagerStats, NoError>.complete() |> delay(1.0, queue: Queue.concurrentDefaultQueue()))) |> restart) |> deliverOnMainQueue).start(next: { [weak self] stats in
|
||||
if let strongSelf = self {
|
||||
let incoming = stats.wwan.incomingBytes + stats.other.incomingBytes
|
||||
let outgoing = stats.wwan.outgoingBytes + stats.other.outgoingBytes
|
||||
strongSelf.listDisplayNode.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [ListViewUpdateItem(index: 0, previousIndex: 0, item: ListControllerDisclosureActionItem(title: "Bytes Sent: \(outgoing / 1024) KB", action: deselectAction), directionHint: nil), ListViewUpdateItem(index: 1, previousIndex: 1, item: ListControllerDisclosureActionItem(title: "Bytes Received: \(incoming / 1024) KB", action: deselectAction), directionHint: nil)], options: [.AnimateInsertion], updateOpaqueState: nil)
|
||||
}
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
||||
@ -7,25 +7,50 @@ struct EditableTokenListToken {
|
||||
let title: String
|
||||
}
|
||||
|
||||
private let caretIndicatorImage = generateVerticallyStretchableFilledCircleImage(radius: 1.0, color: UIColor(0x3350ee))
|
||||
|
||||
private func caretAnimation() -> CAAnimation {
|
||||
let animation = CAKeyframeAnimation(keyPath: "opacity")
|
||||
animation.values = [1.0 as NSNumber, 0.0 as NSNumber, 1.0 as NSNumber, 1.0 as NSNumber]
|
||||
let firstDuration = 0.3
|
||||
let secondDuration = 0.25
|
||||
let restDuration = 0.35
|
||||
let duration = firstDuration + secondDuration + restDuration
|
||||
let keyTimes: [NSNumber] = [0.0 as NSNumber, (firstDuration / duration) as NSNumber, ((firstDuration + secondDuration) / duration) as NSNumber, ((firstDuration + secondDuration + restDuration) / duration) as NSNumber]
|
||||
|
||||
animation.keyTimes = keyTimes
|
||||
animation.duration = duration
|
||||
animation.repeatCount = Float.greatestFiniteMagnitude
|
||||
return animation
|
||||
}
|
||||
|
||||
private final class TokenNode: ASDisplayNode {
|
||||
let token: EditableTokenListToken
|
||||
let titleNode: ASTextNode
|
||||
var isSelected: Bool {
|
||||
didSet {
|
||||
if self.isSelected != oldValue {
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title + ",", font: Font.regular(15.0), textColor: self.isSelected ? UIColor(0x007ee5) : UIColor.black)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(token: EditableTokenListToken) {
|
||||
init(token: EditableTokenListToken, isSelected: Bool) {
|
||||
self.token = token
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.isLayerBacked = true
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
self.isSelected = isSelected
|
||||
|
||||
super.init()
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title + ",", font: Font.regular(15.0), textColor: .black)
|
||||
self.titleNode.attributedText = NSAttributedString(string: token.title + ",", font: Font.regular(15.0), textColor: self.isSelected ? UIColor(0x007ee5) : UIColor.black)
|
||||
self.addSubnode(self.titleNode)
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let titleSize = self.titleNode.measure(CGSize(width: constrainedSize.width - 8.0, height: constrainedSize.height))
|
||||
return CGSize(width: titleSize.width + 8.0, height: 26.0)
|
||||
return CGSize(width: titleSize.width + 8.0, height: 28.0)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
@ -37,10 +62,26 @@ private final class TokenNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
final class EditableTokenListNode: ASDisplayNode {
|
||||
private final class CaretIndicatorNode: ASImageNode {
|
||||
override func willEnterHierarchy() {
|
||||
super.willEnterHierarchy()
|
||||
|
||||
if self.layer.animation(forKey: "blink") == nil {
|
||||
self.layer.add(caretAnimation(), forKey: "blink")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate {
|
||||
private let placeholderNode: ASTextNode
|
||||
private var tokenNodes: [TokenNode] = []
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let textFieldNode: TextFieldNode
|
||||
private let caretIndicatorNode: CaretIndicatorNode
|
||||
private var selectedTokenId: AnyHashable?
|
||||
|
||||
var textUpdated: ((String) -> Void)?
|
||||
var deleteToken: ((AnyHashable) -> Void)?
|
||||
|
||||
override init() {
|
||||
self.placeholderNode = ASTextNode()
|
||||
@ -48,6 +89,15 @@ final class EditableTokenListNode: ASDisplayNode {
|
||||
self.placeholderNode.maximumNumberOfLines = 1
|
||||
self.placeholderNode.attributedText = NSAttributedString(string: "Whom would you like to message?", font: Font.regular(15.0), textColor: UIColor(0x8e8e92))
|
||||
|
||||
self.textFieldNode = TextFieldNode()
|
||||
self.textFieldNode.textField.font = Font.regular(15.0)
|
||||
|
||||
self.caretIndicatorNode = CaretIndicatorNode()
|
||||
self.caretIndicatorNode.isLayerBacked = true
|
||||
self.caretIndicatorNode.displayWithoutProcessing = true
|
||||
self.caretIndicatorNode.displaysAsynchronously = false
|
||||
self.caretIndicatorNode.image = caretIndicatorImage
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
self.separatorNode.backgroundColor = UIColor(0xc7c6cb)
|
||||
@ -57,7 +107,22 @@ final class EditableTokenListNode: ASDisplayNode {
|
||||
self.backgroundColor = UIColor(0xf7f7f7)
|
||||
self.addSubnode(self.placeholderNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.textFieldNode)
|
||||
self.addSubnode(self.caretIndicatorNode)
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.textFieldNode.textField.delegate = self
|
||||
self.textFieldNode.textField.addTarget(self, action: #selector(textFieldChanged(_:)), for: .editingChanged)
|
||||
self.textFieldNode.textField.didDeleteBackwardWhileEmpty = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let selectedTokenId = strongSelf.selectedTokenId {
|
||||
strongSelf.deleteToken?(selectedTokenId)
|
||||
} else if let tokenNode = strongSelf.tokenNodes.last {
|
||||
strongSelf.selectedTokenId = tokenNode.token.id
|
||||
tokenNode.isSelected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(tokens: [EditableTokenListToken], width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
@ -67,20 +132,30 @@ final class EditableTokenListNode: ASDisplayNode {
|
||||
let tokenNode = tokenNodes[i]
|
||||
if !validTokens.contains(tokenNode.token.id) {
|
||||
self.tokenNodes.remove(at: i)
|
||||
transition.updateAlpha(node: tokenNode, alpha: 0.0, completion: { [weak tokenNode] _ in
|
||||
tokenNode?.removeFromSupernode()
|
||||
})
|
||||
if case .immediate = transition {
|
||||
tokenNode.removeFromSupernode()
|
||||
} else {
|
||||
tokenNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tokenNode] _ in
|
||||
tokenNode?.removeFromSupernode()
|
||||
})
|
||||
tokenNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let selectedTokenId = self.selectedTokenId, !validTokens.contains(selectedTokenId) {
|
||||
self.selectedTokenId = nil
|
||||
}
|
||||
|
||||
let sideInset: CGFloat = 4.0
|
||||
let verticalInset: CGFloat = 7.0
|
||||
let verticalInset: CGFloat = 6.0
|
||||
|
||||
let placeholderSize = self.placeholderNode.measure(CGSize(width: max(1.0, width - sideInset - sideInset), height: CGFloat.greatestFiniteMagnitude))
|
||||
self.placeholderNode.frame = CGRect(origin: CGPoint(x: sideInset + 4.0, y: verticalInset + floor((26.0 - placeholderSize.height) / 2.0)), size: placeholderSize)
|
||||
self.placeholderNode.frame = CGRect(origin: CGPoint(x: sideInset + 4.0, y: verticalInset + floor((28.0 - placeholderSize.height) / 2.0)), size: placeholderSize)
|
||||
|
||||
transition.updateAlpha(node: self.placeholderNode, alpha: tokens.isEmpty ? 1.0 : 0.0)
|
||||
|
||||
var animationDelay = 0.0
|
||||
var currentOffset = CGPoint(x: sideInset, y: verticalInset)
|
||||
for token in tokens {
|
||||
var currentNode: TokenNode?
|
||||
@ -95,7 +170,7 @@ final class EditableTokenListNode: ASDisplayNode {
|
||||
if let currentNode = currentNode {
|
||||
tokenNode = currentNode
|
||||
} else {
|
||||
tokenNode = TokenNode(token: token)
|
||||
tokenNode = TokenNode(token: token, isSelected: self.selectedTokenId != nil && token.id == self.selectedTokenId!)
|
||||
self.tokenNodes.append(tokenNode)
|
||||
self.addSubnode(tokenNode)
|
||||
animateIn = true
|
||||
@ -107,22 +182,92 @@ final class EditableTokenListNode: ASDisplayNode {
|
||||
currentOffset.y += tokenSize.height
|
||||
}
|
||||
let tokenFrame = CGRect(origin: CGPoint(x: currentOffset.x, y: currentOffset.y), size: tokenSize)
|
||||
currentOffset.x += tokenSize.width
|
||||
currentOffset.x += ceil(tokenSize.width)
|
||||
|
||||
if animateIn {
|
||||
tokenNode.frame = tokenFrame
|
||||
tokenNode.alpha = 0.0
|
||||
transition.updateAlpha(node: tokenNode, alpha: 1.0)
|
||||
tokenNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
tokenNode.layer.animateSpring(from: 0.2 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||
} else {
|
||||
transition.updateFrame(node: tokenNode, frame: tokenFrame)
|
||||
if case .immediate = transition {
|
||||
transition.updateFrame(node: tokenNode, frame: tokenFrame)
|
||||
} else {
|
||||
let previousFrame = tokenNode.frame
|
||||
if !previousFrame.origin.y.isEqual(to: tokenFrame.origin.y) && previousFrame.size.width.isEqual(to: tokenFrame.size.width) {
|
||||
let initialStartPosition = CGPoint(x: previousFrame.midX, y: previousFrame.midY)
|
||||
let initialEndPosition = CGPoint(x: -previousFrame.size.width / 2.0, y: previousFrame.midY)
|
||||
let targetStartPosition = CGPoint(x: width + tokenFrame.size.width, y: tokenFrame.midY)
|
||||
let targetEndPosition = CGPoint(x: tokenFrame.midX, y: tokenFrame.midY)
|
||||
tokenNode.frame = tokenFrame
|
||||
|
||||
let initialAnimation = tokenNode.layer.makeAnimation(from: NSValue(cgPoint: initialStartPosition), to: NSValue(cgPoint: initialEndPosition), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: 0.12, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: nil)
|
||||
let targetAnimation = tokenNode.layer.makeAnimation(from: NSValue(cgPoint: targetStartPosition), to: NSValue(cgPoint: targetEndPosition), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.2 + animationDelay, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: nil)
|
||||
tokenNode.layer.animateGroup([initialAnimation, targetAnimation], key: "slide")
|
||||
animationDelay += 0.025
|
||||
} else {
|
||||
if !previousFrame.size.width.isEqual(to: tokenFrame.size.width) {
|
||||
tokenNode.frame = tokenFrame
|
||||
} else {
|
||||
let initialStartPosition = CGPoint(x: previousFrame.midX, y: previousFrame.midY)
|
||||
let targetEndPosition = CGPoint(x: tokenFrame.midX, y: tokenFrame.midY)
|
||||
tokenNode.frame = tokenFrame
|
||||
|
||||
let targetAnimation = tokenNode.layer.makeAnimation(from: NSValue(cgPoint: initialStartPosition), to: NSValue(cgPoint: targetEndPosition), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.2 + animationDelay, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: nil)
|
||||
tokenNode.layer.animateGroup([targetAnimation], key: "slide")
|
||||
animationDelay += 0.025
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nodeHeight = currentOffset.y + 28.0 + verticalInset
|
||||
if width - currentOffset.x < 200.0 {
|
||||
currentOffset.y += 28.0
|
||||
currentOffset.x = sideInset
|
||||
}
|
||||
|
||||
let textNodeFrame = CGRect(origin: CGPoint(x: currentOffset.x + 4.0, y: currentOffset.y + UIScreenPixel), size: CGSize(width: width - currentOffset.x - sideInset - 8.0, height: 28.0))
|
||||
let caretNodeFrame = CGRect(origin: CGPoint(x: textNodeFrame.minX, y: textNodeFrame.minY + 4.0 - UIScreenPixel), size: CGSize(width: 2.0, height: 19.0 + UIScreenPixel))
|
||||
if case .immediate = transition {
|
||||
transition.updateFrame(node: self.textFieldNode, frame: textNodeFrame)
|
||||
transition.updateFrame(node: self.caretIndicatorNode, frame: caretNodeFrame)
|
||||
} else {
|
||||
let previousFrame = self.textFieldNode.frame
|
||||
self.textFieldNode.frame = textNodeFrame
|
||||
self.textFieldNode.layer.animateFrame(from: previousFrame, to: textNodeFrame, duration: 0.2 + animationDelay, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
let previousCaretFrame = self.caretIndicatorNode.frame
|
||||
self.caretIndicatorNode.frame = caretNodeFrame
|
||||
self.caretIndicatorNode.layer.animateFrame(from: previousCaretFrame, to: caretNodeFrame, duration: 0.2 + animationDelay, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
let nodeHeight = currentOffset.y + 29.0 + verticalInset
|
||||
|
||||
let separatorHeight = UIScreenPixel
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nodeHeight - separatorHeight), size: CGSize(width: width, height: separatorHeight)))
|
||||
|
||||
return nodeHeight
|
||||
}
|
||||
|
||||
@objc func textFieldChanged(_ textField: UITextField) {
|
||||
self.placeholderNode.isHidden = textField.text != nil && !textField.text!.isEmpty
|
||||
self.textUpdated?(textField.text ?? "")
|
||||
}
|
||||
|
||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
if self.caretIndicatorNode.supernode == self {
|
||||
self.caretIndicatorNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
if self.caretIndicatorNode.supernode != self {
|
||||
self.addSubnode(self.caretIndicatorNode)
|
||||
}
|
||||
}
|
||||
|
||||
func setText(_ text: String) {
|
||||
self.textFieldNode.textField.text = text
|
||||
self.textFieldChanged(self.textFieldNode.textField)
|
||||
}
|
||||
}
|
||||
|
||||
95
TelegramUI/EmojiUtils.swift
Normal file
95
TelegramUI/EmojiUtils.swift
Normal file
@ -0,0 +1,95 @@
|
||||
import Foundation
|
||||
|
||||
extension UnicodeScalar {
|
||||
var isEmoji: Bool {
|
||||
switch value {
|
||||
case 0x3030, 0x00AE, 0x00A9, // Special Characters
|
||||
0x1D000 ... 0x1F77F, // Emoticons
|
||||
0x2100 ... 0x27BF, // Misc symbols and Dingbats
|
||||
0xFE00 ... 0xFE0F, // Variation Selectors
|
||||
0x1F900 ... 0x1F9FF: // Supplemental Symbols and Pictographs
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var isZeroWidthJoiner: Bool {
|
||||
return value == 8205
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
var glyphCount: Int {
|
||||
|
||||
let richText = NSAttributedString(string: self)
|
||||
let line = CTLineCreateWithAttributedString(richText)
|
||||
return CTLineGetGlyphCount(line)
|
||||
}
|
||||
|
||||
var isSingleEmoji: Bool {
|
||||
|
||||
return glyphCount == 1 && containsEmoji
|
||||
}
|
||||
|
||||
var containsEmoji: Bool {
|
||||
|
||||
return !unicodeScalars.filter { $0.isEmoji }.isEmpty
|
||||
}
|
||||
|
||||
var containsOnlyEmoji: Bool {
|
||||
|
||||
return unicodeScalars.first(where: { !$0.isEmoji && !$0.isZeroWidthJoiner }) == nil
|
||||
}
|
||||
|
||||
// The next tricks are mostly to demonstrate how tricky it can be to determine emoji's
|
||||
// If anyone has suggestions how to improve this, please let me know
|
||||
var emojiString: String {
|
||||
|
||||
return emojiScalars.map { String($0) }.reduce("", +)
|
||||
}
|
||||
|
||||
var emojis: [String] {
|
||||
|
||||
var scalars: [[UnicodeScalar]] = []
|
||||
var currentScalarSet: [UnicodeScalar] = []
|
||||
var previousScalar: UnicodeScalar?
|
||||
|
||||
for scalar in emojiScalars {
|
||||
|
||||
if let prev = previousScalar, !prev.isZeroWidthJoiner && !scalar.isZeroWidthJoiner {
|
||||
|
||||
scalars.append(currentScalarSet)
|
||||
currentScalarSet = []
|
||||
}
|
||||
currentScalarSet.append(scalar)
|
||||
|
||||
previousScalar = scalar
|
||||
}
|
||||
|
||||
scalars.append(currentScalarSet)
|
||||
|
||||
return scalars.map { $0.map{ String($0) } .reduce("", +) }
|
||||
}
|
||||
|
||||
fileprivate var emojiScalars: [UnicodeScalar] {
|
||||
|
||||
var chars: [UnicodeScalar] = []
|
||||
var previous: UnicodeScalar?
|
||||
for cur in unicodeScalars {
|
||||
|
||||
if let previous = previous, previous.isZeroWidthJoiner && cur.isEmoji {
|
||||
chars.append(previous)
|
||||
chars.append(cur)
|
||||
|
||||
} else if cur.isEmoji {
|
||||
chars.append(cur)
|
||||
}
|
||||
|
||||
previous = cur
|
||||
}
|
||||
|
||||
return chars
|
||||
}
|
||||
}
|
||||
@ -30,17 +30,39 @@ private func tagsForMessage(_ message: Message) -> MessageTags? {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func galleryMediaForMedia(media: Media) -> Media? {
|
||||
if let media = media as? TelegramMediaImage {
|
||||
return media
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.mimeType.hasPrefix("audio/") {
|
||||
return nil
|
||||
} else if !file.isVideo && file.mimeType.hasPrefix("video/") {
|
||||
return file
|
||||
} else {
|
||||
return file
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func mediaForMessage(message: Message) -> Media? {
|
||||
for media in message.media {
|
||||
if let media = media as? TelegramMediaImage {
|
||||
return media
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if file.mimeType.hasPrefix("audio/") {
|
||||
return nil
|
||||
} else if !file.isVideo && file.mimeType.hasPrefix("video/") {
|
||||
return file
|
||||
} else {
|
||||
return file
|
||||
if let result = galleryMediaForMedia(media: media) {
|
||||
return result
|
||||
} else if let webpage = media as? TelegramMediaWebpage {
|
||||
switch webpage.content {
|
||||
case let .Loaded(content):
|
||||
if let image = content.image {
|
||||
if let result = galleryMediaForMedia(media: image) {
|
||||
return result
|
||||
}
|
||||
} else if let file = content.file {
|
||||
if let result = galleryMediaForMedia(media: file) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
case .Pending:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -171,7 +193,7 @@ class GalleryController: ViewController {
|
||||
if let strongSelf = self {
|
||||
if let view = view {
|
||||
strongSelf.entries = view.entries
|
||||
loop: for i in 0 ..< strongSelf.entries.count {
|
||||
loop: for i in 0 ..< strongSelf.entries.count {
|
||||
switch strongSelf.entries[i] {
|
||||
case let .MessageEntry(message, _, _) where message.id == messageId:
|
||||
strongSelf.centralEntryIndex = i
|
||||
|
||||
@ -8,6 +8,9 @@ final class GridHoleItem: GridItem {
|
||||
func node(layout: GridNodeLayout) -> GridItemNode {
|
||||
return GridHoleItemNode()
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
class GridHoleItemNode: GridItemNode {
|
||||
|
||||
@ -41,6 +41,16 @@ final class GridMessageItem: GridItem {
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
guard let node = node as? GridMessageItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
if let media = mediaForMessage(self.message) {
|
||||
node.setup(account: self.account, media: media, messageId: self.message.id, controllerInteraction: self.controllerInteraction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class GridMessageItemNode: GridItemNode {
|
||||
|
||||
@ -92,10 +92,10 @@ enum GroupInfoEntry: PeerInfoEntry {
|
||||
if !lhsCachedData.isEqual(to: rhsCachedData) {
|
||||
return false
|
||||
}
|
||||
} else if (rhsCachedData == nil) != (rhsCachedData != nil) {
|
||||
} else if (lhsCachedData != nil) != (rhsCachedData != nil) {
|
||||
return false
|
||||
}
|
||||
if lhsState != lhsState {
|
||||
if lhsState != rhsState {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -116,10 +116,10 @@ enum GroupInfoEntry: PeerInfoEntry {
|
||||
}
|
||||
case let .about(lhsText):
|
||||
switch entry {
|
||||
case let .about(lhsText):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
case .about(lhsText):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case .sharedMedia:
|
||||
switch entry {
|
||||
@ -275,7 +275,7 @@ enum GroupInfoEntry: PeerInfoEntry {
|
||||
func item(account: Account, interaction: PeerInfoControllerInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .info(peer, cachedData, state):
|
||||
return ItemListAvatarAndNameInfoItem(account: account, peer: peer, cachedData: cachedData, state: state, sectionId: self.section, style: .blocks, editingNameUpdated: { editingName in
|
||||
return ItemListAvatarAndNameInfoItem(account: account, peer: peer, presence: nil, cachedData: cachedData, state: state, sectionId: self.section, style: .blocks, editingNameUpdated: { editingName in
|
||||
|
||||
})
|
||||
case .setGroupPhoto:
|
||||
|
||||
@ -143,7 +143,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
||||
var insets = UIEdgeInsets()
|
||||
let insets = UIEdgeInsets()
|
||||
|
||||
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||
|
||||
|
||||
111
TelegramUI/HorizontalStickerGridItem.swift
Normal file
111
TelegramUI/HorizontalStickerGridItem.swift
Normal file
@ -0,0 +1,111 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
|
||||
final class HorizontalStickerGridItem: GridItem {
|
||||
let account: Account
|
||||
let file: TelegramMediaFile
|
||||
let interfaceInteraction: ChatPanelInterfaceInteraction
|
||||
|
||||
let section: GridSection? = nil
|
||||
|
||||
init(account: Account, file: TelegramMediaFile, interfaceInteraction: ChatPanelInterfaceInteraction) {
|
||||
self.account = account
|
||||
self.file = file
|
||||
self.interfaceInteraction = interfaceInteraction
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout) -> GridItemNode {
|
||||
let node = HorizontalStickerGridItemNode()
|
||||
node.setup(account: self.account, file: self.file)
|
||||
node.interfaceInteraction = self.interfaceInteraction
|
||||
return node
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
guard let node = node as? HorizontalStickerGridItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.setup(account: self.account, file: self.file)
|
||||
node.interfaceInteraction = self.interfaceInteraction
|
||||
}
|
||||
}
|
||||
|
||||
final class HorizontalStickerGridItemNode: GridItemNode {
|
||||
private var currentState: (Account, TelegramMediaFile, CGSize)?
|
||||
private let imageNode: TransformImageNode
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||
|
||||
override init() {
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat(M_PI / 2.0), 0.0, 0.0, 1.0)
|
||||
self.addSubnode(self.imageNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
stickerFetchedDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
}
|
||||
|
||||
func setup(account: Account, file: TelegramMediaFile) {
|
||||
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1.id != file.id {
|
||||
if let dimensions = file.dimensions {
|
||||
self.imageNode.setSignal(account: account, signal: chatMessageSticker(account: account, file: file, small: true))
|
||||
self.stickerFetchedDisposable.set(fileInteractiveFetched(account: account, file: file).start())
|
||||
|
||||
self.currentState = (account, file, dimensions)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
//self.updateSelectionState(animated: false)
|
||||
//self.updateHiddenMedia()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let bounds = self.bounds
|
||||
let boundingSize = bounds.insetBy(dx: 2.0, dy: 2.0).size
|
||||
|
||||
if let (_, _, mediaDimensions) = self.currentState {
|
||||
let imageSize = mediaDimensions.aspectFitted(boundingSize)
|
||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: CGSize(width: imageSize.width, height: imageSize.height))
|
||||
self.imageNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: imageSize.width, height: imageSize.height))
|
||||
self.imageNode.position = CGPoint(x: imageFrame.midX, y: imageFrame.midY)
|
||||
}
|
||||
}
|
||||
|
||||
/*func transitionNode(id: MessageId, media: Media) -> ASDisplayNode? {
|
||||
if self.messageId == id {
|
||||
return self.imageNode
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}*/
|
||||
|
||||
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
|
||||
if let interfaceInteraction = self.interfaceInteraction, let (_, item, _) = self.currentState, case .ended = recognizer.state {
|
||||
interfaceInteraction.sendSticker(item)
|
||||
}
|
||||
/*if let controllerInteraction = self.controllerInteraction, let messageId = self.messageId, case .ended = recognizer.state {
|
||||
controllerInteraction.openMessage(messageId)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
205
TelegramUI/HorizontalStickersChatContextPanelNode.swift
Normal file
205
TelegramUI/HorizontalStickersChatContextPanelNode.swift
Normal file
@ -0,0 +1,205 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Display
|
||||
|
||||
private let backgroundCenterImage = generateImage(CGSize(width: 30.0, height: 82.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(UIColor(0xbfbfc4).cgColor)
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
let lineWidth = UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
|
||||
context.translateBy(x: 460.5, y: 364)
|
||||
let _ = try? drawSvgPath(context, path: "M-490.476836,-365 L-394.167708,-365 L-394.167708,-291.918214 C-394.167708,-291.918214 -383.538396,-291.918214 -397.691655,-291.918214 C-402.778486,-291.918214 -424.555168,-291.918214 -434.037301,-291.918214 C-440.297129,-291.918214 -440.780682,-283.5 -445.999879,-283.5 C-450.393041,-283.5 -452.491241,-291.918214 -456.502636,-291.918214 C-465.083339,-291.918214 -476.209155,-291.918214 -483.779021,-291.918214 C-503.033963,-291.918214 -490.476836,-291.918214 -490.476836,-291.918214 L-490.476836,-365 ")
|
||||
context.fillPath()
|
||||
context.translateBy(x: 0.0, y: lineWidth / 2.0)
|
||||
let _ = try? drawSvgPath(context, path: "M-490.476836,-365 L-394.167708,-365 L-394.167708,-291.918214 C-394.167708,-291.918214 -383.538396,-291.918214 -397.691655,-291.918214 C-402.778486,-291.918214 -424.555168,-291.918214 -434.037301,-291.918214 C-440.297129,-291.918214 -440.780682,-283.5 -445.999879,-283.5 C-450.393041,-283.5 -452.491241,-291.918214 -456.502636,-291.918214 C-465.083339,-291.918214 -476.209155,-291.918214 -483.779021,-291.918214 C-503.033963,-291.918214 -490.476836,-291.918214 -490.476836,-291.918214 L-490.476836,-365 ")
|
||||
context.strokePath()
|
||||
context.translateBy(x: -460.5, y: -lineWidth / 2.0 - 364.0)
|
||||
context.move(to: CGPoint(x: 0.0, y: lineWidth / 2.0))
|
||||
context.addLine(to: CGPoint(x: size.width, y: lineWidth / 2.0))
|
||||
context.strokePath()
|
||||
})
|
||||
|
||||
private let backgroundLeftImage = generateImage(CGSize(width: 8.0, height: 16.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(UIColor(0xbfbfc4).cgColor)
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
let lineWidth = UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.height, height: size.height)))
|
||||
context.strokeEllipse(in: CGRect(origin: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0), size: CGSize(width: size.height - lineWidth, height: size.height - lineWidth)))
|
||||
})?.stretchableImage(withLeftCapWidth: 8, topCapHeight: 8)
|
||||
|
||||
private struct StickerEntry: Identifiable, Comparable {
|
||||
let index: Int
|
||||
let file: TelegramMediaFile
|
||||
|
||||
var stableId: MediaId {
|
||||
return self.file.fileId
|
||||
}
|
||||
|
||||
static func ==(lhs: StickerEntry, rhs: StickerEntry) -> Bool {
|
||||
return lhs.index == rhs.index && lhs.stableId == rhs.stableId
|
||||
}
|
||||
|
||||
static func <(lhs: StickerEntry, rhs: StickerEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(account: Account, interfaceInteraction: ChatPanelInterfaceInteraction) -> GridItem {
|
||||
let file = self.file
|
||||
return HorizontalStickerGridItem(account: account, file: file, interfaceInteraction: interfaceInteraction)
|
||||
}
|
||||
}
|
||||
|
||||
private struct StickerEntryTransition {
|
||||
let deletions: [Int]
|
||||
let insertions: [GridNodeInsertItem]
|
||||
let updates: [GridNodeUpdateItem]
|
||||
let updateFirstIndexInSectionOffset: Int?
|
||||
let stationaryItems: GridNodeStationaryItems
|
||||
let scrollToItem: GridNodeScrollToItem?
|
||||
}
|
||||
|
||||
private func preparedGridEntryTransition(account: Account, from fromEntries: [StickerEntry], to toEntries: [StickerEntry], interfaceInteraction: ChatPanelInterfaceInteraction) -> StickerEntryTransition {
|
||||
let stationaryItems: GridNodeStationaryItems = .none
|
||||
let scrollToItem: GridNodeScrollToItem? = nil
|
||||
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices
|
||||
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interfaceInteraction: interfaceInteraction), previousIndex: $0.2) }
|
||||
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, item: $0.1.item(account: account, interfaceInteraction: interfaceInteraction)) }
|
||||
|
||||
return StickerEntryTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: nil, stationaryItems: stationaryItems, scrollToItem: scrollToItem)
|
||||
}
|
||||
|
||||
final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
|
||||
private let backgroundLeftNode: ASImageNode
|
||||
private let backgroundNode: ASImageNode
|
||||
private let backgroundRightNode: ASImageNode
|
||||
private let clippingNode: ASDisplayNode
|
||||
private let gridNode: GridNode
|
||||
|
||||
private var validLayout: (CGSize, ChatPresentationInterfaceState)?
|
||||
private var currentEntries: [StickerEntry] = []
|
||||
private var queuedTransitions: [StickerEntryTransition] = []
|
||||
|
||||
override init(account: Account) {
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.image = backgroundCenterImage
|
||||
|
||||
self.backgroundLeftNode = ASImageNode()
|
||||
self.backgroundLeftNode.displayWithoutProcessing = true
|
||||
self.backgroundLeftNode.displaysAsynchronously = false
|
||||
self.backgroundLeftNode.image = backgroundLeftImage
|
||||
|
||||
self.backgroundRightNode = ASImageNode()
|
||||
self.backgroundRightNode.displayWithoutProcessing = true
|
||||
self.backgroundRightNode.displaysAsynchronously = false
|
||||
self.backgroundRightNode.image = backgroundLeftImage
|
||||
self.backgroundRightNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
|
||||
self.clippingNode = ASDisplayNode()
|
||||
self.clippingNode.clipsToBounds = true
|
||||
self.gridNode = GridNode()
|
||||
self.gridNode.transform = CATransform3DMakeRotation(-CGFloat(M_PI / 2.0), 0.0, 0.0, 1.0)
|
||||
self.gridNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
super.init(account: account)
|
||||
|
||||
self.isOpaque = false
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.backgroundLeftNode)
|
||||
self.addSubnode(self.backgroundRightNode)
|
||||
|
||||
self.addSubnode(self.clippingNode)
|
||||
self.clippingNode.addSubnode(self.gridNode)
|
||||
}
|
||||
|
||||
func updateResults(_ results: [TelegramMediaFile]) {
|
||||
let previousEntries = self.currentEntries
|
||||
var entries: [StickerEntry] = []
|
||||
for i in 0 ..< results.count {
|
||||
entries.append(StickerEntry(index: i, file: results[i]))
|
||||
}
|
||||
self.currentEntries = entries
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(size: validLayout.0, transition: .immediate, interfaceState: validLayout.1)
|
||||
}
|
||||
|
||||
let transition = preparedGridEntryTransition(account: self.account, from: previousEntries, to: entries, interfaceInteraction: self.interfaceInteraction!)
|
||||
self.enqueueTransition(transition)
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: StickerEntryTransition) {
|
||||
self.queuedTransitions.append(transition)
|
||||
if self.validLayout != nil {
|
||||
self.dequeueTransitions()
|
||||
}
|
||||
}
|
||||
|
||||
private func dequeueTransitions() {
|
||||
while !self.queuedTransitions.isEmpty {
|
||||
let transition = self.queuedTransitions.removeFirst()
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
||||
let sideInsets: CGFloat = 10.0
|
||||
let contentWidth = min(size.width - sideInsets - sideInsets, max(24.0, CGFloat(self.currentEntries.count) * 66.0 + 6.0))
|
||||
|
||||
var leftInset: CGFloat = 40.0
|
||||
var leftOffset: CGFloat = 0.0
|
||||
if sideInsets + floor(contentWidth / 2.0) < sideInsets + leftInset + 15.0 {
|
||||
let updatedLeftInset = sideInsets + floor(contentWidth / 2.0) - 15.0 - sideInsets
|
||||
leftOffset = leftInset - updatedLeftInset
|
||||
leftInset = updatedLeftInset
|
||||
}
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: sideInsets + leftOffset, y: size.height - 82.0 + 4.0), size: CGSize(width: contentWidth, height: 82.0))
|
||||
let backgroundLeftFrame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: leftInset, height: backgroundFrame.size.height - 10.0 + UIScreenPixel))
|
||||
let backgroundCenterFrame = CGRect(origin: CGPoint(x: backgroundLeftFrame.maxX, y: backgroundFrame.minY), size: CGSize(width: 30.0, height: 82.0))
|
||||
let backgroundRightFrame = CGRect(origin: CGPoint(x: backgroundCenterFrame.maxX, y: backgroundFrame.minY), size: CGSize(width: max(0.0, backgroundFrame.minX + backgroundFrame.size.width - backgroundCenterFrame.maxX), height: backgroundFrame.size.height - 10.0 + UIScreenPixel))
|
||||
transition.updateFrame(node: self.backgroundLeftNode, frame: backgroundLeftFrame)
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundCenterFrame)
|
||||
transition.updateFrame(node: self.backgroundRightNode, frame: backgroundRightFrame)
|
||||
|
||||
let gridFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY + 4.0), size: CGSize(width: backgroundFrame.size.width, height: 66.0))
|
||||
self.clippingNode.frame = gridFrame
|
||||
self.gridNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: gridFrame.size.height, height: gridFrame.size.width))
|
||||
|
||||
let gridBounds = self.gridNode.bounds
|
||||
self.gridNode.bounds = CGRect(x: gridBounds.minX, y: gridBounds.minY, width: gridFrame.size.height, height: gridFrame.size.width)
|
||||
self.gridNode.position = CGPoint(x: gridFrame.size.width / 2.0, y: gridFrame.size.height / 2.0)
|
||||
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: gridFrame.size.height, height: gridFrame.size.width), insets: UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0), preloadSize: 100.0, itemSize: CGSize(width: 66.0, height: 66.0)), transition: .immediate), stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||
|
||||
let dequeue = self.validLayout == nil
|
||||
self.validLayout = (size, interfaceState)
|
||||
|
||||
if dequeue {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.dequeueTransitions()
|
||||
}
|
||||
}
|
||||
|
||||
override func animateOut(completion: @escaping () -> Void) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
@ -111,6 +111,7 @@ public class ImageNode: ASDisplayNode {
|
||||
}
|
||||
if !reportedHasImage {
|
||||
if let hasImage = strongSelf.hasImage {
|
||||
reportedHasImage = true
|
||||
hasImage.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,15 +63,17 @@ struct ItemListAvatarAndNameInfoItemState: Equatable {
|
||||
class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem {
|
||||
let account: Account
|
||||
let peer: Peer?
|
||||
let presence: PeerPresence?
|
||||
let cachedData: CachedPeerData?
|
||||
let state: ItemListAvatarAndNameInfoItemState
|
||||
let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let editingNameUpdated: (ItemListAvatarAndNameInfoItemName) -> Void
|
||||
|
||||
init(account: Account, peer: Peer?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void) {
|
||||
init(account: Account, peer: Peer?, presence: PeerPresence?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void) {
|
||||
self.account = account
|
||||
self.peer = peer
|
||||
self.presence = presence
|
||||
self.cachedData = cachedData
|
||||
self.state = state
|
||||
self.sectionId = sectionId
|
||||
@ -132,6 +134,8 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode {
|
||||
private var inputSecondField: UITextField?
|
||||
|
||||
private var item: ItemListAvatarAndNameInfoItem?
|
||||
private var layoutWidthAndNeighbors: (width: CGFloat, neighbors: ItemListNeighbors)?
|
||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
@ -163,6 +167,13 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.nameNode)
|
||||
self.addSubnode(self.statusNode)
|
||||
|
||||
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||
if let strongSelf = self, let item = strongSelf.item, let layoutWidthAndNeighbors = strongSelf.layoutWidthAndNeighbors {
|
||||
let (_, apply) = strongSelf.asyncLayout()(item, layoutWidthAndNeighbors.0, layoutWidthAndNeighbors.1)
|
||||
apply(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ItemListAvatarAndNameInfoItem, _ width: CGFloat, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
@ -183,9 +194,15 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode {
|
||||
|
||||
let statusText: String
|
||||
let statusColor: UIColor
|
||||
if let user = item.peer as? TelegramUser {
|
||||
statusText = "online"
|
||||
statusColor = UIColor(0x007ee5)
|
||||
if let presence = item.presence as? TelegramUserPresence {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
let (string, activity) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp))
|
||||
statusText = string
|
||||
if activity {
|
||||
statusColor = UIColor(0x007ee5)
|
||||
} else {
|
||||
statusColor = UIColor(0xb3b3b3)
|
||||
}
|
||||
} else if let channel = item.peer as? TelegramChannel {
|
||||
if let cachedChannelData = item.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
||||
statusText = "\(memberCount) members"
|
||||
@ -236,6 +253,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode {
|
||||
return (layout, { [weak self] animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutWidthAndNeighbors = (width, neighbors)
|
||||
|
||||
let avatarOriginY: CGFloat
|
||||
switch item.style {
|
||||
@ -342,7 +360,6 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode {
|
||||
if strongSelf.inputSecondField == nil {
|
||||
let inputSecondField = TextFieldNodeView()
|
||||
inputSecondField.typingAttributes = [NSFontAttributeName: Font.regular(17.0)]
|
||||
//inputSecondField.backgroundColor = UIColor.lightGray
|
||||
inputSecondField.attributedPlaceholder = NSAttributedString(string: "Last Name", font: Font.regular(17.0), textColor: UIColor(0xc8c8ce))
|
||||
inputSecondField.attributedText = NSAttributedString(string: lastName, font: Font.regular(17.0), textColor: UIColor.black)
|
||||
strongSelf.inputSecondField = inputSecondField
|
||||
@ -445,6 +462,9 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode {
|
||||
strongSelf.nameNode.alpha = 1.0
|
||||
}
|
||||
}
|
||||
if let presence = item.presence as? TelegramUserPresence {
|
||||
strongSelf.peerPresenceManager?.reset(presence: presence)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -3,13 +3,14 @@ import UIKit
|
||||
import TelegramLegacyComponents
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
||||
func legacyAttachmentMenu(parentController: LegacyController, presentOverlayController: @escaping (UIViewController) -> (() -> Void), openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, sendMessagesWithSignals: @escaping ([Any]?) -> Void) -> TGMenuSheetController {
|
||||
func legacyAttachmentMenu(parentController: LegacyController, recentlyUsedInlineBots: [Peer], presentOverlayController: @escaping (UIViewController) -> (() -> Void), openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, sendMessagesWithSignals: @escaping ([Any]?) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController {
|
||||
let controller = TGMenuSheetController()
|
||||
controller.applicationInterface = parentController.applicationInterface
|
||||
controller.dismissesByOutsideTap = true
|
||||
controller.hasSwipeGesture = true
|
||||
//controller.maxHeight = 445.0 - TGMenuSheetButtonItemViewHeight
|
||||
controller.maxHeight = 445.0 - TGMenuSheetButtonItemViewHeight
|
||||
|
||||
var itemViews: [Any] = []
|
||||
|
||||
@ -36,228 +37,53 @@ func legacyAttachmentMenu(parentController: LegacyController, presentOverlayCont
|
||||
let galleryItem = TGMenuSheetButtonItemView(title: "Photo or Video", type: TGMenuSheetButtonTypeDefault, action: { [weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
openGallery()
|
||||
})
|
||||
})!
|
||||
itemViews.append(galleryItem)
|
||||
|
||||
let fileItem = TGMenuSheetButtonItemView(title: "File", type: TGMenuSheetButtonTypeDefault, action: {[weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
openFileGallery()
|
||||
})
|
||||
})!
|
||||
itemViews.append(fileItem)
|
||||
|
||||
let locationItem = TGMenuSheetButtonItemView(title: "Location", type: TGMenuSheetButtonTypeDefault, action: {
|
||||
})
|
||||
let locationItem = TGMenuSheetButtonItemView(title: "Location", type: TGMenuSheetButtonTypeDefault, action: { [weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
openMap()
|
||||
})!
|
||||
itemViews.append(locationItem)
|
||||
|
||||
let contactItem = TGMenuSheetButtonItemView(title: "Contact", type: TGMenuSheetButtonTypeDefault, action: {
|
||||
})
|
||||
let contactItem = TGMenuSheetButtonItemView(title: "Contact", type: TGMenuSheetButtonTypeDefault, action: { [weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
openContacts()
|
||||
})!
|
||||
itemViews.append(contactItem)
|
||||
|
||||
carouselItem.underlyingViews = [galleryItem, fileItem]
|
||||
|
||||
for i in 0 ..< min(20, recentlyUsedInlineBots.count) {
|
||||
let peer = recentlyUsedInlineBots[i]
|
||||
let addressName = peer.addressName
|
||||
if let addressName = addressName {
|
||||
let botItem = TGMenuSheetButtonItemView(title: "@" + addressName, type: TGMenuSheetButtonTypeDefault, action: { [weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
|
||||
selectRecentlyUsedInlineBot(peer)
|
||||
})!
|
||||
botItem.overflow = true
|
||||
itemViews.append(botItem)
|
||||
}
|
||||
}
|
||||
|
||||
carouselItem.remainingHeight = TGMenuSheetButtonItemViewHeight * CGFloat(itemViews.count - 1)
|
||||
|
||||
let cancelItem = TGMenuSheetButtonItemView(title: "Cancel", type: TGMenuSheetButtonTypeCancel, action: { [weak controller] in
|
||||
controller?.dismiss(animated: true)
|
||||
})
|
||||
})!
|
||||
itemViews.append(cancelItem)
|
||||
|
||||
controller.setItemViews(itemViews)
|
||||
|
||||
return controller
|
||||
|
||||
/*
|
||||
carouselItem.condensed = !hasContactItem;
|
||||
carouselItem.parentController = self;
|
||||
carouselItem.allowCaptions = [_companion allowCaptionedMedia];
|
||||
carouselItem.inhibitDocumentCaptions = [_companion encryptUploads];
|
||||
|
||||
__weak TGAttachmentCarouselItemView *weakCarouselItem = carouselItem;
|
||||
carouselItem.suggestionContext = [self _suggestionContext];
|
||||
carouselItem.cameraPressed = ^(TGAttachmentCameraView *cameraView)
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongSelf _displayCameraWithView:cameraView menuController:strongController];
|
||||
};
|
||||
carouselItem.sendPressed = ^(TGMediaAsset *currentItem, bool asFiles)
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
__strong TGAttachmentCarouselItemView *strongCarouselItem = weakCarouselItem;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
|
||||
TGMediaAssetsControllerIntent intent = asFiles ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent;
|
||||
[strongSelf _asyncProcessMediaAssetSignals:[TGMediaAssetsController resultSignalsForSelectionContext:strongCarouselItem.selectionContext editingContext:strongCarouselItem.editingContext intent:intent currentItem:currentItem storeAssets:[strongSelf->_companion controllerShouldStoreCapturedAssets] useMediaCache:[strongSelf->_companion controllerShouldCacheServerAssets] descriptionGenerator:^id(id result, NSString *caption, NSString *hash)
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return nil;
|
||||
|
||||
return [strongSelf _descriptionForItem:result caption:caption hash:hash];
|
||||
}]];
|
||||
};
|
||||
carouselItem.editorOpened = ^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
[strongSelf _updateCanReadHistory:TGModernConversationActivityChangeInactive];
|
||||
};
|
||||
carouselItem.editorClosed = ^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
[strongSelf _updateCanReadHistory:TGModernConversationActivityChangeActive];
|
||||
};
|
||||
[itemViews addObject:carouselItem];
|
||||
|
||||
TGMenuSheetButtonItemView *galleryItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"AttachmentMenu.PhotoOrVideo") type:TGMenuSheetButtonTypeDefault action:^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
[strongSelf _displayMediaPicker:false fromFileMenu:false];
|
||||
}];
|
||||
galleryItem.longPressAction = ^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
[strongSelf _displayWebImagePicker];
|
||||
};
|
||||
[itemViews addObject:galleryItem];
|
||||
|
||||
TGMenuSheetButtonItemView *fileItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"AttachmentMenu.File") type:TGMenuSheetButtonTypeDefault action:^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongSelf _displayFileMenuWithController:strongController];
|
||||
}];
|
||||
[itemViews addObject:fileItem];
|
||||
|
||||
carouselItem.underlyingViews = @[ galleryItem, fileItem ];
|
||||
|
||||
TGMenuSheetButtonItemView *locationItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.Location") type:TGMenuSheetButtonTypeDefault action:^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
[strongSelf _displayLocationPicker];
|
||||
}];
|
||||
[itemViews addObject:locationItem];
|
||||
|
||||
if (hasContactItem)
|
||||
{
|
||||
TGMenuSheetButtonItemView *contactItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Conversation.Contact") type:TGMenuSheetButtonTypeDefault action:^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
[strongSelf _displayContactPicker];
|
||||
}];
|
||||
[itemViews addObject:contactItem];
|
||||
}
|
||||
|
||||
if (!TGIsPad()) {
|
||||
NSArray<TGUser *> *inlineBots = [TGDatabaseInstance() _syncCachedRecentInlineBots];
|
||||
NSUInteger counter = 0;
|
||||
for (TGUser *user in inlineBots) {
|
||||
if (user.userName.length == 0)
|
||||
continue;
|
||||
|
||||
TGMenuSheetButtonItemView *botItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:[@"@" stringByAppendingString:user.userName] type:TGMenuSheetButtonTypeDefault action:^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
strongSelf->_inputTextPanel.inputField.userInteractionEnabled = true;
|
||||
[strongSelf->_inputTextPanel.inputField setText:[NSString stringWithFormat:@"@%@ ", user.userName]];
|
||||
[strongSelf openKeyboard];
|
||||
}];
|
||||
botItem.overflow = true;
|
||||
[itemViews addObject:botItem];
|
||||
counter++;
|
||||
if (counter == 20) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
carouselItem.remainingHeight = TGMenuSheetButtonItemViewHeight * (itemViews.count - 1);
|
||||
|
||||
TGMenuSheetButtonItemView *cancelItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel action:^
|
||||
{
|
||||
__strong TGModernConversationController *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
}];
|
||||
[itemViews addObject:cancelItem];
|
||||
|
||||
[controller setItemViews:itemViews];
|
||||
|
||||
[self.view endEditing:true];
|
||||
[controller presentInViewController:self sourceView:_inputTextPanel.attachButton animated:true];*/
|
||||
}
|
||||
|
||||
func legacyFileAttachmentMenu(menuSheetController: TGMenuSheetController) {
|
||||
|
||||
@ -211,6 +211,7 @@ public class PeerMediaCollectionController: ViewController {
|
||||
}, beginAudioRecording: {
|
||||
}, finishAudioRecording: { _ in
|
||||
}, setupMessageAutoremoveTimeout: {
|
||||
}, sendSticker: { _ in
|
||||
}, statuses: nil)
|
||||
|
||||
self.updateInterfaceState(animated: false, { return $0 })
|
||||
|
||||
@ -61,7 +61,7 @@ public final class PeerSelectionController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
self.peerSelectionNode.requestOpenMessageFromSearch = { [weak self] peer, messageId in
|
||||
/*self.peerSelectionNode.requestOpenMessageFromSearch = { [weak self] peer, messageId in
|
||||
if let strongSelf = self {
|
||||
let storedPeer = strongSelf.account.postbox.modify { modifier -> Void in
|
||||
if modifier.getPeer(peer.id) == nil {
|
||||
@ -76,7 +76,7 @@ public final class PeerSelectionController: ViewController {
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
self.peerSelectionNode.requestOpenPeerFromSearch = { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
@ -88,11 +88,10 @@ public final class PeerSelectionController: ViewController {
|
||||
}
|
||||
}
|
||||
strongSelf.openMessageFromSearchDisposable.set((storedPeer |> deliverOnMainQueue).start(completed: { [weak strongSelf] in
|
||||
if let strongSelf = strongSelf {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peer.id))
|
||||
if let strongSelf = strongSelf, let peerSelected = strongSelf.peerSelected {
|
||||
peerSelected(peer.id)
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
192
TelegramUI/PhoneInputNode.swift
Normal file
192
TelegramUI/PhoneInputNode.swift
Normal file
@ -0,0 +1,192 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
|
||||
private func removeDuplicatedPlus(_ text: String?) -> String {
|
||||
var result = ""
|
||||
if let text = text {
|
||||
for c in text.characters {
|
||||
if c == "+" {
|
||||
if result.isEmpty {
|
||||
result += String(c)
|
||||
}
|
||||
} else {
|
||||
result += String(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func removePlus(_ text: String?) -> String {
|
||||
var result = ""
|
||||
if let text = text {
|
||||
for c in text.characters {
|
||||
if c != "+" {
|
||||
result += String(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func cleanPhoneNumber(_ text: String?) -> String {
|
||||
var cleanNumber = ""
|
||||
if let text = text {
|
||||
for c in text.characters {
|
||||
if c == "+" {
|
||||
if cleanNumber.isEmpty {
|
||||
cleanNumber += String(c)
|
||||
}
|
||||
} else if c >= "0" && c <= "9" {
|
||||
cleanNumber += String(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cleanNumber
|
||||
}
|
||||
|
||||
private func cleanPrefix(_ text: String) -> String {
|
||||
var result = ""
|
||||
var checked = false
|
||||
for c in text.characters {
|
||||
if c != " " {
|
||||
checked = true
|
||||
}
|
||||
if checked {
|
||||
result += String(c)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func cleanSuffix(_ text: String) -> String {
|
||||
var result = ""
|
||||
var checked = false
|
||||
for c in text.characters.reversed() {
|
||||
if c != " " {
|
||||
checked = true
|
||||
}
|
||||
if checked {
|
||||
result = String(c) + result
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate {
|
||||
let countryCodeField: TextFieldNode
|
||||
let numberField: TextFieldNode
|
||||
|
||||
var previousCountryCodeText = "+"
|
||||
var previousNumberText = ""
|
||||
var enableEditing: Bool = true
|
||||
|
||||
var number: String {
|
||||
get {
|
||||
return cleanPhoneNumber((self.countryCodeField.textField.text ?? "") + (self.numberField.textField.text ?? ""))
|
||||
} set(value) {
|
||||
self.updateNumber(value)
|
||||
}
|
||||
}
|
||||
|
||||
var codeAndNumber: (Int32?, String) {
|
||||
get {
|
||||
var code: Int32?
|
||||
if let text = self.countryCodeField.textField.text, let number = Int(removePlus(text)) {
|
||||
code = Int32(number)
|
||||
}
|
||||
return (code, cleanPhoneNumber(self.numberField.textField.text))
|
||||
} set(value) {
|
||||
self.updateNumber("+" + (value.0 == nil ? "" : "\(value.0!)") + value.1)
|
||||
}
|
||||
}
|
||||
|
||||
var countryCodeUpdated: ((String) -> Void)?
|
||||
|
||||
private let phoneFormatter = InteractivePhoneFormatter()
|
||||
|
||||
override init() {
|
||||
self.countryCodeField = TextFieldNode()
|
||||
self.countryCodeField.textField.font = Font.regular(20.0)
|
||||
self.countryCodeField.textField.textAlignment = .center
|
||||
self.countryCodeField.textField.keyboardType = .numberPad
|
||||
self.countryCodeField.textField.returnKeyType = .next
|
||||
|
||||
self.numberField = TextFieldNode()
|
||||
self.numberField.textField.font = Font.regular(20.0)
|
||||
self.numberField.textField.keyboardType = .numberPad
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.countryCodeField)
|
||||
self.addSubnode(self.numberField)
|
||||
|
||||
self.numberField.textField.didDeleteBackwardWhileEmpty = { [weak self] in
|
||||
self?.countryCodeField.textField.becomeFirstResponder()
|
||||
}
|
||||
self.countryCodeField.textField.addTarget(self, action: #selector(self.countryCodeTextChanged(_:)), for: .editingChanged)
|
||||
self.numberField.textField.addTarget(self, action: #selector(self.numberTextChanged(_:)), for: .editingChanged)
|
||||
self.countryCodeField.textField.delegate = self
|
||||
self.numberField.textField.delegate = self
|
||||
}
|
||||
|
||||
@objc func countryCodeTextChanged(_ textField: UITextField) {
|
||||
self.updateNumberFromTextFields()
|
||||
}
|
||||
|
||||
@objc func numberTextChanged(_ textField: UITextField) {
|
||||
self.updateNumberFromTextFields()
|
||||
}
|
||||
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
return self.enableEditing
|
||||
}
|
||||
|
||||
private func updateNumberFromTextFields() {
|
||||
let inputText = removeDuplicatedPlus(cleanPhoneNumber(self.countryCodeField.textField.text) + cleanPhoneNumber(self.numberField.textField.text))
|
||||
self.updateNumber(inputText)
|
||||
}
|
||||
|
||||
private func updateNumber(_ inputText: String) {
|
||||
let (regionPrefix, text) = self.phoneFormatter.updateText(inputText)
|
||||
var realRegionPrefix: String
|
||||
let numberText: String
|
||||
if let regionPrefix = regionPrefix, !regionPrefix.isEmpty {
|
||||
realRegionPrefix = cleanSuffix(regionPrefix)
|
||||
if !realRegionPrefix.hasPrefix("+") {
|
||||
realRegionPrefix = "+" + realRegionPrefix
|
||||
}
|
||||
numberText = cleanPrefix(text.substring(from: realRegionPrefix.endIndex))
|
||||
} else {
|
||||
realRegionPrefix = text
|
||||
if !realRegionPrefix.hasPrefix("+") {
|
||||
realRegionPrefix = "+" + realRegionPrefix
|
||||
}
|
||||
numberText = ""
|
||||
}
|
||||
|
||||
var focusOnNumber = false
|
||||
if realRegionPrefix != self.countryCodeField.textField.text {
|
||||
self.countryCodeField.textField.text = realRegionPrefix
|
||||
}
|
||||
if self.previousCountryCodeText != realRegionPrefix {
|
||||
self.previousCountryCodeText = realRegionPrefix
|
||||
self.countryCodeUpdated?(removePlus(realRegionPrefix).trimmingCharacters(in: CharacterSet.whitespaces))
|
||||
}
|
||||
|
||||
if numberText != self.numberField.textField.text {
|
||||
self.numberField.textField.text = numberText
|
||||
}
|
||||
|
||||
if self.previousNumberText.isEmpty && !numberText.isEmpty {
|
||||
focusOnNumber = true
|
||||
}
|
||||
self.previousNumberText = numberText
|
||||
|
||||
if focusOnNumber && !self.numberField.textField.isFirstResponder {
|
||||
self.numberField.textField.becomeFirstResponder()
|
||||
}
|
||||
}
|
||||
}
|
||||
66
TelegramUI/ProgressNavigationButtonNode.swift
Normal file
66
TelegramUI/ProgressNavigationButtonNode.swift
Normal file
@ -0,0 +1,66 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private let indicatorImage = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor(0x007ee5).cgColor)
|
||||
let _ = try? drawSvgPath(context, path: "M11,22 C17.0751322,22 22,17.0751322 22,11 C22,4.92486775 17.0751322,0 11,0 C4.92486775,0 0,4.92486775 0,11 C0,12.4564221 0.28362493,13.8747731 0.827833595,15.1935223 C1.00609922,15.6255031 1.50080164,15.8311798 1.93278238,15.6529142 C2.36476311,15.4746485 2.57043984,14.9799461 2.39217421,14.5479654 C1.93209084,13.4330721 1.69230769,12.233965 1.69230769,11 C1.69230769,5.85950348 5.85950348,1.69230769 11,1.69230769 C16.1404965,1.69230769 20.3076923,5.85950348 20.3076923,11 C20.3076923,16.1404965 16.1404965,20.3076923 11,20.3076923 C10.5326821,20.3076923 10.1538462,20.6865283 10.1538462,21.1538462 C10.1538462,21.621164 10.5326821,22 11,22 Z ")
|
||||
/*
|
||||
|
||||
|
||||
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<path d="M11,22 C17.0751322,22 22,17.0751322 22,11 C22,4.92486775 17.0751322,0 11,0 C4.92486775,0 0,4.92486775 0,11 C0,12.4564221 0.28362493,13.8747731 0.827833595,15.1935223 C1.00609922,15.6255031 1.50080164,15.8311798 1.93278238,15.6529142 C2.36476311,15.4746485 2.57043984,14.9799461 2.39217421,14.5479654 C1.93209084,13.4330721 1.69230769,12.233965 1.69230769,11 C1.69230769,5.85950348 5.85950348,1.69230769 11,1.69230769 C16.1404965,1.69230769 20.3076923,5.85950348 20.3076923,11 C20.3076923,16.1404965 16.1404965,20.3076923 11,20.3076923 C10.5326821,20.3076923 10.1538462,20.6865283 10.1538462,21.1538462 C10.1538462,21.621164 10.5326821,22 11,22 Z" id="Oval" stroke="none" fill="#159BEC" fill-rule="nonzero"></path>
|
||||
</svg>
|
||||
*/
|
||||
|
||||
|
||||
})
|
||||
|
||||
final class ProgressNavigationButtonNode: ASDisplayNode {
|
||||
private var indicatorNode: ASImageNode
|
||||
|
||||
override init() {
|
||||
self.indicatorNode = ASImageNode()
|
||||
self.indicatorNode.isLayerBacked = true
|
||||
self.indicatorNode.displayWithoutProcessing = true
|
||||
self.indicatorNode.displaysAsynchronously = false
|
||||
self.indicatorNode.image = indicatorImage
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
|
||||
self.addSubnode(self.indicatorNode)
|
||||
}
|
||||
|
||||
override func willEnterHierarchy() {
|
||||
super.willEnterHierarchy()
|
||||
|
||||
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
|
||||
basicAnimation.duration = 0.5
|
||||
basicAnimation.fromValue = NSNumber(value: Float(0.0))
|
||||
basicAnimation.toValue = NSNumber(value: Float(M_PI * 2.0))
|
||||
basicAnimation.repeatCount = Float.infinity
|
||||
basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
|
||||
|
||||
self.indicatorNode.layer.add(basicAnimation, forKey: "progressRotation")
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: 26.0, height: 22.0)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let size = self.bounds.size
|
||||
|
||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||
self.indicatorNode.frame = CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: floor((size.height - indicatorSize.height) / 2.0)), size: indicatorSize)
|
||||
}
|
||||
}
|
||||
@ -4,18 +4,16 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private func generateBackground() -> UIImage? {
|
||||
private func generateBackground(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
let diameter: CGFloat = 8.0
|
||||
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor(0xededed).cgColor)
|
||||
context.setFillColor(foregroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
}, opaque: true)?.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
|
||||
}
|
||||
|
||||
private let searchBarBackground = generateBackground()
|
||||
|
||||
private class SearchBarPlaceholderNodeLayer: CALayer {
|
||||
}
|
||||
|
||||
@ -29,6 +27,7 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
var activate: (() -> Void)?
|
||||
|
||||
let backgroundNode: ASImageNode
|
||||
var foregroundColor: UIColor
|
||||
let labelNode: TextNode
|
||||
|
||||
var placeholderString: NSAttributedString?
|
||||
@ -38,12 +37,15 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
self.backgroundNode.isLayerBacked = false
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.image = searchBarBackground
|
||||
|
||||
self.foregroundColor = UIColor(0xededed)
|
||||
|
||||
self.backgroundNode.image = generateBackground(backgroundColor: UIColor.white, foregroundColor: self.foregroundColor)
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isOpaque = true
|
||||
self.labelNode.isLayerBacked = true
|
||||
self.labelNode.backgroundColor = UIColor(0xededed)
|
||||
self.labelNode.backgroundColor = self.foregroundColor
|
||||
|
||||
super.init()
|
||||
/*super.init(viewBlock: {
|
||||
@ -62,16 +64,28 @@ class SearchBarPlaceholderNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
self.backgroundNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backgroundTap(_:))))
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ constrainedSize: CGSize) -> (() -> Void) {
|
||||
func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ constrainedSize: CGSize, _ foregoundColor: UIColor) -> (() -> Void) {
|
||||
let labelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let currentForegroundColor = self.foregroundColor
|
||||
|
||||
return { placeholderString, constrainedSize in
|
||||
let (labelLayoutResult, labelApply) = labelLayout(placeholderString, UIColor(0xededed), 1, .end, constrainedSize, nil)
|
||||
return { placeholderString, constrainedSize, foregroundColor in
|
||||
let (labelLayoutResult, labelApply) = labelLayout(placeholderString, foregroundColor, 1, .end, constrainedSize, nil)
|
||||
|
||||
var updatedBackgroundImage: UIImage?
|
||||
if !currentForegroundColor.isEqual(foregroundColor) {
|
||||
updatedBackgroundImage = generateBackground(backgroundColor: UIColor.white, foregroundColor: foregroundColor)
|
||||
}
|
||||
|
||||
return { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let _ = labelApply()
|
||||
|
||||
strongSelf.foregroundColor = foregroundColor
|
||||
if let updatedBackgroundImage = updatedBackgroundImage {
|
||||
strongSelf.backgroundNode.image = updatedBackgroundImage
|
||||
strongSelf.labelNode.backgroundColor = foregroundColor
|
||||
}
|
||||
|
||||
strongSelf.placeholderString = placeholderString
|
||||
|
||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - labelLayoutResult.size.width) / 2.0), y: floor((28.0 - labelLayoutResult.size.height) / 2.0) + 2.0), size: labelLayoutResult.size)
|
||||
|
||||
@ -8,6 +8,7 @@ private struct SettingsItemArguments {
|
||||
let account: Account
|
||||
|
||||
let pushController: (ViewController) -> Void
|
||||
let presentController: (ViewController) -> Void
|
||||
let updateEditingName: (ItemListAvatarAndNameInfoItemName) -> Void
|
||||
let saveEditingState: () -> Void
|
||||
}
|
||||
@ -171,15 +172,22 @@ private enum SettingsEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: SettingsItemArguments) -> ListViewItem {
|
||||
switch self {
|
||||
case let .userInfo(peer, cachedData, state):
|
||||
return ItemListAvatarAndNameInfoItem(account: arguments.account, peer: peer, cachedData: cachedData, state: state, sectionId: ItemListSectionId(self.section), style: .blocks, editingNameUpdated: { editingName in
|
||||
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 ans Sounds", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
return ItemListDisclosureItem(title: "Notifications and Sounds", label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.pushController(notificationsAndSoundsController(account: arguments.account))
|
||||
})
|
||||
case .privacyAndSecurity:
|
||||
@ -290,6 +298,7 @@ public func settingsController(account: Account) -> ViewController {
|
||||
}
|
||||
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
@ -298,6 +307,8 @@ public func settingsController(account: Account) -> ViewController {
|
||||
|
||||
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 {
|
||||
@ -362,5 +373,8 @@ public func settingsController(account: Account) -> ViewController {
|
||||
pushControllerImpl = { [weak controller] value in
|
||||
(controller?.navigationController as? NavigationController)?.pushViewController(value)
|
||||
}
|
||||
presentControllerImpl = { [weak controller] value in
|
||||
controller?.present(value, in: .window)
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
||||
35
TelegramUI/ShakeAnimation.swift
Normal file
35
TelegramUI/ShakeAnimation.swift
Normal file
@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
extension CALayer {
|
||||
func addShakeAnimation() {
|
||||
let k = Float(UIView.animationDurationFactor())
|
||||
var speed: Float = 1.0
|
||||
if k != 0 && k != 1 {
|
||||
speed = Float(1.0) / k
|
||||
}
|
||||
|
||||
let duration = 0.3
|
||||
let amplitude: CGFloat = 3.0
|
||||
|
||||
let animation = CAKeyframeAnimation(keyPath: "position.x")
|
||||
let values: [CGFloat] = [0.0, amplitude, -amplitude, amplitude, -amplitude, 0.0]
|
||||
animation.values = values.map { ($0 as NSNumber) as AnyObject }
|
||||
var keyTimes: [NSNumber] = []
|
||||
for i in 0 ..< values.count {
|
||||
if i == 0 {
|
||||
keyTimes.append(0.0)
|
||||
} else if i == values.count - 1 {
|
||||
keyTimes.append(1.0)
|
||||
} else {
|
||||
keyTimes.append((Double(i) / Double(values.count - 1)) as NSNumber)
|
||||
}
|
||||
}
|
||||
animation.keyTimes = keyTimes
|
||||
animation.speed = speed
|
||||
animation.duration = duration
|
||||
animation.isAdditive = true
|
||||
|
||||
self.add(animation, forKey: "shake")
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
var nsString: NSString?
|
||||
let string = NSMutableAttributedString(string: text, attributes: [NSFontAttributeName: baseFont, NSForegroundColorAttributeName: UIColor.black])
|
||||
var skipEntity = false
|
||||
let stringLength = string.length
|
||||
for i in 0 ..< entities.count {
|
||||
if skipEntity {
|
||||
skipEntity = false
|
||||
@ -15,10 +16,9 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
}
|
||||
if range.location + range.length > nsString!.length {
|
||||
let upperBound = nsString!.length
|
||||
range.location = max(0, upperBound - range.length)
|
||||
range.length = upperBound - range.location
|
||||
if range.location + range.length > stringLength {
|
||||
range.location = max(0, stringLength - range.length)
|
||||
range.length = stringLength - range.location
|
||||
}
|
||||
switch entity.type {
|
||||
case .Url:
|
||||
|
||||
@ -12,4 +12,5 @@ module TelegramUIPrivateModule {
|
||||
header "../UIImage+WebP.h"
|
||||
header "../RingBuffer.h"
|
||||
header "../TelegramUIIncludes.h"
|
||||
header "../../third-party/RMIntro/platform/ios/RMIntroViewController.h"
|
||||
}
|
||||
|
||||
@ -314,9 +314,20 @@ final class TextNode: ASDisplayNode {
|
||||
|
||||
var updated = false
|
||||
if let existingLayout = existingLayout, existingLayout.constrainedSize == constrainedSize && existingLayout.maximumNumberOfLines == maximumNumberOfLines && existingLayout.truncationType == truncationType && existingLayout.cutout == cutout {
|
||||
|
||||
let stringMatch: Bool
|
||||
if let existingString = existingLayout.attributedString, let string = attributedString {
|
||||
|
||||
var colorMatch: Bool = true
|
||||
if let backgroundColor = backgroundColor, let previousBackgroundColor = existingLayout.backgroundColor {
|
||||
if !backgroundColor.isEqual(previousBackgroundColor) {
|
||||
colorMatch = false
|
||||
}
|
||||
} else if (backgroundColor != nil) != (existingLayout.backgroundColor != nil) {
|
||||
colorMatch = false
|
||||
}
|
||||
|
||||
if !colorMatch {
|
||||
stringMatch = false
|
||||
} else if let existingString = existingLayout.attributedString, let string = attributedString {
|
||||
stringMatch = existingString.isEqual(to: string)
|
||||
} else if existingLayout.attributedString == nil && attributedString == nil {
|
||||
stringMatch = true
|
||||
|
||||
@ -17,7 +17,7 @@ enum DestructiveUserInfoAction {
|
||||
}
|
||||
|
||||
enum UserInfoEntry: PeerInfoEntry {
|
||||
case info(peer: Peer?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState)
|
||||
case info(peer: Peer?, presence: PeerPresence?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState)
|
||||
case about(text: String)
|
||||
case phoneNumber(index: Int, value: PhoneNumberWithLabel)
|
||||
case userName(value: String)
|
||||
@ -53,14 +53,21 @@ enum UserInfoEntry: PeerInfoEntry {
|
||||
}
|
||||
|
||||
switch self {
|
||||
case let .info(lhsPeer, lhsCachedData, lhsState):
|
||||
case let .info(lhsPeer, lhsPresence, lhsCachedData, lhsState):
|
||||
switch entry {
|
||||
case let .info(rhsPeer, rhsCachedData, rhsState):
|
||||
case let .info(rhsPeer, rhsPresence, rhsCachedData, rhsState):
|
||||
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
|
||||
if !lhsPeer.isEqual(rhsPeer) {
|
||||
return false
|
||||
}
|
||||
} else if (lhsPeer == nil) != (rhsPeer != nil) {
|
||||
} else if (lhsPeer != nil) != (rhsPeer != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
||||
if !lhsPresence.isEqual(to: rhsPresence) {
|
||||
return false
|
||||
}
|
||||
} else if (lhsPresence != nil) != (rhsPresence != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsCachedData = lhsCachedData, let rhsCachedData = rhsCachedData {
|
||||
@ -205,8 +212,8 @@ enum UserInfoEntry: PeerInfoEntry {
|
||||
|
||||
func item(account: Account, interaction: PeerInfoControllerInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .info(peer, cachedData, state):
|
||||
return ItemListAvatarAndNameInfoItem(account: account, peer: peer, cachedData: cachedData, state: state, sectionId: self.section, style: .plain, editingNameUpdated: { editingName in
|
||||
case let .info(peer, presence, cachedData, state):
|
||||
return ItemListAvatarAndNameInfoItem(account: account, peer: peer, presence: presence, cachedData: cachedData, state: state, sectionId: self.section, style: .plain, editingNameUpdated: { editingName in
|
||||
|
||||
})
|
||||
case let .about(text):
|
||||
@ -314,7 +321,7 @@ func userInfoEntries(view: PeerView, state: PeerInfoState?) -> PeerInfoEntries {
|
||||
}
|
||||
}
|
||||
|
||||
entries.append(UserInfoEntry.info(peer: user, cachedData: view.cachedData, state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: updatingName)))
|
||||
entries.append(UserInfoEntry.info(peer: user, presence: view.peerPresences[user.id], cachedData: view.cachedData, state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: updatingName)))
|
||||
if let cachedUserData = view.cachedData as? CachedUserData {
|
||||
if let about = cachedUserData.about, !about.isEmpty {
|
||||
entries.append(UserInfoEntry.about(text: about))
|
||||
|
||||
4
third-party/RMIntro/3rdparty/.gitignore
vendored
Executable file
4
third-party/RMIntro/3rdparty/.gitignore
vendored
Executable file
@ -0,0 +1,4 @@
|
||||
libglm/
|
||||
libpng/*
|
||||
!libpng/Android.mk
|
||||
zlib/
|
||||
529
third-party/RMIntro/3rdparty/linmath/linmath.h
vendored
Executable file
529
third-party/RMIntro/3rdparty/linmath/linmath.h
vendored
Executable file
@ -0,0 +1,529 @@
|
||||
#ifndef LINMATH_H
|
||||
#define LINMATH_H
|
||||
|
||||
#include <math.h>
|
||||
|
||||
typedef float vec2[2];
|
||||
|
||||
typedef float vec3[3];
|
||||
static inline void vec3_add(vec3 r, vec3 a, vec3 b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<3; ++i)
|
||||
r[i] = a[i] + b[i];
|
||||
}
|
||||
static inline void vec3_sub(vec3 r, vec3 a, vec3 b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<3; ++i)
|
||||
r[i] = a[i] - b[i];
|
||||
}
|
||||
static inline void vec3_scale(vec3 r, vec3 v, float s)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<3; ++i)
|
||||
r[i] = v[i] * s;
|
||||
}
|
||||
static inline float vec3_mul_inner(vec3 a, vec3 b)
|
||||
{
|
||||
float p = 0.f;
|
||||
int i;
|
||||
for(i=0; i<3; ++i)
|
||||
p += b[i]*a[i];
|
||||
return p;
|
||||
}
|
||||
static inline void vec3_mul_cross(vec3 r, vec3 a, vec3 b)
|
||||
{
|
||||
r[0] = a[1]*b[2] - a[2]*b[1];
|
||||
r[1] = a[2]*b[0] - a[0]*b[2];
|
||||
r[2] = a[0]*b[1] - a[1]*b[0];
|
||||
}
|
||||
static inline float vec3_len(vec3 v)
|
||||
{
|
||||
return sqrtf(vec3_mul_inner(v, v));
|
||||
}
|
||||
static inline void vec3_norm(vec3 r, vec3 v)
|
||||
{
|
||||
float k = 1.f / vec3_len(v);
|
||||
vec3_scale(r, v, k);
|
||||
}
|
||||
|
||||
|
||||
/////
|
||||
static inline void vec2_add(vec2 r, vec2 a, vec2 b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<2; ++i)
|
||||
r[i] = a[i] + b[i];
|
||||
}
|
||||
static inline float vec2_mul_inner(vec2 a, vec2 b)
|
||||
{
|
||||
float p = 0.f;
|
||||
int i;
|
||||
for(i=0; i<2; ++i)
|
||||
p += b[i]*a[i];
|
||||
return p;
|
||||
}
|
||||
static inline float vec2_len(vec2 v)
|
||||
{
|
||||
return sqrtf(vec2_mul_inner(v, v));
|
||||
}
|
||||
static inline void vec2_scale(vec2 r, vec2 v, float s)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<2; ++i)
|
||||
r[i] = v[i] * s;
|
||||
}
|
||||
static inline void vec2_norm(vec2 r, vec2 v)
|
||||
{
|
||||
float k = 1.f / vec2_len(v);
|
||||
vec2_scale(r, v, k);
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
static inline void vec3_reflect(vec3 r, vec3 v, vec3 n)
|
||||
{
|
||||
float p = 2.f*vec3_mul_inner(v, n);
|
||||
int i;
|
||||
for(i=0;i<3;++i)
|
||||
r[i] = v[i] - p*n[i];
|
||||
}
|
||||
|
||||
typedef float vec4[4];
|
||||
static inline void vec4_add(vec4 r, vec4 a, vec4 b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
r[i] = a[i] + b[i];
|
||||
}
|
||||
static inline void vec4_sub(vec4 r, vec4 a, vec4 b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
r[i] = a[i] - b[i];
|
||||
}
|
||||
static inline void vec4_scale(vec4 r, vec4 v, float s)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
r[i] = v[i] * s;
|
||||
}
|
||||
static inline float vec4_mul_inner(vec4 a, vec4 b)
|
||||
{
|
||||
float p = 0.f;
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
p += b[i]*a[i];
|
||||
return p;
|
||||
}
|
||||
static inline void vec4_mul_cross(vec4 r, vec4 a, vec4 b)
|
||||
{
|
||||
r[0] = a[1]*b[2] - a[2]*b[1];
|
||||
r[1] = a[2]*b[0] - a[0]*b[2];
|
||||
r[2] = a[0]*b[1] - a[1]*b[0];
|
||||
r[3] = 1.f;
|
||||
}
|
||||
static inline float vec4_len(vec4 v)
|
||||
{
|
||||
return sqrtf(vec4_mul_inner(v, v));
|
||||
}
|
||||
static inline void vec4_norm(vec4 r, vec4 v)
|
||||
{
|
||||
float k = 1.f / vec4_len(v);
|
||||
vec4_scale(r, v, k);
|
||||
}
|
||||
static inline void vec4_reflect(vec4 r, vec4 v, vec4 n)
|
||||
{
|
||||
float p = 2.f*vec4_mul_inner(v, n);
|
||||
int i;
|
||||
for(i=0;i<4;++i)
|
||||
r[i] = v[i] - p*n[i];
|
||||
}
|
||||
|
||||
typedef vec4 mat4x4[4];
|
||||
static inline void mat4x4_identity(mat4x4 M)
|
||||
{
|
||||
int i, j;
|
||||
for(i=0; i<4; ++i)
|
||||
for(j=0; j<4; ++j)
|
||||
M[i][j] = i==j ? 1.f : 0.f;
|
||||
}
|
||||
|
||||
|
||||
static inline void mat4x4_dup(mat4x4 M, mat4x4 N)
|
||||
{
|
||||
int i, j;
|
||||
for(i=0; i<4; ++i)
|
||||
for(j=0; j<4; ++j)
|
||||
M[i][j] = N[i][j];
|
||||
}
|
||||
static inline void mat4x4_row(vec4 r, mat4x4 M, int i)
|
||||
{
|
||||
int k;
|
||||
for(k=0; k<4; ++k)
|
||||
r[k] = M[k][i];
|
||||
}
|
||||
static inline void mat4x4_col(vec4 r, mat4x4 M, int i)
|
||||
{
|
||||
int k;
|
||||
for(k=0; k<4; ++k)
|
||||
r[k] = M[i][k];
|
||||
}
|
||||
static inline void mat4x4_transpose(mat4x4 M, mat4x4 N)
|
||||
{
|
||||
int i, j;
|
||||
for(j=0; j<4; ++j)
|
||||
for(i=0; i<4; ++i)
|
||||
M[i][j] = N[j][i];
|
||||
}
|
||||
static inline void mat4x4_add(mat4x4 M, mat4x4 a, mat4x4 b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
vec4_add(M[i], a[i], b[i]);
|
||||
}
|
||||
static inline void mat4x4_sub(mat4x4 M, mat4x4 a, mat4x4 b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
vec4_sub(M[i], a[i], b[i]);
|
||||
}
|
||||
static inline void mat4x4_scale(mat4x4 M, mat4x4 a, float k)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
vec4_scale(M[i], a[i], k);
|
||||
}
|
||||
|
||||
static inline void mat4x4_scale_XYZ(mat4x4 M, float x, float y, float z)
|
||||
{
|
||||
mat4x4 a;
|
||||
mat4x4_dup(a, M);
|
||||
vec4_scale(M[0], a[0], x);
|
||||
vec4_scale(M[1], a[1], y);
|
||||
vec4_scale(M[2], a[2], z);
|
||||
}
|
||||
|
||||
static inline void mat4x4_scale_aniso(mat4x4 M, mat4x4 a, float x, float y, float z)
|
||||
{
|
||||
vec4_scale(M[0], a[0], x);
|
||||
vec4_scale(M[1], a[1], y);
|
||||
vec4_scale(M[2], a[2], z);
|
||||
}
|
||||
static inline void mat4x4_mul(mat4x4 M, mat4x4 a, mat4x4 b)
|
||||
{
|
||||
int k, r, c;
|
||||
for(c=0; c<4; ++c) for(r=0; r<4; ++r) {
|
||||
M[c][r] = 0.f;
|
||||
for(k=0; k<4; ++k)
|
||||
M[c][r] += a[k][r] * b[c][k];
|
||||
}
|
||||
}
|
||||
static inline void mat4x4_mul_vec4(vec4 r, mat4x4 M, vec4 v)
|
||||
{
|
||||
int i, j;
|
||||
for(j=0; j<4; ++j) {
|
||||
r[j] = 0.f;
|
||||
for(i=0; i<4; ++i)
|
||||
r[j] += M[i][j] * v[i];
|
||||
}
|
||||
}
|
||||
static inline void mat4x4_translate(mat4x4 T, float x, float y, float z)
|
||||
{
|
||||
mat4x4_identity(T);
|
||||
T[3][0] = x;
|
||||
T[3][1] = y;
|
||||
T[3][2] = z;
|
||||
}
|
||||
|
||||
static inline void mat4x4_from_vec3_mul_outer(mat4x4 M, vec3 a, vec3 b)
|
||||
{
|
||||
int i, j;
|
||||
for(i=0; i<4; ++i) for(j=0; j<4; ++j)
|
||||
M[i][j] = i<3 && j<3 ? a[i] * b[j] : 0.f;
|
||||
}
|
||||
static inline void mat4x4_rotate(mat4x4 R, mat4x4 M, float x, float y, float z, float angle)
|
||||
{
|
||||
float s = sinf(angle);
|
||||
float c = cosf(angle);
|
||||
vec3 u = {x, y, z};
|
||||
vec3_norm(u, u);
|
||||
|
||||
{
|
||||
mat4x4 T;
|
||||
mat4x4_from_vec3_mul_outer(T, u, u);
|
||||
|
||||
mat4x4 S = {
|
||||
{ 0.f, u[2], -u[1], 0.f},
|
||||
{-u[2], 0.f, u[0], 0.f},
|
||||
{ u[1], -u[0], 0.f, 0.f},
|
||||
{ 0.f, 0.f, 0.f, 0.f}
|
||||
};
|
||||
mat4x4_scale(S, S, s);
|
||||
|
||||
mat4x4 C;
|
||||
mat4x4_identity(C);
|
||||
mat4x4_sub(C, C, T);
|
||||
|
||||
mat4x4_scale(C, C, c);
|
||||
|
||||
mat4x4_add(T, T, C);
|
||||
mat4x4_add(T, T, S);
|
||||
|
||||
T[3][3] = 1.f;
|
||||
mat4x4_mul(R, M, T);
|
||||
}
|
||||
}
|
||||
static inline void mat4x4_rotate_X(mat4x4 Q, mat4x4 M, float angle)
|
||||
{
|
||||
float s = sinf(angle);
|
||||
float c = cosf(angle);
|
||||
mat4x4 R = {
|
||||
{1.f, 0.f, 0.f, 0.f},
|
||||
{0.f, c, s, 0.f},
|
||||
{0.f, -s, c, 0.f},
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
};
|
||||
mat4x4_mul(Q, M, R);
|
||||
}
|
||||
static inline void mat4x4_rotate_Y(mat4x4 Q, mat4x4 M, float angle)
|
||||
{
|
||||
float s = sinf(angle);
|
||||
float c = cosf(angle);
|
||||
mat4x4 R = {
|
||||
{ c, 0.f, s, 0.f},
|
||||
{ 0.f, 1.f, 0.f, 0.f},
|
||||
{ -s, 0.f, c, 0.f},
|
||||
{ 0.f, 0.f, 0.f, 1.f}
|
||||
};
|
||||
mat4x4_mul(Q, M, R);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static inline void mat4x4_rotate_Z2(mat4x4 Q, mat4x4 M, float angle)
|
||||
{
|
||||
float s = sinf(angle);
|
||||
float c = cosf(angle);
|
||||
mat4x4 R = {
|
||||
{ c, s, 0.f, 0.f},
|
||||
{ -s, c, 0.f, 0.f},
|
||||
{ 0.f, 0.f, 1.f, 0.f},
|
||||
{ 0.f, 0.f, 0.f, 1.f}
|
||||
};
|
||||
mat4x4_mul(Q, M, R);
|
||||
}
|
||||
|
||||
static inline void mat4x4_rotate_Z(mat4x4 Q, float angle)
|
||||
{
|
||||
mat4x4 temp;
|
||||
mat4x4_dup(temp, Q);
|
||||
|
||||
mat4x4_rotate_Z2(Q, temp, angle);
|
||||
}
|
||||
|
||||
|
||||
static inline void mat4x4_invert(mat4x4 T, mat4x4 M)
|
||||
{
|
||||
float s[6];
|
||||
float c[6];
|
||||
s[0] = M[0][0]*M[1][1] - M[1][0]*M[0][1];
|
||||
s[1] = M[0][0]*M[1][2] - M[1][0]*M[0][2];
|
||||
s[2] = M[0][0]*M[1][3] - M[1][0]*M[0][3];
|
||||
s[3] = M[0][1]*M[1][2] - M[1][1]*M[0][2];
|
||||
s[4] = M[0][1]*M[1][3] - M[1][1]*M[0][3];
|
||||
s[5] = M[0][2]*M[1][3] - M[1][2]*M[0][3];
|
||||
|
||||
c[0] = M[2][0]*M[3][1] - M[3][0]*M[2][1];
|
||||
c[1] = M[2][0]*M[3][2] - M[3][0]*M[2][2];
|
||||
c[2] = M[2][0]*M[3][3] - M[3][0]*M[2][3];
|
||||
c[3] = M[2][1]*M[3][2] - M[3][1]*M[2][2];
|
||||
c[4] = M[2][1]*M[3][3] - M[3][1]*M[2][3];
|
||||
c[5] = M[2][2]*M[3][3] - M[3][2]*M[2][3];
|
||||
|
||||
/* Assumes it is invertible */
|
||||
float idet = 1.0f/( s[0]*c[5]-s[1]*c[4]+s[2]*c[3]+s[3]*c[2]-s[4]*c[1]+s[5]*c[0] );
|
||||
|
||||
T[0][0] = ( M[1][1] * c[5] - M[1][2] * c[4] + M[1][3] * c[3]) * idet;
|
||||
T[0][1] = (-M[0][1] * c[5] + M[0][2] * c[4] - M[0][3] * c[3]) * idet;
|
||||
T[0][2] = ( M[3][1] * s[5] - M[3][2] * s[4] + M[3][3] * s[3]) * idet;
|
||||
T[0][3] = (-M[2][1] * s[5] + M[2][2] * s[4] - M[2][3] * s[3]) * idet;
|
||||
|
||||
T[1][0] = (-M[1][0] * c[5] + M[1][2] * c[2] - M[1][3] * c[1]) * idet;
|
||||
T[1][1] = ( M[0][0] * c[5] - M[0][2] * c[2] + M[0][3] * c[1]) * idet;
|
||||
T[1][2] = (-M[3][0] * s[5] + M[3][2] * s[2] - M[3][3] * s[1]) * idet;
|
||||
T[1][3] = ( M[2][0] * s[5] - M[2][2] * s[2] + M[2][3] * s[1]) * idet;
|
||||
|
||||
T[2][0] = ( M[1][0] * c[4] - M[1][1] * c[2] + M[1][3] * c[0]) * idet;
|
||||
T[2][1] = (-M[0][0] * c[4] + M[0][1] * c[2] - M[0][3] * c[0]) * idet;
|
||||
T[2][2] = ( M[3][0] * s[4] - M[3][1] * s[2] + M[3][3] * s[0]) * idet;
|
||||
T[2][3] = (-M[2][0] * s[4] + M[2][1] * s[2] - M[2][3] * s[0]) * idet;
|
||||
|
||||
T[3][0] = (-M[1][0] * c[3] + M[1][1] * c[1] - M[1][2] * c[0]) * idet;
|
||||
T[3][1] = ( M[0][0] * c[3] - M[0][1] * c[1] + M[0][2] * c[0]) * idet;
|
||||
T[3][2] = (-M[3][0] * s[3] + M[3][1] * s[1] - M[3][2] * s[0]) * idet;
|
||||
T[3][3] = ( M[2][0] * s[3] - M[2][1] * s[1] + M[2][2] * s[0]) * idet;
|
||||
}
|
||||
static inline void mat4x4_frustum(mat4x4 M, float l, float r, float b, float t, float n, float f)
|
||||
{
|
||||
M[0][0] = 2.f*n/(r-l);
|
||||
M[0][1] = M[0][2] = M[0][3] = 0.f;
|
||||
|
||||
M[1][1] = 2.0f*n/(t-b);
|
||||
M[1][0] = M[1][2] = M[1][3] = 0.f;
|
||||
|
||||
M[2][0] = (r+l)/(r-l);
|
||||
M[2][1] = (t+b)/(t-b);
|
||||
M[2][2] = -(f+n)/(f-n);
|
||||
M[2][3] = -1.f;
|
||||
|
||||
M[3][2] = -2.f*(f*n)/(f-n);
|
||||
M[3][0] = M[3][1] = M[3][3] = 0.f;
|
||||
}
|
||||
static inline void mat4x4_ortho(mat4x4 M, float l, float r, float b, float t, float n, float f)
|
||||
{
|
||||
M[0][0] = 2.f/(r-l);
|
||||
M[0][1] = M[0][2] = M[0][3] = 0.f;
|
||||
|
||||
M[1][1] = 2.f/(t-b);
|
||||
M[1][0] = M[1][2] = M[1][3] = 0.f;
|
||||
|
||||
M[2][2] = -2.f/(f-n);
|
||||
M[2][0] = M[2][1] = M[2][3] = 0.f;
|
||||
|
||||
M[3][0] = -(r+l)/(r-l);
|
||||
M[3][1] = -(t+b)/(t-b);
|
||||
M[3][2] = -(f+n)/(f-n);
|
||||
M[3][3] = 1.f;
|
||||
}
|
||||
|
||||
|
||||
typedef float quat[4];
|
||||
static inline void quat_identity(quat q)
|
||||
{
|
||||
q[0] = q[1] = q[2] = 0.f;
|
||||
q[3] = 1.f;
|
||||
}
|
||||
static inline void quat_add(quat r, quat a, quat b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
r[i] = a[i] + b[i];
|
||||
}
|
||||
static inline void quat_sub(quat r, quat a, quat b)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
r[i] = a[i] - b[i];
|
||||
}
|
||||
static inline void quat_mul(quat r, quat p, quat q)
|
||||
{
|
||||
vec3 w;
|
||||
vec3_mul_cross(r, p, q);
|
||||
vec3_scale(w, p, q[3]);
|
||||
vec3_add(r, r, w);
|
||||
vec3_scale(w, q, p[3]);
|
||||
vec3_add(r, r, w);
|
||||
r[3] = p[3]*q[3] - vec3_mul_inner(p, q);
|
||||
}
|
||||
static inline void quat_scale(quat r, quat v, float s)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
r[i] = v[i] * s;
|
||||
}
|
||||
static inline float quat_inner_product(quat a, quat b)
|
||||
{
|
||||
float p = 0.f;
|
||||
int i;
|
||||
for(i=0; i<4; ++i)
|
||||
p += b[i]*a[i];
|
||||
return p;
|
||||
}
|
||||
static inline void quat_conj(quat r, quat q)
|
||||
{
|
||||
int i;
|
||||
for(i=0; i<3; ++i)
|
||||
r[i] = -q[i];
|
||||
r[3] = q[3];
|
||||
}
|
||||
#define quat_norm vec4_norm
|
||||
static inline void quat_mul_vec3(vec3 r, quat q, vec3 v)
|
||||
{
|
||||
quat v_ = {v[0], v[1], v[2], 0.f};
|
||||
|
||||
quat_conj(r, q);
|
||||
quat_norm(r, r);
|
||||
quat_mul(r, v_, r);
|
||||
quat_mul(r, q, r);
|
||||
}
|
||||
static inline void mat4x4_from_quat(mat4x4 M, quat q)
|
||||
{
|
||||
float a = q[3];
|
||||
float b = q[0];
|
||||
float c = q[1];
|
||||
float d = q[2];
|
||||
float a2 = a*a;
|
||||
float b2 = b*b;
|
||||
float c2 = c*c;
|
||||
float d2 = d*d;
|
||||
|
||||
M[0][0] = a2 + b2 - c2 - d2;
|
||||
M[0][1] = 2.f*(b*c + a*d);
|
||||
M[0][2] = 2.f*(b*d - a*c);
|
||||
M[0][3] = 0.f;
|
||||
|
||||
M[1][0] = 2*(b*c - a*d);
|
||||
M[1][1] = a2 - b2 + c2 - d2;
|
||||
M[1][2] = 2.f*(c*d + a*b);
|
||||
M[1][3] = 0.f;
|
||||
|
||||
M[2][0] = 2.f*(b*d + a*c);
|
||||
M[2][1] = 2.f*(c*d - a*b);
|
||||
M[2][2] = a2 - b2 - c2 + d2;
|
||||
M[2][3] = 0.f;
|
||||
|
||||
M[3][0] = M[3][1] = M[3][2] = 0.f;
|
||||
M[3][3] = 1.f;
|
||||
}
|
||||
static inline void mat4x4_mul_quat(mat4x4 R, mat4x4 M, quat q)
|
||||
{
|
||||
quat_mul_vec3(R[0], M[0], q);
|
||||
quat_mul_vec3(R[1], M[1], q);
|
||||
quat_mul_vec3(R[2], M[2], q);
|
||||
|
||||
R[3][0] = R[3][1] = R[3][2] = 0.f;
|
||||
R[3][3] = 1.f;
|
||||
}
|
||||
static inline void quat_from_mat4x4(quat q, mat4x4 M)
|
||||
{
|
||||
float r=0.f;
|
||||
int i;
|
||||
|
||||
int perm[] = { 0, 1, 2, 0, 1 };
|
||||
int *p = perm;
|
||||
|
||||
for(i = 0; i<3; i++) {
|
||||
float m = M[i][i];
|
||||
if( m < r )
|
||||
continue;
|
||||
m = r;
|
||||
p = &perm[i];
|
||||
}
|
||||
|
||||
r = sqrtf(1.f + M[p[0]][p[0]] - M[p[1]][p[1]] - M[p[2]][p[2]] );
|
||||
|
||||
if(r < 1e-6) {
|
||||
q[0] = 1.f;
|
||||
q[1] = q[2] = q[3] = 0.f;
|
||||
return;
|
||||
}
|
||||
|
||||
q[0] = r/2.f;
|
||||
q[1] = (M[p[0]][p[1]] - M[p[1]][p[0]])/(2.f*r);
|
||||
q[2] = (M[p[2]][p[0]] - M[p[0]][p[2]])/(2.f*r);
|
||||
q[3] = (M[p[2]][p[1]] - M[p[1]][p[2]])/(2.f*r);
|
||||
}
|
||||
|
||||
#endif
|
||||
2672
third-party/RMIntro/core/animations.c
vendored
Normal file
2672
third-party/RMIntro/core/animations.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
45
third-party/RMIntro/core/animations.h
vendored
Executable file
45
third-party/RMIntro/core/animations.h
vendored
Executable file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// animations.h
|
||||
// IntroOpenGL
|
||||
//
|
||||
// Created by Ilya Rimchikov on 29/03/14.
|
||||
// Copyright (c) 2014 IntroOpenGL. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
|
||||
void on_surface_created();
|
||||
void on_surface_changed(int a_width_px, int a_height_px, float a_scale_factor, int a1, int a2, int a3, int a4, int a5);
|
||||
void on_draw_frame();
|
||||
|
||||
|
||||
void set_touch_x(int a);
|
||||
|
||||
void set_date(double a);
|
||||
void set_date0(double a);
|
||||
|
||||
void set_page(int page);
|
||||
|
||||
|
||||
void set_pages_textures(int a1, int a2, int a3, int a4, int a5, int a6);
|
||||
|
||||
void set_ic_textures(int a_ic_bubble_dot, int a_ic_bubble, int a_ic_cam_lens, int a_ic_cam, int a_ic_pencil, int a_ic_pin, int a_ic_smile_eye, int a_ic_smile, int a_ic_videocam);
|
||||
|
||||
void set_telegram_textures(int a_telegram_sphere, int a_telegram_plane);
|
||||
void set_fast_textures(int a_fast_body, int a_fast_spiral, int a_fast_arrow, int a_fast_arrow_shadow);
|
||||
void set_free_textures(int a_knot_up, int a_knot_down);
|
||||
void set_powerful_textures(int a_powerful_mask, int a_powerful_star, int a_powerful_infinity, int a_powerful_infinity_white);
|
||||
void set_private_textures(int a_private_door, int a_private_screw);
|
||||
|
||||
void set_ui_textures(int a_pagination_dot);
|
||||
|
||||
void set_need_pages(int a);
|
||||
void set_y_offset(float a);
|
||||
|
||||
|
||||
void set_scroll_offset(float a_offset);
|
||||
|
||||
void inc_stars_rendered();
|
||||
|
||||
|
||||
void set_elements_top_margins(int a_icon_y, int a_text_y, int a_button_y);
|
||||
19
third-party/RMIntro/core/buffer.c
vendored
Executable file
19
third-party/RMIntro/core/buffer.c
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
#include "buffer.h"
|
||||
#include "platform_gl.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
GLuint create_vbo(const GLsizeiptr size, const GLvoid* data, const GLenum usage) {
|
||||
assert(data != NULL);
|
||||
GLuint vbo_object;
|
||||
glGenBuffers(1, &vbo_object);
|
||||
assert(vbo_object != 0);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_object);
|
||||
|
||||
//glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)
|
||||
glBufferData(GL_ARRAY_BUFFER, size, data, usage);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
return vbo_object;
|
||||
}
|
||||
6
third-party/RMIntro/core/buffer.h
vendored
Executable file
6
third-party/RMIntro/core/buffer.h
vendored
Executable file
@ -0,0 +1,6 @@
|
||||
#include "platform_gl.h"
|
||||
|
||||
#define BUFFER_OFFSET(i) ((void*)(i))
|
||||
|
||||
GLuint create_vbo(const GLsizeiptr size, const GLvoid* data, const GLenum usage);
|
||||
GLuint create_vbo2(const GLsizeiptr vertex_data_size, const GLvoid* vertex_data, const GLsizeiptr color_data_size, const GLvoid* color_data, const GLenum usage);
|
||||
1
third-party/RMIntro/core/config.h
vendored
Executable file
1
third-party/RMIntro/core/config.h
vendored
Executable file
@ -0,0 +1 @@
|
||||
#define LOGGING_ON 0
|
||||
1
third-party/RMIntro/core/macros.h
vendored
Executable file
1
third-party/RMIntro/core/macros.h
vendored
Executable file
@ -0,0 +1 @@
|
||||
#define UNUSED(x) (void)(x)
|
||||
13
third-party/RMIntro/core/math_helper.h
vendored
Executable file
13
third-party/RMIntro/core/math_helper.h
vendored
Executable file
@ -0,0 +1,13 @@
|
||||
#include <math.h>
|
||||
|
||||
static inline float deg_to_radf(float deg) {
|
||||
return deg * (float)M_PI / 180.0f;
|
||||
}
|
||||
|
||||
static inline double MAXf(float a, float b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
static inline double MINf(float a, float b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
100
third-party/RMIntro/core/matrix.h
vendored
Executable file
100
third-party/RMIntro/core/matrix.h
vendored
Executable file
@ -0,0 +1,100 @@
|
||||
#include "linmath.h"
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Adapted from Android's OpenGL Matrix.java. */
|
||||
|
||||
static inline void mat4x4_perspective(mat4x4 m, float y_fov_in_degrees, float aspect, float n, float f)
|
||||
{
|
||||
const float angle_in_radians = (float) (y_fov_in_degrees * M_PI / 180.0);
|
||||
const float a = (float) (1.0 / tan(angle_in_radians / 2.0));
|
||||
|
||||
m[0][0] = a / aspect;
|
||||
m[1][0] = 0.0f;
|
||||
m[2][0] = 0.0f;
|
||||
m[3][0] = 0.0f;
|
||||
|
||||
m[1][0] = 0.0f;
|
||||
m[1][1] = a;
|
||||
m[1][2] = 0.0f;
|
||||
m[1][3] = 0.0f;
|
||||
|
||||
m[2][0] = 0.0f;
|
||||
m[2][1] = 0.0f;
|
||||
m[2][2] = -((f + n) / (f - n));
|
||||
m[2][3] = -1.0f;
|
||||
|
||||
m[3][0] = 0.0f;
|
||||
m[3][1] = 0.0f;
|
||||
m[3][2] = -((2.0f * f * n) / (f - n));
|
||||
m[3][3] = 0.0f;
|
||||
}
|
||||
|
||||
static inline void mat4x4_translate_in_place(mat4x4 m, float x, float y, float z)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
m[3][i] += m[0][i] * x
|
||||
+ m[1][i] * y
|
||||
+ m[2][i] * z;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void mat4x4_look_at(mat4x4 m,
|
||||
float eyeX, float eyeY, float eyeZ,
|
||||
float centerX, float centerY, float centerZ,
|
||||
float upX, float upY, float upZ)
|
||||
{
|
||||
// See the OpenGL GLUT documentation for gluLookAt for a description
|
||||
// of the algorithm. We implement it in a straightforward way:
|
||||
|
||||
float fx = centerX - eyeX;
|
||||
float fy = centerY - eyeY;
|
||||
float fz = centerZ - eyeZ;
|
||||
|
||||
// Normalize f
|
||||
vec3 f_vec = {fx, fy, fz};
|
||||
float rlf = 1.0f / vec3_len(f_vec);
|
||||
fx *= rlf;
|
||||
fy *= rlf;
|
||||
fz *= rlf;
|
||||
|
||||
// compute s = f x up (x means "cross product")
|
||||
float sx = fy * upZ - fz * upY;
|
||||
float sy = fz * upX - fx * upZ;
|
||||
float sz = fx * upY - fy * upX;
|
||||
|
||||
// and normalize s
|
||||
vec3 s_vec = {sx, sy, sz};
|
||||
float rls = 1.0f / vec3_len(s_vec);
|
||||
sx *= rls;
|
||||
sy *= rls;
|
||||
sz *= rls;
|
||||
|
||||
// compute u = s x f
|
||||
float ux = sy * fz - sz * fy;
|
||||
float uy = sz * fx - sx * fz;
|
||||
float uz = sx * fy - sy * fx;
|
||||
|
||||
m[0][0] = sx;
|
||||
m[0][1] = ux;
|
||||
m[0][2] = -fx;
|
||||
m[0][3] = 0.0f;
|
||||
|
||||
m[1][0] = sy;
|
||||
m[1][1] = uy;
|
||||
m[1][2] = -fy;
|
||||
m[1][3] = 0.0f;
|
||||
|
||||
m[2][0] = sz;
|
||||
m[2][1] = uz;
|
||||
m[2][2] = -fz;
|
||||
m[2][3] = 0.0f;
|
||||
|
||||
m[3][0] = 0.0f;
|
||||
m[3][1] = 0.0f;
|
||||
m[3][2] = 0.0f;
|
||||
m[3][3] = 1.0f;
|
||||
|
||||
mat4x4_translate_in_place(m, -eyeX, -eyeY, -eyeZ);
|
||||
}
|
||||
1302
third-party/RMIntro/core/objects.c
vendored
Normal file
1302
third-party/RMIntro/core/objects.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
176
third-party/RMIntro/core/objects.h
vendored
Executable file
176
third-party/RMIntro/core/objects.h
vendored
Executable file
@ -0,0 +1,176 @@
|
||||
//
|
||||
// objects.h
|
||||
// IntroOpenGL
|
||||
//
|
||||
// Created by Ilya Rimchikov on 29/03/14.
|
||||
// Copyright (c) 2014 IntroOpenGL. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#include "platform_gl.h"
|
||||
#include "program.h"
|
||||
#include "linmath.h"
|
||||
|
||||
extern float scale_factor;
|
||||
extern int width, height;
|
||||
extern int y_offset_absolute;
|
||||
|
||||
typedef enum {NORMAL, NORMAL_ONE, RED, BLUE, LIGHT_RED, LIGHT_BLUE} texture_program_type;
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} CPoint;
|
||||
|
||||
typedef struct {
|
||||
float width;
|
||||
float height;
|
||||
} CSize;
|
||||
|
||||
typedef struct {
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
} CColor;
|
||||
|
||||
|
||||
CPoint CPointMake(float x, float y);
|
||||
CSize CSizeMake(float width, float height);
|
||||
|
||||
|
||||
float D2R(float a);
|
||||
float R2D(float a);
|
||||
|
||||
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} xyz;
|
||||
|
||||
xyz xyzMake(float x, float y, float z);
|
||||
|
||||
|
||||
typedef struct {
|
||||
float side_length;
|
||||
float start_angle;
|
||||
float end_angle;
|
||||
float angle;
|
||||
CSize size;
|
||||
float radius;
|
||||
float width;
|
||||
} VarParams;
|
||||
|
||||
typedef struct {
|
||||
int datasize;
|
||||
int round_count;
|
||||
GLenum triangle_mode;
|
||||
int is_star;
|
||||
} ConstParams;
|
||||
|
||||
typedef struct {
|
||||
xyz anchor;
|
||||
xyz position;
|
||||
float rotation;
|
||||
xyz scale;
|
||||
} LayerParams;
|
||||
|
||||
typedef struct {
|
||||
xyz anchor;
|
||||
xyz position;
|
||||
float rotation;
|
||||
xyz scale;
|
||||
float alpha;
|
||||
|
||||
VarParams var_params;
|
||||
ConstParams const_params;
|
||||
|
||||
LayerParams *layer_params;
|
||||
|
||||
} Params;
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
vec4 color;
|
||||
CPoint *data;
|
||||
GLuint buffer;
|
||||
int num_points;
|
||||
|
||||
Params params;
|
||||
} Shape;
|
||||
|
||||
|
||||
typedef struct {
|
||||
GLuint texture;
|
||||
CPoint *data;
|
||||
GLuint buffer;
|
||||
int num_points;
|
||||
|
||||
Params params;
|
||||
} TexturedShape;
|
||||
|
||||
|
||||
|
||||
Params default_params();
|
||||
LayerParams default_layer_params();
|
||||
|
||||
void mat4x4_translate_independed(mat4x4 m, float x, float y, float z);
|
||||
|
||||
|
||||
void set_y_offset_objects(float a);
|
||||
|
||||
|
||||
|
||||
void setup_shaders();
|
||||
|
||||
|
||||
void draw_shape(const Shape* shape, mat4x4 view_projection_matrix);
|
||||
|
||||
void draw_textured_shape(const TexturedShape* shape, mat4x4 view_projection_matrix, texture_program_type program_type);
|
||||
|
||||
|
||||
TexturedShape create_segmented_square(float side_length, float start_angle, float end_angle, GLuint texture);
|
||||
void change_segmented_square(TexturedShape* shape, float side_length, float start_angle, float end_angle);
|
||||
|
||||
Shape create_rounded_rectangle(CSize size, float radius, int round_count, const vec4 color);
|
||||
void change_rounded_rectangle(Shape* shape, CSize size, float radius);
|
||||
|
||||
|
||||
Shape create_rectangle(CSize size, const vec4 color);
|
||||
|
||||
|
||||
Shape create_circle(float radius, int vertex_count, const vec4 color);
|
||||
void change_circle(Shape* shape, float radius);
|
||||
|
||||
|
||||
void draw_colored_rectangle(const Shape* shape, mat4x4 view_projection_matrix);
|
||||
|
||||
TexturedShape create_textured_rectangle(CSize size, GLuint texture);
|
||||
void change_textured_rectangle(TexturedShape* shape, CSize size);
|
||||
|
||||
Shape create_ribbon(float length, const vec4 color);
|
||||
void change_ribbon(Shape* shape, float length);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Shape create_segmented_circle(float radius, int vertex_count, float start_angle, float angle, const vec4 color);
|
||||
void change_segmented_circle(Shape* shape, float radius, float start_angle, float angle);
|
||||
|
||||
Shape create_infinity(float width, float angle, int segment_count, const vec4 color);
|
||||
void change_infinity(Shape* shape, float angle);
|
||||
void draw_infinity(const Shape* shape, mat4x4 view_projection_matrix);
|
||||
|
||||
Shape create_rounded_rectangle_stroked(CSize size, float radius, float stroke_width, int round_count, const vec4 color);
|
||||
void change_rounded_rectangle_stroked(Shape* shape, CSize size, float radius, float stroke_width);
|
||||
|
||||
Shape create_rounded_rectangle(CSize size, float radius, int round_count, const vec4 color);
|
||||
void change_rounded_rectangle(Shape* shape, CSize size, float radius);
|
||||
|
||||
|
||||
void mat4x4_log(mat4x4 M);
|
||||
|
||||
41
third-party/RMIntro/core/program.c
vendored
Executable file
41
third-party/RMIntro/core/program.c
vendored
Executable file
@ -0,0 +1,41 @@
|
||||
#include "program.h"
|
||||
#include "platform_gl.h"
|
||||
|
||||
|
||||
|
||||
TextureProgram get_texture_program(GLuint program)
|
||||
{
|
||||
return (TextureProgram) {
|
||||
program,
|
||||
glGetAttribLocation(program, "a_Position"),
|
||||
glGetAttribLocation(program, "a_TextureCoordinates"),
|
||||
glGetUniformLocation(program, "u_MvpMatrix"),
|
||||
glGetUniformLocation(program, "u_TextureUnit"),
|
||||
glGetUniformLocation(program, "u_Alpha")};
|
||||
}
|
||||
|
||||
ColorProgram get_color_program(GLuint program)
|
||||
{
|
||||
return (ColorProgram) {
|
||||
program,
|
||||
glGetAttribLocation(program, "a_Position"),
|
||||
glGetUniformLocation(program, "u_MvpMatrix"),
|
||||
glGetUniformLocation(program, "u_Color"),
|
||||
glGetUniformLocation(program, "u_Alpha")};
|
||||
}
|
||||
|
||||
GradientProgram get_gradient_program(GLuint program)
|
||||
{
|
||||
return (GradientProgram) {
|
||||
program,
|
||||
glGetAttribLocation(program, "a_Position"),
|
||||
glGetUniformLocation(program, "u_MvpMatrix"),
|
||||
glGetAttribLocation(program, "a_Color"),
|
||||
glGetUniformLocation(program, "u_Alpha")};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
37
third-party/RMIntro/core/program.h
vendored
Executable file
37
third-party/RMIntro/core/program.h
vendored
Executable file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "platform_gl.h"
|
||||
|
||||
typedef struct {
|
||||
GLuint program;
|
||||
|
||||
GLint a_position_location;
|
||||
GLint a_texture_coordinates_location;
|
||||
GLint u_mvp_matrix_location;
|
||||
GLint u_texture_unit_location;
|
||||
GLint u_alpha_loaction;
|
||||
} TextureProgram;
|
||||
|
||||
typedef struct {
|
||||
GLuint program;
|
||||
|
||||
GLint a_position_location;
|
||||
GLint u_mvp_matrix_location;
|
||||
GLint u_color_location;
|
||||
GLint u_alpha_loaction;
|
||||
} ColorProgram;
|
||||
|
||||
typedef struct {
|
||||
GLuint program;
|
||||
|
||||
GLint a_position_location;
|
||||
GLint u_mvp_matrix_location;
|
||||
GLint a_color_location;
|
||||
GLint u_alpha_loaction;
|
||||
} GradientProgram;
|
||||
|
||||
|
||||
|
||||
|
||||
TextureProgram get_texture_program(GLuint program);
|
||||
ColorProgram get_color_program(GLuint program);
|
||||
GradientProgram get_gradient_program(GLuint program);
|
||||
28
third-party/RMIntro/core/rngs.c
vendored
Normal file
28
third-party/RMIntro/core/rngs.c
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include "rngs.h"
|
||||
|
||||
|
||||
float frand(float from, float to)
|
||||
{
|
||||
// srand(time(NULL));
|
||||
return (float)(((double)random()/RAND_MAX)*(to-from)+from);
|
||||
}
|
||||
|
||||
int irand(int from, int to)
|
||||
{
|
||||
// srand(time(NULL));
|
||||
return (int)(((double)random()/RAND_MAX)*(to-from+1)+from);
|
||||
}
|
||||
|
||||
int signrand()
|
||||
{
|
||||
// srand(time(NULL));
|
||||
return irand(0, 1)*2-1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
7
third-party/RMIntro/core/rngs.h
vendored
Normal file
7
third-party/RMIntro/core/rngs.h
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
int irand(int from, int to);
|
||||
|
||||
float frand(float from, float to);
|
||||
|
||||
int signrand();
|
||||
|
||||
110
third-party/RMIntro/core/shader.c
vendored
Executable file
110
third-party/RMIntro/core/shader.c
vendored
Executable file
@ -0,0 +1,110 @@
|
||||
#include "shader.h"
|
||||
#include "platform_gl.h"
|
||||
#include "platform_log.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "shaders"
|
||||
|
||||
static void log_v_fixed_length(const GLchar* source, const GLint length) {
|
||||
if (LOGGING_ON) {
|
||||
char log_buffer[length + 1];
|
||||
memcpy(log_buffer, source, length);
|
||||
log_buffer[length] = '\0';
|
||||
|
||||
DEBUG_LOG_WRITE_V(TAG, log_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static void log_shader_info_log(GLuint shader_object_id) {
|
||||
if (LOGGING_ON) {
|
||||
GLint log_length;
|
||||
glGetShaderiv(shader_object_id, GL_INFO_LOG_LENGTH, &log_length);
|
||||
GLchar log_buffer[log_length];
|
||||
glGetShaderInfoLog(shader_object_id, log_length, NULL, log_buffer);
|
||||
|
||||
DEBUG_LOG_WRITE_V(TAG, log_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static void log_program_info_log(GLuint program_object_id) {
|
||||
if (LOGGING_ON) {
|
||||
GLint log_length;
|
||||
glGetProgramiv(program_object_id, GL_INFO_LOG_LENGTH, &log_length);
|
||||
GLchar log_buffer[log_length];
|
||||
glGetProgramInfoLog(program_object_id, log_length, NULL, log_buffer);
|
||||
|
||||
DEBUG_LOG_WRITE_V(TAG, log_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
GLuint compile_shader(const GLenum type, const GLchar* source, const GLint length) {
|
||||
assert(source != NULL);
|
||||
GLuint shader_object_id = glCreateShader(type);
|
||||
GLint compile_status;
|
||||
|
||||
assert(shader_object_id != 0);
|
||||
|
||||
glShaderSource(shader_object_id, 1, (const GLchar **)&source, &length);
|
||||
glCompileShader(shader_object_id);
|
||||
glGetShaderiv(shader_object_id, GL_COMPILE_STATUS, &compile_status);
|
||||
|
||||
if (LOGGING_ON) {
|
||||
DEBUG_LOG_WRITE_D(TAG, "Results of compiling shader source:");
|
||||
log_v_fixed_length(source, length);
|
||||
log_shader_info_log(shader_object_id);
|
||||
}
|
||||
|
||||
assert(compile_status != 0);
|
||||
|
||||
return shader_object_id;
|
||||
}
|
||||
|
||||
GLuint link_program(const GLuint vertex_shader, const GLuint fragment_shader) {
|
||||
GLuint program_object_id = glCreateProgram();
|
||||
GLint link_status;
|
||||
|
||||
assert(program_object_id != 0);
|
||||
|
||||
glAttachShader(program_object_id, vertex_shader);
|
||||
glAttachShader(program_object_id, fragment_shader);
|
||||
glLinkProgram(program_object_id);
|
||||
glGetProgramiv(program_object_id, GL_LINK_STATUS, &link_status);
|
||||
|
||||
if (LOGGING_ON) {
|
||||
DEBUG_LOG_WRITE_D(TAG, "Results of linking program:");
|
||||
log_program_info_log(program_object_id);
|
||||
}
|
||||
|
||||
assert(link_status != 0);
|
||||
|
||||
return program_object_id;
|
||||
}
|
||||
|
||||
GLuint build_program(
|
||||
const GLchar * vertex_shader_source, const GLint vertex_shader_source_length,
|
||||
const GLchar * fragment_shader_source, const GLint fragment_shader_source_length) {
|
||||
assert(vertex_shader_source != NULL);
|
||||
assert(fragment_shader_source != NULL);
|
||||
|
||||
GLuint vertex_shader = compile_shader(
|
||||
GL_VERTEX_SHADER, vertex_shader_source, vertex_shader_source_length);
|
||||
GLuint fragment_shader = compile_shader(
|
||||
GL_FRAGMENT_SHADER, fragment_shader_source, fragment_shader_source_length);
|
||||
return link_program(vertex_shader, fragment_shader);
|
||||
}
|
||||
|
||||
GLint validate_program(const GLuint program) {
|
||||
if (LOGGING_ON) {
|
||||
int validate_status;
|
||||
|
||||
glValidateProgram(program);
|
||||
glGetProgramiv(program, GL_VALIDATE_STATUS, &validate_status);
|
||||
DEBUG_LOG_PRINT_D(TAG, "Results of validating program: %d", validate_status);
|
||||
log_program_info_log(program);
|
||||
return validate_status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
10
third-party/RMIntro/core/shader.h
vendored
Executable file
10
third-party/RMIntro/core/shader.h
vendored
Executable file
@ -0,0 +1,10 @@
|
||||
#include "platform_gl.h"
|
||||
|
||||
GLuint compile_shader(const GLenum type, const GLchar* source, const GLint length);
|
||||
GLuint link_program(const GLuint vertex_shader, const GLuint fragment_shader);
|
||||
GLuint build_program(
|
||||
const GLchar * vertex_shader_source, const GLint vertex_shader_source_length,
|
||||
const GLchar * fragment_shader_source, const GLint fragment_shader_source_length);
|
||||
|
||||
/* Should be called just before using a program to draw, if validation is needed. */
|
||||
GLint validate_program(const GLuint program);
|
||||
139
third-party/RMIntro/core/timing.c
vendored
Normal file
139
third-party/RMIntro/core/timing.c
vendored
Normal file
@ -0,0 +1,139 @@
|
||||
//
|
||||
// timing.c
|
||||
// IntroOpenGL
|
||||
//
|
||||
// Created by Ilya Rimchikov on 03/05/14.
|
||||
// Copyright (c) 2014 IntroOpenGL. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
#include "timing.h"
|
||||
|
||||
static inline float evaluateAtParameterWithCoefficients(float t, float coefficients[])
|
||||
{
|
||||
return coefficients[0] + t*coefficients[1] + t*t*coefficients[2] + t*t*t*coefficients[3];
|
||||
}
|
||||
|
||||
static inline float evaluateDerivationAtParameterWithCoefficients(float t, float coefficients[])
|
||||
{
|
||||
return coefficients[1] + 2*t*coefficients[2] + 3*t*t*coefficients[3];
|
||||
}
|
||||
|
||||
static inline float calcParameterViaNewtonRaphsonUsingXAndCoefficientsForX(float x, float coefficientsX[])
|
||||
{
|
||||
// see http://en.wikipedia.org/wiki/Newton's_method
|
||||
|
||||
// start with X being the correct value
|
||||
float t = x;
|
||||
|
||||
// iterate several times
|
||||
//const float epsilon = 0.00001;
|
||||
int i;
|
||||
for(i = 0; i < 10; i++)
|
||||
{
|
||||
float x2 = evaluateAtParameterWithCoefficients(t, coefficientsX) - x;
|
||||
float d = evaluateDerivationAtParameterWithCoefficients(t, coefficientsX);
|
||||
|
||||
float dt = x2/d;
|
||||
|
||||
t = t - dt;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static inline float calcParameterUsingXAndCoefficientsForX (float x, float coefficientsX[])
|
||||
{
|
||||
// for the time being, we'll guess Newton-Raphson always
|
||||
// returns the correct value.
|
||||
|
||||
// if we find it doesn't find the solution often enough,
|
||||
// we can add additional calculation methods.
|
||||
|
||||
float t = calcParameterViaNewtonRaphsonUsingXAndCoefficientsForX(x, coefficientsX);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static int is_initialized=0;
|
||||
|
||||
static float _coefficientsX[TIMING_NUM][4], _coefficientsY[TIMING_NUM][4];
|
||||
|
||||
static const float _c0x = 0.0;
|
||||
static const float _c0y = 0.0;
|
||||
static const float _c3x = 1.0;
|
||||
static const float _c3y = 1.0;
|
||||
|
||||
float timing(float x, timing_type type)
|
||||
{
|
||||
|
||||
if (is_initialized==0) {
|
||||
is_initialized=1;
|
||||
|
||||
float c[TIMING_NUM][4];
|
||||
|
||||
c[Default][0]=0.25f;
|
||||
c[Default][1]=0.1f;
|
||||
c[Default][2]=0.25f;
|
||||
c[Default][3]=1.0f;
|
||||
|
||||
c[EaseInEaseOut][0]=0.42f;
|
||||
c[EaseInEaseOut][1]=0.0f;
|
||||
c[EaseInEaseOut][2]=0.58f;
|
||||
c[EaseInEaseOut][3]=1.0f;
|
||||
|
||||
c[EaseIn][0]=0.42f;
|
||||
c[EaseIn][1]=0.0f;
|
||||
c[EaseIn][2]=1.0f;
|
||||
c[EaseIn][3]=1.0f;
|
||||
|
||||
c[EaseOut][0]=0.0f;
|
||||
c[EaseOut][1]=0.0f;
|
||||
c[EaseOut][2]=0.58f;
|
||||
c[EaseOut][3]=1.0f;
|
||||
|
||||
|
||||
c[EaseOutBounce][0]=0.0;
|
||||
c[EaseOutBounce][1]=0.0;
|
||||
c[EaseOutBounce][2]=0.;
|
||||
c[EaseOutBounce][3]=1.25;
|
||||
|
||||
|
||||
c[Linear][0]=0.0;
|
||||
c[Linear][1]=0.0;
|
||||
c[Linear][2]=1.0;
|
||||
c[Linear][3]=1.0;
|
||||
|
||||
|
||||
|
||||
|
||||
int i;
|
||||
for (i=0; i<TIMING_NUM; i++) {
|
||||
float _c1x = c[i][0];
|
||||
float _c1y = c[i][1];
|
||||
float _c2x = c[i][2];
|
||||
float _c2y = c[i][3];
|
||||
|
||||
_coefficientsX[i][0] = _c0x; // t^0
|
||||
_coefficientsX[i][1] = -3.0f*_c0x + 3.0f*_c1x; // t^1
|
||||
_coefficientsX[i][2] = 3.0f*_c0x - 6.0f*_c1x + 3.0f*_c2x; // t^2
|
||||
_coefficientsX[i][3] = -_c0x + 3.0f*_c1x - 3.0f*_c2x + _c3x; // t^3
|
||||
|
||||
_coefficientsY[i][0] = _c0y; // t^0
|
||||
_coefficientsY[i][1] = -3.0f*_c0y + 3.0f*_c1y; // t^1
|
||||
_coefficientsY[i][2] = 3.0f*_c0y - 6.0f*_c1y + 3.0f*_c2y; // t^2
|
||||
_coefficientsY[i][3] = -_c0y + 3.0f*_c1y - 3.0f*_c2y + _c3y; // t^3
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (x==0.0 || x==1.0) {
|
||||
return x;
|
||||
}
|
||||
|
||||
float t = calcParameterUsingXAndCoefficientsForX(x, _coefficientsX[type]);
|
||||
float y = evaluateAtParameterWithCoefficients(t, _coefficientsY[type]);
|
||||
|
||||
return y;
|
||||
|
||||
}
|
||||
22
third-party/RMIntro/core/timing.h
vendored
Normal file
22
third-party/RMIntro/core/timing.h
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// timing.h
|
||||
// IntroOpenGL
|
||||
//
|
||||
// Created by Ilya Rimchikov on 03/05/14.
|
||||
// Copyright (c) 2014 IntroOpenGL. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
typedef enum
|
||||
{
|
||||
Default=0,
|
||||
EaseIn=1,
|
||||
EaseOut=2,
|
||||
EaseInEaseOut=3,
|
||||
Linear=4,
|
||||
Sin=5,
|
||||
EaseOutBounce,
|
||||
TIMING_NUM
|
||||
} timing_type;
|
||||
|
||||
float timing(float x, timing_type type);
|
||||
27
third-party/RMIntro/platform/common/platform_log.c
vendored
Executable file
27
third-party/RMIntro/platform/common/platform_log.c
vendored
Executable file
@ -0,0 +1,27 @@
|
||||
#include "platform_log.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define LOG_VPRINTF(priority) printf("(" priority ") %s: ", tag); \
|
||||
va_list arg_ptr; \
|
||||
va_start(arg_ptr, fmt); \
|
||||
vprintf(fmt, arg_ptr); \
|
||||
va_end(arg_ptr); \
|
||||
printf("\n");
|
||||
|
||||
void _debug_log_v(const char *tag, const char *fmt, ...) {
|
||||
LOG_VPRINTF("VERBOSE");
|
||||
}
|
||||
|
||||
void _debug_log_d(const char *tag, const char *fmt, ...) {
|
||||
LOG_VPRINTF("DEBUG");
|
||||
}
|
||||
|
||||
void _debug_log_w(const char *tag, const char *fmt, ...) {
|
||||
LOG_VPRINTF("WARN");
|
||||
}
|
||||
|
||||
void _debug_log_e(const char *tag, const char *fmt, ...) {
|
||||
LOG_VPRINTF("ERROR");
|
||||
}
|
||||
19
third-party/RMIntro/platform/common/platform_log.h
vendored
Executable file
19
third-party/RMIntro/platform/common/platform_log.h
vendored
Executable file
@ -0,0 +1,19 @@
|
||||
#include "platform_macros.h"
|
||||
#include "../../core/config.h"
|
||||
|
||||
void _debug_log_v(const char* tag, const char* text, ...) PRINTF_ATTRIBUTE(2, 3);
|
||||
void _debug_log_d(const char* tag, const char* text, ...) PRINTF_ATTRIBUTE(2, 3);
|
||||
void _debug_log_w(const char* tag, const char* text, ...) PRINTF_ATTRIBUTE(2, 3);
|
||||
void _debug_log_e(const char* tag, const char* text, ...) PRINTF_ATTRIBUTE(2, 3);
|
||||
|
||||
#define DEBUG_LOG_PRINT_V(tag, fmt, ...) do { if (LOGGING_ON) _debug_log_v(tag, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); } while (0)
|
||||
#define DEBUG_LOG_PRINT_D(tag, fmt, ...) do { if (LOGGING_ON) _debug_log_d(tag, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); } while (0)
|
||||
#define DEBUG_LOG_PRINT_W(tag, fmt, ...) do { if (LOGGING_ON) _debug_log_w(tag, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); } while (0)
|
||||
#define DEBUG_LOG_PRINT_E(tag, fmt, ...) do { if (LOGGING_ON) _debug_log_e(tag, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); } while (0)
|
||||
|
||||
#define DEBUG_LOG_WRITE_V(tag, text) DEBUG_LOG_PRINT_V(tag, "%s", text)
|
||||
#define DEBUG_LOG_WRITE_D(tag, text) DEBUG_LOG_PRINT_D(tag, "%s", text)
|
||||
#define DEBUG_LOG_WRITE_W(tag, text) DEBUG_LOG_PRINT_W(tag, "%s", text)
|
||||
#define DEBUG_LOG_WRITE_E(tag, text) DEBUG_LOG_PRINT_E(tag, "%s", text)
|
||||
|
||||
#define CRASH(e) DEBUG_LOG_WRITE_E("Assert", #e); __builtin_trap()
|
||||
5
third-party/RMIntro/platform/common/platform_macros.h
vendored
Executable file
5
third-party/RMIntro/platform/common/platform_macros.h
vendored
Executable file
@ -0,0 +1,5 @@
|
||||
#if defined(__GNUC__)
|
||||
#define PRINTF_ATTRIBUTE(format_pos, arg_pos) __attribute__((format(printf, format_pos, arg_pos)))
|
||||
#else
|
||||
#define PRINTF_ATTRIBUTE(format_pos, arg_pos)
|
||||
#endif
|
||||
34
third-party/RMIntro/platform/ios/.gitignore
vendored
Executable file
34
third-party/RMIntro/platform/ios/.gitignore
vendored
Executable file
@ -0,0 +1,34 @@
|
||||
# Based from http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects
|
||||
|
||||
# Temporary files
|
||||
.DS_Store
|
||||
.Trashes
|
||||
*.swp
|
||||
*.lock
|
||||
*~.nib
|
||||
|
||||
# Meta files
|
||||
*._*
|
||||
|
||||
# Build files
|
||||
DerivedData/
|
||||
build/
|
||||
|
||||
# Private settings
|
||||
# *.pbxuser should not be ignored if using "custom executables".
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.perspectivev3
|
||||
|
||||
# Some projects need the below
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
|
||||
# XCode 4 semi-personal settings
|
||||
xcuserdata/
|
||||
|
||||
# Deprecated classes
|
||||
*.moved-aside
|
||||
33
third-party/RMIntro/platform/ios/Base.lproj/LaunchScreen.xib
vendored
Normal file
33
third-party/RMIntro/platform/ios/Base.lproj/LaunchScreen.xib
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6254" systemVersion="14A329f" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Intro 2.0" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
BIN
third-party/RMIntro/platform/ios/Default-568h@2x.png
vendored
Executable file
BIN
third-party/RMIntro/platform/ios/Default-568h@2x.png
vendored
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
third-party/RMIntro/platform/ios/Default.png
vendored
Executable file
BIN
third-party/RMIntro/platform/ios/Default.png
vendored
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
BIN
third-party/RMIntro/platform/ios/Default@2x.png
vendored
Executable file
BIN
third-party/RMIntro/platform/ios/Default@2x.png
vendored
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user