From 0915a42e64f95eafedcdde9e119551b346107734 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 16 Oct 2025 05:30:06 +0400 Subject: [PATCH 1/4] Various improvements --- .../Sources/AccountContext.swift | 3 +- .../Sources/ContactSelectionController.swift | 10 +- .../Sources/PeersNearbyManager.swift | 8 - submodules/AttachmentTextInputPanelNode/BUILD | 1 + ...AttachmentTextInputActionButtonsNode.swift | 48 +- .../AttachmentTextInputPanelNode.swift | 121 +- submodules/AttachmentUI/BUILD | 2 + .../Sources/AttachmentContainer.swift | 56 +- .../Sources/AttachmentController.swift | 442 ++++-- .../Sources/AttachmentPanel.swift | 345 +++-- .../AuthorizationSequenceController.swift | 18 +- .../CallListUI/Sources/CallListCallItem.swift | 18 +- .../Sources/CallListControllerNode.swift | 16 +- .../Sources/CallListGroupCallItem.swift | 4 +- .../ChatListFilterPresetCategoryItem.swift | 15 +- .../ChatListFilterPresetController.swift | 20 +- .../ChatListFilterPresetListController.swift | 10 +- .../ChatListFilterPresetListItem.swift | 22 +- ...hatListFilterPresetListSuggestedItem.swift | 19 +- .../ItemListFilterTitleInputItem.swift | 14 +- submodules/CheckNode/Sources/CheckNode.swift | 6 + submodules/Components/SheetComponent/BUILD | 1 + .../Sources/SheetComponent.swift | 58 +- .../Sources/ViewControllerComponent.swift | 5 + submodules/ComposePollUI/BUILD | 2 + .../Sources/ComposePollScreen.swift | 155 +- submodules/ContactListUI/BUILD | 1 + .../Sources/ContactListNode.swift | 433 ++++-- .../Sources/ContactsController.swift | 46 +- .../Sources/ContactsSearchContainerNode.swift | 84 +- .../Sources/ContactsPeerItem.swift | 10 +- submodules/CountrySelectionUI/BUILD | 5 + ...onSequenceCountrySelectionController.swift | 135 +- ...quenceCountrySelectionControllerNode.swift | 129 +- .../Sources/DatePickerNode.swift | 48 +- .../Sources/DebugAccountsController.swift | 4 +- .../Sources/DebugController.swift | 134 +- .../Navigation/NavigationContainer.swift | 4 +- .../Navigation/NavigationModalContainer.swift | 6 +- .../Navigation/NavigationModalFrame.swift | 2 +- submodules/Display/Source/NavigationBar.swift | 7 +- .../NavigationTransitionCoordinator.swift | 34 +- submodules/Display/Source/UIKitUtils.swift | 52 + .../Display/Source/ViewController.swift | 3 +- .../Sources/InviteRequestsSearchItem.swift | 2 +- .../ItemListFolderInviteLinkListItem.swift | 13 +- .../Sources/ItemListAvatarAndNameItem.swift | 21 +- .../Sources/ItemListPeerActionItem.swift | 38 +- .../Sources/ItemListPeerItem.swift | 28 +- .../Sources/ItemListStickerPackItem.swift | 19 +- .../Sources/ItemListController.swift | 16 +- .../Sources/ItemListControllerNode.swift | 9 +- .../Sources/ItemListControllerSearch.swift | 2 +- .../ItemListUI/Sources/ItemListItem.swift | 2 +- .../Sources/Items/ItemListActionItem.swift | 25 +- .../Sources/Items/ItemListCheckboxItem.swift | 21 +- .../Items/ItemListDisclosureItem.swift | 36 +- .../Sources/Items/ItemListInfoItem.swift | 35 +- .../Items/ItemListMultilineInputItem.swift | 21 +- .../Items/ItemListSingleLineInputItem.swift | 21 +- .../Sources/Items/ItemListSwitchItem.swift | 27 +- .../Sources/ItemListVenueItem.swift | 13 +- .../Sources/ListMessageFileItemNode.swift | 25 +- .../Sources/ListMessageItem.swift | 4 +- .../Sources/ListSectionHeaderNode.swift | 3 +- .../Sources/VenueIconResources.swift | 16 +- submodules/LocationUI/BUILD | 8 + .../Sources/LocationActionListItem.swift | 11 +- .../Sources/LocationLiveListItem.swift | 7 +- .../Sources/LocationMapHeaderNode.swift | 513 ++++++- .../LocationUI/Sources/LocationMapNode.swift | 4 +- .../Sources/LocationOptionsNode.swift | 2 +- .../Sources/LocationPickerController.swift | 93 +- .../LocationPickerControllerNode.swift | 421 +++++- .../Sources/LocationSearchContainerNode.swift | 19 +- .../Sources/LocationSectionHeaderItem.swift | 6 +- .../Sources/LocationViewControllerNode.swift | 12 +- submodules/MediaPickerUI/BUILD | 4 + .../Sources/MediaPickerScreen.swift | 508 +++++-- .../Sources/MediaPickerTitleView.swift | 85 +- .../Sources/NotificationSoundSelection.swift | 8 +- .../Sources/ResetPasswordController.swift | 2 +- .../Sources/PaymentMethodListScreen.swift | 3 +- .../Sources/PeerInfoAvatarListNode.swift | 2 +- .../Sources/ChannelAdminsController.swift | 12 +- .../Sources/ChannelBlacklistController.swift | 4 +- ...hannelDiscussionGroupSetupController.swift | 6 +- ...hannelDiscussionGroupSetupSearchItem.swift | 2 +- .../Sources/ChannelMembersController.swift | 8 +- .../Sources/DeviceContactInfoController.swift | 4 +- .../Sources/GroupInfoSearchItem.swift | 2 +- .../GroupPreHistorySetupController.swift | 4 +- .../Sources/PhoneLabelController.swift | 2 +- .../Sources/PremiumIntroScreen.swift | 12 +- .../Sources/ItemListController.swift | 8 +- submodules/SettingsUI/BUILD | 3 + .../ChangePhoneNumberCodeController.swift | 2 +- .../Sources/ChangePhoneNumberController.swift | 4 +- ...AutodownloadConnectionTypeController.swift | 12 +- .../AutodownloadDataUsagePickerItem.swift | 6 +- .../AutodownloadMediaCategoryController.swift | 12 +- .../AutodownloadSizeLimitItem.swift | 9 +- .../DataAndStorageSettingsController.swift | 30 +- .../EnergySavingSettingsScreen.swift | 3 +- .../EnergyUsageBatteryLevelItem.swift | 24 +- .../IntentsSettingsController.swift | 16 +- .../ProxyListSettingsController.swift | 10 +- .../ProxyServerSettingsController.swift | 18 +- .../ProxySettingsActionItem.swift | 21 +- .../ProxySettingsServerItem.swift | 20 +- .../SaveIncomingMediaController.swift | 12 +- .../VoiceCallDataSavingController.swift | 6 +- .../WebBrowserDomainExceptionItem.swift | 17 +- .../Data and Storage/WebBrowserItem.swift | 21 +- .../WebBrowserSettingsController.swift | 12 +- .../Sources/DeleteAccountPhoneItem.swift | 6 +- .../LocalizationListControllerNode.swift | 8 +- .../TranslatonSettingsController.swift | 2 +- .../NotificationsAndSoundsController.swift | 32 +- .../NotificationsCategoryItemListItem.swift | 21 +- .../NotificationsPeerCategoryController.swift | 14 +- .../BlockedPeersController.swift | 4 +- .../ConfirmPhoneNumberController.swift | 2 +- .../CreatePasswordController.swift | 10 +- .../DataPrivacySettingsController.swift | 13 +- .../ForwardPrivacyChatPreviewItem.swift | 6 +- .../GlobalAutoremoveScreen.swift | 4 +- .../PasscodeOptionsController.swift | 8 +- .../PrivacyAndSecurityController.swift | 40 +- .../PrivacyIntroControllerNode.swift | 8 +- .../ItemListRecentSessionItem.swift | 18 +- .../Recent Sessions/ItemListWebsiteItem.swift | 24 +- .../RecentSessionsController.swift | 18 +- .../RecentSessionsHeaderItem.swift | 46 +- .../SelectivePrivacySettingsController.swift | 46 +- ...ectivePrivacySettingsPeersController.swift | 10 +- .../TwoStepVerificationUnlockController.swift | 14 +- ...actionNotificationSettingsController.swift | 8 +- .../Sources/Search/SettingsSearchItem.swift | 2 +- .../ArchivedStickerPacksController.swift | 2 +- .../InstalledStickerPacksController.swift | 20 +- .../Sources/ThemePickerController.swift | 6 +- .../Sources/Themes/EditThemeController.swift | 14 +- .../ThemeAutoNightSettingsController.swift | 18 +- .../Themes/ThemeSettingsAppIconItem.swift | 6 +- .../Themes/ThemeSettingsChatPreviewItem.swift | 6 +- .../Themes/ThemeSettingsController.swift | 24 +- .../Watch/WatchSettingsController.swift | 2 +- submodules/TelegramCallsUI/BUILD | 2 + .../Sources/CallStatusBarNode.swift | 94 +- .../Components/MessageItemComponent.swift | 87 +- .../Components/MessageListComponent.swift | 2 +- .../Sources/VideoChatScreen.swift | 11 +- .../Sources/Account/AccountManager.swift | 1 + .../ApiUtils/StoreMessage_Telegram.swift | 6 +- .../PendingUpdateMessageManager.swift | 2 +- .../PendingMessages/RequestEditMessage.swift | 24 +- .../StandaloneSendMessage.swift | 20 +- .../Sources/State/PendingMessageManager.swift | 18 +- .../SyncCore/ScheduledRepeatAttribute.swift | 23 + ...OutgoingScheduleInfoMessageAttribute.swift | 29 +- .../Contacts/ImportContact.swift | 9 +- .../Contacts/TelegramEngineContacts.swift | 4 +- ...OutgoingMessageWithChatContextResult.swift | 2 +- .../Messages/TelegramEngineMessages.swift | 6 +- .../Sources/PresentationTheme.swift | 12 + .../Resources/PresentationResourceKey.swift | 3 + .../PresentationResourcesItemList.swift | 15 +- submodules/TelegramUI/BUILD | 2 + .../Components/AttachmentFileController/BUILD | 47 + .../Sources/AttachmentFileController.swift | 164 ++- .../Sources/AttachmentFileEmptyItem.swift | 0 .../Sources/AttachmentFileSearchItem.swift | 130 +- .../Sources/ButtonComponent.swift | 2 +- .../Sources/CameraCodeFrameView.swift | 2 +- .../CameraScreen/Sources/CameraScreen.swift | 4 +- .../CameraScreen/Sources/ZoomComponent.swift | 2 +- .../StringForMessageTimestampStatus.swift | 28 + .../Sources/ChatTextInputPanelNode.swift | 4 + .../ChatScheduleTimeController/BUILD | 9 + .../Sources/ChatScheduleTimeScreen.swift | 1241 +++++++++++++++++ .../Components/ChatScheduleTimeScreen/BUILD | 34 + .../Sources/ChatScheduleTimeScreen.swift | 122 ++ .../Components/ComposeTodoScreen/BUILD | 2 + .../Sources/ComposeTodoScreen.swift | 154 +- .../Contacts/NewContactScreen/BUILD | 51 + .../Sources/NewContactScreen.swift | 922 ++++++++++++ .../Sources/PhoneInputItem.swift | 436 ++++++ .../EdgeEffect/Sources/EdgeEffect.swift | 143 +- .../Components/FaceScanScreen/BUILD | 1 + .../Sources/AgeVerificationScreen.swift | 53 +- .../Sources/ForumSettingsScreen.swift | 3 + .../Sources/GiftItemComponent.swift | 22 +- .../Components/Gifts/GiftOptionsScreen/BUILD | 2 + .../Sources/GiftOptionsScreen.swift | 226 ++- .../Components/Gifts/GiftSetupScreen/BUILD | 1 + .../Sources/GiftSetupScreen.swift | 19 +- .../Sources/GlassBackgroundComponent.swift | 108 +- .../Components/GlassBarButtonComponent/BUILD | 21 + .../Sources/GlassBarButtonComponent.swift | 346 +++++ .../Sources/GroupStickerPackCurrentItem.swift | 28 +- .../GroupStickerPackSetupController.swift | 6 +- .../Sources/GroupStickerSearchItem.swift | 2 +- .../LegacyInstantVideoController.swift | 2 +- .../ListComposePollOptionComponent.swift | 21 +- .../Sources/ListItemComponentAdaptor.swift | 20 +- .../Sources/ListTextFieldItemComponent.swift | 20 +- .../MediaEditor/Sources/MediaEditor.swift | 8 +- .../Sources/AdjustmentsComponent.swift | 2 +- .../Sources/BlurComponent.swift | 4 +- .../Sources/MediaEditorScreen.swift | 2 +- .../Sources/MediaToolsScreen.swift | 2 +- .../Sources/MediaScrubberComponent.swift | 2 +- .../NotificationPeerExceptionController.swift | 18 +- .../Components/PeerInfo/PeerInfoScreen/BUILD | 1 + .../ListItems/PeerInfoScreenActionItem.swift | 12 +- .../PeerInfoScreenDisclosureItem.swift | 13 +- .../ListItems/PeerInfoScreenInfoItem.swift | 4 +- .../PeerInfoScreenLabeledValueItem.swift | 8 +- .../ListItems/PeerInfoScreenMemberItem.swift | 8 +- .../PeerInfoScreenNoteListItem.swift | 6 +- .../PeerInfoScreenPersonalChannelItem.swift | 10 +- .../ListItems/PeerInfoScreenSwitchItem.swift | 12 +- .../PeerInfoHeaderEditingContentNode.swift | 4 +- ...PeerInfoHeaderMultiLineTextFieldNode.swift | 11 +- ...eerInfoHeaderSingleLineTextFieldNode.swift | 9 +- .../PeerInfoScreenMultilineInputtem.swift | 4 +- .../Sources/PeerInfoStoryPaneNode.swift | 4 + .../Sources/OldChannelsSearch.swift | 2 +- .../SearchInputPanelComponent/BUILD | 29 + .../Sources/SearchInputPanelComponent.swift | 318 +++++ .../AutomaticBusinessMessageSetupScreen/BUILD | 3 +- .../AutomaticBusinessMessageSetupScreen.swift | 16 + .../Sources/BusinessLinksSetupScreen.swift | 3 + .../Sources/BotListSettingsScreen.swift | 1 + .../Sources/BotSettingsScreen.swift | 1 + .../Sources/BusinessHoursSetupScreen.swift | 6 + .../Sources/BusinessLocationSetupScreen.swift | 4 + .../Sources/BusinessRecipientListScreen.swift | 4 + .../Sources/ChatbotSetupScreen.swift | 11 + .../Sources/PeerNameColorItem.swift | 18 +- .../Settings/PeerNameColorScreen/BUILD | 1 + .../Sources/ChannelAppearanceScreen.swift | 23 +- .../Sources/UserApperanceScreen.swift | 2 + .../Sources/ItemListReactionItem.swift | 20 +- .../QuickReactionSetupController.swift | 3 +- .../Sources/ReactionChatPreviewItem.swift | 9 +- .../ThemeColorsGridControllerNode.swift | 4 +- .../Sources/ThemeGridControllerNode.swift | 18 +- .../Components/ShareWithPeersScreen/BUILD | 1 + .../Sources/CoverListItemComponent.swift | 2 +- .../Sources/OptionListItemComponent.swift | 2 +- .../Sources/ShareWithPeersScreen.swift | 53 +- .../Sources/StarsBalanceComponent.swift | 6 +- .../Sources/StarsTransactionsScreen.swift | 6 + .../StoryItemSetContainerComponent.swift | 2 +- ...StoryItemSetContainerViewSendMessage.swift | 14 +- .../OptionLocate.imageset/Contents.json | 12 + .../headerlocation_30.pdf | Bin 0 -> 6001 bytes .../Location/OptionMap.imageset/Contents.json | 12 + .../OptionMap.imageset/headermap_30.pdf | Bin 0 -> 6388 bytes .../Check.imageset/Contents.json | 12 + .../Check.imageset/checkattach_30.pdf | Bin 0 -> 5675 bytes .../Navigation/Back.imageset/Contents.json | 12 + .../Back.imageset/headerback_30.pdf | Bin 0 -> 5662 bytes .../Navigation/Close.imageset/Contents.json | 12 + .../Close.imageset/headercross_30.pdf | Bin 0 -> 5916 bytes .../Navigation/Done.imageset/Contents.json | 12 + .../Done.imageset/headercheck_30.pdf | Bin 0 -> 5711 bytes .../Navigation/Search.imageset/Contents.json | 12 + .../Search.imageset/headersearch_30.pdf | Bin 0 -> 6286 bytes .../TitleExpand.imageset/Contents.json | 12 + .../TitleExpand.imageset/down_24.pdf | Bin 0 -> 4616 bytes .../Resources/Animations/anim_morewide.json | 1 + .../TelegramUI/Sources/AccountContext.swift | 5 +- .../Chat/ChatControllerLoadDisplayNode.swift | 8 +- .../Chat/ChatControllerMediaRecording.swift | 5 +- .../TelegramUI/Sources/ChatController.swift | 49 +- .../ChatControllerForwardMessages.swift | 6 +- .../Sources/ChatControllerNode.swift | 13 +- .../ChatControllerOpenAttachmentMenu.swift | 22 +- .../Sources/ContactSelectionController.swift | 207 ++- .../ContactSelectionControllerNode.swift | 184 ++- .../Sources/PeersNearbyManager.swift | 86 -- .../Sources/SharedAccountContext.swift | 6 + .../Sources/LocalizationListItem.swift | 22 +- submodules/WebUI/BUILD | 3 + .../WebUI/Sources/WebAppController.swift | 129 +- .../WebUI/Sources/WebAppTitleView.swift | 11 +- 289 files changed, 9723 insertions(+), 2071 deletions(-) delete mode 100644 submodules/AccountContext/Sources/PeersNearbyManager.swift create mode 100644 submodules/TelegramCore/Sources/SyncCore/ScheduledRepeatAttribute.swift create mode 100644 submodules/TelegramUI/Components/AttachmentFileController/BUILD rename submodules/TelegramUI/{ => Components/AttachmentFileController}/Sources/AttachmentFileController.swift (65%) rename submodules/TelegramUI/{ => Components/AttachmentFileController}/Sources/AttachmentFileEmptyItem.swift (100%) rename submodules/TelegramUI/{ => Components/AttachmentFileController}/Sources/AttachmentFileSearchItem.swift (82%) create mode 100644 submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift create mode 100644 submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD create mode 100644 submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift create mode 100644 submodules/TelegramUI/Components/Contacts/NewContactScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift create mode 100644 submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift create mode 100644 submodules/TelegramUI/Components/GlassBarButtonComponent/BUILD create mode 100644 submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift create mode 100644 submodules/TelegramUI/Components/SearchInputPanelComponent/BUILD create mode 100644 submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/headerlocation_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/headermap_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/Check.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/Check.imageset/checkattach_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Back.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Back.imageset/headerback_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Close.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Close.imageset/headercross_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Done.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Done.imageset/headercheck_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Search.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/Search.imageset/headersearch_30.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/TitleExpand.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Navigation/TitleExpand.imageset/down_24.pdf create mode 100644 submodules/TelegramUI/Resources/Animations/anim_morewide.json delete mode 100644 submodules/TelegramUI/Sources/PeersNearbyManager.swift diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index dd0814a390..5cdfb1c534 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1416,6 +1416,8 @@ public protocol SharedAccountContext: AnyObject { func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController) + func makeNewContactScreen(context: AccountContext, firstName: String?, lastName: String?, phoneNumber: String?) -> ViewController + func navigateToCurrentCall() var hasOngoingCall: ValuePromise { get } var immediateHasOngoingCall: Bool { get } @@ -1486,7 +1488,6 @@ public protocol AccountContext: AnyObject { var engine: TelegramEngine { get } var liveLocationManager: LiveLocationManager? { get } - var peersNearbyManager: PeersNearbyManager? { get } var fetchManager: FetchManager { get } var prefetchManager: PrefetchManager? { get } var downloadedMediaStoreManager: DownloadedMediaStoreManager { get } diff --git a/submodules/AccountContext/Sources/ContactSelectionController.swift b/submodules/AccountContext/Sources/ContactSelectionController.swift index 4b1ae89cca..54d5cb44ad 100644 --- a/submodules/AccountContext/Sources/ContactSelectionController.swift +++ b/submodules/AccountContext/Sources/ContactSelectionController.swift @@ -9,7 +9,7 @@ public protocol ContactSelectionController: ViewController { var result: Signal<([ContactListPeer], ContactListAction, Bool, Int32?, NSAttributedString?, ChatSendMessageActionSheetController.SendParameters?)?, NoError> { get } var displayProgress: Bool { get set } var dismissed: (() -> Void)? { get set } - var presentScheduleTimePicker: (@escaping (Int32) -> Void) -> Void { get set } + var presentScheduleTimePicker: (@escaping (Int32, Int32?) -> Void) -> Void { get set } func dismissSearch() } @@ -105,7 +105,13 @@ public final class ContactSelectionControllerParams { case always } + public enum Style { + case glass + case legacy + } + public let context: AccountContext + public let style: Style public let updatedPresentationData: (initial: PresentationData, signal: Signal)? public let mode: ContactSelectionControllerMode public let autoDismiss: Bool @@ -123,6 +129,7 @@ public final class ContactSelectionControllerParams { public init( context: AccountContext, + style: Style = .legacy, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ContactSelectionControllerMode = .generic, autoDismiss: Bool = true, @@ -139,6 +146,7 @@ public final class ContactSelectionControllerParams { sendMessage: ((EnginePeer) -> Void)? = nil ) { self.context = context + self.style = style self.updatedPresentationData = updatedPresentationData self.mode = mode self.autoDismiss = autoDismiss diff --git a/submodules/AccountContext/Sources/PeersNearbyManager.swift b/submodules/AccountContext/Sources/PeersNearbyManager.swift deleted file mode 100644 index e4b69f00b3..0000000000 --- a/submodules/AccountContext/Sources/PeersNearbyManager.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation -import SwiftSignalKit -import TelegramCore -import TelegramPresentationData - -public protocol PeersNearbyManager { - -} diff --git a/submodules/AttachmentTextInputPanelNode/BUILD b/submodules/AttachmentTextInputPanelNode/BUILD index 8539e94668..25e0d545af 100644 --- a/submodules/AttachmentTextInputPanelNode/BUILD +++ b/submodules/AttachmentTextInputPanelNode/BUILD @@ -36,6 +36,7 @@ swift_library( "//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities", "//submodules/TelegramUI/Components/Chat/ChatInputTextNode", "//submodules/AnimatedCountLabelNode", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift index 55f234013a..7d1b162759 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputActionButtonsNode.swift @@ -9,12 +9,15 @@ import ChatPresentationInterfaceState import ComponentFlow import AccountContext import AnimatedCountLabelNode +import GlassBackgroundComponent final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode { private let strings: PresentationStrings + private let glass: Bool let sendContainerNode: ASDisplayNode - let backgroundNode: ASDisplayNode + let backgroundView: GlassBackgroundView? + let backgroundNode: ASDisplayNode? let sendButton: HighlightTrackingButtonNode var sendButtonHasApplyIcon = false var animatingSendButton = false @@ -35,16 +38,24 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage private var validLayout: CGSize? - init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) { + init(presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool, presentController: @escaping (ViewController) -> Void) { self.theme = presentationInterfaceState.theme self.strings = presentationInterfaceState.strings + self.glass = glass self.sendContainerNode = ASDisplayNode() self.sendContainerNode.layer.allowsGroupOpacity = true - self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor - self.backgroundNode.clipsToBounds = true + if glass { + self.backgroundView = GlassBackgroundView() + self.backgroundNode = nil + } else { + self.backgroundNode = ASDisplayNode() + self.backgroundNode?.backgroundColor = theme.chat.inputPanel.actionControlFillColor + self.backgroundNode?.clipsToBounds = true + self.backgroundView = nil + } + self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil) self.textNode = ImmediateAnimatedCountLabelNode() @@ -78,7 +89,11 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage } self.addSubnode(self.sendContainerNode) - self.sendContainerNode.addSubnode(self.backgroundNode) + if let backgroundView = self.backgroundView { + self.sendContainerNode.view.addSubview(backgroundView) + } else if let backgroundNode = self.backgroundNode { + self.sendContainerNode.addSubnode(backgroundNode) + } self.sendContainerNode.addSubnode(self.sendButton) self.sendContainerNode.addSubnode(self.textNode) } @@ -99,11 +114,13 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage } } - self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: self.backgroundNode.view, style: .lift) + if let backgroundNode = self.backgroundNode { + self.sendButtonPointerInteraction = PointerInteraction(view: self.sendButton.view, customInteractionView: backgroundNode.view, style: .lift) + } } func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) { - self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor + self.backgroundNode?.backgroundColor = theme.chat.inputPanel.actionControlFillColor } private var absoluteRect: (CGRect, CGSize)? @@ -141,7 +158,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage let textSize = self.textNode.updateLayout(size: CGSize(width: 100.0, height: 100.0), animated: transition.isAnimated) if minimized { - width = 44.0 + width = 53.0 } else { width = textSize.width + buttonInset * 2.0 } @@ -155,9 +172,16 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: buttonSize)) transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: buttonSize)) - let backgroundSize = CGSize(width: width - 11.0, height: 33.0) - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)) - self.backgroundNode.cornerRadius = backgroundSize.height / 2.0 + if let backgroundView = self.backgroundView { + let backgroundSize = CGSize(width: width - 13.0, height: 40.0) + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize) + transition.updateFrame(view: backgroundView, frame: backgroundFrame) + backgroundView.update(size: backgroundSize, cornerRadius: backgroundSize.height * 0.5, isDark: false, tintColor: .init(kind: .custom, color: self.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition)) + } else if let backgroundNode { + let backgroundSize = CGSize(width: width - 11.0, height: 33.0) + transition.updateFrame(node: backgroundNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)) + backgroundNode.cornerRadius = backgroundSize.height / 2.0 + } return buttonSize } diff --git a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift index cfcd09a668..7374e3fd03 100644 --- a/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift +++ b/submodules/AttachmentTextInputPanelNode/Sources/AttachmentTextInputPanelNode.swift @@ -28,7 +28,7 @@ import ChatInputTextNode private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) private let minInputFontSize: CGFloat = 5.0 -private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { +private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false, metrics: LayoutMetrics) -> CGFloat { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) var result: CGFloat if baseFontSize.isEqual(to: 26.0) { @@ -49,25 +49,34 @@ private func calclulateTextFieldMinHeight(_ presentationInterfaceState: ChatPres result = max(33.0, result) } + if glass { + result = max(38.0, result) + } + return result } -private func calculateTextFieldRealInsets(_ presentationInterfaceState: ChatPresentationInterfaceState) -> UIEdgeInsets { +private func calculateTextFieldRealInsets(_ presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false) -> UIEdgeInsets { let baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) let top: CGFloat let bottom: CGFloat - if baseFontSize.isEqual(to: 14.0) { - top = 2.0 + if glass { + top = 4.0 bottom = 1.0 - } else if baseFontSize.isEqual(to: 15.0) { - top = 1.0 - bottom = 1.0 - } else if baseFontSize.isEqual(to: 16.0) { - top = 0.5 - bottom = 0.0 } else { - top = 0.0 - bottom = 0.0 + if baseFontSize.isEqual(to: 14.0) { + top = 2.0 + bottom = 1.0 + } else if baseFontSize.isEqual(to: 15.0) { + top = 1.0 + bottom = 1.0 + } else if baseFontSize.isEqual(to: 16.0) { + top = 0.5 + bottom = 0.0 + } else { + top = 0.0 + bottom = 0.0 + } } return UIEdgeInsets(top: 4.5 + top, left: 0.0, bottom: 5.5 + bottom, right: 32.0) } @@ -243,15 +252,16 @@ private func makeTextInputTheme(context: AccountContext, interfaceState: ChatPre public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, ASEditableTextNodeDelegate, ChatInputTextNodeDelegate { private let context: AccountContext + private let glass: Bool private let isCaption: Bool private let isAttachment: Bool private let presentController: (ViewController) -> Void private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? - private var textPlaceholderNode: ImmediateTextNode + public var textPlaceholderNode: ImmediateTextNode private let textInputContainerBackgroundNode: ASImageNode - private let textInputContainer: ASDisplayNode + public let textInputContainer: ASDisplayNode public var textInputNode: ChatInputTextNode? private var dustNode: InvisibleInkDustNode? private var customEmojiContainerView: CustomEmojiContainerView? @@ -265,7 +275,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private let actionButtons: AttachmentTextInputActionButtonsNode private let counterTextNode: ImmediateTextNode - private let inputModeView: ComponentHostView + public var opaqueActionButtons: ASDisplayNode { + return self.actionButtons + } + + public let inputModeView: ComponentHostView private var validLayout: (CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool)? @@ -379,9 +393,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private var maxCaptionLength: Int32? - public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, isCaption: Bool = false, isAttachment: Bool = false, isScheduledMessages: Bool = false, presentController: @escaping (ViewController) -> Void, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + public init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, glass: Bool = false, isCaption: Bool = false, isAttachment: Bool = false, isScheduledMessages: Bool = false, presentController: @escaping (ViewController) -> Void, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.context = context self.presentationInterfaceState = presentationInterfaceState + self.glass = glass self.isCaption = isCaption self.isAttachment = isAttachment self.presentController = presentController @@ -401,7 +416,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.textInputContainerBackgroundNode.displaysAsynchronously = false self.textInputContainer = ASDisplayNode() - if !isCaption { + if !isCaption && !glass { self.textInputContainer.addSubnode(self.textInputContainerBackgroundNode) } @@ -420,7 +435,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.oneLineNode = TextNodeWithEntities() self.oneLineNode.textNode.isUserInteractionEnabled = false - self.actionButtons = AttachmentTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, presentController: presentController) + self.actionButtons = AttachmentTextInputActionButtonsNode(presentationInterfaceState: presentationInterfaceState, glass: glass, presentController: presentController) self.counterTextNode = ImmediateTextNode() self.counterTextNode.textAlignment = .center @@ -441,7 +456,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.addSubnode(self.textInputContainer) self.addSubnode(self.textInputBackgroundNode) - self.textInputBackgroundNode.addSubnode(self.textInputBackgroundImageNode) + + if !glass { + self.textInputBackgroundNode.addSubnode(self.textInputBackgroundImageNode) + } self.addSubnode(self.textPlaceholderNode) @@ -589,7 +607,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS if let presentationInterfaceState = self.presentationInterfaceState { refreshChatTextInputTypingAttributes(textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize) - textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState) + textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) } if !self.textInputContainer.bounds.size.width.isZero { @@ -628,7 +646,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics) -> CGFloat { let textFieldInsets = self.textFieldInsets(metrics: metrics) - return max(33.0, maxHeight - (textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)) + return max(self.glass ? 40.0 : 33.0, maxHeight - (textFieldInsets.top + textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)) } private func calculateTextFieldMetrics(width: CGFloat, maxHeight: CGFloat, metrics: LayoutMetrics) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) { @@ -641,8 +659,8 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS var textFieldMinHeight: CGFloat = 35.0 var textFieldRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { - textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) - textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) + textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) + textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) } let textFieldHeight: CGFloat @@ -666,7 +684,12 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS private func textFieldInsets(metrics: LayoutMetrics) -> UIEdgeInsets { var insets = UIEdgeInsets(top: 6.0, left: 6.0, bottom: 6.0, right: 42.0) - if case .regular = metrics.widthClass, case .regular = metrics.heightClass { + if self.glass { + insets.left = 8.0 + insets.top = 0.0 + insets.bottom = 0.0 + insets.right += 3.0 + } else if case .regular = metrics.widthClass, case .regular = metrics.heightClass { insets.top += 1.0 insets.bottom += 1.0 } @@ -680,7 +703,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { - let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics) + let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, glass: self.glass, metrics: metrics) var minimalHeight: CGFloat = 14.0 + textFieldMinHeight if case .regular = metrics.widthClass, case .regular = metrics.heightClass { minimalHeight += 2.0 @@ -689,6 +712,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard self.isUserInteractionEnabled else { + return nil + } if !self.inputModeView.isHidden, let result = self.inputModeView.hitTest(self.view.convert(point, to: self.inputModeView), with: event) { return result } @@ -703,7 +729,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let leftInset = leftInset + 8.0 let rightInset = rightInset + 8.0 - + var transition = transition if let previousAdditionalSideInsets = previousAdditionalSideInsets, previousAdditionalSideInsets.right != additionalSideInsets.right { if case .animated = transition { @@ -758,7 +784,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics) + let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, glass: self.glass, metrics: metrics) let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight let backgroundColor: UIColor @@ -828,9 +854,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS } } - var textFieldMinHeight: CGFloat = 33.0 + var textFieldMinHeight: CGFloat = self.glass ? 40.0 : 33.0 if let presentationInterfaceState = self.presentationInterfaceState { - textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) + textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) } let minimalHeight: CGFloat = 14.0 + textFieldMinHeight @@ -852,7 +878,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS var textInputViewRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { - textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) + textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) } if self.isCaption { @@ -922,7 +948,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom)) let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size if let presentationInterfaceState = self.presentationInterfaceState { - textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState) + textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) } transition.updateFrame(node: textInputNode, frame: textFieldFrame) if shouldUpdateLayout { @@ -932,6 +958,10 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS self.actionButtons.updateAccessibility() + if self.glass { + panelHeight += 11.0 + } + return panelHeight } @@ -943,11 +973,14 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let leftInset = leftInsetValue + 8.0 let rightInset = rightInsetValue + 8.0 - var textFieldMinHeight: CGFloat = 33.0 + var textFieldMinHeight: CGFloat = self.glass ? 40.0 : 33.0 if let presentationInterfaceState = self.presentationInterfaceState { - textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) + textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) + } + var minimalHeight: CGFloat = textFieldMinHeight + if !self.glass { + minimalHeight += 14.0 } - let minimalHeight: CGFloat = 14.0 + textFieldMinHeight var panelHeight = panelHeight var composeButtonsOffset: CGFloat = 0.0 @@ -983,16 +1016,18 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS isMinimized = !self.isAttachment || inputHasText text = presentationInterfaceState.strings.MediaPicker_Send } - actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, minimized: isMinimized, text: text, interfaceState: presentationInterfaceState) + actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, minimized: isMinimized && !self.glass, text: text, interfaceState: presentationInterfaceState) textBackgroundInset = actionButtonsSize.width - 44.0 } else { actionButtonsSize = CGSize(width: 44.0, height: minimalHeight) } - let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - actionButtonsSize.width + 1.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight), size: actionButtonsSize) + let actionButtonsOriginOffset: CGFloat = self.glass ? -3.0 : 0.0 + let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - actionButtonsSize.width + 1.0 - UIScreenPixel + composeButtonsOffset + actionButtonsOriginOffset, y: panelHeight - minimalHeight), size: actionButtonsSize) transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame) - let textInputBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: textInputFrame.size.height)) + let textInputHeight = panelHeight - textFieldInsets.top - textFieldInsets.bottom + let textInputBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: textInputHeight)) transition.updateFrame(node: self.textInputContainerBackgroundNode, frame: textInputBackgroundFrame) transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + composeButtonsOffset - textBackgroundInset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)) @@ -1000,13 +1035,13 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS var textInputViewRealInsets = UIEdgeInsets() if let presentationInterfaceState = self.presentationInterfaceState { - textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState) + textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState, glass: self.glass) var colors: [String: UIColor] = [:] let colorKeys: [String] = [ "__allcolors__" ] - let color = defaultDarkPresentationTheme.chat.inputPanel.inputControlColor + let color = self.theme?.chat.inputPanel.inputControlColor ?? defaultDarkPresentationTheme.chat.inputPanel.inputControlColor for colorKey in colorKeys { colors[colorKey] = color } @@ -1028,7 +1063,11 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS environment: {}, containerSize: CGSize(width: 32.0, height: 32.0) ) - transition.updateFrame(view: self.inputModeView, frame: CGRect(origin: CGPoint(x: textInputBackgroundFrame.maxX - inputNodeSize.width - 1.0, y: textInputBackgroundFrame.maxY - inputNodeSize.height - 1.0), size: inputNodeSize)) + var inputNodeOffset: CGPoint = CGPoint(x: -1.0, y: -1.0) + if self.glass { + inputNodeOffset = CGPoint(x: -4.0, y: -4.0) + } + transition.updateFrame(view: self.inputModeView, frame: CGRect(origin: CGPoint(x: textInputBackgroundFrame.maxX - inputNodeSize.width + inputNodeOffset.x, y: textInputBackgroundFrame.maxY - inputNodeSize.height + inputNodeOffset.y), size: inputNodeSize)) } let placeholderFrame: CGRect @@ -1294,7 +1333,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) var textFieldMinHeight: CGFloat = 33.0 if let presentationInterfaceState = self.presentationInterfaceState { - textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics) + textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, glass: self.glass, metrics: metrics) } let minimalHeight: CGFloat = 14.0 + textFieldMinHeight diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD index 9a116297b8..d29a2ee478 100644 --- a/submodules/AttachmentUI/BUILD +++ b/submodules/AttachmentUI/BUILD @@ -42,6 +42,8 @@ swift_library( "//submodules/ReactionSelectionNode", "//submodules/TelegramUI/Components/Chat/TopMessageReactions", "//submodules/TelegramUI/Components/MinimizedContainer", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 627547891c..8282e36401 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -8,6 +8,8 @@ import DirectionalPanGesture import TelegramPresentationData import MapKit import WebKit +import ComponentFlow +import EdgeEffect private let overflowInset: CGFloat = 0.0 @@ -27,10 +29,13 @@ public func attachmentDefaultTopInset(layout: ContainerViewLayout?) -> CGFloat { } final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { + private let glass: Bool let wrappingNode: ASDisplayNode let clipNode: ASDisplayNode + let bottomClipNode: ASDisplayNode let container: NavigationContainer - + private let pillView: UIImageView + private(set) var isReady: Bool = false private(set) var dismissProgress: CGFloat = 0.0 var isReadyUpdated: (() -> Void)? @@ -66,6 +71,12 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { } } + var presentationData: PresentationData { + didSet { + self.pillView.tintColor = self.presentationData.theme.rootController.navigationBar.primaryTextColor + } + } + private var panGestureRecognizer: UIPanGestureRecognizer? var isPanningUpdated: (Bool) -> Void = { _ in } @@ -74,14 +85,19 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { var isInnerPanGestureEnabled: (() -> Bool)? var onExpandAnimationCompleted: () -> Void = {} - init(isFullSize: Bool) { + init(presentationData: PresentationData, isFullSize: Bool, glass: Bool) { + self.presentationData = presentationData self.isFullSize = isFullSize if isFullSize { self.isExpanded = true } + self.glass = glass self.wrappingNode = ASDisplayNode() self.clipNode = ASDisplayNode() + self.bottomClipNode = ASDisplayNode() + self.bottomClipNode.clipsToBounds = true + self.bottomClipNode.cornerRadius = 62.0 var controllerRemovedImpl: ((ViewController) -> Void)? self.container = NavigationContainer(isFlat: false, controllerRemoved: { c in @@ -90,12 +106,18 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { self.container.clipsToBounds = true self.container.overflowInset = overflowInset self.container.shouldAnimateDisappearance = true + + self.pillView = UIImageView() + self.pillView.alpha = 0.2 + self.pillView.image = generateFilledRoundedRectImage(size: CGSize(width: 36.0, height: 5.0), cornerRadius: 2.5, color: .black)?.withRenderingMode(.alwaysTemplate) + self.pillView.tintColor = self.presentationData.theme.rootController.navigationBar.primaryTextColor super.init() self.addSubnode(self.wrappingNode) self.wrappingNode.addSubnode(self.clipNode) - self.clipNode.addSubnode(self.container) + self.clipNode.addSubnode(self.bottomClipNode) + self.bottomClipNode.addSubnode(self.container) self.isReady = self.container.isReady self.container.isReadyUpdated = { [weak self] in @@ -126,6 +148,8 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { panRecognizer.cancelsTouchesInView = true self.panGestureRecognizer = panRecognizer self.wrappingNode.view.addGestureRecognizer(panRecognizer) + + self.clipNode.view.addSubview(self.pillView) } func cancelPanGesture() { @@ -480,13 +504,17 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { }) let modalProgress: CGFloat + let scaleProgress: CGFloat if isLandscape { modalProgress = 0.0 + scaleProgress = 1.0 } else { if self.isFullSize, self.panGestureArguments != nil { modalProgress = 1.0 - min(1.0, max(0.0, -1.0 * self.bounds.minY / defaultTopInset)) + scaleProgress = min(1.0, max(0.0, -1.0 * self.bounds.minY / defaultTopInset)) } else { modalProgress = 1.0 - topInset / defaultTopInset + scaleProgress = topInset / defaultTopInset } } @@ -514,7 +542,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { if isLandscape { self.clipNode.cornerRadius = 0.0 } else { - self.clipNode.cornerRadius = 10.0 + self.clipNode.cornerRadius = self.glass ? 38.0 : 10.0 } if #available(iOS 11.0, *) { @@ -562,7 +590,10 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition containerFrame = unscaledFrame.offsetBy(dx: -overflowInset, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) - clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height) + let topPortion = self.wrappingNode.frame.minY + let clipTopOffset: CGFloat = 2.0 * scaleProgress + UIScreenPixel + + clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY + clipTopOffset, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height - topPortion) } } else { containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: .zero, additionalInsets: .zero, statusBarHeight: isFullscreen ? layout.statusBarHeight : nil, inputHeight: isFullscreen ? layout.inputHeight : nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver) @@ -573,10 +604,21 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { clipFrame = unscaledFrame } transition.updateFrameAsPositionAndBounds(node: self.clipNode, frame: clipFrame) - transition.updateFrameAsPositionAndBounds(node: self.container, frame: CGRect(origin: CGPoint(x: containerFrame.minX, y: 0.0), size: containerFrame.size)) + + let clipDeltaScale: CGFloat = 1.0 - (clipFrame.width - 10.0) / clipFrame.width + let clipScale = 1.0 - clipDeltaScale + clipDeltaScale * (1.0 - scaleProgress) + transition.updateTransformScale(node: self.clipNode, scale: CGPoint(x: clipScale, y: clipScale)) + + transition.updateFrameAsPositionAndBounds(node: self.bottomClipNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -100.0), size: CGSize(width: clipFrame.size.width, height: self.clipNode.bounds.height + 100.0))) + transition.updateFrameAsPositionAndBounds(node: self.container, frame: CGRect(origin: CGPoint(x: containerFrame.minX, y: 100.0), size: containerFrame.size)) transition.updateTransformScale(node: self.container, scale: containerScale) self.container.update(layout: containerLayout, canBeClosed: true, controllers: controllers, transition: transition) + if let image = self.pillView.image { + transition.updateFrame(view: self.pillView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((clipFrame.width - image.size.width) / 2.0), y: 5.0), size: image.size)) + self.pillView.isHidden = layout.metrics.isTablet + } + self.isUpdatingState = false } @@ -647,7 +689,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate { } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - let convertedPoint = self.view.convert(point, to: self.container.view) + let convertedPoint = self.view.convert(point, to: self.bottomClipNode.view) if !self.container.frame.contains(convertedPoint) { return false } diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index e77adda347..c1301ffdc9 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -4,6 +4,7 @@ import Display import AsyncDisplayKit import SwiftSignalKit import Postbox +import ComponentFlow import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -16,6 +17,8 @@ import LegacyMessageInputPanelInputView import AttachmentTextInputPanelNode import ChatSendMessageActionUI import MinimizedContainer +import ComponentFlow +import GlassBackgroundComponent public enum AttachmentButtonType: Equatable { case gallery @@ -308,42 +311,74 @@ public extension AttachmentMediaPickerContext { } } -private func generateShadowImage() -> UIImage? { - return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in +private func generateShadowImage(glass: Bool) -> UIImage? { + let shadowInset: CGFloat = 60.0 + let middle: CGFloat = 10.0 + let innerSide = glass ? 62.0 * 2.0 : 10.0 * 2.0 + let side = innerSide + middle + shadowInset * 2.0 + return generateImage(CGSize(width: side, height: side), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.saveGState() - context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor) + context.setShadow(offset: CGSize(), blur: glass ? 80.0 : 60.0, color: UIColor(white: 0.0, alpha: glass ? 0.3 : 0.4).cgColor) - let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 10.0).cgPath - context.addPath(path) - context.fillPath() - - context.restoreGState() - - context.setBlendMode(.clear) - context.addPath(path) - context.fillPath() - })?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70) + if glass { + let firstPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide + middle), cornerRadius: 62.0).cgPath + let secondPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide * 0.65), cornerRadius: 38.0).cgPath + context.addPath(firstPath) + context.addPath(secondPath) + context.fillPath() + + context.restoreGState() + + context.setBlendMode(.clear) + context.addPath(firstPath) + context.addPath(secondPath) + context.fillPath() + } else { + let path = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide, height: innerSide), cornerRadius: 10.0).cgPath + context.addPath(path) + context.fillPath() + + context.restoreGState() + + context.setBlendMode(.clear) + context.addPath(path) + context.fillPath() + } + })?.stretchableImage(withLeftCapWidth: Int(side / 2.0), topCapHeight: Int(side / 2.0)) } -private func generateMaskImage() -> UIImage? { +private func generateMaskImage(glass: Bool) -> UIImage? { return generateImage(CGSize(width: 390.0, height: 220.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor.white.cgColor) - let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath - context.addPath(path) - context.fillPath() - + if glass { + let firstPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 62.0).cgPath + let secondPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 130.0), cornerRadius: 38.0).cgPath + context.addPath(firstPath) + context.addPath(secondPath) + context.fillPath() + } else { + let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath + context.addPath(path) + context.fillPath() + } try? drawSvgPath(context, path: "M183.219,208.89 H206.781 C205.648,208.89 204.567,209.371 203.808,210.214 L197.23,217.523 C196.038,218.848 193.962,218.848 192.77,217.523 L186.192,210.214 C185.433,209.371 184.352,208.89 183.219,208.89 Z ") })?.stretchableImage(withLeftCapWidth: 195, topCapHeight: 110) } public class AttachmentController: ViewController, MinimizableController { + public enum Style { + case glass + case legacy + } + private let context: AccountContext private let updatedPresentationData: (initial: PresentationData, signal: Signal)? + private let style: Style private let chatLocation: ChatLocation? private let isScheduledMessages: Bool private var buttons: [AttachmentButtonType] @@ -380,6 +415,8 @@ public class AttachmentController: ViewController, MinimizableController { public var isFullscreen: Bool { return self.mainController.isFullscreen } + + public weak var attachmentButton: UIView? private final class Node: ASDisplayNode { private weak var controller: AttachmentController? @@ -426,7 +463,7 @@ public class AttachmentController: ViewController, MinimizableController { if let strongSelf = self { strongSelf.panel.updateLoadingProgress(progress) if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring)) } } })) @@ -441,7 +478,7 @@ public class AttachmentController: ViewController, MinimizableController { if let strongSelf = self { strongSelf.panel.updateMainButtonState(mainButtonState) if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring)) } } }) @@ -458,7 +495,7 @@ public class AttachmentController: ViewController, MinimizableController { if let strongSelf = self { strongSelf.panel.updateSecondaryButtonState(mainButtonState) if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring)) } } }) @@ -491,12 +528,21 @@ public class AttachmentController: ViewController, MinimizableController { private let wrapperNode: ASDisplayNode + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + private var isMinimizing = false init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.controller = controller self.makeEntityInputView = makeEntityInputView + if let updatedPresentationData = controller.updatedPresentationData { + self.presentationData = updatedPresentationData.initial + } else { + self.presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } + } + self.dim = ASDisplayNode() self.dim.alpha = 0.0 self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) @@ -507,9 +553,18 @@ public class AttachmentController: ViewController, MinimizableController { self.wrapperNode = ASDisplayNode() self.wrapperNode.clipsToBounds = true - self.container = AttachmentContainer(isFullSize: controller.isFullSize) + self.container = AttachmentContainer(presentationData: self.presentationData, isFullSize: controller.isFullSize, glass: controller.style == .glass) self.container.canHaveKeyboardFocus = true - self.panel = AttachmentPanel(controller: controller, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView) + + let panelStyle: AttachmentPanel.Style + switch controller.style { + case .glass: + panelStyle = .glass + case .legacy: + panelStyle = .legacy + } + + self.panel = AttachmentPanel(controller: controller, style: panelStyle, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView) self.panel.fromMenu = controller.fromMenu self.panel.isStandalone = controller.isStandalone @@ -529,10 +584,10 @@ public class AttachmentController: ViewController, MinimizableController { } self.container.updateModalProgress = { [weak self] progress, topInset, bounds, transition in - if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing { + if let strongSelf = self, let controller = strongSelf.controller, let layout = strongSelf.validLayout, !strongSelf.isDismissing { var transition = transition if strongSelf.container.supernode == nil { - transition = .animated(duration: 0.4, curve: .spring) + transition = .animated(duration: 0.5, curve: .spring) } strongSelf.modalProgress = progress @@ -540,33 +595,37 @@ public class AttachmentController: ViewController, MinimizableController { strongSelf.controller?.minimizedBounds = bounds if !strongSelf.isMinimizing { - strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition) + if controller.style != .glass { + strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition) + } strongSelf.containerLayoutUpdated(layout, transition: transition) } } } self.container.isReadyUpdated = { [weak self] in if let strongSelf = self, let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring)) } } self.container.interactivelyDismissed = { [weak self] velocity in - if let strongSelf = self, let layout = strongSelf.validLayout { - if let controller = strongSelf.controller, controller.shouldMinimizeOnSwipe?(strongSelf.currentType) == true { - var delta = layout.size.height - if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset { - delta -= minimizedTopEdgeOffset - } - let damping: CGFloat = 180.0 - let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0 - - strongSelf.minimize(damping: damping, initialVelocity: initialVelocity) - - return false - } else { - strongSelf.controller?.dismiss(animated: true) - } + guard let self, let controller = self.controller, let layout = self.validLayout else { + return true + } + let damping: CGFloat = 180.0 + var delta = layout.size.height + if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset { + delta -= minimizedTopEdgeOffset + } + let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0 + + if controller.shouldMinimizeOnSwipe?(self.currentType) == true { + self.minimize(damping: damping, initialVelocity: initialVelocity) + return false + } else { + self.animateOut(damping: damping, initialVelocity: initialVelocity, completion: { + self.controller?.dismiss(animated: false) + }) } return true } @@ -637,7 +696,7 @@ public class AttachmentController: ViewController, MinimizableController { self.panel.beganTextEditing = { [weak self] in if let strongSelf = self { - strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.5, curve: .spring)) } } @@ -699,6 +758,17 @@ public class AttachmentController: ViewController, MinimizableController { return currentController.getCurrentSendMessageContextMediaPreview?() } + + if let updatedPresentationData = controller.updatedPresentationData { + self.presentationDataDisposable = (updatedPresentationData.signal + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + guard let self else { + return + } + self.presentationData = presentationData + self.container.presentationData = presentationData + }) + } } deinit { @@ -708,6 +778,7 @@ public class AttachmentController: ViewController, MinimizableController { self.mainButtonStateDisposable.dispose() self.secondaryButtonStateDisposable.dispose() self.bottomPanelBackgroundColorDisposable.dispose() + self.presentationDataDisposable?.dispose() } private var inputContainerHeight: CGFloat? @@ -783,7 +854,7 @@ public class AttachmentController: ViewController, MinimizableController { fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) { self.selectionCount = count if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate) + self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) } } @@ -808,7 +879,7 @@ public class AttachmentController: ViewController, MinimizableController { func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool { guard self.currentType != type else { - if self.animating { + if self.isAnimating { return false } if let controller = self.currentControllers.last { @@ -822,12 +893,15 @@ public class AttachmentController: ViewController, MinimizableController { self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in if let strongSelf = self { if let controller = controller { + if case .glass = strongSelf.controller?.style { + controller._hasGlassStyle = true + } strongSelf.controller?._ready.set(controller.ready.get()) controller._presentedInModal = true controller.navigation_setPresenting(strongSelf.controller) controller.requestAttachmentMenuExpansion = { [weak self] in if let strongSelf = self, !strongSelf.container.isTracking { - strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.5, curve: .spring)) } } controller.updateNavigationStack = { [weak self] f in @@ -836,7 +910,7 @@ public class AttachmentController: ViewController, MinimizableController { strongSelf.currentControllers = controllers strongSelf.mediaPickerContext = mediaPickerContext if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring)) + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring)) } } } @@ -902,9 +976,9 @@ public class AttachmentController: ViewController, MinimizableController { guard let snapshotView = self.container.container.view.snapshotView(afterScreenUpdates: false) else { return } - + snapshotView.alpha = 0.0 snapshotView.frame = self.container.container.frame - self.container.clipNode.view.addSubview(snapshotView) + self.container.bottomClipNode.view.insertSubview(snapshotView, at: self.container.bottomClipNode.view.subviews.count) let _ = (controller.ready.get() |> filter { @@ -915,22 +989,22 @@ public class AttachmentController: ViewController, MinimizableController { guard let strongSelf = self, let layout = strongSelf.validLayout else { return } - + let _ = layout if case .compact = layout.metrics.widthClass { - let offset = 25.0 + let offset = 10.0 - let initialPosition = strongSelf.container.clipNode.layer.position + let initialPosition = strongSelf.container.wrappingNode.layer.position let targetPosition = initialPosition.offsetBy(dx: 0.0, dy: offset) var startPosition = initialPosition - if let presentation = strongSelf.container.clipNode.layer.presentation() { + if let presentation = strongSelf.container.wrappingNode.layer.presentation() { startPosition = presentation.position } - strongSelf.container.clipNode.layer.animatePosition(from: startPosition, to: targetPosition, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in + strongSelf.container.wrappingNode.layer.animatePosition(from: startPosition, to: targetPosition, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in if let strongSelf = self, finished { - strongSelf.container.clipNode.layer.animateSpring(from: NSValue(cgPoint: targetPosition), to: NSValue(cgPoint: initialPosition), keyPath: "position", duration: 0.4, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, completion: { [weak self] finished in + strongSelf.container.wrappingNode.layer.animateSpring(from: NSValue(cgPoint: targetPosition), to: NSValue(cgPoint: initialPosition), keyPath: "position", duration: 0.3, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, completion: { [weak self] finished in if finished { - self?.container.clipNode.layer.removeAllAnimations() + self?.container.wrappingNode.layer.removeAllAnimations() } }) } @@ -944,13 +1018,13 @@ public class AttachmentController: ViewController, MinimizableController { }) } - private var animating = false + private var isAnimating = false func animateIn() { guard let layout = self.validLayout, let controller = self.controller else { return } - self.animating = true + self.isAnimating = true if case .regular = layout.metrics.widthClass { if controller.animateAppearance { let targetPosition = self.position @@ -960,30 +1034,103 @@ public class AttachmentController: ViewController, MinimizableController { let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) transition.animateView(allowUserInteraction: true, { self.position = targetPosition - }, completion: { _ in - self.animating = false + }, completion: { _ in + self.isAnimating = false }) } else { - self.animating = false + self.isAnimating = false } ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1) } else { ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) - let targetPosition = self.container.position - let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height) - - self.container.position = startPosition - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - transition.animateView(allowUserInteraction: true, { - self.container.position = targetPosition - }, completion: { _ in - self.animating = false - }) + if case .glass = controller.style, let attachmentButton = controller.attachmentButton { + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .customSpring(damping: 115.0, initialVelocity: 0.0)) + let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut) + + let targetFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view) + + let sourceButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view) + let sourceButtonScale = sourceButtonFrame.width / targetFrame.width + + if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params { + let containerView = UIView() + containerView.clipsToBounds = true + containerView.frame = targetFrame + if #available(iOS 26.0, *) { + containerView.cornerConfiguration = .uniformCorners(radius: .fixed(containerView.bounds.width * 0.5)) + } else { + containerView.layer.cornerRadius = containerView.bounds.width * 0.5 + } + self.view.addSubview(containerView) + + let localGlassView = GlassBackgroundView() + localGlassView.update( + size: targetFrame.size, + cornerRadius: 0.0, + isDark: glassParams.isDark, + tintColor: glassParams.tintColor, + transition: .immediate + ) + localGlassView.frame = CGRect(origin: .zero, size: targetFrame.size) + containerView.addSubview(localGlassView) + + let initialContainerBounds = self.container.bounds + let initialContainerFrame = self.container.frame + + let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view) + self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size) + self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels ((targetFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size) + self.container.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + containerView.addSubnode(self.container) + + let buttonIcon = GlassBackgroundView.ContentImageView() + let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } + buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme) + buttonIcon.tintColor = presentationData.theme.chat.inputPanel.panelControlColor + if let image = buttonIcon.image { + buttonIcon.bounds = CGRect(origin: .zero, size: image.size) + buttonIcon.center = CGPoint(x: targetFrame.width * 0.5, y: targetFrame.height * 0.5) + buttonIcon.transform = CGAffineTransformMakeScale(1.0 / sourceButtonScale, 1.0 / sourceButtonScale) + } + localGlassView.contentView.addSubview(buttonIcon) + ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 0.0, toRadius: 10.0) + + transition.animateBounds(layer: containerView.layer, from: CGRect(origin: CGPoint(x: 0.0, y: (targetFrame.height - targetFrame.width) * 0.5), size: CGSize(width: targetFrame.width, height: targetFrame.width))) + ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).animateView { + if #available(iOS 26.0, *) { + containerView.cornerConfiguration = .corners(topLeftRadius: 38.0, topRightRadius: 38.0, bottomLeftRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0), bottomRightRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0)) + } else { + containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0 + } + } + transition.animateTransformScale(view: containerView, from: sourceButtonScale) + transition.animatePosition(layer: containerView.layer, from: sourceButtonFrame.center, to: containerView.center, completion: { _ in + self.container.bounds = initialContainerBounds + self.container.frame = initialContainerFrame + self.wrapperNode.addSubnode(self.container) + containerView.removeFromSuperview() + sourceGlassView.isHidden = false + self.isAnimating = false + }) + sourceGlassView.isHidden = true + } + } else { + let targetPosition = self.container.position + let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height) + + self.container.position = startPosition + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + transition.animateView(allowUserInteraction: true, { + self.container.position = targetPosition + }, completion: { _ in + self.isAnimating = false + }) + } } } - func animateOut(completion: @escaping () -> Void = {}) { + func animateOut(damping: CGFloat? = nil, initialVelocity: CGFloat? = nil, completion: @escaping () -> Void = {}) { guard let controller = self.controller else { return } @@ -993,28 +1140,102 @@ public class AttachmentController: ViewController, MinimizableController { return } - self.animating = true - if case .regular = layout.metrics.widthClass { + self.isAnimating = true + + switch layout.metrics.widthClass { + case .regular: self.layer.allowsGroupOpacity = true self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in let _ = self?.container.dismiss(transition: .immediate, completion: completion) - self?.animating = false + self?.isAnimating = false self?.layer.removeAllAnimations() }) - } else { - let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) - positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in - let _ = self?.container.dismiss(transition: .immediate, completion: completion) - self?.animating = false - }) + case .compact: let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) - self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) - - if controller.fromMenu && self.hasButton, let (_, _, getTransition) = controller.getInputContainerNode(), let inputTransition = getTransition() { - self.panel.animateTransitionOut(inputTransition: inputTransition, dismissed: true, transition: positionTransition) - self.containerLayoutUpdated(layout, transition: positionTransition) + if case .glass = controller.style, let attachmentButton = controller.attachmentButton { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: damping ?? 124.0, initialVelocity: initialVelocity ?? 0.0)) + let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) + + let initialFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view) + + let targetButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view) + let targetButtonScale = targetButtonFrame.width / initialFrame.width + + if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params { + let containerView = UIView() + containerView.clipsToBounds = true + containerView.frame = initialFrame + if #available(iOS 26.0, *) { + containerView.cornerConfiguration = .corners(topLeftRadius: 38.0, topRightRadius: 38.0, bottomLeftRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0), bottomRightRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0)) + } else { + containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0 + } + self.view.addSubview(containerView) + + let localGlassView = GlassBackgroundView() + localGlassView.update( + size: initialFrame.size, + cornerRadius: 0.0, + isDark: glassParams.isDark, + tintColor: glassParams.tintColor, + transition: .immediate + ) + localGlassView.frame = CGRect(origin: .zero, size: initialFrame.size) + containerView.addSubview(localGlassView) + + let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view) + self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size) + self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((initialFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size) + self.container.isUserInteractionEnabled = false + self.container.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + containerView.addSubnode(self.container) + + let buttonIcon = GlassBackgroundView.ContentImageView() + let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 } + buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme) + buttonIcon.tintColor = presentationData.theme.chat.inputPanel.panelControlColor + if let image = buttonIcon.image { + buttonIcon.bounds = CGRect(origin: .zero, size: image.size) + buttonIcon.center = CGPoint(x: initialFrame.width * 0.5, y: initialFrame.height * 0.5) + buttonIcon.transform = CGAffineTransformMakeScale(1.0 / targetButtonScale, 1.0 / targetButtonScale) + } + localGlassView.contentView.addSubview(buttonIcon) + ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 10.0, toRadius: 0.0) + + transition.updateBounds(layer: containerView.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: (initialFrame.height - initialFrame.width) * 0.5), size: CGSize(width: initialFrame.width, height: initialFrame.width))) + transition.animateView { + if #available(iOS 26.0, *) { + containerView.cornerConfiguration = .uniformCorners(radius: .fixed(containerView.bounds.width * 0.5)) + } else { + containerView.layer.cornerRadius = containerView.bounds.width * 0.5 + } + } + transition.updateTransformScale(layer: containerView.layer, scale: targetButtonScale) + transition.updatePosition(layer: containerView.layer, position: targetButtonFrame.center, completion: { [weak self] _ in + sourceGlassView.isHidden = false + let _ = self?.container.dismiss(transition: .immediate, completion: completion) + self?.isAnimating = false + }) + + sourceGlassView.isHidden = true + } + } else { + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in + let _ = self?.container.dismiss(transition: .immediate, completion: completion) + self?.isAnimating = false + }) + + if controller.style != .glass { + self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) + } + + if controller.fromMenu && self.hasButton, let (_, _, getTransition) = controller.getInputContainerNode(), let inputTransition = getTransition() { + self.panel.animateTransitionOut(inputTransition: inputTransition, dismissed: true, transition: positionTransition) + self.containerLayoutUpdated(layout, transition: positionTransition) + } } } } @@ -1080,9 +1301,9 @@ public class AttachmentController: ViewController, MinimizableController { let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) let position: CGPoint - let positionY = layout.size.height - size.height - insets.bottom - 40.0 + let positionY = layout.size.height - size.height - insets.bottom - 54.0 if let sourceRect = controller.getSourceRect?() { - position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0)), y: min(positionY, sourceRect.minY - size.height)) + position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0) - 2.0), y: min(positionY, sourceRect.minY - size.height)) } else { position = CGPoint(x: masterWidth - 174.0, y: positionY) } @@ -1104,7 +1325,7 @@ public class AttachmentController: ViewController, MinimizableController { self.wrapperNode.cornerRadius = 10.0 } else if self.wrapperNode.view.mask == nil { let maskView = UIImageView() - maskView.image = generateMaskImage() + maskView.image = generateMaskImage(glass: controller.style == .glass) maskView.contentMode = .scaleToFill self.wrapperNode.view.mask = maskView } @@ -1115,7 +1336,7 @@ public class AttachmentController: ViewController, MinimizableController { self.shadowNode.alpha = 1.0 if self.shadowNode.image == nil { - self.shadowNode.image = generateShadowImage() + self.shadowNode.image = generateShadowImage(glass: controller.style == .glass) } } } else { @@ -1148,22 +1369,36 @@ public class AttachmentController: ViewController, MinimizableController { if !self.isPanelVisible { hasPanel = false } - + + var panelOffset: CGFloat = 0.0 + if case .glass = controller.style { + if layout.metrics.isTablet { + panelOffset = 18.0 + } else { + panelOffset = 8.0 + } + } + let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting) let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition) if hasPanel || hasButton { - containerInsets.bottom = panelHeight + containerInsets.bottom = panelHeight + panelOffset } var panelTransition = transition if isEffecitvelyCollapsedUpdated { - panelTransition = .animated(duration: 0.25, curve: .easeInOut) + if case .glass = controller.style { + } else { + panelTransition = .animated(duration: 0.25, curve: .easeInOut) + } } var panelY = containerRect.height - panelHeight + if case .glass = controller.style { + panelY -= panelOffset + } if !hasPanel && !hasButton { panelY = containerRect.height } - panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: containerRect.width, height: panelHeight))) var shadowFrame = containerRect.insetBy(dx: -60.0, dy: -60.0) @@ -1182,7 +1417,7 @@ public class AttachmentController: ViewController, MinimizableController { } let controllers = self.currentControllers - if !self.animating { + if !self.isAnimating { containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) } @@ -1190,9 +1425,9 @@ public class AttachmentController: ViewController, MinimizableController { self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition) - if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing { + if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing && !self.isAnimating { self.wrapperNode.addSubnode(self.container) - + if fromMenu, let _ = controller.getInputContainerNode() { self.addSubnode(self.panel) } else { @@ -1220,6 +1455,7 @@ public class AttachmentController: ViewController, MinimizableController { public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + style: Style = .legacy, chatLocation: ChatLocation?, isScheduledMessages: Bool = false, buttons: [AttachmentButtonType], @@ -1231,6 +1467,7 @@ public class AttachmentController: ViewController, MinimizableController { { self.context = context self.updatedPresentationData = updatedPresentationData + self.style = style self.chatLocation = chatLocation self.isScheduledMessages = isScheduledMessages self.buttons = buttons @@ -1242,6 +1479,8 @@ public class AttachmentController: ViewController, MinimizableController { super.init(navigationBarPresentationData: nil) + self._hasGlassStyle = style == .glass + self.statusBar.statusBarStyle = .Ignore self.blocksBackgroundWhenInOverlay = true self.acceptsFocusWhenInOverlay = true @@ -1446,3 +1685,12 @@ public class AttachmentController: ViewController, MinimizableController { return snapshotView } } + +private func findParentGlassBackgroundView(_ view: UIView) -> GlassBackgroundView? { + if let view = view as? GlassBackgroundView { + return view + } else if let superview = view.superview { + return findParentGlassBackgroundView(superview) + } + return nil +} diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index ed89869a34..30383b408b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -23,8 +23,11 @@ import LegacyMessageInputPanel import LegacyMessageInputPanelInputView import ReactionSelectionNode import TopMessageReactions +import GlassBackgroundComponent -private let buttonSize = CGSize(width: 88.0, height: 49.0) +private let legacyButtonSize = CGSize(width: 88.0, height: 49.0) +private let glassButtonSize = CGSize(width: 72.0, height: 62.0) +private let smallGlassButtonSize = CGSize(width: 70.0, height: 62.0) private let smallButtonWidth: CGFloat = 69.0 private let iconSize = CGSize(width: 30.0, height: 30.0) private let sideInset: CGFloat = 3.0 @@ -124,7 +127,12 @@ private final class IconComponent: Component { private final class AttachButtonComponent: CombinedComponent { + enum Style { + case glass + case legacy + } let context: AccountContext + let style: Style let type: AttachmentButtonType let isSelected: Bool let strings: PresentationStrings @@ -134,6 +142,7 @@ private final class AttachButtonComponent: CombinedComponent { init( context: AccountContext, + style: Style, type: AttachmentButtonType, isSelected: Bool, strings: PresentationStrings, @@ -142,6 +151,7 @@ private final class AttachButtonComponent: CombinedComponent { longPressAction: @escaping () -> Void ) { self.context = context + self.style = style self.type = type self.isSelected = isSelected self.strings = strings @@ -154,6 +164,9 @@ private final class AttachButtonComponent: CombinedComponent { if lhs.context !== rhs.context { return false } + if lhs.style != rhs.style { + return false + } if lhs.type != rhs.type { return false } @@ -227,11 +240,21 @@ private final class AttachButtonComponent: CombinedComponent { imageName = "Chat/Attach Menu/Reply" } - let tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor + let tintColor: UIColor + switch component.style { + case .glass: + tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor + case .legacy: + tintColor = component.isSelected ? component.theme.rootController.tabBar.selectedIconColor : component.theme.rootController.tabBar.iconColor + } let iconSize = CGSize(width: 30.0, height: 30.0) - let topInset: CGFloat = 4.0 + UIScreenPixel - let spacing: CGFloat = 15.0 + UIScreenPixel + var topInset: CGFloat = 4.0 + UIScreenPixel + var spacing: CGFloat = 15.0 + UIScreenPixel + if case .glass = component.style { + topInset += 5.0 + spacing += UIScreenPixel + } let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - iconSize.width) / 2.0), y: topInset), size: iconSize) if let animationFile = animationFile { @@ -275,11 +298,19 @@ private final class AttachButtonComponent: CombinedComponent { ) } + let titleFont: UIFont + switch component.style { + case .glass: + titleFont = Font.medium(10.0) + case .legacy: + titleFont = Font.regular(10.0) + } + let title = title.update( component: MultilineTextComponent( text: .plain(NSAttributedString( string: name, - font: Font.regular(10.0), + font: titleFont, textColor: context.component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor, paragraphAlignment: .center)), horizontalAlignment: .center, @@ -476,6 +507,7 @@ private final class BadgeNode: ASDisplayNode { private final class MainButtonNode: HighlightTrackingButtonNode { private var state: AttachmentMainButtonState + private var panelStyle: AttachmentPanel.Style? private var size: CGSize? private let backgroundAnimationNode: ASImageNode @@ -512,13 +544,13 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.addSubnode(self.statusNode) self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self, strongSelf.state.isEnabled { + if let self, self.state.isEnabled { if highlighted { - strongSelf.layer.removeAnimation(forKey: "opacity") - strongSelf.alpha = 0.65 + self.layer.removeAnimation(forKey: "opacity") + self.alpha = 0.65 } else { - strongSelf.alpha = 1.0 - strongSelf.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2) + self.alpha = 1.0 + self.layer.animateAlpha(from: 0.65, to: 1.0, duration: 0.2) } } } @@ -527,7 +559,6 @@ private final class MainButtonNode: HighlightTrackingButtonNode { override func didLoad() { super.didLoad() - self.cornerRadius = 12.0 if #available(iOS 13.0, *) { self.layer.cornerCurve = .continuous } @@ -624,8 +655,8 @@ private final class MainButtonNode: HighlightTrackingButtonNode { self.updateShimmerParameters() - if let size = self.size { - self.updateLayout(size: size, state: state, transition: .immediate) + if let size = self.size, let style = self.panelStyle { + self.updateLayout(size: size, style: style, state: state, transition: .immediate) } } } else if self.shimmerView != nil { @@ -695,11 +726,19 @@ private final class MainButtonNode: HighlightTrackingButtonNode { } } - func updateLayout(size: CGSize, state: AttachmentMainButtonState, animateBackground: Bool = false, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, style: AttachmentPanel.Style, state: AttachmentMainButtonState, animateBackground: Bool = false, transition: ContainedViewLayoutTransition) { let previousState = self.state self.state = state + self.panelStyle = style self.size = size + switch style { + case .glass: + self.cornerRadius = size.height * 0.5 + case .legacy: + self.cornerRadius = 12.0 + } + self.isUserInteractionEnabled = state.isVisible self.setupShimmering() @@ -842,6 +881,13 @@ private final class MainButtonNode: HighlightTrackingButtonNode { } final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { + enum Style { + case glass + case legacy + } + + private let panelStyle: Style + private weak var controller: AttachmentController? private let context: AccountContext private let isScheduledMessages: Bool @@ -858,9 +904,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { private let makeEntityInputView: () -> AttachmentTextInputPanelInputView? private let containerNode: ASDisplayNode + private var backgroundView: GlassBackgroundView? private let backgroundNode: NavigationBackgroundNode private let scrollNode: ASScrollNode private let separatorNode: ASDisplayNode + + private let selectionNode: ASImageNode private var buttonViews: [AnyHashable: ComponentHostView] = [:] private var textInputPanelNode: AttachmentTextInputPanelNode? @@ -904,10 +953,11 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { var onMainButtonPressed: () -> Void = { } var onSecondaryButtonPressed: () -> Void = { } - - init(controller: AttachmentController, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { + + init(controller: AttachmentController, style: Style, context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) { self.controller = controller self.context = context + self.panelStyle = style self.updatedPresentationData = updatedPresentationData self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.isScheduledMessages = isScheduledMessages @@ -917,9 +967,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) self.containerNode = ASDisplayNode() - self.containerNode.clipsToBounds = true + self.containerNode.clipsToBounds = false self.scrollNode = ASScrollNode() + self.scrollNode.clipsToBounds = true + + self.selectionNode = ASImageNode() self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) self.separatorNode = ASDisplayNode() @@ -931,9 +984,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { super.init() self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.backgroundNode) - self.containerNode.addSubnode(self.separatorNode) - self.containerNode.addSubnode(self.scrollNode) + + switch style { + case .glass: + self.scrollNode.cornerRadius = glassButtonSize.height * 0.5 + self.scrollNode.addSubnode(self.selectionNode) + case .legacy: + self.containerNode.addSubnode(self.backgroundNode) + self.containerNode.addSubnode(self.separatorNode) + self.containerNode.addSubnode(self.scrollNode) + } self.addSubnode(self.secondaryButtonNode) self.addSubnode(self.mainButtonNode) @@ -1294,6 +1354,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { strongSelf.backgroundNode.updateColor(color: strongSelf.customBottomPanelBackgroundColor ?? presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) strongSelf.separatorNode.backgroundColor = presentationData.theme.rootController.tabBar.separatorColor + strongSelf.selectionNode.backgroundColor = presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05) strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) }) @@ -1396,29 +1457,61 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) } + var buttonSize: CGSize { + switch self.panelStyle { + case .glass: + return glassButtonSize + case .legacy: + return legacyButtonSize + } + } + func updateViews(transition: ComponentTransition) { guard let layout = self.validLayout else { return } + let width: CGFloat + switch self.panelStyle { + case .glass: + width = layout.size.width - 44.0 + case .legacy: + width = layout.size.width + } + let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0) - var distanceBetweenNodes = layout.size.width / CGFloat(self.buttons.count) + var distanceBetweenNodes = (width - sideInset * 2.0) / CGFloat(self.buttons.count) let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1) - var leftNodeOriginX = (layout.size.width - internalWidth) / 2.0 - - var buttonWidth = buttonSize.width - if self.buttons.count > 6 && layout.size.width < layout.size.height { - buttonWidth = smallButtonWidth - distanceBetweenNodes = buttonWidth + var buttonWidth = self.buttonSize.width + var leftNodeOriginX: CGFloat + switch self.panelStyle { + case .glass: + leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0 + case .legacy: + leftNodeOriginX = (width - internalWidth) / 2.0 + } + + if self.buttons.count > 5 && layout.size.width < layout.size.height { + switch self.panelStyle { + case .glass: + buttonWidth = smallGlassButtonSize.width + distanceBetweenNodes = 60.0 + case .legacy: + buttonWidth = smallButtonWidth + distanceBetweenNodes = 60.0 + } leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0 } var validIds = Set() + var selectionFrame = CGRect() + var mostRightX = 0.0 for i in 0 ..< self.buttons.count { let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0) - let buttonFrame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: CGSize(width: buttonWidth, height: buttonSize.height)) + let buttonFrame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height)) + mostRightX = buttonFrame.maxX if !visibleRect.intersects(buttonFrame) { continue } @@ -1470,6 +1563,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { transition: buttonTransition, component: AnyComponent(AttachButtonComponent( context: self.context, + style: self.panelStyle == .glass ? .glass : .legacy, type: type, isSelected: i == self.selectedIndex, strings: self.presentationData.strings, @@ -1480,7 +1574,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { strongSelf.selectedIndex = i strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) - if strongSelf.buttons.count > 6, let button = strongSelf.buttonViews[i] { + if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[i] { strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true) } } @@ -1492,8 +1586,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }) ), environment: {}, - containerSize: CGSize(width: buttonWidth, height: buttonSize.height) + containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height) ) + + if i == self.selectedIndex { + selectionFrame = buttonFrame + } buttonTransition.setFrame(view: buttonView, frame: buttonFrame) var accessibilityTitle = "" switch type { @@ -1532,34 +1630,24 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { for id in removeIds { self.buttonViews.removeValue(forKey: id) } - } - - private func updateScrollLayoutIfNeeded(force: Bool, transition: ContainedViewLayoutTransition) -> Bool { - guard let layout = self.validLayout else { - return false - } - if self.scrollLayout?.width == layout.size.width && !force { - return false - } - var contentSize = CGSize(width: layout.size.width, height: buttonSize.height) - var buttonWidth = buttonSize.width - if self.buttons.count > 6 && layout.size.width < layout.size.height { - buttonWidth = smallButtonWidth - contentSize.width = layout.safeInsets.left + layout.safeInsets.right + sideInset * 2.0 + CGFloat(self.buttons.count) * buttonWidth + selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0) + self.selectionNode.cornerRadius = selectionFrame.height * 0.5 + transition.setFrame(view: self.selectionNode.view, frame: selectionFrame) + + mostRightX += layout.safeInsets.right + sideInset + + let contentSize = CGSize(width: mostRightX, height: self.buttonSize.height) + if contentSize != self.scrollNode.view.contentSize { + self.scrollNode.view.contentSize = contentSize + self.scrollNode.view.isScrollEnabled = abs(contentSize.width - self.scrollNode.view.bounds.width) > 1.0 } - self.scrollLayout = (layout.size.width, contentSize) - - transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.isSelecting || self._isButtonVisible ? -buttonSize.height : 0.0), size: CGSize(width: layout.size.width, height: buttonSize.height))) - self.scrollNode.view.contentSize = contentSize - - return true } private func loadTextNodeIfNeeded() { if let _ = self.textInputPanelNode { } else { - let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, isAttachment: true, isScheduledMessages: self.isScheduledMessages, presentController: { [weak self] c in + let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, glass: self.panelStyle == .glass, isAttachment: true, isScheduledMessages: self.isScheduledMessages, presentController: { [weak self] c in if let strongSelf = self { strongSelf.present(c) } @@ -1788,7 +1876,10 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } else { self.textInputPanelNode?.ensureUnfocused() } + + let panelSideInset: CGFloat = isSelecting ? 8.0 : 22.0 var textPanelHeight: CGFloat = 0.0 + var textPanelWidth: CGFloat = 0.0 if let textInputPanelNode = self.textInputPanelNode { textInputPanelNode.isUserInteractionEnabled = isSelecting @@ -1805,16 +1896,52 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { if panelFrame.height > 0.0 { textPanelHeight = panelFrame.height } else { - textPanelHeight = 45.0 + textPanelHeight = self.panelStyle == .glass ? 40.0 : 45.0 } + textPanelWidth = layout.size.width - panelSideInset - 88.0 - 6.0 } - let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: buttonSize.height + insets.bottom)) - var containerTransition: ContainedViewLayoutTransition - let containerFrame: CGRect + let glassPanelHeight: CGFloat = 62.0 + let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom)) + if case .glass = self.panelStyle { + let backgroundView: GlassBackgroundView + if let current = self.backgroundView { + backgroundView = current + } else { + backgroundView = GlassBackgroundView() + self.containerNode.view.addSubview(backgroundView) + self.containerNode.view.addSubview(self.scrollNode.view) + self.backgroundView = backgroundView + } + let panelSize = CGSize(width: isSelecting ? textPanelWidth : layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0, height: isSelecting ? textPanelHeight - 11.0 : glassPanelHeight) + + let backgroundViewColor: UIColor + if self.presentationData.theme.overallDarkAppearance { + backgroundViewColor = self.presentationData.theme.list.modalBlocksBackgroundColor.withAlphaComponent(0.55) + } else { + backgroundViewColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.75) + } + + let backgroundOriginX: CGFloat = isSelecting ? panelSideInset + 8.0 : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0) + backgroundView.update(size: panelSize, cornerRadius: isSelecting ? 20.0 : glassPanelHeight * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: backgroundViewColor), transition: ComponentTransition(transition)) + transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5)) + transition.updateBounds(layer: backgroundView.layer, bounds: CGRect(origin: .zero, size: panelSize)) + } - let sideInset: CGFloat = 16.0 - let buttonHeight: CGFloat = 50.0 + var containerTransition: ContainedViewLayoutTransition + var containerFrame: CGRect + + let buttonSideInset: CGFloat + let buttonSpacing: CGFloat = 16.0 + let buttonHeight: CGFloat + switch self.panelStyle { + case .glass: + buttonHeight = 52.0 + buttonSideInset = 22.0 + case .legacy: + buttonHeight = 50.0 + buttonSideInset = 16.0 + } if isAnyButtonVisible { var height: CGFloat @@ -1833,10 +1960,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { if isTwoVerticalButtons && self.secondaryButtonState.smallSpacing { } else if !isNarrowButton { - height += 9.0 + if case .glass = self.panelStyle { + } else { + height += 9.0 + } } if isTwoVerticalButtons { - height += buttonHeight + sideInset + height += buttonHeight + buttonSpacing } containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height)) } else if isSelecting { @@ -1846,26 +1976,74 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } let containerBounds = CGRect(origin: CGPoint(), size: containerFrame.size) if isSelectingUpdated || isButtonVisibleUpdated { - containerTransition = .animated(duration: 0.25, curve: .easeInOut) + if case .glass = self.panelStyle { + containerTransition = transition + } else { + containerTransition = .animated(duration: 0.25, curve: .easeInOut) + } } else { containerTransition = transition } - containerTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0) + let alphaTransition = ContainedViewLayoutTransition.animated(duration: isSelecting ? 0.1 : 0.25, curve: .easeInOut) + alphaTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0) containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0) + if let backgroundView = self.backgroundView { + containerTransition.updateTransformScale(layer: backgroundView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0) + } if isSelectingUpdated { if isSelecting { self.loadTextNodeIfNeeded() if let textInputPanelNode = self.textInputPanelNode { - textInputPanelNode.alpha = 1.0 - textInputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - textInputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: CGPoint(), duration: 0.25, additive: true) + textInputPanelNode.isUserInteractionEnabled = true + if case .glass = self.panelStyle { + var textInputPanelHeight = textInputPanelNode.frame.height + if textInputPanelHeight < 1.0 { + textInputPanelHeight = 51.0 + } + let heightDelta = glassPanelHeight - textInputPanelHeight + + let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 1.0) + + ComponentTransition.easeInOut(duration: 0.25).animateBlur(layer: self.scrollNode.layer, fromRadius: 0.0, toRadius: 10.0) + + transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, from: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x + 62.0, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta)) + transition.animateTransformScale(node: textInputPanelNode.opaqueActionButtons, from: 0.01) + + transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: CGPoint(x: 6.0, y: heightDelta)) + transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: CGPoint(x: 64.0, y: heightDelta)) + + } else { + textInputPanelNode.alpha = 1.0 + textInputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + textInputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: CGPoint(), duration: 0.25, additive: true) + } } } else { if let textInputPanelNode = self.textInputPanelNode { - textInputPanelNode.alpha = 0.0 - textInputPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) - textInputPanelNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 44.0), duration: 0.25, additive: true) + textInputPanelNode.isUserInteractionEnabled = false + if case .glass = self.panelStyle { + var textInputPanelHeight = textInputPanelNode.frame.height + if textInputPanelHeight < 1.0 { + textInputPanelHeight = 51.0 + } + let heightDelta = glassPanelHeight - textInputPanelHeight + + let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 0.0) + + ComponentTransition.easeInOut(duration: 0.25).animateBlur(layer: self.scrollNode.layer, fromRadius: 10.0, toRadius: 0.0) + + transition.animatePosition(node: textInputPanelNode.opaqueActionButtons, to: CGPoint(x: textInputPanelNode.opaqueActionButtons.position.x + 62.0, y: textInputPanelNode.opaqueActionButtons.position.y + heightDelta)) + transition.animateTransformScale(layer: textInputPanelNode.opaqueActionButtons.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.01, y: 0.01)) + transition.animatePositionAdditive(layer: textInputPanelNode.textPlaceholderNode.layer, offset: .zero, to: CGPoint(x: 6.0, y: heightDelta)) + transition.animatePositionAdditive(layer: textInputPanelNode.inputModeView.layer, offset: .zero, to: CGPoint(x: 64.0, y: heightDelta)) + } else { + textInputPanelNode.alpha = 0.0 + textInputPanelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) + textInputPanelNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 44.0), duration: 0.25, additive: true) + } } } } @@ -1879,7 +2057,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.backgroundNode.update(size: containerBounds.size, transition: transition) containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) - let _ = self.updateScrollLayoutIfNeeded(force: isSelectingUpdated || isButtonVisibleUpdated, transition: containerTransition) + if case .glass = self.panelStyle { + transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - 22.0 : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height))) + } self.updateViews(transition: .immediate) @@ -1904,14 +2084,21 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }) } - var buttonSize = CGSize(width: layout.size.width - (sideInset + layout.safeInsets.left) * 2.0, height: buttonHeight) + var buttonSize = CGSize(width: layout.size.width - (buttonSideInset + layout.safeInsets.left) * 2.0, height: buttonHeight) if isTwoHorizontalButtons { - buttonSize = CGSize(width: (buttonSize.width - sideInset) / 2.0, height: buttonSize.height) + buttonSize = CGSize(width: (buttonSize.width - buttonSideInset) / 2.0, height: buttonSize.height) } - let buttonTopInset: CGFloat = isNarrowButton ? 2.0 : 8.0 + let buttonTopInset: CGFloat + switch self.panelStyle { + case .glass: + buttonTopInset = 5.0 + case .legacy: + buttonTopInset = isNarrowButton ? 2.0 : 8.0 + } + if !self.animatingTransition { - let buttonOriginX = layout.safeInsets.left + sideInset + let buttonOriginX = layout.safeInsets.left + buttonSideInset let buttonOriginY = isAnyButtonVisible || self.fromMenu ? buttonTopInset : containerFrame.height var mainButtonFrame: CGRect? var secondaryButtonFrame: CGRect? @@ -1919,17 +2106,17 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { switch position { case .top: secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) - mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + sideInset + buttonSize.height), size: buttonSize) + mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + buttonSpacing + buttonSize.height), size: buttonSize) case .bottom: mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) - let buttonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : sideInset - secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + buttonSpacing + buttonSize.height), size: buttonSize) + let effectiveButtonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : buttonSpacing + secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + effectiveButtonSpacing + buttonSize.height), size: buttonSize) case .left: secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) - mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + sideInset, y: buttonOriginY), size: buttonSize) + mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + buttonSideInset, y: buttonOriginY), size: buttonSize) case .right: mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize) - secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + sideInset, y: buttonOriginY), size: buttonSize) + secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + buttonSideInset, y: buttonOriginY), size: buttonSize) } } else { if self.mainButtonState.isVisible { @@ -1942,7 +2129,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { if let mainButtonFrame { if !self.dismissed { - self.mainButtonNode.updateLayout(size: buttonSize, state: self.mainButtonState, animateBackground: self.mainButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition) + self.mainButtonNode.updateLayout(size: buttonSize, style: self.panelStyle, state: self.mainButtonState, animateBackground: self.mainButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition) } if self.mainButtonNode.frame.width.isZero { self.mainButtonNode.frame = mainButtonFrame @@ -1955,7 +2142,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } if let secondaryButtonFrame { if !self.dismissed { - self.secondaryButtonNode.updateLayout(size: buttonSize, state: self.secondaryButtonState, animateBackground: self.secondaryButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition) + self.secondaryButtonNode.updateLayout(size: buttonSize, style: self.panelStyle, state: self.secondaryButtonState, animateBackground: self.secondaryButtonState.background.colorValue == self.backgroundNode.color && transition.isAnimated, transition: transition) } if self.secondaryButtonNode.frame.width.isZero { self.secondaryButtonNode.frame = secondaryButtonFrame diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index ff71e9e9cf..1074767824 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -149,7 +149,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth let masterDatacenterId = strongSelf.account.masterDatacenterId let isTestingEnvironment = strongSelf.account.testingEnvironment - let countryCode = AuthorizationSequenceController.defaultCountryCode() + let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: isTestingEnvironment, masterDatacenterId: masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() } @@ -338,7 +338,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth guard let strongSelf = self else { return } - let countryCode = AuthorizationSequenceController.defaultCountryCode() + let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() }) @@ -705,7 +705,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth guard let strongSelf = self else { return } - let countryCode = AuthorizationSequenceController.defaultCountryCode() + let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() }) @@ -772,7 +772,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth guard let self else { return } - let countryCode = AuthorizationSequenceController.defaultCountryCode() + let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() }) return controller @@ -889,7 +889,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth guard let strongSelf = self else { return } - let countryCode = AuthorizationSequenceController.defaultCountryCode() + let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() }) @@ -1034,7 +1034,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth guard let strongSelf = self else { return } - let countryCode = AuthorizationSequenceController.defaultCountryCode() + let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() }) @@ -1094,7 +1094,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth guard let strongSelf = self else { return } - let countryCode = AuthorizationSequenceController.defaultCountryCode() + let countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() let _ = strongSelf.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: strongSelf.account.testingEnvironment, masterDatacenterId: strongSelf.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone() }, displayCancel: displayCancel) @@ -1213,7 +1213,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth if self.otherAccountPhoneNumbers.1.isEmpty { controllers.append(self.splashController()) } else { - controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceController.defaultCountryCode(), number: "", splashController: nil)) + controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceCountrySelectionController.defaultCountryCode(), number: "", splashController: nil)) } self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) } @@ -1241,7 +1241,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth if !self.otherAccountPhoneNumbers.1.isEmpty { controllers.append(self.splashController()) } - controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceController.defaultCountryCode(), number: "", splashController: nil)) + controllers.append(self.phoneEntryController(countryCode: AuthorizationSequenceCountrySelectionController.defaultCountryCode(), number: "", splashController: nil)) var isGoingBack = false if case let .emailSetupRequired(appleSignInAllowed) = type { diff --git a/submodules/CallListUI/Sources/CallListCallItem.swift b/submodules/CallListUI/Sources/CallListCallItem.swift index 970dd08c86..41f076af4d 100644 --- a/submodules/CallListUI/Sources/CallListCallItem.swift +++ b/submodules/CallListUI/Sources/CallListCallItem.swift @@ -65,6 +65,7 @@ private func callListNeighbors(item: ListViewItem, topItem: ListViewItem?, botto class CallListCallItem: ListViewItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let dateTimeFormat: PresentationDateTimeFormat let context: AccountContext let style: ItemListStyle @@ -78,8 +79,9 @@ class CallListCallItem: ListViewItem { let headerAccessoryItem: ListViewAccessoryItem? let header: ListViewItemHeader? - init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, style: ItemListStyle, topMessage: EngineMessage, messages: [EngineMessage], editing: Bool, revealed: Bool, displayHeader: Bool, interaction: CallListNodeInteraction) { + init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, style: ItemListStyle, topMessage: EngineMessage, messages: [EngineMessage], editing: Bool, revealed: Bool, displayHeader: Bool, interaction: CallListNodeInteraction) { self.presentationData = presentationData + self.systemStyle = systemStyle self.dateTimeFormat = dateTimeFormat self.context = context self.style = style @@ -345,6 +347,8 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -550,7 +554,13 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - dateRightInset - dateLayout.size.width - (item.editing ? -30.0 : 10.0)), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let titleSpacing: CGFloat = -1.0 - let verticalInset: CGFloat = 6.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 10.0 + case .legacy: + verticalInset = 6.0 + } let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: titleLayout.size.height + titleSpacing + statusLayout.size.height + verticalInset * 2.0), insets: UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)) @@ -672,12 +682,12 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) - transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrameAdditive(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) } diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index cc6f4e543c..9b248b7a84 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -120,20 +120,20 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item return entries.map { entry -> ListViewInsertItem in switch entry.entry { case let .displayTab(_, text, value): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in nodeInteraction.updateShowCallsTab(value) }), directionHint: entry.directionHint) case let .displayTabInfo(_, text): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint) case .openNewCall: - let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: { + let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, systemStyle: .glass, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: { nodeInteraction.openNewCall() }) return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .groupCall(peer, _, isActive): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, systemStyle: .glass, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint) case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint) case let .holeEntry(_, theme): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListHoleItem(theme: theme), directionHint: entry.directionHint) } @@ -144,20 +144,20 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item return entries.map { entry -> ListViewUpdateItem in switch entry.entry { case let .displayTab(_, text, value): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: true, noCorners: false, sectionId: 0, style: .blocks, updated: { value in nodeInteraction.updateShowCallsTab(value) }), directionHint: entry.directionHint) case let .displayTabInfo(_, text): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: 0), directionHint: entry.directionHint) case .openNewCall: - let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: { + let item = ItemListPeerActionItem(presentationData: presentationData, style: showSettings ? .blocks : .plain, systemStyle: .glass, icon: PresentationResourcesRootController.callListCallIcon(presentationData.theme), title: presentationData.strings.CallList_NewCall, hasSeparator: false, sectionId: 1, height: .generic, noInsets: !showSettings, editing: false, action: { nodeInteraction.openNewCall() }) return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .groupCall(peer, _, isActive): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListGroupCallItem(presentationData: presentationData, systemStyle: .glass, context: context, style: showSettings ? .blocks : .plain, peer: peer, isActive: isActive, editing: false, interaction: nodeInteraction), directionHint: entry.directionHint) case let .messageEntry(topMessage, messages, _, _, dateTimeFormat, editing, hasActiveRevealControls, displayHeader, _): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint) + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListCallItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, context: context, style: showSettings ? .blocks : .plain, topMessage: topMessage, messages: messages, editing: editing, revealed: hasActiveRevealControls, displayHeader: displayHeader, interaction: nodeInteraction), directionHint: entry.directionHint) case let .holeEntry(_, theme): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: CallListHoleItem(theme: theme), directionHint: entry.directionHint) } diff --git a/submodules/CallListUI/Sources/CallListGroupCallItem.swift b/submodules/CallListUI/Sources/CallListGroupCallItem.swift index 592cd360db..5875b22936 100644 --- a/submodules/CallListUI/Sources/CallListGroupCallItem.swift +++ b/submodules/CallListUI/Sources/CallListGroupCallItem.swift @@ -57,6 +57,7 @@ private func callListNeighbors(item: ListViewItem, topItem: ListViewItem?, botto class CallListGroupCallItem: ListViewItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let context: AccountContext let style: ItemListStyle let peer: EnginePeer @@ -68,8 +69,9 @@ class CallListGroupCallItem: ListViewItem { let headerAccessoryItem: ListViewAccessoryItem? let header: ListViewItemHeader? - init(presentationData: ItemListPresentationData, context: AccountContext, style: ItemListStyle, peer: EnginePeer, isActive: Bool, editing: Bool, interaction: CallListNodeInteraction) { + init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, context: AccountContext, style: ItemListStyle, peer: EnginePeer, isActive: Bool, editing: Bool, interaction: CallListNodeInteraction) { self.presentationData = presentationData + self.systemStyle = systemStyle self.context = context self.style = style self.peer = peer diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift index 30689e6729..24e3612de7 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift @@ -23,6 +23,7 @@ enum ChatListFilterCategoryIcon { final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let title: String let icon: ChatListFilterCategoryIcon let isRevealed: Bool @@ -33,6 +34,7 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem { init( presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle, title: String, icon: ChatListFilterCategoryIcon, isRevealed: Bool, @@ -41,6 +43,7 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem { remove: @escaping () -> Void ) { self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title self.icon = icon self.isRevealed = isRevealed @@ -185,11 +188,14 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: titleColor) let leftInset: CGFloat - let verticalInset: CGFloat + var verticalInset: CGFloat = 14.0 let verticalOffset: CGFloat let avatarSize: CGFloat - verticalInset = 14.0 + if case .glass = item.systemStyle { + verticalInset += 4.0 + } + verticalOffset = 0.0 avatarSize = 40.0 leftInset = 65.0 + params.leftInset @@ -208,6 +214,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -340,12 +347,12 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 93d9d7bcdc..30ffe647a5 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -389,6 +389,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { return ItemListFilterTitleInputItem( context: arguments.context, presentationData: presentationData, + systemStyle: .glass, text: value, enableAnimations: enableAnimations, placeholder: placeholder, @@ -407,12 +408,13 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { case .includePeerInfo(let text), .excludePeerInfo(let text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .addIncludePeer(title): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { arguments.openAddIncludePeer() }) case let .includeCategory(_, category, title, isRevealed): return ChatListFilterPresetCategoryItem( presentationData: presentationData, + systemStyle: .glass, title: title, icon: ChatListFilterCategoryIcon(category: category), isRevealed: isRevealed, @@ -429,12 +431,13 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { } ) case let .addExcludePeer(title): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: title, alwaysPlain: false, sectionId: self.section, height: .peerList, editing: false, action: { arguments.openAddExcludePeer() }) case let .excludeCategory(_, category, title, isRevealed): return ChatListFilterPresetCategoryItem( presentationData: presentationData, + systemStyle: .glass, title: title, icon: ChatListFilterCategoryIcon(category: category), isRevealed: isRevealed, @@ -451,7 +454,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { } ) case let .includePeer(_, peer, isRevealed): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { arguments.deleteIncludePeer(peer.peerId) })]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) @@ -465,7 +468,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { arguments.peerContextAction(peer, sourceNode, gesture, nil) }) case let .excludePeer(_, peer, isRevealed): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.chatMainPeer!, height: .peerList, aliasHandling: .threatSelfAsSaved, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: isRevealed), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { arguments.deleteExcludePeer(peer.peerId) })]), switchValue: nil, enabled: true, selectable: false, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) @@ -479,11 +482,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { arguments.peerContextAction(peer, sourceNode, gesture, nil) }) case let .includeExpand(text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: { arguments.expandSection(.include) }) case let .excludeExpand(text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(presentationData.theme), title: text, sectionId: self.section, editing: false, action: { arguments.expandSection(.exclude) }) case let .tagColorHeader(name, color, isPremium): @@ -507,6 +510,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { case let .tagColor(colors, color, isPremium): return PeerNameColorItem( theme: presentationData.theme, + systemStyle: .glass, colors: colors, mode: .folderTag, displayEmptyColor: true, @@ -526,11 +530,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { case .inviteLinkHeader: return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ChatListFilter_SectionShare, badge: nil, sectionId: self.section) case let .inviteLinkCreate(hasLinks): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? presentationData.strings.ChatListFilter_CreateLink : presentationData.strings.ChatListFilter_CreateLinkNew, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? presentationData.strings.ChatListFilter_CreateLink : presentationData.strings.ChatListFilter_CreateLinkNew, sectionId: self.section, editing: false, action: { arguments.createLink() }) case let .inviteLink(_, link): - return ItemListFolderInviteLinkListItem(presentationData: presentationData, invite: link, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in + return ItemListFolderInviteLinkListItem(presentationData: presentationData, systemStyle: .glass, invite: link, share: false, sectionId: self.section, style: .blocks, tapAction: { invite in arguments.openLink(invite) }, removeAction: { invite in arguments.removeLink(invite) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 8b0636d941..56c5c9ba62 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -176,11 +176,11 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { case let .suggestedListHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) case let .suggestedPreset(_, title, label, preset): - return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: { + return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, systemStyle: .glass, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: { arguments.addSuggestedPressed(title, preset) }, tag: nil) case let .suggestedAddCustom(text): - return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, sectionId: self.section, height: .generic, editing: false, action: { arguments.addNew() }) case let .listHeader(text): @@ -194,7 +194,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { } } - return ChatListFilterPresetListItem(context: arguments.context, presentationData: presentationData, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: { + return ChatListFilterPresetListItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: { if isDisabled { arguments.addNew() } else { @@ -206,13 +206,13 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { arguments.removePreset(preset.id) }) case let .addItem(text, isEditing): - return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, editing: isEditing, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, sectionId: self.section, height: .generic, editing: isEditing, action: { arguments.addNew() }) case let .listFooter(text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .displayTags(value): - return ItemListSwitchItem(presentationData: presentationData, title: presentationData.strings.ChatListFilterList_ShowTags, value: value == true, enableInteractiveChanges: value != nil, enabled: true, displayLocked: value == nil, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.ChatListFilterList_ShowTags, value: value == true, enableInteractiveChanges: value != nil, enabled: true, displayLocked: value == nil, sectionId: self.section, style: .blocks, updated: { updatedValue in if value != nil { arguments.updateDisplayTags(updatedValue) } else { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index 01d1063a17..555404425c 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -19,6 +19,7 @@ struct ChatListFilterPresetListItemEditing: Equatable { final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let preset: ChatListFilter let title: ChatFolderTitle let label: String @@ -36,6 +37,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { init( context: AccountContext, presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle, preset: ChatListFilter, title: ChatFolderTitle, label: String, @@ -52,6 +54,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { ) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.preset = preset self.title = title self.label = label @@ -278,9 +281,18 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { let labelConstrain: CGFloat = params.width - params.rightInset - leftInset - 40.0 - titleLayout.size.width - 10.0 let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: titleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } + let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 11.0 * 2.0) + let contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -405,17 +417,17 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size)) - let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: 11.0), size: labelLayout.size) + let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: verticalInset), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift index 1dc40b2ea8..34f96f727a 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift @@ -8,6 +8,7 @@ import ItemListUI public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let title: String let label: String public let sectionId: ItemListSectionId @@ -17,6 +18,7 @@ public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem { public init( presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle, title: String, label: String, sectionId: ItemListSectionId, @@ -25,6 +27,7 @@ public class ChatListFilterPresetListSuggestedItem: ListViewItem, ItemListItem { tag: ItemListItemTag? = nil ) { self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title self.label = label self.sectionId = sectionId @@ -180,6 +183,8 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -210,7 +215,13 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor:labelBadgeColor), backgroundColor: nil, maximumNumberOfLines: multilineLabel ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: labelConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let verticalInset: CGFloat = 11.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } let titleSpacing: CGFloat = 3.0 let height: CGFloat @@ -301,15 +312,15 @@ public class ChatListFilterPresetListSuggestedItemNode: ListViewItemNode, ItemLi strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame let labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size) diff --git a/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift b/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift index ea0101a66e..127a4ae567 100644 --- a/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift +++ b/submodules/ChatListUI/Sources/ItemListFilterTitleInputItem.swift @@ -14,6 +14,7 @@ import TextFieldComponent public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let text: NSAttributedString let enableAnimations: Bool let placeholder: String @@ -29,6 +30,7 @@ public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem { public init( context: AccountContext, presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle, text: NSAttributedString, enableAnimations: Bool, placeholder: String, @@ -43,6 +45,7 @@ public class ItemListFilterTitleInputItem: ListViewItem, ItemListItem { ) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.text = text self.enableAnimations = enableAnimations self.placeholder = placeholder @@ -144,8 +147,9 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele let _ = rightInset let separatorHeight = UIScreenPixel - - let contentSize = CGSize(width: params.width, height: 44.0) + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + let contentSize = CGSize(width: params.width, height: item.systemStyle == .glass ? 52.0 : 44.0) let insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -200,12 +204,12 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele self.bottomStripeNode.isHidden = hasCorners } - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) self.maskNode.frame = self.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) self.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + self.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) self.textField.parentState = self.componentState self.componentState._updated = { [weak self] transition, _ in @@ -249,7 +253,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele environment: {}, containerSize: CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: layout.size.height) ) - let textFieldFrame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: textFieldSize) + let textFieldFrame = CGRect(origin: CGPoint(x: params.leftInset, y: floorToScreenPixels((layoutSize.height - textFieldSize.height) / 2.0)), size: textFieldSize) if let textFieldView = self.textField.view { if textFieldView.superview == nil { self.view.addSubview(textFieldView) diff --git a/submodules/CheckNode/Sources/CheckNode.swift b/submodules/CheckNode/Sources/CheckNode.swift index 4861d8b6f9..3e1de195d2 100644 --- a/submodules/CheckNode/Sources/CheckNode.swift +++ b/submodules/CheckNode/Sources/CheckNode.swift @@ -221,7 +221,13 @@ public class InteractiveCheckNode: CheckNode { } } + private var lastPressTime: Double? @objc private func buttonPressed() { + let currentTime = CACurrentMediaTime() + if let lastPressTime = self.lastPressTime, currentTime - lastPressTime < 0.5 { + return + } + self.lastPressTime = currentTime self.setSelected(!self.selected, animated: true) self.valueChanged?(self.selected) } diff --git a/submodules/Components/SheetComponent/BUILD b/submodules/Components/SheetComponent/BUILD index 5d52191429..e621f6be17 100644 --- a/submodules/Components/SheetComponent/BUILD +++ b/submodules/Components/SheetComponent/BUILD @@ -14,6 +14,7 @@ swift_library( "//submodules/ComponentFlow:ComponentFlow", "//submodules/Components/ViewControllerComponent:ViewControllerComponent", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/TelegramUI/Components/DynamicCornerRadiusView", ], visibility = [ "//visibility:public", diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index 812c580b44..db41ba334e 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -4,6 +4,7 @@ import Display import ComponentFlow import ViewControllerComponent import SwiftSignalKit +import DynamicCornerRadiusView public final class SheetComponentEnvironment: Equatable { public let isDisplaying: Bool @@ -59,8 +60,14 @@ public final class SheetComponent: C case blur(BlurStyle) } + public enum Style: Equatable { + case glass + case legacy + } + public let content: AnyComponent public let headerContent: AnyComponent? + public let style: Style public let backgroundColor: BackgroundColor public let followContentSizeChanges: Bool public let clipsContent: Bool @@ -75,6 +82,7 @@ public final class SheetComponent: C public init( content: AnyComponent, headerContent: AnyComponent? = nil, + style: Style = .legacy, backgroundColor: BackgroundColor, followContentSizeChanges: Bool = false, clipsContent: Bool = false, @@ -88,6 +96,7 @@ public final class SheetComponent: C ) { self.content = content self.headerContent = headerContent + self.style = style self.backgroundColor = backgroundColor self.followContentSizeChanges = followContentSizeChanges self.clipsContent = clipsContent @@ -107,6 +116,9 @@ public final class SheetComponent: C if lhs.headerContent != rhs.headerContent { return false } + if lhs.style != rhs.style { + return false + } if lhs.backgroundColor != rhs.backgroundColor { return false } @@ -162,7 +174,7 @@ public final class SheetComponent: C private let dimView: UIView private let scrollView: ScrollView - private let backgroundView: UIView + private let backgroundView: DynamicCornerRadiusView private var effectView: UIVisualEffectView? private let contentView: ComponentView private var headerView: ComponentView? @@ -186,9 +198,7 @@ public final class SheetComponent: C self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.alwaysBounceVertical = true - self.backgroundView = UIView() - self.backgroundView.layer.cornerRadius = 12.0 - self.backgroundView.layer.masksToBounds = true + self.backgroundView = DynamicCornerRadiusView() self.contentView = ComponentView() @@ -354,6 +364,19 @@ public final class SheetComponent: C self.component = component self.currentHasInputHeight = sheetEnvironment.hasInputHeight + let glassInset: CGFloat = 6.0 + + var topCornerRadius: CGFloat + var bottomCornerRadius: CGFloat + switch component.style { + case .glass: + topCornerRadius = 38.0 + bottomCornerRadius = 56.0 + case .legacy: + topCornerRadius = 12.0 + bottomCornerRadius = 12.0 + } + switch component.backgroundColor { case let .blur(style): self.backgroundView.isHidden = true @@ -365,7 +388,7 @@ public final class SheetComponent: C self.effectView = effectView } case let .color(color): - self.backgroundView.backgroundColor = color + self.backgroundView.updateColor(color: color, transition: .immediate) self.backgroundView.isHidden = false self.effectView?.removeFromSuperview() self.effectView = nil @@ -383,7 +406,12 @@ public final class SheetComponent: C containerSize = regularMetricsSize } } else { - containerSize = CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) + switch component.style { + case .glass: + containerSize = CGSize(width: availableSize.width - glassInset * 2.0, height: .greatestFiniteMagnitude) + case .legacy: + containerSize = CGSize(width: availableSize.width, height: .greatestFiniteMagnitude) + } } self.contentView.parentState = state @@ -403,7 +431,7 @@ public final class SheetComponent: C self.scrollView.addSubview(contentView) } contentView.clipsToBounds = component.clipsContent - contentView.layer.cornerRadius = self.backgroundView.layer.cornerRadius + contentView.layer.cornerRadius = topCornerRadius if sheetEnvironment.isCentered { let y: CGFloat = floorToScreenPixels((availableSize.height - contentSize.height) / 2.0) transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) @@ -411,12 +439,20 @@ public final class SheetComponent: C if let effectView = self.effectView { transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil) } + self.backgroundView.update(size: contentSize, corners: .init(minXMinY: topCornerRadius, maxXMinY: topCornerRadius, minXMaxY: bottomCornerRadius, maxXMaxY: bottomCornerRadius), transition: transition) } else { - transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 100.0)), completion: nil) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) - if let effectView = self.effectView { - transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + switch component.style { + case .glass: + transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil) + case .legacy: + transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 100.0)), completion: nil) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + if let effectView = self.effectView { + transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil) + } } + self.backgroundView.update(size: contentSize, corners: .init(minXMinY: topCornerRadius, maxXMinY: topCornerRadius, minXMaxY: bottomCornerRadius, maxXMaxY: bottomCornerRadius), transition: transition) } } transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index bd1bfaceac..25d70a78e8 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -42,6 +42,11 @@ open class ViewControllerComponentContainer: ViewController { case custom(PresentationTheme) } + public enum Style { + case glass + case legacy + } + public final class Environment: Equatable { public let statusBarHeight: CGFloat public let navigationHeight: CGFloat diff --git a/submodules/ComposePollUI/BUILD b/submodules/ComposePollUI/BUILD index 4b1cb85132..04f0e88a47 100644 --- a/submodules/ComposePollUI/BUILD +++ b/submodules/ComposePollUI/BUILD @@ -42,6 +42,8 @@ swift_library( "//submodules/ChatPresentationInterfaceState", "//submodules/TelegramUI/Components/EmojiSuggestionsComponent", "//submodules/TelegramUI/Components/ListComposePollOptionComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index 9d8e090b41..b88f2558f7 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -27,6 +27,8 @@ import EmojiSuggestionsComponent import TextFormat import TextFieldComponent import ListComposePollOptionComponent +import EdgeEffect +import GlassBarButtonComponent public final class ComposedPoll { public struct Text { @@ -110,6 +112,8 @@ final class ComposePollScreenComponent: Component { final class View: UIView, UIScrollViewDelegate { private let scrollView: UIScrollView + private let edgeEffectView: EdgeEffectView + private var reactionInput: ComponentView? private let pollTextSection = ComponentView() private let quizAnswerSection = ComponentView() @@ -120,7 +124,10 @@ final class ComposePollScreenComponent: Component { private var pollOptionsSectionContainer: ListSectionContentView private let pollSettingsSection = ComponentView() - private let actionButton = ComponentView() + + private let title = ComponentView() + private let cancelButton = ComponentView() + private let doneButton = ComponentView() private var reactionSelectionControl: ComponentView? @@ -178,6 +185,8 @@ final class ComposePollScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true + self.edgeEffectView = EdgeEffectView() + self.pollOptionsSectionContainer = ListSectionContentView(frame: CGRect()) super.init(frame: frame) @@ -185,6 +194,8 @@ final class ComposePollScreenComponent: Component { self.scrollView.delegate = self self.addSubview(self.scrollView) + self.addSubview(self.edgeEffectView) + let reorderRecognizer = ReorderGestureRecognizer( shouldBegin: { [weak self] point in guard let self, let (id, item) = self.item(at: point) else { @@ -851,6 +862,7 @@ final class ComposePollScreenComponent: Component { pollTextSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( externalState: self.pollTextInputState, context: component.context, + style: .glass, theme: theme, strings: environment.strings, resetText: self.resetPollText.flatMap { resetText in @@ -894,6 +906,7 @@ final class ComposePollScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.CreatePoll_TextHeader, @@ -951,6 +964,7 @@ final class ComposePollScreenComponent: Component { pollOptionsSectionItems.append(AnyComponentWithIdentity(id: pollOption.id, component: AnyComponent(ListComposePollOptionComponent( externalState: pollOption.textInputState, context: component.context, + style: .glass, theme: theme, strings: environment.strings, resetText: pollOption.resetText.flatMap { resetText in @@ -1092,6 +1106,7 @@ final class ComposePollScreenComponent: Component { let pollOptionsSectionUpdateResult = self.pollOptionsSectionContainer.update( configuration: ListSectionContentView.Configuration( theme: theme, + style: .glass, displaySeparators: true, extendsItemHighlightToSection: false, background: .all @@ -1231,6 +1246,7 @@ final class ComposePollScreenComponent: Component { if canBePublic { pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1253,6 +1269,7 @@ final class ComposePollScreenComponent: Component { } pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "multiAnswer", component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1277,6 +1294,7 @@ final class ComposePollScreenComponent: Component { )))) pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "quiz", component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1304,6 +1322,7 @@ final class ComposePollScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1334,6 +1353,7 @@ final class ComposePollScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.CreatePoll_ExplanationHeader, @@ -1354,6 +1374,7 @@ final class ComposePollScreenComponent: Component { AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( externalState: self.quizAnswerTextInputState, context: component.context, + style: .glass, theme: theme, strings: environment.strings, resetText: self.resetQuizAnswerText.flatMap { resetText in @@ -1620,6 +1641,104 @@ final class ComposePollScreenComponent: Component { self.scrollView.verticalScrollIndicatorInsets = scrollInsets } + let edgeEffectHeight: CGFloat = 66.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + + let title = self.isQuiz ? environment.strings.CreatePoll_QuizTitle : environment.strings.CreatePoll_Title + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: title, + font: Font.semibold(17.0), + textColor: environment.theme.rootController.navigationBar.primaryTextColor + ) + ) + ) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 40.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? ComposePollScreen else { + return + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + + let isValid = self.validatedInput() != nil + let doneButtonSize = self.doneButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: isValid ? environment.theme.list.itemCheckColors.fillColor : environment.theme.list.itemCheckColors.fillColor.desaturated().withMultipliedAlpha(0.5), + isDark: environment.theme.overallDarkAppearance, + state: .tintedGlass, + isEnabled: isValid, + component: AnyComponentWithIdentity(id: "done", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Done", + tintColor: environment.theme.list.itemCheckColors.foregroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? ComposePollScreen else { + return + } + if let input = self.validatedInput() { + controller.completion(input) + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) + if let doneButtonView = self.doneButton.view { + if doneButtonView.superview == nil { + self.addSubview(doneButtonView) + } + transition.setFrame(view: doneButtonView, frame: doneButtonFrame) + } + if let recenterOnTag { if let targetView = self.collectTextInputStates().first(where: { $0.view.currentTag === recenterOnTag })?.view { let caretRect = targetView.convert(targetView.bounds, to: self.scrollView) @@ -1655,18 +1774,6 @@ final class ComposePollScreenComponent: Component { } } - let isValid = self.validatedInput() != nil - if let controller = environment.controller() as? ComposePollScreen, let sendButtonItem = controller.sendButtonItem { - if sendButtonItem.isEnabled != isValid { - sendButtonItem.isEnabled = isValid - } - - let controllerTitle = self.isQuiz ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title - if controller.title != controllerTitle { - controller.title = controllerTitle - } - } - if let currentEditingTag = self.currentEditingTag, previousEditingTag !== currentEditingTag, self.currentInputMode != .keyboard { DispatchQueue.main.async { [weak self] in guard let self else { @@ -1708,7 +1815,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont } private let context: AccountContext - private let completion: (ComposedPoll) -> Void + fileprivate let completion: (ComposedPoll) -> Void private var isDismissed: Bool = false fileprivate private(set) var sendButtonItem: UIBarButtonItem? @@ -1761,17 +1868,25 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont isQuiz: isQuiz, initialData: initialData, completion: completion - ), navigationBarAppearance: .default, theme: .default) + ), navigationBarAppearance: .transparent, theme: .default) + + self._hasGlassStyle = true let presentationData = context.sharedContext.currentPresentationData.with { $0 } - - self.title = isQuiz == true ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title - - self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) + + if self._hasGlassStyle { + self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: UIView()), animated: false) + } else { + self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) + } let sendButtonItem = UIBarButtonItem(title: presentationData.strings.CreatePoll_Create, style: .done, target: self, action: #selector(self.sendPressed)) self.sendButtonItem = sendButtonItem - self.navigationItem.setRightBarButton(sendButtonItem, animated: false) + if self._hasGlassStyle { + + } else { + self.navigationItem.setRightBarButton(sendButtonItem, animated: false) + } sendButtonItem.isEnabled = false self.scrollToTop = { [weak self] in diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index 0fb6ab5bde..99168662b1 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -47,6 +47,7 @@ swift_library( "//submodules/TelegramIntents", "//submodules/ContextUI", "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/SearchInputPanelComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 67b130f2a0..05b60323ca 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -50,6 +50,7 @@ private enum ContactListNodeEntryId: Hashable { case sort case permission(index: Int) case option(index: Int) + case header(index: Int64) case peerId(peerId: Int64, section: ContactListNodeEntrySection) case deviceContact(DeviceContactStableId) } @@ -101,7 +102,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable { case permissionEnable(PresentationTheme, String) case permissionLimited(PresentationTheme, PresentationStrings) case option(Int, ContactListAdditionalOption, ListViewItemHeader?, PresentationTheme, PresentationStrings) - case peer(Int, ContactListPeer, EnginePeer.Presence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool, Bool, StoryData?, Bool, String?) + case header(Int64, ListViewItemHeader?) + case peer(Int64, ContactListPeer, EnginePeer.Presence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool, Bool, StoryData?, Bool, String?) var stableId: ContactListNodeEntryId { switch self { @@ -117,6 +119,8 @@ private enum ContactListNodeEntry: Comparable, Identifiable { return .permission(index: 2) case let .option(index, _, _, _, _): return .option(index: index) + case let .header(index, _): + return .header(index: index) case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, storyData, _, _): switch peer { case let .peer(peer, _, _): @@ -127,7 +131,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } } - func item(context: AccountContext, presentationData: PresentationData, interaction: ContactListNodeInteraction, isSearch: Bool) -> ListViewItem { + func item(context: AccountContext, presentationData: PresentationData, interaction: ContactListNodeInteraction, isSearch: Bool, listStyle: ItemListStyle) -> ListViewItem { switch self { case let .search(theme, strings): return ChatListSearchItem(theme: theme, placeholder: strings.Contacts_SearchLabel, activate: { @@ -164,6 +168,16 @@ private enum ContactListNodeEntry: Comparable, Identifiable { height = .tall } return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, subtitle: option.subtitle, icon: option.icon, style: style, height: height, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action) + case let .header(_, header): + var sectionId: Int32 = 0 + var text = "" + if let header = header as? ContactListNameIndexHeader, let id = header.id.id.base as? Int64 { + if let scalar = UnicodeScalar(header.letter) { + text = String(Character(scalar) ) + } + sectionId = Int32(clamping: id) + } + return ItemListSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), text: text, sectionId: sectionId) case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, hasMoreButton, enabled, storyData, requiresPremiumForMessaging, customSubtitle): var status: ContactsPeerItemStatus let itemPeer: ContactsPeerItemPeer @@ -236,8 +250,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable { if let customSubtitle { status = .custom(string: NSAttributedString(string: customSubtitle), multiline: false, isActive: false, icon: nil) } - - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in + var sectionId: Int32 = 0 + if case .blocks = listStyle, let id = header?.id.id.base as? Int64 { + sectionId = Int32(clamping: id) + } + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), style: listStyle, systemStyle: listStyle == .blocks ? .glass : .legacy, sectionId: listStyle == .blocks ? sectionId : 0, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: listStyle == .blocks ? nil : header, action: { _ in interaction.openPeer(peer, .generic, nil, nil) }, disabledAction: { _ in if case let .peer(peer, _, _) = peer { @@ -289,6 +306,12 @@ private enum ContactListNodeEntry: Comparable, Identifiable { } else { return false } + case let .header(lhsIndex, lhsHeader): + if case let .header(rhsIndex, rhsHeader) = rhs, lhsIndex == rhsIndex, lhsHeader?.id == rhsHeader?.id { + return true + } else { + return false + } case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsDisplayCallIcons, lhsHasMoreButton, lhsEnabled, lhsStoryData, lhsRequiresPremiumForMessaging, lhsCustomSubtitle): switch rhs { case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsDisplayCallIcons, rhsHasMoreButton, rhsEnabled, rhsStoryData, rhsRequiresPremiumForMessaging, rhsCustomSubtitle): @@ -386,31 +409,62 @@ private enum ContactListNodeEntry: Comparable, Identifiable { case let .option(lhsIndex, _, _, _, _): switch rhs { case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited: - return false - case let .option(rhsIndex, _, _, _, _): - return lhsIndex < rhsIndex - case .peer: - return true + return false + case let .option(rhsIndex, _, _, _, _): + return lhsIndex < rhsIndex + case .header, .peer: + return true } - case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, lhsStoryData, _, _): + case let .header(lhsIndex, _): + switch rhs { + case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option: + return false + case let .header(rhsIndex, _), let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + return lhsIndex < rhsIndex + } + case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): switch rhs { case .search, .sort, .permissionInfo, .permissionEnable, .permissionLimited, .option: return false - case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, rhsStoryData, _, _): - if (lhsStoryData == nil) != (rhsStoryData == nil) { - if lhsStoryData != nil { - return true - } else { - return false - } - } + case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _), let .header(rhsIndex, _): +// if (lhsStoryData == nil) != (rhsStoryData == nil) { +// if lhsStoryData != nil { +// return true +// } else { +// return false +// } +// } return lhsIndex < rhsIndex } } } } -private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set, peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], peersWithStories: [EnginePeer.Id: PeerStoryStats], authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?, topPeers: [EnginePeer], topPeersPresentation: ContactListPresentation.TopPeers, isPeerEnabled: ((EnginePeer) -> Bool)?, interaction: ContactListNodeInteraction) -> [ContactListNodeEntry] { +private func contactListNodeEntries( + listStyle: ItemListStyle = .plain, + accountPeer: EnginePeer?, + peers: [ContactListPeer], + presences: [EnginePeer.Id: EnginePeer.Presence], + presentation: ContactListPresentation, + selectionState: ContactListNodeGroupSelectionState?, + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + sortOrder: PresentationPersonNameOrder, + displayOrder: PresentationPersonNameOrder, + disabledPeerIds: Set, + peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], + peersWithStories: [EnginePeer.Id: PeerStoryStats], + authorizationStatus: AccessType, + warningSuppressed: (Bool, Bool), + displaySortOptions: Bool, + displayCallIcons: Bool, + storySubscriptions: EngineStorySubscriptions?, + topPeers: [EnginePeer], + topPeersPresentation: ContactListPresentation.TopPeers, + isPeerEnabled: ((EnginePeer) -> Bool)?, + interaction: ContactListNodeInteraction +) -> [ContactListNodeEntry] { var entries: [ContactListNodeEntry] = [] var commonHeader: ListViewItemHeader? @@ -579,7 +633,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis interaction.deselectAll() }) - var index: Int = 0 + var index: Int64 = 0 for peer in topPeers.prefix(15) { if peer.isDeleted { continue @@ -601,7 +655,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis } case let .custom(showSelf, selfSubtitle, sections): if !topPeers.isEmpty { - var index: Int = 0 + var index: Int64 = 0 var sectionId: Int = 2 for (title, peerIds, hasActions) in sections { @@ -732,7 +786,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis }*/ } - var index: Int = 0 + var index: Int64 = 0 if let selectionState = selectionState { for peer in selectionState.foundPeers { @@ -767,6 +821,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis } } + var existingHeaders = Set() for i in 0 ..< orderedPeers.count { let peer = orderedPeers[i] if existingPeerIds.contains(peer.id) { @@ -819,18 +874,24 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis } } + if case .natural = presentation, case .blocks = listStyle, let header, let headerId = header.id.id.base as? Int64, !existingHeaders.contains(headerId) { + entries.append(.header(index, header)) + index += 1 + existingHeaders.insert(headerId) + } + entries.append(.peer(index, peer, presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, false, enabled, storyData, requiresPremiumForMessaging, nil)) index += 1 } return entries } -private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, hasOptions: Bool, generateIndexSections: Bool, animation: ContactListAnimation, isSearch: Bool) -> ContactsListNodeTransition { +private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, hasOptions: Bool, generateIndexSections: Bool, animation: ContactListAnimation, isSearch: Bool, listStyle: ItemListStyle) -> ContactsListNodeTransition { 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(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch, listStyle: listStyle), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction, isSearch: isSearch, listStyle: listStyle), directionHint: nil) } var shouldFixScroll = false var indexSections: [String] = [] @@ -974,6 +1035,7 @@ public struct ContactListNodeGroupSelectionState: Equatable { public final class ContactListNode: ASDisplayNode { private let context: AccountContext + private let listStyle: ItemListStyle private var presentation: ContactListPresentation? private let filters: [ContactListFilter] private let onlyWriteable: Bool @@ -1161,8 +1223,25 @@ public final class ContactListNode: ASDisplayNode { private let isPeerEnabled: ((EnginePeer) -> Bool)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, presentation: Signal, filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool, isGroupInvitation: Bool, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true, displaySortOptions: Bool = false, displayCallIcons: Bool = false, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? = nil, isSearch: Bool = false, multipleSelection: Bool = false) { + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + listStyle: ItemListStyle = .plain, + presentation: Signal, + filters: [ContactListFilter] = [.excludeSelf], + onlyWriteable: Bool, + isGroupInvitation: Bool, + isPeerEnabled: ((EnginePeer) -> Bool)? = nil, + selectionState: ContactListNodeGroupSelectionState? = nil, + displayPermissionPlaceholder: Bool = true, + displaySortOptions: Bool = false, + displayCallIcons: Bool = false, + contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? = nil, + isSearch: Bool = false, + multipleSelection: Bool = false + ) { self.context = context + self.listStyle = listStyle self.filters = filters self.displayPermissionPlaceholder = displayPermissionPlaceholder self.contextAction = contextAction @@ -1217,8 +1296,13 @@ public final class ContactListNode: ASDisplayNode { super.init() - self.backgroundColor = self.presentationData.theme.chatList.backgroundColor - self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor + self.backgroundColor = listStyle == .blocks ? self.presentationData.theme.list.blocksBackgroundColor : self.presentationData.theme.chatList.backgroundColor + + if listStyle == .blocks { + self.listNode.verticalScrollIndicatorColor = .clear + } else { + self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor + } self.selectionStateValue = selectionState self.selectionStatePromise.set(.single(selectionState)) @@ -1524,7 +1608,14 @@ public final class ContactListNode: ASDisplayNode { } return combineLatest(accountPeer, foundPeers.get(), peerRequiresPremiumForMessaging, foundDeviceContacts, selectionStateSignal, pendingRemovalPeerIdsSignal, presentationDataPromise.get()) - |> mapToQueue { accountPeer, foundPeers, peerRequiresPremiumForMessaging, deviceContacts, selectionState, pendingRemovalPeerIds, presentationData -> Signal in + |> mapToQueue { + accountPeer, + foundPeers, + peerRequiresPremiumForMessaging, + deviceContacts, + selectionState, + pendingRemovalPeerIds, + presentationData -> Signal in let localPeersAndStatuses = foundPeers.foundLocalContacts let remotePeers = foundPeers.foundRemoteContacts @@ -1535,7 +1626,7 @@ public final class ContactListNode: ASDisplayNode { var existingPeerIds = Set() var disabledPeerIds = Set() - + var existingNormalizedPhoneNumbers = Set() var excludeSelf = false var requirePhoneNumbers = false @@ -1580,7 +1671,9 @@ public final class ContactListNode: ASDisplayNode { } existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: false, participantCount: peer.subscribers)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + if searchDeviceContacts, + let user = peer.peer as? TelegramUser, + let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } @@ -1615,7 +1708,9 @@ public final class ContactListNode: ASDisplayNode { } existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + if searchDeviceContacts, + let user = peer.peer as? TelegramUser, + let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } @@ -1651,7 +1746,9 @@ public final class ContactListNode: ASDisplayNode { } existingPeerIds.insert(peer.peer.id) peers.append(.peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers)) - if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone { + if searchDeviceContacts, + let user = peer.peer as? TelegramUser, + let phone = user.phone { existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone))) } } @@ -1672,9 +1769,33 @@ public final class ContactListNode: ASDisplayNode { peers.append(.deviceContact(stableId, contact.0)) } - let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, peersWithStories: [:], authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [], topPeersPresentation: .none, isPeerEnabled: isPeerEnabled, interaction: interaction) + let entries = contactListNodeEntries( + listStyle: listStyle, + accountPeer: nil, + peers: peers, + presences: localPeersAndStatuses.1, + presentation: presentation, + selectionState: selectionState, + theme: presentationData.theme, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + sortOrder: presentationData.nameSortOrder, + displayOrder: presentationData.nameDisplayOrder, + disabledPeerIds: disabledPeerIds, + peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, + peersWithStories: [:], + authorizationStatus: .allowed, + warningSuppressed: (true, true), + displaySortOptions: false, + displayCallIcons: displayCallIcons, + storySubscriptions: nil, + topPeers: [], + topPeersPresentation: .none, + isPeerEnabled: isPeerEnabled, + interaction: interaction + ) let previous = previousEntries.swap(entries) - return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, hasOptions: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch)) + return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, hasOptions: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch, listStyle: listStyle)) } if OSAtomicCompareAndSwap32(1, 0, &firstTime) { @@ -1808,115 +1929,151 @@ public final class ContactListNode: ASDisplayNode { topPeers = .single([]) } - return (combineLatest( - self.contactPeersViewPromise.get(), - chatListSignal, - selectionStateSignal, - pendingRemovalPeerIdsSignal, - presentationDataPromise.get(), - contactsAuthorization.get(), - contactsWarningSuppressed.get(), - self.storySubscriptions.get(), - topPeers - ) - |> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, topPeers -> Signal in - let signal = deferred { () -> Signal in - if !view.2.isEmpty { - context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) - } - - var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) }) - for (peer, memberCount) in chatListPeers { - peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount)) - } - var existingPeerIds = Set() - var disabledPeerIds = Set() - var requirePhoneNumbers = false - for filter in filters { - switch filter { - case .excludeSelf: - existingPeerIds.insert(context.account.peerId) - case let .exclude(peerIds): - existingPeerIds = existingPeerIds.union(peerIds) - case let .disable(peerIds): - disabledPeerIds = disabledPeerIds.union(peerIds) - case .excludeWithoutPhoneNumbers: - requirePhoneNumbers = true - case .excludeBots: - break + return ( + combineLatest( + self.contactPeersViewPromise.get(), + chatListSignal, + selectionStateSignal, + pendingRemovalPeerIdsSignal, + presentationDataPromise.get(), + contactsAuthorization.get(), + contactsWarningSuppressed.get(), + self.storySubscriptions.get(), + topPeers + ) + |> mapToQueue { + view, + chatListPeers, + selectionState, + pendingRemovalPeerIds, + presentationData, + authorizationStatus, + warningSuppressed, + storySubscriptions, + topPeers -> Signal in + let signal = deferred { () -> Signal in + if !view.2.isEmpty { + context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) } - } - - peers = peers.filter { contact in - switch contact { - case let .peer(peer, _, _): - if requirePhoneNumbers, let user = peer as? TelegramUser { - let phone = user.phone ?? "" - if phone.isEmpty { - return false + + var peers = view.0.peers.map({ ContactListPeer.peer(peer: $0._asPeer(), isGlobal: false, participantCount: nil) }) + for (peer, memberCount) in chatListPeers { + peers.append(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: memberCount)) + } + var existingPeerIds = Set() + var disabledPeerIds = Set() + var requirePhoneNumbers = false + for filter in filters { + switch filter { + case .excludeSelf: + existingPeerIds.insert(context.account.peerId) + case let .exclude(peerIds): + existingPeerIds = existingPeerIds.union(peerIds) + case let .disable(peerIds): + disabledPeerIds = disabledPeerIds.union(peerIds) + case .excludeWithoutPhoneNumbers: + requirePhoneNumbers = true + case .excludeBots: + break + } + } + + peers = peers.filter { contact in + switch contact { + case let .peer(peer, _, _): + if requirePhoneNumbers, + let user = peer as? TelegramUser { + let phone = user.phone ?? "" + if phone.isEmpty { + return false + } + } + return !existingPeerIds.contains(peer.id) && !pendingRemovalPeerIds.contains(peer.id) + default: + return true + } + } + + var presences = view.0.presences + for peer in topPeers { + if let presence = peer.presence { + presences[peer.peer.id] = presence + } + } + + var isEmpty = false + if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty && topPeers.isEmpty { + isEmpty = true + } + + let entries = contactListNodeEntries( + listStyle: listStyle, + accountPeer: view.1, + peers: peers, + presences: presences, + presentation: presentation, + selectionState: selectionState, + theme: presentationData.theme, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + sortOrder: presentationData.nameSortOrder, + displayOrder: presentationData.nameDisplayOrder, + disabledPeerIds: disabledPeerIds, + peerRequiresPremiumForMessaging: view.2, + peersWithStories: view.3, + authorizationStatus: authorizationStatus, + warningSuppressed: warningSuppressed, + displaySortOptions: displaySortOptions, + displayCallIcons: displayCallIcons, + storySubscriptions: storySubscriptions, + topPeers: topPeers.map { $0.peer + }, + topPeersPresentation: displayTopPeers, + isPeerEnabled: isPeerEnabled, + interaction: interaction + ) + let previous = previousEntries.swap(entries) + let previousSelection = previousSelectionState.swap(selectionState) + let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds) + + var hadPermissionInfo = false + var previousOptionsCount = 0 + if let previous = previous { + for entry in previous { + if case .permissionInfo = entry { + hadPermissionInfo = true + } + if case .option = entry { + previousOptionsCount += 1 } } - return !existingPeerIds.contains(peer.id) && !pendingRemovalPeerIds.contains(peer.id) - default: - return true } - } - - var presences = view.0.presences - for peer in topPeers { - if let presence = peer.presence { - presences[peer.peer.id] = presence - } - } - - var isEmpty = false - if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty && topPeers.isEmpty { - isEmpty = true - } - - let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, peersWithStories: view.3, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers.map { $0.peer }, topPeersPresentation: displayTopPeers, isPeerEnabled: isPeerEnabled, interaction: interaction) - let previous = previousEntries.swap(entries) - let previousSelection = previousSelectionState.swap(selectionState) - let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds) - - var hadPermissionInfo = false - var previousOptionsCount = 0 - if let previous = previous { - for entry in previous { + var hasPermissionInfo = false + var optionsCount = 0 + for entry in entries { if case .permissionInfo = entry { - hadPermissionInfo = true + hasPermissionInfo = true } if case .option = entry { - previousOptionsCount += 1 + optionsCount += 1 } } - } - var hasPermissionInfo = false - var optionsCount = 0 - for entry in entries { - if case .permissionInfo = entry { - hasPermissionInfo = true - } - if case .option = entry { - optionsCount += 1 + + let animation: ContactListAnimation + if (previousSelection == nil) != (selectionState == nil) { + animation = .insertion + } else if previousPendingRemovalPeerIds != pendingRemovalPeerIds { + animation = .insertion + } else if hadPermissionInfo != hasPermissionInfo { + animation = .insertion + } else if optionsCount < previousOptionsCount { + animation = .insertion + } else { + animation = .none } + + return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, hasOptions: optionsCount != 0, generateIndexSections: generateSections, animation: animation, isSearch: isSearch, listStyle: listStyle)) } - - let animation: ContactListAnimation - if (previousSelection == nil) != (selectionState == nil) { - animation = .insertion - } else if previousPendingRemovalPeerIds != pendingRemovalPeerIds { - animation = .insertion - } else if hadPermissionInfo != hasPermissionInfo { - animation = .insertion - } else if optionsCount < previousOptionsCount { - animation = .insertion - } else { - animation = .none - } - - return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, hasOptions: optionsCount != 0, generateIndexSections: generateSections, animation: animation, isSearch: isSearch)) - } if OSAtomicCompareAndSwap32(1, 0, &firstTime) { return signal |> runOn(Queue.mainQueue()) @@ -2082,7 +2239,10 @@ public final class ContactListNode: ASDisplayNode { insets.left = layout.safeInsets.left insets.right = layout.safeInsets.right - let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) + var indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) + if case .blocks = self.listStyle { + indexNodeFrame.origin.x += 18.0 + } transition.updateFrame(node: indexNode, frame: indexNodeFrame) self.indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition) } @@ -2139,7 +2299,10 @@ public final class ContactListNode: ASDisplayNode { insets.bottom -= inputHeight } - let indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) + var indexNodeFrame = CGRect(origin: CGPoint(x: layout.size.width - insets.right - 20.0, y: insets.top), size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom)) + if case .blocks = self.listStyle { + indexNodeFrame.origin.x += 18.0 + } self.indexNode.frame = indexNodeFrame self.indexNode.update(size: CGSize(width: 20.0, height: layout.size.height - insets.top - insets.bottom), color: self.presentationData.theme.list.itemAccentColor, sections: transition.indexSections, transition: .animated(duration: 0.2, curve: .easeInOut)) diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 2eb1303454..2526b7739a 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -740,26 +740,34 @@ public class ContactsController: ViewController { switch status { case .allowed: - let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") + //let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: "", lastName: "", phoneNumbers: [DeviceContactPhoneNumberData(label: "_$!!$_", value: "+")]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "") if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in - guard let strongSelf = self else { - return - } - if let peer = peer { - DispatchQueue.main.async { - if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { - if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(infoController) - } - } - } - } else { - if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { - navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) - } - } - }), completed: nil, cancelled: nil)) + let controller = strongSelf.context.sharedContext.makeNewContactScreen( + context: strongSelf.context, + firstName: nil, + lastName: nil, + phoneNumber: nil + ) + navigationController.pushViewController(controller) + +// navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in +// guard let strongSelf = self else { +// return +// } +// if let peer = peer { +// DispatchQueue.main.async { +// if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { +// if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { +// navigationController.pushViewController(infoController) +// } +// } +// } +// } else { +// if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController { +// navigationController.pushViewController(strongSelf.context.sharedContext.makeDeviceContactInfoController(context: ShareControllerAppAccountContext(context: strongSelf.context), environment: ShareControllerAppEnvironment(sharedContext: strongSelf.context.sharedContext), subject: .vcard(nil, stableId, contactData), completed: nil, cancelled: nil)) +// } +// } +// }), completed: nil, cancelled: nil)) } case .notDetermined: DeviceAccess.authorizeAccess(to: .contacts) diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index 442df4f39d..d03d0e0089 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -16,6 +16,8 @@ import PhoneNumberFormat import ItemListUI import AnimatedStickerNode import TelegramAnimatedStickerNode +import ComponentFlow +import SearchInputPanelComponent private enum ContactListSearchGroup { case contacts @@ -229,6 +231,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo } private let context: AccountContext + private let glass: Bool private let isPeerEnabled: (ContactListPeer) -> Bool private let addContact: ((String) -> Void)? private let openPeer: (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void @@ -252,12 +255,28 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo private var containerViewLayout: (ContainerViewLayout, CGFloat)? private var enqueuedTransitions: [ContactListSearchContainerTransition] = [] + private let searchInput = ComponentView() + public override var hasDim: Bool { return true } - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], displayCallIcons: Bool = false, isPeerEnabled: @escaping (ContactListPeer) -> Bool = { _ in true }, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?) { + public init( + context: AccountContext, + glass: Bool = false, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + onlyWriteable: Bool, + categories: ContactsSearchCategories, + filters: [ContactListFilter] = [.excludeSelf], + displayCallIcons: Bool = false, + isPeerEnabled: @escaping (ContactListPeer) -> Bool = { _ in true }, + addContact: ((String) -> Void)?, + openPeer: @escaping (ContactListPeer, ContactsSearchContainerNode.OpenPeerAction) -> Void, + openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, + contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? + ) { self.context = context + self.glass = glass self.isPeerEnabled = isPeerEnabled self.addContact = addContact self.openPeer = openPeer @@ -270,10 +289,11 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = glass ? .clear : UIColor.black.withAlphaComponent(0.5) self.listNode = ListView() self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.listNode.isHidden = true + self.listNode.alpha = 0.0 + self.listNode.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } @@ -307,9 +327,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo self.addSubnode(self.emptyResultsAnimationNode) self.addSubnode(self.emptyResultsTitleNode) self.addSubnode(self.emptyResultsTextNode) - - self.listNode.isHidden = true - + let themeAndStringsPromise = self.themeAndStringsPromise let previousFoundRemoteContacts = Atomic<([FoundPeer], [FoundPeer])?>(value: nil) @@ -615,7 +633,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo } override public func scrollToTop() { - if !self.listNode.isHidden { + if self.listNode.alpha > 0.0 { self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } @@ -642,6 +660,13 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo } } + private func deactivateInput() { + if let (layout, _) = self.containerViewLayout, let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { + let transition = ComponentTransition.spring(duration: 0.4) + transition.setFrame(view: searchInputView, frame: CGRect(origin: CGPoint(x: searchInputView.frame.minX, y: layout.size.height), size: searchInputView.frame.size)) + } + } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) @@ -675,6 +700,47 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo textTransition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: sideInset + padding + (size.width - sideInset * 2.0 - padding * 2.0 - emptyTextSize.width) / 2.0, y: emptyAnimationY + emptyAnimationHeight + emptyAnimationSpacing + emptyTitleSize.height + emptyTextSpacing), size: emptyTextSize)) self.emptyResultsAnimationNode.updateLayout(size: self.emptyResultsAnimationSize) + if self.glass { + let searchInputSize = self.searchInput.update( + transition: .immediate, + component: AnyComponent( + SearchInputPanelComponent( + theme: self.presentationData.theme, + strings: self.presentationData.strings, + placeholder: nil, + resetText: nil, + updated: { [weak self] query in + guard let self else { + return + } + self.searchTextUpdated(text: query) + }, + cancel: { [weak self] in + guard let self else { + return + } + self.cancel?() + self.deactivateInput() + } + ) + ), + environment: {}, + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + ) + + let bottomInset: CGFloat = layout.insets(options: .input).bottom + let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { + if searchInputView.superview == nil { + self.view.addSubview(searchInputView) + searchInputView.frame = CGRect(origin: CGPoint(x: searchInputFrame.minX, y: layout.size.height), size: searchInputFrame.size) + + searchInputView.activateInput() + } + transition.updateFrame(view: searchInputView, frame: searchInputFrame) + } + } + if !hadValidLayout { while !self.enqueuedTransitions.isEmpty { self.dequeueTransition() @@ -713,7 +779,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo if let (layout, navigationBarHeight) = strongSelf.containerViewLayout { strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } - strongSelf.listNode.isHidden = !isSearching + + let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) + containerTransition.updateAlpha(node: strongSelf.listNode, alpha: isSearching ? 1.0 : 0.0) strongSelf.dimNode.isHidden = isSearching strongSelf.emptyResultsAnimationNode.isHidden = !emptyResults diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index ba104d746a..bd21c902fc 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -176,6 +176,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { let presentationData: ItemListPresentationData let style: ItemListStyle + let systemStyle: ItemListSystemStyle public let sectionId: ItemListSectionId let sortOrder: PresentationPersonNameOrder let displayOrder: PresentationPersonNameOrder @@ -221,6 +222,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { public init( presentationData: ItemListPresentationData, style: ItemListStyle = .plain, + systemStyle: ItemListSystemStyle = .legacy, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, @@ -260,6 +262,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader { ) { self.presentationData = presentationData self.style = style + self.systemStyle = systemStyle self.sectionId = sectionId self.sortOrder = sortOrder self.displayOrder = displayOrder @@ -1847,16 +1850,17 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height)) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset)) strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(nodeLayout.insets.top, separatorHeight)), size: CGSize(width: nodeLayout.contentSize.width, height: separatorHeight)) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset), height: separatorHeight)) - if !item.alwaysShowLastSeparator { + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - leftInset - separatorRightInset), height: separatorHeight)) + if !item.alwaysShowLastSeparator && item.style != .blocks { strongSelf.separatorNode.isHidden = last } diff --git a/submodules/CountrySelectionUI/BUILD b/submodules/CountrySelectionUI/BUILD index 1104b6f78c..fe0f0049ad 100644 --- a/submodules/CountrySelectionUI/BUILD +++ b/submodules/CountrySelectionUI/BUILD @@ -18,6 +18,11 @@ swift_library( "//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/SearchBarNode:SearchBarNode", "//submodules/AppBundle:AppBundle", + "//submodules/ComponentFlow", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/SearchInputPanelComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift index 186e7ded8c..867dc0b843 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift @@ -8,6 +8,9 @@ import TelegramStringFormatting import SearchBarNode import AppBundle import TelegramCore +import ComponentFlow +import BundleIconComponent +import GlassBarButtonComponent private func loadCountryCodes() -> [Country] { guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else { @@ -289,10 +292,31 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll return nil } + public static func defaultCountryCode() -> Int32 { + let countryId = (Locale.current as NSLocale).object(forKey: .countryCode) as? String + + var countryCode: Int32 = 1 + if let countryId = countryId { + let normalizedId = countryId.uppercased() + for (code, idAndName) in countryCodeToIdAndName { + if idAndName.0 == normalizedId { + countryCode = Int32(code) + break + } + } + } + + return countryCode + } + private let theme: PresentationTheme private let strings: PresentationStrings private let displayCodes: Bool + private let glass: Bool + + private var closeButtonNode: BarComponentHostNode? + private var searchButtonNode: BarComponentHostNode? private var navigationContentNode: AuthorizationSequenceCountrySelectionNavigationContentNode? private var controllerNode: AuthorizationSequenceCountrySelectionControllerNode { @@ -302,29 +326,39 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll public var completeWithCountryCode: ((Int, String) -> Void)? public var dismissed: (() -> Void)? - public init(strings: PresentationStrings, theme: PresentationTheme, displayCodes: Bool = true) { + public init(strings: PresentationStrings, theme: PresentationTheme, displayCodes: Bool = true, glass: Bool = false) { self.theme = theme self.strings = strings self.displayCodes = displayCodes + self.glass = glass - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme), strings: NavigationBarStrings(presentationStrings: strings))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: strings))) + + self._hasGlassStyle = glass self.navigationPresentation = .modal self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style - let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in - self?.dismissed?() - self?.dismiss() - }) - self.navigationContentNode = navigationContentNode - navigationContentNode.setQueryUpdated { [weak self] query in - guard let strongSelf = self, strongSelf.isNodeLoaded else { - return + //TODO:localize + self.title = "Select Country" + + if glass { + + } else { + let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in + self?.dismissed?() + self?.dismiss() + }) + self.navigationContentNode = navigationContentNode + navigationContentNode.setQueryUpdated { [weak self] query in + guard let strongSelf = self, strongSelf.isNodeLoaded else { + return + } + strongSelf.controllerNode.updateSearchQuery(query) } - strongSelf.controllerNode.updateSearchQuery(query) + self.navigationBar?.setContentNode(navigationContentNode, animated: false) } - self.navigationBar?.setContentNode(navigationContentNode, animated: false) } required public init(coder aDecoder: NSCoder) { @@ -332,11 +366,15 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll } override public func loadDisplayNode() { - self.displayNode = AuthorizationSequenceCountrySelectionControllerNode(theme: self.theme, strings: self.strings, displayCodes: self.displayCodes, itemSelected: { [weak self] args in + self.displayNode = AuthorizationSequenceCountrySelectionControllerNode(theme: self.theme, strings: self.strings, displayCodes: self.displayCodes, glass: self.glass, itemSelected: { [weak self] args in let (_, countryId, code) = args self?.completeWithCountryCode?(code, countryId) self?.dismiss() }) + self.controllerNode.deactivateSearch = { [weak self] in + self?.controllerNode.isSearching = false + self?.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) + } self.displayNodeDidLoad() } @@ -348,9 +386,80 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll } } + private func updateNavigationButtons() { + guard self.glass else { + return + } + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let closeComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( + id: "close", + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: self.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.cancelPressed() + } + )) + ) + + let searchComponent: AnyComponentWithIdentity? + if !self.controllerNode.isSearching { + searchComponent = AnyComponentWithIdentity( + id: "search", + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "search", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Search", + tintColor: self.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.controllerNode.isSearching = true + self?.requestLayout(transition: .animated(duration: 0.5, curve: .spring)) + } + )) + ) + } else { + searchComponent = nil + } + + let closeButtonNode: BarComponentHostNode + if let current = self.closeButtonNode { + closeButtonNode = current + closeButtonNode.component = closeComponent + } else { + closeButtonNode = BarComponentHostNode(component: closeComponent, size: barButtonSize) + self.closeButtonNode = closeButtonNode + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: closeButtonNode) + } + + let searchButtonNode: BarComponentHostNode + if let current = self.searchButtonNode { + searchButtonNode = current + searchButtonNode.component = searchComponent + } else { + searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) + self.searchButtonNode = searchButtonNode + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + } + } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) + self.updateNavigationButtons() self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift index 5563711e0d..b9f45e1ca8 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift @@ -6,6 +6,9 @@ import TelegramCore import TelegramPresentationData import TelegramStringFormatting import AppBundle +import ComponentFlow +import EdgeEffect +import SearchInputPanelComponent private func loadCountryCodes() -> [(String, Int)] { guard let filePath = getAppBundle().path(forResource: "PhoneCountries", ofType: "txt") else { @@ -186,10 +189,12 @@ public func searchCountries(items: [((String, String), String, [Int])], query: S final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, UITableViewDelegate, UITableViewDataSource { let itemSelected: (((String, String), String, Int)) -> Void + var deactivateSearch: () -> Void = {} private let theme: PresentationTheme private let strings: PresentationStrings private let displayCodes: Bool + private let glass: Bool private let needsSubtitle: Bool private let tableView: UITableView @@ -201,19 +206,28 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, private var searchResults: [((String, String), String, Int)] = [] private let countryNamesAndCodes: [((String, String), String, [Int])] - init(theme: PresentationTheme, strings: PresentationStrings, displayCodes: Bool, itemSelected: @escaping (((String, String), String, Int)) -> Void) { + private let topEdgeEffectView: EdgeEffectView + + private var searchInput: ComponentView? + var isSearching = true + + + private var validLayout: ContainerViewLayout? + + init(theme: PresentationTheme, strings: PresentationStrings, displayCodes: Bool, glass: Bool, itemSelected: @escaping (((String, String), String, Int)) -> Void) { self.theme = theme self.strings = strings self.displayCodes = displayCodes + self.glass = glass self.itemSelected = itemSelected self.needsSubtitle = strings.baseLanguageCode != "en" - self.tableView = UITableView(frame: CGRect(), style: .plain) + self.tableView = UITableView(frame: CGRect(), style: glass ? .insetGrouped : .plain) if #available(iOS 15.0, *) { self.tableView.sectionHeaderTopPadding = 0.0 } - self.searchTableView = UITableView(frame: CGRect(), style: .plain) + self.searchTableView = UITableView(frame: CGRect(), style: glass ? .insetGrouped : .plain) self.searchTableView.isHidden = true if #available(iOS 11.0, *) { @@ -239,28 +253,32 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, self.sections = sections self.sectionTitles = sections.map { $0.0 } + self.topEdgeEffectView = EdgeEffectView() + super.init() self.setViewBlock({ return UITracingLayerView() }) - self.backgroundColor = theme.list.plainBackgroundColor - - self.tableView.backgroundColor = theme.list.plainBackgroundColor - - self.tableView.backgroundColor = self.theme.list.plainBackgroundColor - self.tableView.separatorColor = self.theme.list.itemPlainSeparatorColor - self.tableView.backgroundView = UIView() - self.tableView.sectionIndexColor = self.theme.list.itemAccentColor - - self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor - - self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor - self.searchTableView.separatorColor = self.theme.list.itemPlainSeparatorColor - self.searchTableView.backgroundView = UIView() - self.searchTableView.sectionIndexColor = self.theme.list.itemAccentColor - + if glass { + self.backgroundColor = theme.list.blocksBackgroundColor + self.tableView.backgroundColor = theme.list.blocksBackgroundColor + self.searchTableView.backgroundColor = theme.list.blocksBackgroundColor + } else { + self.backgroundColor = theme.list.plainBackgroundColor + + self.tableView.backgroundColor = self.theme.list.plainBackgroundColor + self.tableView.separatorColor = self.theme.list.itemPlainSeparatorColor + self.tableView.backgroundView = UIView() + self.tableView.sectionIndexColor = self.theme.list.itemAccentColor + + self.searchTableView.backgroundColor = self.theme.list.plainBackgroundColor + self.searchTableView.separatorColor = self.theme.list.itemPlainSeparatorColor + self.searchTableView.backgroundView = UIView() + self.searchTableView.sectionIndexColor = self.theme.list.itemAccentColor + } + self.tableView.delegate = self self.tableView.dataSource = self @@ -269,13 +287,76 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, self.view.addSubview(self.tableView) self.view.addSubview(self.searchTableView) + + if glass { + self.view.addSubview(self.topEdgeEffectView) + } } - + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.tableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0) - self.searchTableView.contentInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0) - transition.updateFrame(view: self.tableView, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) - transition.updateFrame(view: self.searchTableView, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) + self.validLayout = layout + self.tableView.contentInset = UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0) + self.searchTableView.contentInset = UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0) + transition.updateFrame(view: self.tableView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) + transition.updateFrame(view: self.searchTableView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height))) + + let edgeEffectHeight: CGFloat = 88.0 + let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) + self.topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + + if self.isSearching { + let searchInput: ComponentView + if let current = self.searchInput { + searchInput = current + } else { + searchInput = ComponentView() + self.searchInput = searchInput + } + + let searchInputSize = searchInput.update( + transition: .immediate, + component: AnyComponent( + SearchInputPanelComponent( + theme: self.theme, + strings: self.strings, + updated: { [weak self] query in + guard let self else { + return + } + self.updateSearchQuery(query) + }, + cancel: { [weak self] in + guard let self else { + return + } + self.deactivateSearch() + self.updateSearchQuery("") + } + ) + ), + environment: {}, + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + ) + let bottomInset: CGFloat = layout.insets(options: .input).bottom + let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + if let searchInputView = searchInput.view as? SearchInputPanelComponent.View { + if searchInputView.superview == nil { + self.view.addSubview(searchInputView) + searchInputView.frame = CGRect(origin: CGPoint(x: searchInputFrame.minX, y: layout.size.height), size: searchInputFrame.size) + + searchInputView.activateInput() + } + transition.updateFrame(view: searchInputView, frame: searchInputFrame) + } + } else if let searchInput = self.searchInput { + self.searchInput = nil + if let searchInputView = searchInput.view { + transition.updateFrame(view: searchInputView, frame: CGRect(origin: CGPoint(x: searchInputView.frame.minX, y: layout.size.height), size: searchInputView.frame.size), completion: { _ in + searchInputView.removeFromSuperview() + }) + } + } } func animateIn() { diff --git a/submodules/DatePickerNode/Sources/DatePickerNode.swift b/submodules/DatePickerNode/Sources/DatePickerNode.swift index 61ca6ce416..b32f3a8205 100644 --- a/submodules/DatePickerNode/Sources/DatePickerNode.swift +++ b/submodules/DatePickerNode/Sources/DatePickerNode.swift @@ -63,17 +63,17 @@ public final class DatePickerTheme: Equatable { public extension DatePickerTheme { convenience init(theme: PresentationTheme) { - self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemDisabledTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor, separatorColor: theme.list.itemBlocksSeparatorColor, segmentedControlTheme: SegmentedControlTheme(theme: theme), overallDarkAppearance: theme.overallDarkAppearance) + self.init(backgroundColor: theme.list.itemBlocksBackgroundColor, textColor: theme.list.itemPrimaryTextColor, secondaryTextColor: theme.list.itemSecondaryTextColor, accentColor: theme.list.itemAccentColor, disabledColor: theme.list.itemSecondaryTextColor, selectionColor: theme.list.itemCheckColors.fillColor, selectionTextColor: theme.list.itemCheckColors.foregroundColor, separatorColor: theme.list.itemBlocksSeparatorColor, segmentedControlTheme: SegmentedControlTheme(theme: theme), overallDarkAppearance: theme.overallDarkAppearance) } } private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0) private let upperLimitDate = Date(timeIntervalSince1970: Double(Int32.max - 1)) -private let controlFont = Font.regular(17.0) -private let dayFont = Font.regular(13.0) -private let dateFont = Font.with(size: 17.0, design: .regular, traits: .monospacedNumbers) -private let selectedDateFont = Font.with(size: 17.0, design: .regular, weight: .bold, traits: .monospacedNumbers) +private let controlFont = Font.semibold(17.0) +private let dayFont = Font.medium(13.0) +private let dateFont = Font.with(size: 19.0, design: .regular, traits: .monospacedNumbers) +private let selectedDateFont = Font.with(size: 19.0, design: .regular, weight: .bold, traits: .monospacedNumbers) private var calendar: Calendar = { var calendar = Calendar(identifier: .gregorian) @@ -295,6 +295,7 @@ public final class DatePickerNode: ASDisplayNode { private let strings: PresentationStrings private let dateTimeFormat: PresentationDateTimeFormat private let title: String + private let hasValueRow: Bool private let timeTitleNode: ImmediateTextNode @@ -387,10 +388,11 @@ public final class DatePickerNode: ASDisplayNode { } } - public init(theme: DatePickerTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, title: String) { + public init(theme: DatePickerTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, title: String = "", hasValueRow: Bool = true) { self.theme = theme self.strings = strings self.dateTimeFormat = dateTimeFormat + self.hasValueRow = hasValueRow self.state = State(minDate: telegramReleaseDate, maxDate: upperLimitDate, date: nil, displayingMonthSelection: false, displayingDateSelection: false, displayingTimeSelection: false, selectedMonth: monthForDate(Date())) self.title = title @@ -483,10 +485,11 @@ public final class DatePickerNode: ASDisplayNode { self.addSubnode(self.timePickerBackgroundNode) self.timePickerBackgroundNode.addSubnode(self.timePickerNode) - self.addSubnode(self.timeTitleNode) - - self.addSubnode(self.dateButtonNode) - self.addSubnode(self.timeButtonNode) + if hasValueRow { + self.addSubnode(self.timeTitleNode) + self.addSubnode(self.dateButtonNode) + self.addSubnode(self.timeButtonNode) + } self.monthArrowNode.image = generateSmallArrowImage(color: theme.accentColor) self.previousButtonNode.setImage(generateNavigationArrowImage(color: theme.accentColor, mirror: true), for: .normal) @@ -497,21 +500,6 @@ public final class DatePickerNode: ASDisplayNode { self.setupItems() self.monthButtonNode.addTarget(self, action: #selector(self.monthButtonPressed), forControlEvents: .touchUpInside) - self.monthButtonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.monthTextNode.layer.removeAnimation(forKey: "opacity") - strongSelf.monthTextNode.alpha = 0.4 - strongSelf.monthArrowNode.layer.removeAnimation(forKey: "opacity") - strongSelf.monthArrowNode.alpha = 0.4 - } else { - strongSelf.monthTextNode.alpha = 1.0 - strongSelf.monthTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - strongSelf.monthArrowNode.alpha = 1.0 - strongSelf.monthArrowNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } self.previousButtonNode.addTarget(self, action: #selector(self.previousButtonPressed), forControlEvents: .touchUpInside) self.nextButtonNode.addTarget(self, action: #selector(self.nextButtonPressed), forControlEvents: .touchUpInside) @@ -697,7 +685,7 @@ public final class DatePickerNode: ASDisplayNode { } self.transitionFraction = transitionFraction if let size = self.validLayout { - let topInset: CGFloat = 78.0 + 44.0 + let topInset: CGFloat = self.hasValueRow ? 78.0 + 44.0 : 65.0 let containerSize = CGSize(width: size.width, height: size.height - topInset) self.updateItems(size: containerSize, transition: .animated(duration: 0.3, curve: .spring)) } @@ -783,7 +771,7 @@ public final class DatePickerNode: ASDisplayNode { let constrainedSize = CGSize(width: min(390.0, size.width), height: size.height) let timeHeight: CGFloat = 50.0 - let topInset: CGFloat = 78.0 + timeHeight + let topInset: CGFloat = self.hasValueRow ? 78.0 + timeHeight : 65.0 let sideInset: CGFloat = 16.0 let month = monthForDate(self.state.selectedMonth) @@ -798,7 +786,7 @@ public final class DatePickerNode: ASDisplayNode { self.monthTextNode.attributedText = NSAttributedString(string: stringForMonth(strings: self.strings, month: components.month.flatMap { Int32($0) - 1 } ?? 0, ofYear: components.year.flatMap { Int32($0) - 1900 } ?? 100), font: controlFont, textColor: self.state.displayingMonthSelection ? self.theme.accentColor : self.theme.textColor) let monthSize = self.monthTextNode.updateLayout(size) - let monthTextFrame = CGRect(x: sideInset, y: 11.0 + timeHeight, width: monthSize.width, height: monthSize.height) + let monthTextFrame = CGRect(x: sideInset, y: self.hasValueRow ? 11.0 + timeHeight : 0.0, width: monthSize.width, height: monthSize.height) self.monthTextNode.frame = monthTextFrame let monthArrowFrame = CGRect(x: monthTextFrame.maxX + 10.0, y: monthTextFrame.minY + 4.0, width: 7.0, height: 12.0) @@ -856,7 +844,7 @@ public final class DatePickerNode: ASDisplayNode { self.updateItems(size: containerSize, transition: transition) - let monthInset: CGFloat = timeHeight + 30.0 + let monthInset: CGFloat = self.hasValueRow ? timeHeight + 30.0 : 30.0 self.monthPickerBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: monthInset), size: size) self.monthPickerBackgroundNode.isUserInteractionEnabled = self.state.displayingMonthSelection transition.updateAlpha(node: self.monthPickerBackgroundNode, alpha: self.state.displayingMonthSelection ? 1.0 : 0.0) @@ -872,7 +860,7 @@ public final class DatePickerNode: ASDisplayNode { self.datePickerBackgroundNode.isUserInteractionEnabled = self.state.displayingDateSelection transition.updateAlpha(node: self.datePickerBackgroundNode, alpha: self.state.displayingDateSelection ? 1.0 : 0.0) - self.monthPickerNode.frame = CGRect(x: sideInset, y: topInset - monthInset, width: size.width - sideInset * 2.0, height: 180.0) + self.monthPickerNode.frame = CGRect(x: sideInset, y: topInset - monthInset, width: size.width - sideInset * 2.0, height: 260.0) } public var toggleDateSelection: () -> Void = {} diff --git a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift index d90e35e19a..cf3dfd46c0 100644 --- a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift @@ -76,11 +76,11 @@ private enum DebugAccountsControllerEntry: ItemListNodeEntry { let arguments = arguments as! DebugAccountsControllerArguments switch self { case let .record(_, record, current): - return ItemListCheckboxItem(presentationData: presentationData, title: "\(UInt64(bitPattern: record.id.int64))", style: .left, checked: current, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: "\(UInt64(bitPattern: record.id.int64))", style: .left, checked: current, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.switchAccount(record.id) }) case .loginNewAccount: - return ItemListActionItem(presentationData: presentationData, title: "Login to another account", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Login to another account", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.loginNewAccount() }) } diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 4e221c4aa6..27725379af 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -287,7 +287,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { let arguments = arguments as! DebugControllerArguments switch self { case .testStickerImport: - return ItemListActionItem(presentationData: presentationData, title: "Simulate Stickers Import", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Simulate Stickers Import", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -302,7 +302,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } }) case .sendLogs: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { let _ = (Logger.shared.collectLogs() |> deliverOnMainQueue).start(next: { logs in let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } @@ -386,7 +386,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .sendOneLog: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Latest Logs (Up to 4 MB)", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Latest Logs (Up to 4 MB)", label: "", sectionId: self.section, style: .blocks, action: { let _ = (Logger.shared.collectLogs() |> deliverOnMainQueue).start(next: { logs in let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } @@ -468,7 +468,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .sendShareLogs: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Share Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Share Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { let _ = (Logger.shared.collectLogs(prefix: "/logs/share-logs") |> deliverOnMainQueue).start(next: { logs in let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } @@ -552,7 +552,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .sendGroupCallLogs: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Group Call Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Group Call Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { let _ = (Logger.shared.collectLogs(basePath: arguments.context!.account.basePath + "/group-calls") |> deliverOnMainQueue).start(next: { logs in let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } @@ -636,7 +636,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .sendNotificationLogs: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Notification Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: { let logsPath = arguments.sharedContext.basePath + "/logs/notification-logs" let _ = (Logger(rootPath: logsPath, basePath: logsPath).collectLogs() |> deliverOnMainQueue).start(next: { logs in @@ -721,7 +721,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .sendCriticalLogs: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Critical Logs", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Critical Logs", label: "", sectionId: self.section, style: .blocks, action: { let _ = (Logger.shared.collectShortLogFiles() |> deliverOnMainQueue).start(next: { logs in let presentationData = arguments.sharedContext.currentPresentationData.with { $0 } @@ -774,7 +774,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .sendAllLogs: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send All Logs", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send All Logs", label: "", sectionId: self.section, style: .blocks, action: { let logTypes: [String] = [ "app-logs", "broadcast-logs", @@ -870,7 +870,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .sendStorageStats: - return ItemListDisclosureItem(presentationData: presentationData, title: "Send Storage Stats", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Send Storage Stats", label: "", sectionId: self.section, style: .blocks, action: { guard let context = arguments.context, context.sharedContext.applicationBindings.isMainApp else { return } @@ -925,32 +925,32 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .accounts: - return ItemListDisclosureItem(presentationData: presentationData, title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } arguments.pushController(debugAccountsController(context: context, accountManager: arguments.sharedContext.accountManager)) }) case let .logToFile(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Log to File", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Log to File", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, { $0.withUpdatedLogToFile(value) }).start() }) case let .logToConsole(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Log to Console", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Log to Console", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, { $0.withUpdatedLogToConsole(value) }).start() }) case let .redactSensitiveData(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Remove Sensitive Data", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Remove Sensitive Data", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, { $0.withUpdatedRedactSensitiveData(value) }).start() }) case let .keepChatNavigationStack(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Keep Chat Stack", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Keep Chat Stack", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.keepChatNavigationStack = value @@ -958,7 +958,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .skipReadHistory(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Skip read history", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Skip read history", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.skipReadHistory = value @@ -966,7 +966,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .alwaysDisplayTyping(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Show Typing", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Show Typing", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.alwaysDisplayTyping = value @@ -974,7 +974,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .debugRatingLayout(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Rating Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Rating Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.debugRatingLayout = value @@ -982,7 +982,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .crashOnSlowQueries(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.crashOnLongQueries = value @@ -990,7 +990,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .crashOnMemoryPressure(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Crash on memory pressure", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Crash on memory pressure", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.crashOnMemoryPressure = value @@ -998,7 +998,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case .clearTips: - return ItemListActionItem(presentationData: presentationData, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in transaction.clearNotices() }).start() @@ -1012,7 +1012,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } }) case let .logTranslationRecognition(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Log Language Recognition", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Log Language Recognition", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.logLanguageRecognition = value @@ -1020,7 +1020,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case .resetTranslationStates: - return ItemListActionItem(presentationData: presentationData, title: "Reset Translation States", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Translation States", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { if let context = arguments.context { let _ = context.engine.itemCache.clear(collectionIds: [ ApplicationSpecificItemCacheCollectionId.translationState @@ -1028,7 +1028,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } }) case .resetNotifications: - return ItemListActionItem(presentationData: presentationData, title: "Reset Notifications", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Notifications", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { UIApplication.shared.unregisterForRemoteNotifications() if let context = arguments.context { @@ -1037,11 +1037,11 @@ private enum DebugControllerEntry: ItemListNodeEntry { } }) case .crash: - return ItemListActionItem(presentationData: presentationData, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { preconditionFailure() }) case .fillLocalSavedMessageCache: - return ItemListActionItem(presentationData: presentationData, title: "Reload Saved Messages", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reload Saved Messages", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1054,7 +1054,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .resetDatabase: - return ItemListActionItem(presentationData: presentationData, title: "Clear Database", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Database", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1076,7 +1076,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { arguments.presentController(actionSheet, nil) }) case .resetDatabaseAndCache: - return ItemListActionItem(presentationData: presentationData, title: "Clear Database and Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Database and Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1098,7 +1098,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { arguments.presentController(actionSheet, nil) }) case .resetHoles: - return ItemListActionItem(presentationData: presentationData, title: "Reset Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1111,7 +1111,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .resetTagHoles: - return ItemListActionItem(presentationData: presentationData, title: "Reset Tag Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Tag Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1124,7 +1124,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .reindexUnread: - return ItemListActionItem(presentationData: presentationData, title: "Reindex Unread Counters", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reindex Unread Counters", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1137,7 +1137,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case .resetCacheIndex: - return ItemListActionItem(presentationData: presentationData, title: "Reset Cache Index [!]", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Cache Index [!]", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1145,7 +1145,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { context.account.postbox.mediaBox.storageBox.reset() }) case .reindexCache: - return ItemListActionItem(presentationData: presentationData, title: "Reindex Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reindex Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1185,13 +1185,13 @@ private enum DebugControllerEntry: ItemListNodeEntry { })) }) case .resetBiometricsData: - return ItemListActionItem(presentationData: presentationData, title: "Reset Biometrics Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Reset Biometrics Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { let _ = updatePresentationPasscodeSettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil) }).start() }) case let .webViewInspection(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Allow Web View Inspection", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Allow Web View Inspection", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.allowWebViewInspection = value @@ -1199,11 +1199,11 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case .resetWebViewCache: - return ItemListActionItem(presentationData: presentationData, title: "Clear Web View Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Web View Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ }) }) case .optimizeDatabase: - return ItemListActionItem(presentationData: presentationData, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { guard let context = arguments.context else { return } @@ -1211,15 +1211,15 @@ private enum DebugControllerEntry: ItemListNodeEntry { let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) arguments.presentController(controller, nil) let _ = (context.account.postbox.optimizeStorage() - |> deliverOnMainQueue).start(completed: { - controller.dismiss() - - let controller = OverlayStatusController(theme: presentationData.theme, type: .success) - arguments.presentController(controller, nil) - }) + |> deliverOnMainQueue).start(completed: { + controller.dismiss() + + let controller = OverlayStatusController(theme: presentationData.theme, type: .success) + arguments.presentController(controller, nil) + }) }) case let .photoPreview(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Media Preview (Updated)", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Media Preview (Updated)", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1229,7 +1229,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .knockoutWallpaper(_, value): - return ItemListSwitchItem(presentationData: presentationData, title: "Knockout Wallpaper", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Knockout Wallpaper", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1239,7 +1239,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .experimentalCompatibility(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Experimental Compatibility", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Experimental Compatibility", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1249,7 +1249,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .enableDebugDataDisplay(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Debug Data Display", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Debug Data Display", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1259,7 +1259,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .fakeGlass(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Fake glass", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Fake glass", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1269,7 +1269,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .browserExperiment(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Inline UI", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Inline UI", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1279,7 +1279,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .allForumsHaveTabs(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Forum Tabs Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Forum Tabs Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1289,7 +1289,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .enableReactionOverrides(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Effect Overrides", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Effect Overrides", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1303,7 +1303,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .compressedEmojiCache(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Compressed Emoji Cache", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Compressed Emoji Cache", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1313,7 +1313,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .storiesJpegExperiment(value): - return ItemListSwitchItem(presentationData: presentationData, title: "JPEG X", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "JPEG X", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1323,7 +1323,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .checkSerializedData(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1333,7 +1333,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .enableQuickReactionSwitch(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Enable Quick Reaction", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Enable Quick Reaction", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1343,7 +1343,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .liveStreamV2(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Live Stream V2", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Live Stream V2", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1353,7 +1353,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .experimentalCallMute(value): - return ItemListSwitchItem(presentationData: presentationData, title: "[WIP] OS mic mute", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "[WIP] OS mic mute", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1363,7 +1363,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .playerV2(value): - return ItemListSwitchItem(presentationData: presentationData, title: "PlayerV2", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "PlayerV2", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1373,7 +1373,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .devRequests(value): - return ItemListSwitchItem(presentationData: presentationData, title: "DevRequests", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "DevRequests", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1383,7 +1383,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .enableUpdates(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Enable Updates", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Enable Updates", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1393,7 +1393,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .fakeAds(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Fake Ads", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Fake Ads", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1403,7 +1403,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .enableLocalTranslation(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Local Translation", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Local Translation", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1413,7 +1413,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .preferredVideoCodec(_, title, value, isSelected): - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1423,7 +1423,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .disableVideoAspectScaling(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Video Cropping Optimization", value: !value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Video Cropping Optimization", value: !value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings @@ -1433,7 +1433,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }).start() }) case let .enableNetworkFramework(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Network X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Network X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in if let context = arguments.context { let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in var settings = settings @@ -1443,7 +1443,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } }) case let .enableNetworkExperiments(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Download X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Download X [Restart App]", value: value, sectionId: self.section, style: .blocks, updated: { value in if let context = arguments.context { let _ = updateNetworkSettingsInteractively(postbox: context.account.postbox, network: context.account.network, { settings in var settings = settings @@ -1453,7 +1453,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { } }) case .restorePurchases: - return ItemListActionItem(presentationData: presentationData, title: "Restore Purchases", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Restore Purchases", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.context?.inAppPurchaseManager?.restorePurchases(completion: { state in let text: String switch state { @@ -1469,7 +1469,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }) case let .disableReloginTokens(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Disable Relogin Tokens", value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Disable Relogin Tokens", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in var settings = settings settings.disableReloginTokens = value diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index d19431465f..2aa94dcd55 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -250,7 +250,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega bottomController.viewWillAppear(true) let bottomNode = bottomController.displayNode - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, isFlat: self.isFlat, container: self, topNode: topNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.transitionNavigationBar, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, isFlat: self.isFlat, container: self, topNode: topNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: layout.deviceMetrics.screenCornerRadius, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in if let strongSelf = self { if let top = strongSelf.state.top { strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition) @@ -490,7 +490,7 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega } toValue.value.setIgnoreAppearanceMethodInvocations(false) - let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, isFlat: self.isFlat, container: self, topNode: topController.displayNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.transitionNavigationBar, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in + let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, isFlat: self.isFlat, container: self, topNode: topController.displayNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: layout.deviceMetrics.screenCornerRadius, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in guard let strongSelf = self else { return } diff --git a/submodules/Display/Source/Navigation/NavigationModalContainer.swift b/submodules/Display/Source/Navigation/NavigationModalContainer.swift index 7b1b433995..722f77f787 100644 --- a/submodules/Display/Source/Navigation/NavigationModalContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationModalContainer.swift @@ -388,7 +388,11 @@ final class NavigationModalContainer: ASDisplayNode, ASScrollViewDelegate, ASGes if isStandaloneModal || isLandscape || (self.isFlat && !flatReceivesModalTransition) { self.container.cornerRadius = 0.0 } else { - self.container.cornerRadius = 10.0 + var cornerRadius: CGFloat = 10.0 + if let controller = controllers.first, controller._hasGlassStyle { + cornerRadius = 38.0 + } + self.container.cornerRadius = cornerRadius } if #available(iOS 11.0, *) { diff --git a/submodules/Display/Source/Navigation/NavigationModalFrame.swift b/submodules/Display/Source/Navigation/NavigationModalFrame.swift index c1f919be46..2120a419fe 100644 --- a/submodules/Display/Source/Navigation/NavigationModalFrame.swift +++ b/submodules/Display/Source/Navigation/NavigationModalFrame.swift @@ -109,7 +109,7 @@ public final class NavigationModalFrame: ASDisplayNode { let contentScale = (layout.size.width - sideInset * 2.0) / layout.size.width let bottomInset: CGFloat = layout.size.height - contentScale * layout.size.height - topInset - let cornerRadius: CGFloat = 9.0 + let cornerRadius: CGFloat = 28.0 let initialCornerRadius: CGFloat if !layout.safeInsets.top.isZero { initialCornerRadius = layout.deviceMetrics.screenCornerRadius diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 45b6568209..03f4f71898 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -1503,7 +1503,12 @@ open class NavigationBar: ASDisplayNode { transition = .immediate } self.titleNode.alpha = 1.0 - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) + + var titleOffset: CGFloat = 0.0 + if self.presentationData.theme.backgroundColor == .clear && self.presentationData.theme.separatorColor == .clear { + titleOffset += 3.0 + } + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + titleOffset + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)) } } diff --git a/submodules/Display/Source/NavigationTransitionCoordinator.swift b/submodules/Display/Source/NavigationTransitionCoordinator.swift index 3643832216..c616774639 100644 --- a/submodules/Display/Source/NavigationTransitionCoordinator.swift +++ b/submodules/Display/Source/NavigationTransitionCoordinator.swift @@ -46,6 +46,8 @@ final class NavigationTransitionCoordinator { private let shadowNode: ASImageNode private let customTransitionNode: CustomNavigationTransitionNode? + private var topNodeInitialParameters: (clipsToBounds: Bool, cornerRadius: CGFloat)? + private let inlineNavigationBarTransition: Bool private(set) var animatingCompletion = false @@ -54,7 +56,7 @@ final class NavigationTransitionCoordinator { private var frameRateLink: SharedDisplayLinkDriver.Link? - init(transition: NavigationTransition, isInteractive: Bool, isFlat: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) { + init(transition: NavigationTransition, isInteractive: Bool, isFlat: Bool, container: NavigationContainer, topNode: ASDisplayNode, topNavigationBar: NavigationBar?, bottomNode: ASDisplayNode, bottomNavigationBar: NavigationBar?, screenCornerRadius: CGFloat, didUpdateProgress: ((CGFloat, ContainedViewLayoutTransition, CGRect, CGRect) -> Void)? = nil) { self.transition = transition self.isInteractive = isInteractive self.isFlat = isFlat @@ -109,6 +111,15 @@ final class NavigationTransitionCoordinator { if !self.isFlat { self.container.insertSubnode(self.dimNode, belowSubnode: topNode) self.container.insertSubnode(self.shadowNode, belowSubnode: self.dimNode) + + if screenCornerRadius > 0.0 { + self.topNodeInitialParameters = (topNode.clipsToBounds, topNode.cornerRadius) + if #available(iOS 13.0, *) { + topNode.layer.cornerCurve = .continuous + } + topNode.clipsToBounds = true + topNode.cornerRadius = screenCornerRadius + } } if let customTransitionNode = self.customTransitionNode { self.container.addSubnode(customTransitionNode) @@ -159,7 +170,7 @@ final class NavigationTransitionCoordinator { } }) canInvokeCompletion = true - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX + self.container.overflowInset), height: self.container.bounds.size.height - dimInset))) + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, topFrame.minX + self.container.overflowInset + 62.0), height: self.container.bounds.size.height - dimInset))) transition.updateFrame(node: self.shadowNode, frame: CGRect(origin: CGPoint(x: self.dimNode.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset))) transition.updateAlpha(node: self.dimNode, alpha: (1.0 - position) * 0.15) transition.updateAlpha(node: self.shadowNode, alpha: (1.0 - position) * 0.9) @@ -241,6 +252,8 @@ final class NavigationTransitionCoordinator { strongSelf.endNavigationBarTransition() + strongSelf.restoreTopNodeCorners() + if let currentCompletion = strongSelf.currentCompletion { strongSelf.currentCompletion = nil currentCompletion() @@ -261,6 +274,8 @@ final class NavigationTransitionCoordinator { self.endNavigationBarTransition() + self.restoreTopNodeCorners() + if let currentCompletion = self.currentCompletion { self.currentCompletion = nil currentCompletion() @@ -278,6 +293,8 @@ final class NavigationTransitionCoordinator { strongSelf.endNavigationBarTransition() + strongSelf.restoreTopNodeCorners() + if let currentCompletion = strongSelf.currentCompletion { strongSelf.currentCompletion = nil currentCompletion() @@ -300,6 +317,8 @@ final class NavigationTransitionCoordinator { self.endNavigationBarTransition() + self.restoreTopNodeCorners() + if let currentCompletion = self.currentCompletion { self.currentCompletion = nil currentCompletion() @@ -316,4 +335,15 @@ final class NavigationTransitionCoordinator { }) } } + + private func restoreTopNodeCorners() { + guard let (clipsToBounds, cornerRadius) = self.topNodeInitialParameters else { + return + } + if #available(iOS 13.0, *) { + self.topNode.layer.cornerCurve = .circular + } + self.topNode.clipsToBounds = clipsToBounds + self.topNode.cornerRadius = cornerRadius + } } diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index 13ad7ac7e9..60de5fe558 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -243,6 +243,58 @@ public extension UIColor { return UIColor(hue: max(0.0, min(1.0, hueValue * hue)), saturation: max(0.0, min(1.0, saturationValue * saturation)), brightness: max(0.0, min(1.0, brightnessValue * brightness)), alpha: alphaValue) } + func desaturatedHSL(by amount: CGFloat) -> UIColor { + let amount = max(0, min(1, amount)) + var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 + guard self.getRed(&r, green: &g, blue: &b, alpha: &a) else { return self } + + let maxC = max(r, g, b) + let minC = min(r, g, b) + let delta = maxC - minC + + var h: CGFloat = 0 + let l: CGFloat = (maxC + minC) / 2 + var s: CGFloat = 0 + + if delta != 0 { + s = delta / (1 - abs(2 * l - 1)) + if maxC == r { + h = ((g - b) / delta).truncatingRemainder(dividingBy: 6) + } else if maxC == g { + h = ((b - r) / delta) + 2 + } else { + h = ((r - g) / delta) + 4 + } + h /= 6 + if h < 0 { h += 1 } + } + + let s2 = s * (1 - amount) + + func hue2rgb(_ p: CGFloat, _ q: CGFloat, _ t: CGFloat) -> CGFloat { + var t = t + if t < 0 { t += 1 } + if t > 1 { t -= 1 } + if t < 1/6 { return p + (q - p) * 6 * t } + if t < 1/2 { return q } + if t < 2/3 { return p + (q - p) * (2/3 - t) * 6 } + return p + } + + let q: CGFloat = l < 0.5 ? l * (1 + s2) : l + s2 - l * s2 + let p: CGFloat = 2 * l - q + + let r2 = hue2rgb(p, q, h + 1/3) + let g2 = hue2rgb(p, q, h) + let b2 = hue2rgb(p, q, h - 1/3) + + return UIColor(red: r2, green: g2, blue: b2, alpha: a) + } + + func desaturated() -> UIColor { + return desaturatedHSL(by: 1.0) + } + func mixedWith(_ other: UIColor, alpha: CGFloat) -> UIColor { let alpha = min(1.0, max(0.0, alpha)) let oneMinusAlpha = 1.0 - alpha diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 3fabd72a08..54ff57dc37 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -163,6 +163,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { open var navigationPresentation: ViewControllerNavigationPresentation = .default open var _presentedInModal: Bool = false + open var _hasGlassStyle: Bool = false open var flatReceivesModalTransition: Bool = false public var presentedOverCoveringView: Bool = false @@ -241,7 +242,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 var defaultNavigationBarHeight: CGFloat if self._presentedInModal && layout.orientation == .portrait { - defaultNavigationBarHeight = 56.0 + defaultNavigationBarHeight = self._hasGlassStyle ? 66.0 : 56.0 } else { defaultNavigationBarHeight = 44.0 } diff --git a/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift b/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift index 9d24d564c0..60dd4a8abe 100644 --- a/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift +++ b/submodules/InviteLinksUI/Sources/InviteRequestsSearchItem.swift @@ -150,7 +150,7 @@ final class InviteRequestsSearchItem: ItemListControllerSearch { } } - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if let current = current as? SearchNavigationContentNode { current.updateTheme(presentationData.theme) diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift index ae9b294313..d34dab0625 100644 --- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift @@ -33,6 +33,7 @@ private enum ItemBackgroundColor: Equatable { public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let invite: ExportedChatFolderLink? let share: Bool public let sectionId: ItemListSectionId @@ -44,6 +45,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem { public init( presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle, invite: ExportedChatFolderLink?, share: Bool, sectionId: ItemListSectionId, @@ -54,6 +56,7 @@ public class ItemListFolderInviteLinkListItem: ListViewItem, ItemListItem { tag: ItemListItemTag? = nil ) { self.presentationData = presentationData + self.systemStyle = systemStyle self.invite = invite self.share = share self.sectionId = sectionId @@ -319,7 +322,10 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode let leftInset: CGFloat = 65.0 + params.leftInset let rightInset: CGFloat = 16.0 + params.rightInset - let verticalInset: CGFloat = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 + var verticalInset: CGFloat = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 + if case .glass = item.systemStyle { + verticalInset += 4.0 + } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -347,6 +353,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -453,12 +460,12 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } let iconSize: CGSize = CGSize(width: 40.0, height: 40.0) diff --git a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift index e4e335c9bc..3e4bece6a9 100644 --- a/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift +++ b/submodules/ItemListAvatarAndNameInfoItem/Sources/ItemListAvatarAndNameItem.swift @@ -145,6 +145,7 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem { } let itemContext: ItemContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let dateTimeFormat: PresentationDateTimeFormat let mode: ItemListAvatarAndNameInfoItemMode let peer: EnginePeer? @@ -166,9 +167,10 @@ public class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem { public let selectable: Bool - public init(itemContext: ItemContext, presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, mode: ItemListAvatarAndNameInfoItemMode, peer: EnginePeer?, presence: EnginePeer.Presence?, label: String? = nil, memberCount: Int?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, editingNameCompleted: @escaping () -> Void = {}, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { + public init(itemContext: ItemContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, dateTimeFormat: PresentationDateTimeFormat, mode: ItemListAvatarAndNameInfoItemMode, peer: EnginePeer?, presence: EnginePeer.Presence?, label: String? = nil, memberCount: Int?, state: ItemListAvatarAndNameInfoItemState, sectionId: ItemListSectionId, style: ItemListAvatarAndNameInfoItemStyle, editingNameUpdated: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, editingNameCompleted: @escaping () -> Void = {}, avatarTapped: @escaping () -> Void, context: ItemListAvatarAndNameInfoItemContext? = nil, updatingImage: ItemListAvatarAndNameInfoItemUpdatingAvatar? = nil, call: (() -> Void)? = nil, action: (() -> Void)? = nil, longTapAction: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { self.itemContext = itemContext self.presentationData = presentationData + self.systemStyle = systemStyle self.dateTimeFormat = dateTimeFormat self.mode = mode self.peer = peer @@ -511,6 +513,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo let (statusNodeLayout, statusNodeApply) = layoutStatusNode(TextNodeLayoutArguments(attributedString: NSAttributedString(string: statusText, font: statusFont, textColor: statusColor), backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: availableStatusWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let nameSpacing: CGFloat = 3.0 @@ -528,7 +531,13 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo contentSize = CGSize(width: params.width, height: max(baseHeight, verticalInset * 2.0 + 66.0)) insets = itemListNeighborsPlainInsets(neighbors) case let .blocks(withTopInset, withExtendedBottomInset): - let verticalInset: CGFloat = 13.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 17.0 + case .legacy: + verticalInset = 13.0 + } itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor let baseHeight = nameNodeLayout.size.height + nameSpacing + statusNodeLayout.size.height + 30.0 @@ -667,13 +676,13 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height)) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel)) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: layoutSize.height - insets.top - insets.bottom), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: layoutSize.height - insets.top - insets.bottom), size: CGSize(width: layoutSize.width - bottomStripeInset - separatorRightInset, height: separatorHeight)) } let _ = nameNodeApply() @@ -820,7 +829,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo strongSelf.addSubnode(strongSelf.inputSecondClearButton!) } - strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 46.0), size: CGSize(width: params.width - params.leftInset - 100.0, height: separatorHeight)) + strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 46.0), size: CGSize(width: params.width - params.leftInset - 100.0 - separatorRightInset, height: separatorHeight)) strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 12.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 30.0)) strongSelf.inputSecondField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 52.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 30.0)) @@ -882,7 +891,7 @@ public class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNo strongSelf.addSubnode(strongSelf.inputFirstClearButton!) } - strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 64.0), size: CGSize(width: params.width - params.leftInset - 100.0, height: separatorHeight)) + strongSelf.inputSeparator?.frame = CGRect(origin: CGPoint(x: params.leftInset + 100.0, y: 64.0), size: CGSize(width: params.width - params.leftInset - 100.0 - separatorRightInset, height: separatorHeight)) strongSelf.inputFirstField?.frame = CGRect(origin: CGPoint(x: params.leftInset + 111.0, y: 28.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 111.0 - 36.0, height: 35.0)) if let image = strongSelf.inputFirstClearButton?.image(for: []), let inputFieldFrame = strongSelf.inputFirstField?.frame { diff --git a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift index 70d4039d64..49b9f75a53 100644 --- a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift +++ b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift @@ -22,6 +22,7 @@ public enum ItemListPeerActionItemColor { public class ItemListPeerActionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let style: ItemListStyle + let systemStyle: ItemListSystemStyle let icon: UIImage? let iconSignal: Signal? let title: String @@ -35,9 +36,10 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem { public let sectionId: ItemListSectionId public let action: (() -> Void)? - public init(presentationData: ItemListPresentationData, style: ItemListStyle = .blocks, icon: UIImage?, iconSignal: Signal? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, noInsets: Bool = false, editing: Bool = false, action: (() -> Void)?) { + public init(presentationData: ItemListPresentationData, style: ItemListStyle = .blocks, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage?, iconSignal: Signal? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, noInsets: Bool = false, editing: Bool = false, action: (() -> Void)?) { self.presentationData = presentationData self.style = style + self.systemStyle = systemStyle self.icon = icon self.iconSignal = iconSignal self.title = title @@ -186,22 +188,33 @@ public final class ItemListPeerActionItemNode: ListViewItemNode { let leftInset: CGFloat let iconOffset: CGFloat let verticalInset: CGFloat - let verticalOffset: CGFloat switch item.height { case .generic: iconOffset = 1.0 - verticalInset = 12.0 - verticalOffset = 0.0 + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 12.0 + } leftInset = (item.icon == nil && item.iconSignal == nil ? 16.0 : 59.0) + params.leftInset case .peerList: iconOffset = 3.0 - verticalInset = 14.0 - verticalOffset = 0.0 + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 14.0 + } leftInset = 65.0 + params.leftInset case .compactPeerList: iconOffset = 3.0 - verticalInset = 11.0 - verticalOffset = 0.0 + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } leftInset = 65.0 + params.leftInset } @@ -220,6 +233,7 @@ public final class ItemListPeerActionItemNode: ListViewItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: textColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - editingOffset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 var insets = itemListNeighborsGroupedInsets(neighbors, params) if item.noInsets { @@ -264,7 +278,7 @@ public final class ItemListPeerActionItemNode: ListViewItemNode { strongSelf.iconNode.image = item.icon if let image = item.icon { - transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + iconOffset, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)) + transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + editingOffset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + iconOffset, y: floorToScreenPixels((contentSize.height - image.size.height) / 2.0)), size: image.size)) } else if let iconSignal = item.iconSignal { let imageSize = CGSize(width: 28.0, height: 28.0) strongSelf.iconDisposable.set((iconSignal @@ -330,15 +344,15 @@ public final class ItemListPeerActionItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners || !item.hasSeparator } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - separatorRightInset - params.rightInset, height: separatorHeight))) } - let titleFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size) transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index ced811977e..d951750171 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -440,6 +440,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { } let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let dateTimeFormat: PresentationDateTimeFormat let nameDisplayOrder: PresentationPersonNameOrder let context: Context @@ -482,6 +483,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { public init( presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .legacy, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, @@ -523,6 +525,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { openStories: ((UIView) -> Void)? = nil ) { self.presentationData = presentationData + self.systemStyle = systemStyle self.dateTimeFormat = dateTimeFormat self.nameDisplayOrder = nameDisplayOrder self.context = .account(context) @@ -566,6 +569,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { public init( presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .legacy, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: Context, @@ -607,6 +611,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { openStories: ((UIView) -> Void)? = nil ) { self.presentationData = presentationData + self.systemStyle = systemStyle self.dateTimeFormat = dateTimeFormat self.nameDisplayOrder = nameDisplayOrder self.context = context @@ -1153,7 +1158,11 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo switch item.height { case .generic: if case .none = item.text { - verticalInset = 11.0 + if case .glass = item.systemStyle { + verticalInset = 15.0 + } else { + verticalInset = 11.0 + } } else { verticalInset = 6.0 } @@ -1163,9 +1172,17 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo avatarFontSize = floor(31.0 * 16.0 / 37.0) case .peerList: if case .none = item.text { - verticalInset = 14.0 + if case .glass = item.systemStyle { + verticalInset = 15.0 + } else { + verticalInset = 14.0 + } } else { - verticalInset = 8.0 + if case .glass = item.systemStyle { + verticalInset = 10.0 + } else { + verticalInset = 8.0 + } } verticalOffset = 0.0 avatarSize = 40.0 @@ -1266,6 +1283,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -1438,12 +1456,12 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo strongSelf.bottomStripeNode.isHidden = hasCorners || !item.displayDecorations } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) var titleFrame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size) diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 4833ed62b3..81f65a8c0a 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -40,6 +40,7 @@ public enum ItemListStickerPackItemControl: Equatable { public final class ItemListStickerPackItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData let context: AccountContext + let systemStyle: ItemListSystemStyle let packInfo: StickerPackCollectionInfo.Accessor let itemCount: String let topItem: StickerPackItem? @@ -56,9 +57,10 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem { let removePack: () -> Void let toggleSelected: () -> Void - public init(presentationData: ItemListPresentationData, context: AccountContext, packInfo: StickerPackCollectionInfo.Accessor, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) { + public init(presentationData: ItemListPresentationData, context: AccountContext, systemStyle: ItemListSystemStyle = .legacy, packInfo: StickerPackCollectionInfo.Accessor, itemCount: String, topItem: StickerPackItem?, unread: Bool, control: ItemListStickerPackItemControl, editing: ItemListStickerPackItemEditing, enabled: Bool, playAnimatedStickers: Bool, style: ItemListStyle = .blocks, sectionId: ItemListSectionId, action: (() -> Void)?, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) { self.presentationData = presentationData self.context = context + self.systemStyle = systemStyle self.packInfo = packInfo self.itemCount = itemCount self.topItem = topItem @@ -409,10 +411,19 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = 65.0 + params.leftInset - let verticalInset: CGFloat = 11.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 13.0 + case .legacy: + verticalInset = 11.0 + } + let titleSpacing: CGFloat = 2.0 let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let insets: UIEdgeInsets let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -775,13 +786,13 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) if let unreadImage = unreadImage { strongSelf.unreadNode.image = unreadImage diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift index 2ea7923f7b..9149f3f635 100644 --- a/submodules/ItemListUI/Sources/ItemListController.swift +++ b/submodules/ItemListUI/Sources/ItemListController.swift @@ -121,6 +121,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable private var tabsNavigationContentNode: ItemListControllerTabsContentNode? private var presentationData: ItemListPresentationData + private let hideNavigationBarBackground: Bool private var validLayout: ContainerViewLayout? @@ -265,15 +266,22 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable } } - public init(presentationData: ItemListPresentationData, updatedPresentationData: Signal, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal?) { + public init( + presentationData: ItemListPresentationData, + updatedPresentationData: Signal, + state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, + tabBarItem: Signal?, + hideNavigationBarBackground: Bool = false + ) { self.state = state |> map { controllerState, nodeStateAndArgument -> (ItemListControllerState, (ItemListNodeState, Any)) in return (controllerState, (nodeStateAndArgument.0, nodeStateAndArgument.1)) } self.presentationData = presentationData + self.hideNavigationBarBackground = hideNavigationBarBackground - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme, hideBackground: hideNavigationBarBackground, hideSeparator: hideNavigationBarBackground), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) self.isOpaqueWhenInOverlay = true self.blocksBackgroundWhenInOverlay = true @@ -392,7 +400,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable } } strongSelf.navigationButtonActions = (left: controllerState.leftNavigationButton?.action, right: controllerState.rightNavigationButton?.action, secondaryRight: controllerState.secondaryRightNavigationButton?.action) - + let themeUpdated = strongSelf.presentationData != controllerState.presentationData if strongSelf.leftNavigationButtonTitleAndStyle?.0 != controllerState.leftNavigationButton?.content || strongSelf.leftNavigationButtonTitleAndStyle?.1 != controllerState.leftNavigationButton?.style || themeUpdated { if let leftNavigationButton = controllerState.leftNavigationButton { @@ -510,7 +518,7 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable if strongSelf.presentationData != controllerState.presentationData { strongSelf.presentationData = controllerState.presentationData - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) + strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: strongSelf.hideNavigationBarBackground, hideSeparator: strongSelf.hideNavigationBarBackground), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) strongSelf.statusBar.updateStatusBarStyle(strongSelf.presentationData.theme.rootController.statusBarStyle.style, animated: true) strongSelf.segmentedTitleView?.theme = controllerState.presentationData.theme diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift index 07c3cc8c8b..3053ff1154 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift @@ -70,6 +70,11 @@ public enum ItemListStyle { case blocks } +public enum ItemListSystemStyle { + case glass + case legacy +} + open class ItemListToolbarItem { public struct Action { public let title: String @@ -887,13 +892,13 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { if let titleContentNode = self.navigationBar.contentNode as? ItemListControllerSearchNavigationContentNode { titleContentNode.deactivate() } - updatedTitleContentNode.setQueryUpdated { [weak self] query in + updatedTitleContentNode?.setQueryUpdated { [weak self] query in if let strongSelf = self { strongSelf.searchNode?.queryUpdated(query) } } self.navigationBar.setContentNode(updatedTitleContentNode, animated: true) - updatedTitleContentNode.activate() + updatedTitleContentNode?.activate() } let updatedNode = searchItem.node(current: self.searchNode, titleContentNode: updatedTitleContentNode) diff --git a/submodules/ItemListUI/Sources/ItemListControllerSearch.swift b/submodules/ItemListUI/Sources/ItemListControllerSearch.swift index 2c0cdd9746..214c09cca1 100644 --- a/submodules/ItemListUI/Sources/ItemListControllerSearch.swift +++ b/submodules/ItemListUI/Sources/ItemListControllerSearch.swift @@ -12,7 +12,7 @@ public protocol ItemListControllerSearchNavigationContentNode { public protocol ItemListControllerSearch { func isEqual(to: ItemListControllerSearch) -> Bool - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode } diff --git a/submodules/ItemListUI/Sources/ItemListItem.swift b/submodules/ItemListUI/Sources/ItemListItem.swift index 1cadb4b7d7..44d4897eef 100644 --- a/submodules/ItemListUI/Sources/ItemListItem.swift +++ b/submodules/ItemListUI/Sources/ItemListItem.swift @@ -157,7 +157,7 @@ public func itemListNeighborsGroupedInsets(_ neighbors: ItemListNeighbors, _ par } public func itemListHasRoundedBlockLayout(_ params: ListViewItemLayoutParams) -> Bool { - return params.width >= 375.0 + return params.width >= 350.0 } public final class ItemListPresentationData: Equatable { diff --git a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift index ad4bb4681f..a9e6d0dd34 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift @@ -19,6 +19,7 @@ public enum ItemListActionAlignment { public class ItemListActionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let title: String let kind: ItemListActionKind let alignment: ItemListActionAlignment @@ -29,8 +30,9 @@ public class ItemListActionItem: ListViewItem, ItemListItem { let clearHighlightAutomatically: Bool public let tag: Any? - public init(presentationData: ItemListPresentationData, title: String, kind: ItemListActionKind, alignment: ItemListActionAlignment, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void, longTapAction: (() -> Void)? = nil, clearHighlightAutomatically: Bool = true, tag: Any? = nil) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String, kind: ItemListActionKind, alignment: ItemListActionAlignment, sectionId: ItemListSectionId, style: ItemListStyle, action: @escaping () -> Void, longTapAction: (() -> Void)? = nil, clearHighlightAutomatically: Bool = true, tag: Any? = nil) { self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title self.kind = kind self.alignment = alignment @@ -161,6 +163,15 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -168,12 +179,12 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { case .plain: itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor - contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0) + contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0) insets = itemListNeighborsPlainInsets(neighbors) case .blocks: itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor - contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0) + contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0) insets = itemListNeighborsGroupedInsets(neighbors, params) } @@ -259,19 +270,19 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight)) } switch item.alignment { case .natural: - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size) case .center: - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((params.width - params.leftInset - params.rightInset - titleLayout.size.width) / 2.0), y: 12.0), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: params.leftInset + floor((params.width - params.leftInset - params.rightInset - titleLayout.size.width) / 2.0), y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size) } strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) diff --git a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift index b3eec18ce1..667a471d9d 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift @@ -27,6 +27,7 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem { } let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let icon: UIImage? let iconSize: CGSize? let iconPlacement: IconPlacement @@ -42,8 +43,9 @@ public class ItemListCheckboxItem: ListViewItem, ItemListItem { let action: () -> Void let deleteAction: (() -> Void)? - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, subtitle: String? = nil, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, enabled: Bool = true, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, iconSize: CGSize? = nil, iconPlacement: IconPlacement = .default, title: String, subtitle: String? = nil, style: ItemListCheckboxItemStyle, color: ItemListCheckboxItemColor = .accent, textColor: TextColor = .primary, checked: Bool, enabled: Bool = true, zeroSeparatorInsets: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void, deleteAction: (() -> Void)? = nil) { self.presentationData = presentationData + self.systemStyle = systemStyle self.icon = icon self.iconSize = iconSize self.iconPlacement = iconPlacement @@ -230,9 +232,18 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.subtitle ?? "", font: subtitleFont, textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 28.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } let insets = itemListNeighborsGroupedInsets(neighbors, params) - var contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0) + var contentSize = CGSize(width: params.width, height: titleLayout.size.height + verticalInset * 2.0) if item.subtitle != nil { contentSize.height += subtitleLayout.size.height } @@ -341,14 +352,14 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode { } } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight)) - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + 1.0), size: titleLayout.size) strongSelf.subtitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY), size: subtitleLayout.size) if let icon = item.icon { diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift index dc201ad059..7b03132a31 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift @@ -49,6 +49,7 @@ public enum ItemListDisclosureItemDetailLabelColor { public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let icon: UIImage? let context: AccountContext? let iconPeer: EnginePeer? @@ -73,8 +74,9 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemCompone public let tag: ItemListItemTag? public let shimmeringIndex: Int? - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, titleBadge: String? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, titleBadge: String? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { self.presentationData = presentationData + self.systemStyle = systemStyle self.icon = icon self.context = context self.iconPeer = iconPeer @@ -364,6 +366,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { let contentSize: CGSize var insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -472,16 +476,34 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { if item.iconPeer != nil { verticalInset = 6.0 } else { - verticalInset = 11.0 + switch item.systemStyle { + case .glass: + if !item.label.isEmpty { + switch item.labelStyle { + case .detailText, .multilineDetailText: + verticalInset = 13.0 + default: + if let additionalDetailLabel = item.additionalDetailLabel, !additionalDetailLabel.isEmpty { + verticalInset = 13.0 + } else { + verticalInset = 15.0 + } + } + } else if let additionalDetailLabel = item.additionalDetailLabel, !additionalDetailLabel.isEmpty { + verticalInset = 13.0 + } else { + verticalInset = 15.0 + } + case .legacy: + verticalInset = 11.0 + } } let titleSpacing: CGFloat = 1.0 var height: CGFloat switch item.labelStyle { - case .detailText: - height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height - case .multilineDetailText: + case .detailText, .multilineDetailText: height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height default: height = verticalInset * 2.0 + titleLayout.size.height @@ -646,12 +668,12 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight)) } var centralContentHeight: CGFloat = titleLayout.size.height diff --git a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift index 10806d9bba..3ff5e32610 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift @@ -20,6 +20,7 @@ public class InfoListItem: ListViewItem { public let selectable: Bool = false let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let title: String let text: InfoListItemText let style: ItemListStyle @@ -28,8 +29,9 @@ public class InfoListItem: ListViewItem { let linkAction: ((InfoListItemLinkAction) -> Void)? let closeAction: (() -> Void)? - public init(presentationData: ItemListPresentationData, title: String, text: InfoListItemText, style: ItemListStyle, hasDecorations: Bool = true, isWarning: Bool = false, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String, text: InfoListItemText, style: ItemListStyle, hasDecorations: Bool = true, isWarning: Bool = false, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) { self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title self.text = text self.style = style @@ -76,9 +78,18 @@ public class InfoListItem: ListViewItem { public class ItemListInfoItem: InfoListItem, ItemListItem { public let sectionId: ItemListSectionId - public init(presentationData: ItemListPresentationData, title: String, text: InfoListItemText, style: ItemListStyle, sectionId: ItemListSectionId, linkAction: ((InfoListItemLinkAction) -> Void)? = nil, closeAction: (() -> Void)?) { + public init( + presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .legacy, + title: String, + text: InfoListItemText, + style: ItemListStyle, + sectionId: ItemListSectionId, + linkAction: ((InfoListItemLinkAction) -> Void)? = nil, + closeAction: (() -> Void)? + ) { self.sectionId = sectionId - super.init(presentationData: presentationData, title: title, text: text, style: style, linkAction: linkAction, closeAction: closeAction) + super.init(presentationData: presentationData, systemStyle: systemStyle, title: title, text: text, style: style, linkAction: linkAction, closeAction: closeAction) } override public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { @@ -237,6 +248,7 @@ public class InfoItemNode: ListViewItemNode { insets = UIEdgeInsets() } let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -264,7 +276,12 @@ public class InfoItemNode: ListViewItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - badgeDiameter - 8.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 38.0) + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset = 4.0 + } + + let contentSize = CGSize(width: params.width, height: titleLayout.size.height + textLayout.size.height + 38.0 + verticalInset * 2.0) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) return (layout, { [weak self] in @@ -331,12 +348,12 @@ public class InfoItemNode: ListViewItemNode { strongSelf.badgeNode.isHidden = item.isWarning strongSelf.closeButton.isHidden = item.closeAction == nil - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) if let updateBadgeImage = updatedBadgeImage { if strongSelf.badgeNode.supernode == nil { @@ -349,15 +366,15 @@ public class InfoItemNode: ListViewItemNode { strongSelf.closeButton.setImage(updatedCloseIcon, for: []) } - strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 15.0 + (item.isWarning ? 4.0 : 0.0)), size: CGSize(width: badgeDiameter, height: badgeDiameter)) + strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + 15.0 + (item.isWarning ? 4.0 : 0.0)), size: CGSize(width: badgeDiameter, height: badgeDiameter)) strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 2.0 + UIScreenPixel), size: labelLayout.size) - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 16.0), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: verticalInset + 16.0), size: titleLayout.size) strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size) - strongSelf.closeButton.frame = CGRect(x: params.width - rightInset - 26.0, y: 10.0, width: 32.0, height: 32.0) + strongSelf.closeButton.frame = CGRect(x: params.width - rightInset - 26.0, y: verticalInset + 10.0, width: 32.0, height: 32.0) } }) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift b/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift index 5d8a2ddf5f..c2465dec24 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListMultilineInputItem.swift @@ -34,6 +34,7 @@ public struct ItemListMultilineInputInlineAction { public class ItemListMultilineInputItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let text: String let placeholder: String public let sectionId: ItemListSectionId @@ -52,8 +53,9 @@ public class ItemListMultilineInputItem: ListViewItem, ItemListItem { let noInsets: Bool public let tag: ItemListItemTag? - public init(presentationData: ItemListPresentationData, text: String, placeholder: String, maxLength: ItemListMultilineInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil, noInsets: Bool = false) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, text: String, placeholder: String, maxLength: ItemListMultilineInputItemTextLimit?, sectionId: ItemListSectionId, style: ItemListStyle, capitalization: Bool = true, autocorrection: Bool = true, returnKeyType: UIReturnKeyType = .default, minimalHeight: CGFloat? = nil, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> Void)? = nil, updatedFocus: ((Bool) -> Void)? = nil, tag: ItemListItemTag? = nil, action: (() -> Void)? = nil, inlineAction: ItemListMultilineInputInlineAction? = nil, noInsets: Bool = false) { self.presentationData = presentationData + self.systemStyle = systemStyle self.text = text self.placeholder = placeholder self.maxLength = maxLength @@ -238,9 +240,18 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedMeasureText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 16.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 - let textTopInset: CGFloat = 11.0 - let textBottomInset: CGFloat = 11.0 + let textTopInset: CGFloat + let textBottomInset: CGFloat + switch item.systemStyle { + case .glass: + textTopInset = 15.0 + textBottomInset = 15.0 + case .legacy: + textTopInset = 11.0 + textBottomInset = 11.0 + } var contentHeight: CGFloat = textLayout.size.height + textTopInset + textBottomInset if let minimalHeight = item.minimalHeight { @@ -332,11 +343,11 @@ public class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNod strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil if !item.noInsets { strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } if strongSelf.textNode.attributedPlaceholderText == nil || !strongSelf.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) { diff --git a/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift index 81dc1224b2..f2bdd08d55 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSingleLineInputItem.swift @@ -46,6 +46,7 @@ public enum ItemListSingleLineInputAlignment { public class ItemListSingleLineInputItem: ListViewItem, ItemListItem { let context: AccountContext? + let systemStyle: ItemListSystemStyle let presentationData: ItemListPresentationData let title: NSAttributedString let text: String @@ -69,9 +70,10 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem { let cleared: (() -> Void)? public let tag: ItemListItemTag? - public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, label: String? = nil, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) { + public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: NSAttributedString, text: String, placeholder: String, label: String? = nil, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title self.text = text self.placeholder = placeholder @@ -272,8 +274,17 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label ?? "", font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize), textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel - - let contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + 22.0) + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } + + let contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + verticalInset * 2.0) let insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -444,12 +455,12 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - UIScreenPixel), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) if strongSelf.textNode.textField.attributedPlaceholder == nil || !strongSelf.textNode.textField.attributedPlaceholder!.isEqual(to: attributedPlaceholderText) { strongSelf.textNode.textField.attributedPlaceholder = attributedPlaceholderText diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift index 9c3f8d4303..18a0027cee 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift @@ -20,6 +20,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem { } let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let icon: UIImage? let title: String let text: String? @@ -40,8 +41,9 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem { let action: (() -> Void)? public let tag: ItemListItemTag? - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, text: String? = nil, textColor: TextColor = .primary, titleBadgeComponent: AnyComponent? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, action: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, title: String, text: String? = nil, textColor: TextColor = .primary, titleBadgeComponent: AnyComponent? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, action: (() -> Void)? = nil, tag: ItemListItemTag? = nil) { self.presentationData = presentationData + self.systemStyle = systemStyle self.icon = icon self.title = title self.text = text @@ -243,6 +245,8 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { var contentSize: CGSize var insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -277,12 +281,18 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { insets = itemListNeighborsGroupedInsets(neighbors, params) } - let topInset: CGFloat + + + var topInset: CGFloat if item.text != nil { topInset = 9.0 } else { topInset = 11.0 } + if case .glass = item.systemStyle { + contentSize.height = 52.0 + topInset += 4.0 + } var leftInset = 16.0 + params.leftInset if let _ = item.icon { @@ -452,12 +462,12 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))) transition.updateFrame(node: strongSelf.maskNode, frame: strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))) } let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset), size: titleLayout.size) @@ -531,7 +541,14 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode { let switchFrame = strongSelf.switchNode.frame if let icon = lockedIconNode.image { - lockedIconTransition.updateFrame(node: lockedIconNode, frame: CGRect(origin: CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 11.0 : switchFrame.minX + 11.0, y: switchFrame.minY + 9.0), size: icon.size)) + let iconOrigin: CGPoint + switch item.systemStyle { + case .glass: + iconOrigin = CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 16.0 + UIScreenPixel : switchFrame.minX + 16.0 - UIScreenPixel, y: switchFrame.minY + 8.0) + case .legacy: + iconOrigin = CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 11.0 : switchFrame.minX + 11.0, y: switchFrame.minY + 9.0) + } + lockedIconTransition.updateFrame(node: lockedIconNode, frame: CGRect(origin: iconOrigin, size: icon.size)) } } else if let lockedIconNode = strongSelf.lockedIconNode { strongSelf.lockedIconNode = nil diff --git a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift index 509508353a..0cff7f0ac5 100644 --- a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift +++ b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift @@ -16,6 +16,7 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem { } let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let engine: TelegramEngine let venue: TelegramMediaMap? let title: String? @@ -28,8 +29,9 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem { public let sectionId: ItemListSectionId let header: ListViewItemHeader? - public init(presentationData: ItemListPresentationData, engine: TelegramEngine, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, icon: ItemListVenueItem.InfoIcon = .info, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, engine: TelegramEngine, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, icon: ItemListVenueItem.InfoIcon = .info, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) { self.presentationData = presentationData + self.systemStyle = systemStyle self.engine = engine self.venue = venue self.title = title @@ -201,8 +203,8 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { var updatedTheme: PresentationTheme? var updatedVenueType: String? - let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) - let addressFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize) + let addressFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme @@ -265,6 +267,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let separatorHeight = UIScreenPixel + let separatorRightInset = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -328,7 +331,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { } else { stripeInset = leftInset } - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: stripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - stripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: stripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - stripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) strongSelf.bottomStripeNode.isHidden = last case .blocks: placeholderBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor @@ -371,7 +374,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)) diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 0de2906f25..021f2da0e2 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -607,6 +607,8 @@ public final class ListMessageFileItemNode: ListMessageNode { let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let iconCornerRadius: CGFloat = item.systemStyle == .glass ? 12.0 : 6.0 + let leftInset: CGFloat = 65.0 + params.leftInset let rightInset: CGFloat = 8.0 + params.rightInset @@ -1020,7 +1022,7 @@ public final class ListMessageFileItemNode: ListMessageNode { switch iconImage { case let .imageRepresentation(_, representation): let iconSize = CGSize(width: 40.0, height: 40.0) - let imageCorners = ImageCorners(radius: 6.0) + let imageCorners = ImageCorners(radius: iconCornerRadius) let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) case .albumArt: @@ -1067,7 +1069,14 @@ public final class ListMessageFileItemNode: ListMessageNode { insets.bottom += 35.0 } - let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 8.0 * 2.0 + titleNodeLayout.height - 5.0 + descriptionNodeLayout.height + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), insets: insets) + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset += 2.0 + UIScreenPixel + } + + let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: verticalInset * 2.0 + 8.0 * 2.0 + titleNodeLayout.height - 5.0 + descriptionNodeLayout.height + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), insets: insets) return (nodeLayout, { animation in if let strongSelf = self { @@ -1172,7 +1181,7 @@ public final class ListMessageFileItemNode: ListMessageNode { }) } - transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel))) + transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset - separatorRightInset - params.rightInset, height: UIScreenPixel))) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel - nodeLayout.insets.bottom)) if let backgroundNode = strongSelf.backgroundNode { @@ -1203,13 +1212,13 @@ public final class ListMessageFileItemNode: ListMessageNode { strongSelf.separatorNode.isHidden = false } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil if let backgroundNode = strongSelf.backgroundNode { strongSelf.maskNode.frame = backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) } } - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset - 1.0, y: 7.0), size: titleNodeLayout)) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset - 1.0, y: 7.0 + verticalInset), size: titleNodeLayout)) let _ = titleNodeApply() var descriptionOffset: CGFloat = 0.0 @@ -1227,7 +1236,7 @@ public final class ListMessageFileItemNode: ListMessageNode { } } - transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0), size: textNodeLayout.size)) + transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0 + verticalInset), size: textNodeLayout.size)) let _ = textNodeApply() transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset - 1.0, y: strongSelf.titleNode.frame.maxY - 3.0 + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), size: descriptionNodeLayout)) @@ -1238,7 +1247,7 @@ public final class ListMessageFileItemNode: ListMessageNode { strongSelf.dateNode.isHidden = !item.isGlobalSearchResult let iconSize = CGSize(width: 40.0, height: 40.0) - let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 12.0, y: 8.0), size: iconSize) + let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + leftOffset + 12.0, y: 8.0 + verticalInset), size: iconSize) transition.updateFrame(node: strongSelf.extensionIconNode, frame: iconFrame) strongSelf.extensionIconNode.image = extensionIconImage transition.updateFrame(node: strongSelf.extensionIconText, frame: CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - extensionTextLayout.size.width) / 2.0), y: iconFrame.minY + 7.0 + floorToScreenPixels((iconFrame.height - extensionTextLayout.size.height) / 2.0)), size: extensionTextLayout.size)) @@ -1347,7 +1356,7 @@ public final class ListMessageFileItemNode: ListMessageNode { if let media = selectedMedia as? TelegramMediaFile, media.isInstantVideo { shapes.append(.circle(iconFrame)) } else { - shapes.append(.roundedRect(rect: iconFrame, cornerRadius: 6.0)) + shapes.append(.roundedRect(rect: iconFrame, cornerRadius: iconCornerRadius)) } shimmerNode.update(backgroundColor: item.presentationData.theme.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: nodeLayout.contentSize) diff --git a/submodules/ListMessageItem/Sources/ListMessageItem.swift b/submodules/ListMessageItem/Sources/ListMessageItem.swift index 5968f972ed..a4c7ed9ba3 100644 --- a/submodules/ListMessageItem/Sources/ListMessageItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageItem.swift @@ -46,6 +46,7 @@ public final class ListMessageItemInteraction { public final class ListMessageItem: ListViewItem { let presentationData: ChatPresentationData + let systemStyle: ItemListSystemStyle let context: AccountContext let chatLocation: ChatLocation let interaction: ListMessageItemInteraction @@ -65,8 +66,9 @@ public final class ListMessageItem: ListViewItem { public let selectable: Bool = true - public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, isSavedMusic: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, canReorder: Bool = false, style: ItemListStyle = .plain) { + public init(presentationData: ChatPresentationData, systemStyle: ItemListSystemStyle = .legacy, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, isSavedMusic: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, canReorder: Bool = false, style: ItemListStyle = .plain) { self.presentationData = presentationData + self.systemStyle = systemStyle self.context = context self.chatLocation = chatLocation self.interaction = interaction diff --git a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift index 204443f6e1..c781d07ffb 100644 --- a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift +++ b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift @@ -147,7 +147,7 @@ public final class ListSectionHeaderNode: ASDisplayNode { } } - public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, showBackground: Bool = true) { self.validLayout = (size, leftInset, rightInset) let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height)) self.label.frame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: CGSize(width: labelSize.width, height: size.height)) @@ -158,6 +158,7 @@ public final class ListSectionHeaderNode: ASDisplayNode { actionButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize) } + self.backgroundLayer.isHidden = !showBackground self.backgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel)) } diff --git a/submodules/LocationResources/Sources/VenueIconResources.swift b/submodules/LocationResources/Sources/VenueIconResources.swift index 09d0dc0948..9bf84d1762 100644 --- a/submodules/LocationResources/Sources/VenueIconResources.swift +++ b/submodules/LocationResources/Sources/VenueIconResources.swift @@ -80,9 +80,9 @@ private func venueIconData(engine: TelegramEngine, resource: VenueIconResource) private let randomColors = [UIColor(rgb: 0xe56cd5), UIColor(rgb: 0xf89440), UIColor(rgb: 0x9986ff), UIColor(rgb: 0x44b3f5), UIColor(rgb: 0x6dc139), UIColor(rgb: 0xff5d5a), UIColor(rgb: 0xf87aad), UIColor(rgb: 0x6e82b3), UIColor(rgb: 0xf5ba21)] private let venueColors: [String: UIColor] = [ - "building/medical": UIColor(rgb: 0x43b3f4), - "building/gym": UIColor(rgb: 0x43b3f4), - "arts_entertainment": UIColor(rgb: 0xe56dd6), + "building/medical": UIColor(rgb: 0x43b3f4), // light blue? + "building/gym": UIColor(rgb: 0x43b3f4), // light blue? + "arts_entertainment": UIColor(rgb: 0xaf52de), // purple "travel/bedandbreakfast": UIColor(rgb: 0x9987ff), "travel/hotel": UIColor(rgb: 0x9987ff), "travel/hostel": UIColor(rgb: 0x9987ff), @@ -90,11 +90,11 @@ private let venueColors: [String: UIColor] = [ "building": UIColor(rgb: 0x6e81b2), "education": UIColor(rgb: 0xa57348), "event": UIColor(rgb: 0x959595), - "food": UIColor(rgb: 0xf7943f), - "education/cafeteria": UIColor(rgb: 0xf7943f), - "nightlife": UIColor(rgb: 0xe56dd6), - "travel/hotel_bar": UIColor(rgb: 0xe56dd6), - "parks_outdoors": UIColor(rgb: 0x6cc039), + "food": UIColor(rgb: 0xff9500), // orange + "education/cafeteria": UIColor(rgb: 0xff9500), // orange + "nightlife": UIColor(rgb: 0xaf52de), // purple + "travel/hotel_bar": UIColor(rgb: 0xaf52de), // purple + "parks_outdoors": UIColor(rgb: 0x6cc138), // green "shops": UIColor(rgb: 0xffb300), "travel": UIColor(rgb: 0x1c9fff), "work": UIColor(rgb: 0xad7854), diff --git a/submodules/LocationUI/BUILD b/submodules/LocationUI/BUILD index 4d8245d7ad..5b1581f58e 100644 --- a/submodules/LocationUI/BUILD +++ b/submodules/LocationUI/BUILD @@ -45,6 +45,14 @@ swift_library( "//submodules/AttachmentUI:AttachmentUI", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/ComponentFlow", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/SearchInputPanelComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift index 0b576c9540..e84192d11b 100644 --- a/submodules/LocationUI/Sources/LocationActionListItem.swift +++ b/submodules/LocationUI/Sources/LocationActionListItem.swift @@ -82,13 +82,13 @@ private func generateLiveLocationIcon(theme: PresentationTheme, type: LiveLocati switch type { case .start: imageName = "Location/SendLiveLocationIcon" - color = UIColor(rgb: 0x6cc139) + color = UIColor(rgb: 0x6cc138) case .stop: imageName = "Location/SendLocationIcon" color = UIColor(rgb: 0xff6464) case .extend: imageName = "Location/SendLocationIcon" - color = UIColor(rgb: 0x6cc139) + color = UIColor(rgb: 0x6cc138) } context.clear(CGRect(origin: CGPoint(), size: size)) @@ -283,8 +283,8 @@ final class LocationActionListItemNode: ListViewItemNode { let verticalInset: CGFloat = 8.0 let iconSize: CGFloat = 40.0 - let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) - let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize) + let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) let titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -392,6 +392,7 @@ final class LocationActionListItemNode: ListViewItemNode { let separatorHeight = UIScreenPixel let topHighlightInset: CGFloat = separatorHeight + let separatorRightInset: CGFloat = 16.0 let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize)) strongSelf.iconNode.frame = iconNodeFrame @@ -401,7 +402,7 @@ final class LocationActionListItemNode: ListViewItemNode { strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - leftInset - params.rightInset - separatorRightInset, height: separatorHeight)) strongSelf.separatorNode.isHidden = !hasSeparator if let (beginTimestamp, timeout) = item.beginTimeAndTimeout { diff --git a/submodules/LocationUI/Sources/LocationLiveListItem.swift b/submodules/LocationUI/Sources/LocationLiveListItem.swift index 4c73eb62fc..7df3350349 100644 --- a/submodules/LocationUI/Sources/LocationLiveListItem.swift +++ b/submodules/LocationUI/Sources/LocationLiveListItem.swift @@ -173,8 +173,8 @@ final class LocationLiveListItemNode: ListViewItemNode { let rightInset: CGFloat = params.rightInset let verticalInset: CGFloat = 8.0 - let titleFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize) - let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) + let titleFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize) + let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) var title: String = "" if let author = item.message.author { @@ -302,6 +302,7 @@ final class LocationLiveListItemNode: ListViewItemNode { let separatorHeight = UIScreenPixel let topHighlightInset: CGFloat = separatorHeight + let separatorRightInset: CGFloat = 16.0 let avatarSize: CGFloat = 40.0 if let peer = item.message.author { @@ -312,7 +313,7 @@ final class LocationLiveListItemNode: ListViewItemNode { strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) - strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) + strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width - leftInset - params.rightInset - separatorRightInset, height: separatorHeight)) strongSelf.separatorNode.isHidden = !hasSeparator var liveBroadcastingTimeout: Int32 = 0 diff --git a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift index 8c72f2fa9a..f10b92c778 100644 --- a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift +++ b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift @@ -5,9 +5,16 @@ import Display import TelegramPresentationData import AppBundle import CoreLocation +import ComponentFlow +import GlassBackgroundComponent +import PlainButtonComponent +import BundleIconComponent +import MultilineTextComponent +import EdgeEffect private let panelInset: CGFloat = 4.0 private let panelButtonSize = CGSize(width: 46.0, height: 46.0) +private let glassPanelButtonSize = CGSize(width: 40.0, height: 40.0) private func generateBackgroundImage(theme: PresentationTheme) -> UIImage? { let cornerRadius: CGFloat = 9.0 @@ -36,7 +43,9 @@ private func generateShadowImage(theme: PresentationTheme, highlighted: Bool) -> public final class LocationMapHeaderNode: ASDisplayNode { private var presentationData: PresentationData + private let glass: Bool private let toggleMapModeSelection: () -> Void + private let updateMapMode: (LocationMapMode) -> Void private let goToUserLocation: () -> Void private let showPlacesInThisArea: () -> Void private let setupProximityNotification: (Bool) -> Void @@ -47,52 +56,76 @@ public final class LocationMapHeaderNode: ASDisplayNode { public let mapNode: LocationMapNode public var trackingMode: LocationTrackingMode = .none + private let edgeEffectView: EdgeEffectView + + private let options: ComponentView? + + private let optionsBackgroundView: GlassBackgroundView? private let optionsBackgroundNode: ASImageNode private let optionsSeparatorNode: ASDisplayNode private let optionsSecondSeparatorNode: ASDisplayNode private let infoButtonNode: HighlightableButtonNode private let locationButtonNode: HighlightableButtonNode private let notificationButtonNode: HighlightableButtonNode + private let placesBackgroundView: GlassBackgroundView? private let placesBackgroundNode: ASImageNode private let placesButtonNode: HighlightableButtonNode private let shadowNode: ASImageNode - private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGFloat, CGSize)? + private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGSize)? - public init(presentationData: PresentationData, toggleMapModeSelection: @escaping () -> Void, goToUserLocation: @escaping () -> Void, setupProximityNotification: @escaping (Bool) -> Void = { _ in }, showPlacesInThisArea: @escaping () -> Void = {}) { + public init( + presentationData: PresentationData, + glass: Bool, + toggleMapModeSelection: @escaping () -> Void, + updateMapMode: @escaping (LocationMapMode) -> Void, + goToUserLocation: @escaping () -> Void, + setupProximityNotification: @escaping (Bool) -> Void = { _ in }, + showPlacesInThisArea: @escaping () -> Void = {} + ) { self.presentationData = presentationData + self.glass = glass self.toggleMapModeSelection = toggleMapModeSelection + self.updateMapMode = updateMapMode self.goToUserLocation = goToUserLocation self.setupProximityNotification = setupProximityNotification self.showPlacesInThisArea = showPlacesInThisArea self.mapNode = LocationMapNode() + if glass { + self.options = ComponentView() + } else { + self.options = nil + } + self.optionsBackgroundNode = ASImageNode() self.optionsBackgroundNode.contentMode = .scaleToFill self.optionsBackgroundNode.displaysAsynchronously = false self.optionsBackgroundNode.displayWithoutProcessing = true self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) self.optionsBackgroundNode.isUserInteractionEnabled = true - + self.optionsSeparatorNode = ASDisplayNode() self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.optionsSecondSeparatorNode = ASDisplayNode() self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor + let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor + self.infoButtonNode = HighlightableButtonNode() - self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) - self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) - self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) + self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Location/OptionMap" : "Location/InfoIcon"), color: buttonColor), for: .normal) + self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: .selected) + self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: [.selected, .highlighted]) self.locationButtonNode = HighlightableButtonNode() - self.locationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/TrackIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) + self.locationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/TrackIcon"), color: buttonColor), for: .normal) self.notificationButtonNode = HighlightableButtonNode() - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: buttonColor), for: .normal) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: .selected) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: [.selected, .highlighted]) self.placesBackgroundNode = ASImageNode() self.placesBackgroundNode.contentMode = .scaleToFill @@ -102,7 +135,7 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.placesBackgroundNode.isUserInteractionEnabled = true self.placesButtonNode = HighlightableButtonNode() - self.placesButtonNode.setTitle(presentationData.strings.Map_PlacesInThisArea, with: Font.regular(17.0), with: presentationData.theme.rootController.navigationBar.buttonColor, for: .normal) + self.placesButtonNode.setTitle(presentationData.strings.Map_PlacesInThisArea, with: Font.medium(17.0), with: self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor : buttonColor, for: .normal) self.shadowNode = ASImageNode() self.shadowNode.contentMode = .scaleToFill @@ -110,20 +143,48 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.shadowNode.displayWithoutProcessing = true self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false) + if glass { + self.optionsBackgroundView = GlassBackgroundView() + self.optionsBackgroundNode.image = nil + + self.placesBackgroundView = GlassBackgroundView() + self.placesBackgroundNode.image = nil + } else { + self.optionsBackgroundView = nil + self.placesBackgroundView = nil + } + + self.edgeEffectView = EdgeEffectView() + self.edgeEffectView.isUserInteractionEnabled = false + super.init() self.clipsToBounds = true self.addSubnode(self.mapNode) - self.addSubnode(self.optionsBackgroundNode) - self.optionsBackgroundNode.addSubnode(self.optionsSeparatorNode) - self.optionsBackgroundNode.addSubnode(self.optionsSecondSeparatorNode) - self.optionsBackgroundNode.addSubnode(self.infoButtonNode) - self.optionsBackgroundNode.addSubnode(self.locationButtonNode) - self.optionsBackgroundNode.addSubnode(self.notificationButtonNode) + + self.view.addSubview(self.edgeEffectView) + + if glass { + if let placesBackgroundView = self.placesBackgroundView { + self.placesBackgroundNode.view.addSubview(placesBackgroundView) + } + } else { + if let optionsBackgroundView = self.optionsBackgroundView { + self.optionsSeparatorNode.isHidden = true + self.view.addSubview(optionsBackgroundView) + } + self.addSubnode(self.optionsBackgroundNode) + self.optionsBackgroundNode.addSubnode(self.optionsSeparatorNode) + self.optionsBackgroundNode.addSubnode(self.optionsSecondSeparatorNode) + self.optionsBackgroundNode.addSubnode(self.infoButtonNode) + self.optionsBackgroundNode.addSubnode(self.locationButtonNode) + self.optionsBackgroundNode.addSubnode(self.notificationButtonNode) + } + self.addSubnode(self.placesBackgroundNode) self.placesBackgroundNode.addSubnode(self.placesButtonNode) - self.addSubnode(self.shadowNode) + //self.addSubnode(self.shadowNode) self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside) self.locationButtonNode.addTarget(self, action: #selector(self.locationPressed), forControlEvents: .touchUpInside) @@ -132,44 +193,52 @@ public final class LocationMapHeaderNode: ASDisplayNode { } public func updateState(mapMode: LocationMapMode, trackingMode: LocationTrackingMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) { + let mapModeUpdated = self.mapNode.mapMode != mapMode + let displayingMapModesUpdated = self.infoButtonNode.isSelected != displayingMapModeOptions self.mapNode.mapMode = mapMode self.trackingMode = trackingMode self.infoButtonNode.isSelected = displayingMapModeOptions self.notificationButtonNode.isSelected = proximityNotification ?? false - self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: self.presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) + let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor - let updateLayout = self.displayingPlacesButton != displayingPlacesButton || self.proximityNotification != proximityNotification + self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: buttonColor), for: .normal) + + let updateLayout = self.displayingPlacesButton != displayingPlacesButton || self.proximityNotification != proximityNotification || mapModeUpdated || displayingMapModesUpdated self.displayingPlacesButton = displayingPlacesButton self.proximityNotification = proximityNotification - if updateLayout, let (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size) = self.validLayout { + if updateLayout, let (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) = self.validLayout { let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .spring) : .immediate - self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, offset: offset, size: size, transition: transition) + self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, controlsBottomPadding: controlsBottomPadding, offset: offset, size: size, transition: transition) } } public func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData + let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor + self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) - self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) - self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) - self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) - self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) + self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Location/OptionMap" : "Location/InfoIcon"), color: buttonColor), for: .normal) + self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: .selected) + self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: buttonColor), for: [.selected, .highlighted]) + self.locationButtonNode.setImage(generateTintedImage(image: self.iconForTracking(), color: buttonColor), for: .normal) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: buttonColor), for: .normal) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: .selected) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: buttonColor), for: [.selected, .highlighted]) + if !self.glass { + self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) + } self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false) } private func iconForTracking() -> UIImage? { switch self.trackingMode { case .none: - return UIImage(bundleImageName: "Location/TrackIcon") + return UIImage(bundleImageName: self.glass ? "Location/OptionLocate" : "Location/TrackIcon") case .follow: return UIImage(bundleImageName: "Location/TrackActiveIcon") case .followWithHeading: @@ -177,8 +246,8 @@ public final class LocationMapHeaderNode: ASDisplayNode { } } - public func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, controlsTopPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) { - self.validLayout = (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size) + public func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, controlsTopPadding: CGFloat, controlsBottomPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayout = (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) let mapHeight: CGFloat = floor(layout.size.height * 1.3) + layout.intrinsicInsets.top * 2.0 let mapFrame = CGRect(x: 0.0, y: floorToScreenPixels((size.height - mapHeight + navigationBarHeight) / 2.0) + offset + floor(layout.intrinsicInsets.top * 0.5), width: size.width, height: mapHeight) @@ -188,38 +257,98 @@ public final class LocationMapHeaderNode: ASDisplayNode { let inset: CGFloat = 6.0 let placesButtonSize = CGSize(width: 180.0 + panelInset * 2.0, height: 45.0 + panelInset * 2.0) - let placesButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - placesButtonSize.width) / 2.0), y: self.displayingPlacesButton ? navigationBarHeight + topPadding + inset : 0.0), size: placesButtonSize) + let placesButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - placesButtonSize.width) / 2.0), y: navigationBarHeight + topPadding - 6.0), size: placesButtonSize) transition.updateFrame(node: self.placesBackgroundNode, frame: placesButtonFrame) + + if let placesBackgroundView = self.placesBackgroundView { + let backgroundViewFrame = CGRect(origin: .zero, size: placesButtonFrame.size).insetBy(dx: 5.0, dy: 6.0) + transition.updateFrame(view: placesBackgroundView, frame: backgroundViewFrame) + placesBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor), transition: .immediate) + } + transition.updateFrame(node: self.placesButtonNode, frame: CGRect(origin: CGPoint(), size: placesButtonSize)) transition.updateAlpha(node: self.placesBackgroundNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0) transition.updateAlpha(node: self.placesButtonNode, alpha: self.displayingPlacesButton ? 1.0 : 0.0) transition.updateFrame(node: self.shadowNode, frame: CGRect(x: 0.0, y: size.height - 14.0, width: size.width, height: 14.0)) - transition.updateFrame(node: self.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: panelButtonSize.width, height: panelButtonSize.height)) - transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: panelButtonSize.height)) - transition.updateFrame(node: self.notificationButtonNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: panelButtonSize.height)) - transition.updateFrame(node: self.optionsSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height, width: panelButtonSize.width, height: UIScreenPixel)) - transition.updateFrame(node: self.optionsSecondSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + panelButtonSize.height * 2.0, width: panelButtonSize.width, height: UIScreenPixel)) + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: edgeEffectHeight)) + transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: self.mapNode.mapMode == .map ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: ComponentTransition(transition)) - var panelHeight: CGFloat = panelButtonSize.height * 2.0 - if self.proximityNotification != nil { - panelHeight += panelButtonSize.height + if let options = self.options { + let optionsSize = options.update( + transition: ComponentTransition(transition), + component: AnyComponent( + LocationOptionsComponent( + theme: self.presentationData.theme, + strings: self.presentationData.strings, + mapMode: self.mapNode.mapMode, + showMapModes: self.infoButtonNode.isSelected, + updateMapMode: { [weak self] mode in + guard let self else { + return + } + self.updateMapMode(mode) + }, + goToUserLocation: { [weak self] in + guard let self else { + return + } + self.goToUserLocation() + }, + requestedMapModes: { [weak self] in + guard let self else { + return + } + self.toggleMapModeSelection() + } + ) + ), + environment: {}, + containerSize: layout.size + ) + if let optionsView = options.view { + if optionsView.superview == nil { + self.view.addSubview(optionsView) + } + transition.updateFrame(view: optionsView, frame: CGRect(origin: CGPoint(x: size.width - optionsSize.width - inset - 10.0, y: size.height - optionsSize.height - inset - controlsBottomPadding - 10.0), size: optionsSize)) + } + } else { + let buttonSize = self.glass ? glassPanelButtonSize : panelButtonSize + + transition.updateFrame(node: self.infoButtonNode, frame: CGRect(x: panelInset, y: panelInset, width: buttonSize.width, height: buttonSize.height)) + transition.updateFrame(node: self.locationButtonNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height, width: buttonSize.width, height: buttonSize.height)) + transition.updateFrame(node: self.notificationButtonNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height * 2.0, width: buttonSize.width, height: buttonSize.height)) + transition.updateFrame(node: self.optionsSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height, width: buttonSize.width, height: UIScreenPixel)) + transition.updateFrame(node: self.optionsSecondSeparatorNode, frame: CGRect(x: panelInset, y: panelInset + buttonSize.height * 2.0, width: buttonSize.width, height: UIScreenPixel)) + + var panelHeight: CGFloat = buttonSize.height * 2.0 + if self.proximityNotification != nil { + panelHeight += buttonSize.height + } + transition.updateAlpha(node: self.notificationButtonNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0) + transition.updateAlpha(node: self.optionsSecondSeparatorNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0) + + let backgroundFrame = CGRect(x: size.width - inset - buttonSize.width - panelInset * 2.0 - layout.safeInsets.right - 6.0, y: size.height - panelHeight - inset - 14.0 - controlsBottomPadding, width: buttonSize.width + panelInset * 2.0, height: panelHeight + panelInset * 2.0) + transition.updateFrame(node: self.optionsBackgroundNode, frame: backgroundFrame) + if let optionsBackgroundView = self.optionsBackgroundView { + let backgroundViewFrame = backgroundFrame.insetBy(dx: 4.0, dy: 4.0) + transition.updateFrame(view: optionsBackgroundView, frame: backgroundViewFrame) + optionsBackgroundView.update(size: backgroundViewFrame.size, cornerRadius: backgroundViewFrame.width * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor), transition: .immediate) + } + + let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight && !self.forceIsHidden ? 1.0 : 0.0 + alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha) } - transition.updateAlpha(node: self.notificationButtonNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0) - transition.updateAlpha(node: self.optionsSecondSeparatorNode, alpha: self.proximityNotification != nil ? 1.0 : 0.0) - - transition.updateFrame(node: self.optionsBackgroundNode, frame: CGRect(x: size.width - inset - panelButtonSize.width - panelInset * 2.0 - layout.safeInsets.right, y: navigationBarHeight + controlsTopPadding + inset, width: panelButtonSize.width + panelInset * 2.0, height: panelHeight + panelInset * 2.0)) - - let alphaTransition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) - let optionsAlpha: CGFloat = size.height > 160.0 + navigationBarHeight && !self.forceIsHidden ? 1.0 : 0.0 - alphaTransition.updateAlpha(node: self.optionsBackgroundNode, alpha: optionsAlpha) } public var forceIsHidden: Bool = false { didSet { - if let (layout, navigationBarHeight, topPadding, controlsTopPadding, offset, size) = self.validLayout { - self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, offset: offset, size: size, transition: .immediate) + if let (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) = self.validLayout { + self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, controlsBottomPadding: controlsBottomPadding, offset: offset, size: size, transition: .immediate) } } } @@ -254,3 +383,283 @@ public final class LocationMapHeaderNode: ASDisplayNode { self.showPlacesInThisArea() } } + + +public final class LocationOptionsComponent: Component { + public let theme: PresentationTheme + public let strings: PresentationStrings + public let mapMode: LocationMapMode + public let showMapModes: Bool + public let updateMapMode: (LocationMapMode) -> Void + public let goToUserLocation: () -> Void + public let requestedMapModes: () -> Void + + public init( + theme: PresentationTheme, + strings: PresentationStrings, + mapMode: LocationMapMode, + showMapModes: Bool, + updateMapMode: @escaping (LocationMapMode) -> Void, + goToUserLocation: @escaping () -> Void, + requestedMapModes: @escaping () -> Void + ) { + self.theme = theme + self.strings = strings + self.mapMode = mapMode + self.showMapModes = showMapModes + self.updateMapMode = updateMapMode + self.goToUserLocation = goToUserLocation + self.requestedMapModes = requestedMapModes + } + + public static func ==(lhs: LocationOptionsComponent, rhs: LocationOptionsComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.mapMode != rhs.mapMode { + return false + } + if lhs.showMapModes != rhs.showMapModes { + return false + } + return true + } + + public final class View: HighlightTrackingButton { + private let backgroundView: GlassBackgroundView + private let clippingView: UIView + + private let collapsedContainerView = UIView() + private var mapModeButton = ComponentView() + private var trackingButton = ComponentView() + + private let expandedContainerView = UIView() + private let checkIcon = UIImageView() + private var mapButton = ComponentView() + private var satelliteButton = ComponentView() + private var hybridButton = ComponentView() + + private var component: LocationOptionsComponent? + + public override init(frame: CGRect) { + self.backgroundView = GlassBackgroundView() + self.clippingView = UIView() + self.clippingView.clipsToBounds = true + + self.checkIcon.image = UIImage(bundleImageName: "Media Gallery/Check")?.withRenderingMode(.alwaysTemplate) + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.addSubview(self.clippingView) + self.clippingView.addSubview(self.collapsedContainerView) + self.clippingView.addSubview(self.expandedContainerView) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: LocationOptionsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let mapButtonSize = self.mapButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Map_Map, font: Font.regular(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor))) + ), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.updateMapMode(.map) + }, + animateScale: false + ) + ), + environment: {}, + containerSize: availableSize + ) + + let satelliteButtonSize = self.satelliteButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Map_Satellite, font: Font.regular(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor))) + ), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.updateMapMode(.satellite) + }, + animateScale: false + ) + ), + environment: {}, + containerSize: availableSize + ) + + let hybridButtonSize = self.hybridButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: component.strings.Map_Hybrid, font: Font.regular(17.0), textColor: component.theme.rootController.navigationBar.primaryTextColor))) + ), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.updateMapMode(.hybrid) + }, + animateScale: false + ) + ), + environment: {}, + containerSize: availableSize + ) + + self.checkIcon.tintColor = component.theme.rootController.navigationBar.primaryTextColor + if let image = self.checkIcon.image { + self.checkIcon.frame = CGRect(origin: CGPoint(x: -34.0, y: floorToScreenPixels((mapButtonSize.height - image.size.height) / 2.0)), size: image.size) + } + + let leftInset: CGFloat = 60.0 + let rightInset: CGFloat = 44.0 + let verticalInset: CGFloat = 23.0 + let maxWidth = max(mapButtonSize.width, max(satelliteButtonSize.width, hybridButtonSize.width)) + let cornerRadius: CGFloat = component.showMapModes ? 27.0 : 20.0 + + let expandedSize = CGSize(width: leftInset + maxWidth + rightInset, height: 150.0) + + let mapButtonFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: mapButtonSize) + if let mapButtonView = self.mapButton.view { + if mapButtonView.superview == nil { + self.expandedContainerView.addSubview(mapButtonView) + } + if component.mapMode == .map && component.showMapModes { + mapButtonView.addSubview(self.checkIcon) + } + transition.setFrame(view: mapButtonView, frame: mapButtonFrame) + } + + let satelliteButtonFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((expandedSize.height - satelliteButtonSize.height) / 2.0)), size: satelliteButtonSize) + if let satelliteButtonView = self.satelliteButton.view { + if satelliteButtonView.superview == nil { + self.expandedContainerView.addSubview(satelliteButtonView) + } + if component.mapMode == .satellite && component.showMapModes { + satelliteButtonView.addSubview(self.checkIcon) + } + transition.setFrame(view: satelliteButtonView, frame: satelliteButtonFrame) + } + + let hybridButtonFrame = CGRect(origin: CGPoint(x: leftInset, y: expandedSize.height - hybridButtonSize.height - verticalInset), size: hybridButtonSize) + if let hybridButtonView = self.hybridButton.view { + if hybridButtonView.superview == nil { + self.expandedContainerView.addSubview(hybridButtonView) + } + if component.mapMode == .hybrid && component.showMapModes { + hybridButtonView.addSubview(self.checkIcon) + } + transition.setFrame(view: hybridButtonView, frame: hybridButtonFrame) + } + + let normalSize = CGSize(width: 40.0, height: 80.0) + + let expandedFrame = CGRect(origin: .zero, size: expandedSize) + let collapsedFrame = CGRect(origin: CGPoint(x: expandedSize.width - normalSize.width, y: expandedSize.height - normalSize.height), size: normalSize) + + let effectiveBackgroundFrame = component.showMapModes ? expandedFrame : collapsedFrame + self.backgroundView.update(size: effectiveBackgroundFrame.size, cornerRadius: cornerRadius, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.rootController.navigationBar.glassBarButtonBackgroundColor), transition: transition) + transition.setFrame(view: self.backgroundView, frame: effectiveBackgroundFrame) + + transition.setFrame(view: self.clippingView, frame: effectiveBackgroundFrame) + + transition.setFrame(view: self.expandedContainerView, frame: expandedFrame.offsetBy(dx: effectiveBackgroundFrame.width - expandedFrame.width, dy: effectiveBackgroundFrame.height - expandedFrame.height)) + transition.setFrame(view: self.collapsedContainerView, frame: collapsedFrame.offsetBy(dx: -effectiveBackgroundFrame.minX, dy: -effectiveBackgroundFrame.minY)) + + let mapModeButtonSize = self.mapModeButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + BundleIconComponent(name: "Location/OptionMap", tintColor: component.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62)) + ), + minSize: CGSize(width: 40.0, height: 40.0), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.requestedMapModes() + }, + animateAlpha: true, + animateScale: false + ) + ), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let mapModeButtonFrame = CGRect(origin: .zero, size: mapModeButtonSize) + if let mapModeButtonView = self.mapModeButton.view { + if mapModeButtonView.superview == nil { + self.collapsedContainerView.addSubview(mapModeButtonView) + } + transition.setFrame(view: mapModeButtonView, frame: mapModeButtonFrame) + } + + let trackingButtonSize = self.trackingButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + BundleIconComponent(name: "Location/OptionLocate", tintColor: component.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62)) + ), + minSize: CGSize(width: 40.0, height: 40.0), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.goToUserLocation() + }, + animateAlpha: true, + animateScale: false + ) + ), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let trackingButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: 40.0), size: trackingButtonSize) + if let trackingButtonView = self.trackingButton.view { + if trackingButtonView.superview == nil { + self.collapsedContainerView.addSubview(trackingButtonView) + } + transition.setFrame(view: trackingButtonView, frame: trackingButtonFrame) + } + + transition.setAlpha(view: self.collapsedContainerView, alpha: component.showMapModes ? 0.0 : 1.0) + transition.setAlpha(view: self.expandedContainerView, alpha: component.showMapModes ? 1.0 : 0.0) + + return expandedSize + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return self.backgroundView.frame.contains(point) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index 206ac3c899..5fcd51e3c5 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -9,12 +9,12 @@ private let pinOffset = CGPoint(x: 0.0, y: 33.0) public enum LocationMapMode { case map - case sattelite + case satellite case hybrid var mapType: MKMapType { switch self { - case .sattelite: + case .satellite: return .satellite case .hybrid: return .hybrid diff --git a/submodules/LocationUI/Sources/LocationOptionsNode.swift b/submodules/LocationUI/Sources/LocationOptionsNode.swift index 3a7debafe2..1036a19a41 100644 --- a/submodules/LocationUI/Sources/LocationOptionsNode.swift +++ b/submodules/LocationUI/Sources/LocationOptionsNode.swift @@ -36,7 +36,7 @@ public final class LocationOptionsNode: ASDisplayNode { case 0: updateMapMode(.map) case 1: - updateMapMode(.sattelite) + updateMapMode(.satellite) case 2: updateMapMode(.hybrid) default: diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 16cb1f95dd..68129cfaf6 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -90,8 +90,15 @@ public final class LocationPickerController: ViewController, AttachmentContainab public var isContainerExpanded: () -> Bool = { return false } public var isMinimized: Bool = false - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: LocationPickerMode, source: Source = .generic, initialLocation: CLLocationCoordinate2D? = nil, completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void) { + private let style: Style + public enum Style { + case glass + case legacy + } + + public init(context: AccountContext, style: Style = .legacy, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: LocationPickerMode, source: Source = .generic, initialLocation: CLLocationCoordinate2D? = nil, completion: @escaping (TelegramMediaMap, Int64?, String?, String?, String?) -> Void) { self.context = context + self.style = style self.mode = mode self.source = source self.initialLocation = initialLocation @@ -99,12 +106,18 @@ public final class LocationPickerController: ViewController, AttachmentContainab self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.updatedPresentationData = updatedPresentationData - super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) + let navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: style == .glass, hideSeparator: true), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)) + + super.init(navigationBarPresentationData: navigationBarPresentationData) self.navigationPresentation = .modal - self.title = self.presentationData.strings.Map_ChooseLocationTitle - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + if case .glass = style { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + } else { + self.title = self.presentationData.strings.Map_ChooseLocationTitle + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + } self.updateBarButtons() self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) @@ -114,7 +127,9 @@ public final class LocationPickerController: ViewController, AttachmentContainab } strongSelf.presentationData = presentationData - strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) + let navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme, hideBackground: style == .glass, hideSeparator: true), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings)) + + strongSelf.navigationBar?.updatePresentationData(navigationBarPresentationData) strongSelf.searchNavigationContentNode?.updatePresentationData(strongSelf.presentationData) strongSelf.updateBarButtons() @@ -243,45 +258,55 @@ public final class LocationPickerController: ViewController, AttachmentContainab return state } }, openSearch: { [weak self] in - guard let strongSelf = self, let interaction = strongSelf.interaction, let navigationBar = strongSelf.navigationBar else { + guard let self, let interaction = self.interaction, let navigationBar = self.navigationBar else { return } - strongSelf.controllerNode.updateState { state in + self.controllerNode.updateState { state in var state = state state.displayingMapModeOptions = false return state } - let contentNode = LocationSearchNavigationContentNode(presentationData: strongSelf.presentationData, interaction: interaction) - strongSelf.searchNavigationContentNode = contentNode - navigationBar.setContentNode(contentNode, animated: true) - let isSearching = strongSelf.controllerNode.activateSearch(navigationBar: navigationBar) - contentNode.activate() + + switch style { + case .glass: + let _ = self.controllerNode.activateSearch(navigationBar: navigationBar) + self.updateTabBarVisibility(false, .animated(duration: 0.4, curve: .spring)) + case .legacy: + let contentNode = LocationSearchNavigationContentNode(presentationData: self.presentationData, interaction: interaction) + self.searchNavigationContentNode = contentNode + navigationBar.setContentNode(contentNode, animated: true) + let isSearching = self.controllerNode.activateSearch(navigationBar: navigationBar) + contentNode.activate() - strongSelf.isSearchingDisposable.set((isSearching - |> deliverOnMainQueue).start(next: { [weak self] value in - if let strongSelf = self, let searchNavigationContentNode = strongSelf.searchNavigationContentNode { - searchNavigationContentNode.updateActivity(value) - } - })) + self.isSearchingDisposable.set((isSearching + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self, let searchNavigationContentNode = strongSelf.searchNavigationContentNode { + searchNavigationContentNode.updateActivity(value) + } + })) + } }, updateSearchQuery: { [weak self] query in guard let strongSelf = self else { return } strongSelf.controllerNode.searchContainerNode?.searchTextUpdated(text: query) }, dismissSearch: { [weak self] in - guard let strongSelf = self, let navigationBar = strongSelf.navigationBar else { + guard let self, let navigationBar = self.navigationBar else { return } - strongSelf.isSearchingDisposable.set(nil) - strongSelf.searchNavigationContentNode?.deactivate() - strongSelf.searchNavigationContentNode = nil + self.isSearchingDisposable.set(nil) + self.searchNavigationContentNode?.deactivate() + self.searchNavigationContentNode = nil navigationBar.setContentNode(nil, animated: true) - strongSelf.controllerNode.deactivateSearch() + self.controllerNode.deactivateSearch() + + self.updateTabBarVisibility(true, .animated(duration: 0.4, curve: .spring)) }, dismissInput: { [weak self] in - guard let strongSelf = self else { + guard let self else { return } - strongSelf.searchNavigationContentNode?.deactivate() + self.searchNavigationContentNode?.deactivate() + self.controllerNode.deactivateInput() }, updateSendActionHighlight: { [weak self] highlighted in guard let strongSelf = self else { return @@ -322,8 +347,12 @@ public final class LocationPickerController: ViewController, AttachmentContainab if self.locationAccessDenied { self.navigationItem.rightBarButtonItem = nil } else { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.searchPressed)) - self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.Common_Search + if case .glass = self.style { + + } else { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.searchPressed)) + self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.Common_Search + } } } @@ -379,12 +408,20 @@ public final class LocationPickerController: ViewController, AttachmentContainab self.dismiss() } - @objc private func searchPressed() { + @objc func searchPressed() { self.requestAttachmentMenuExpansion() self.interaction?.openSearch() } + func dismissSearchPressed() { + self.interaction?.dismissSearch() + } + + func updateSearchQuery(_ query: String) { + self.interaction?.updateSearchQuery(query) + } + public func resetForReuse() { self.interaction?.updateMapMode(.map) self.interaction?.dismissSearch() diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index f34b5e9527..0813263b1c 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -16,6 +16,13 @@ import CoreLocation import Geocoding import PhoneNumberFormat import DeviceAccess +import GlassBarButtonComponent +import ComponentFlow +import BundleIconComponent +import MultilineTextComponent +import SearchInputPanelComponent +import ButtonComponent +import EdgeEffect private struct LocationPickerTransaction { let deletions: [ListViewDeleteItem] @@ -175,9 +182,9 @@ private enum LocationPickerEntry: Comparable, Identifiable { icon = .location } return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: { - if let venue = venue { + if let venue { interaction?.sendVenue(venue, queryId, resultId) - } else if let coordinate = coordinate { + } else if let coordinate { interaction?.sendLocation(coordinate, name, address) } }, highlighted: { highlighted in @@ -192,10 +199,10 @@ private enum LocationPickerEntry: Comparable, Identifiable { } }) case let .header(_, title): - return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title) + return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .legacy, title: title) case let .venue(_, venue, queryId, resultId, _): let venueType = venue?.venue?.type ?? "" - return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in + return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in return { interaction?.sendVenue(venue, queryId, resultId) } }, infoAction: ["home", "work"].contains(venueType) ? { interaction?.openHomeWorkInfo() @@ -344,6 +351,14 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private let shadeNode: ASDisplayNode private let innerShadeNode: ASDisplayNode + private let cancelButton = ComponentView() + private let searchButton = ComponentView() + private let title = ComponentView() + + fileprivate let bottomEdgeEffectView: EdgeEffectView + + private var sendButton: ComponentView? + private let optionsNode: LocationOptionsNode private(set) var searchContainerNode: LocationSearchContainerNode? @@ -360,6 +375,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private let searchVenuesPromise = Promise() + private var searchInput: ComponentView? + private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? private var listOffset: CGFloat? @@ -394,9 +411,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.emptyResultsTextNode.textAlignment = .center self.emptyResultsTextNode.isHidden = true - self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.goToUserLocation, showPlacesInThisArea: interaction.showPlacesInThisArea) + self.headerNode = LocationMapHeaderNode( + presentationData: presentationData, + glass: true, + toggleMapModeSelection: interaction.toggleMapModeSelection, + updateMapMode: interaction.updateMapMode, + goToUserLocation: interaction.goToUserLocation, + showPlacesInThisArea: interaction.showPlacesInThisArea + ) self.headerNode.mapNode.isRotateEnabled = false - + self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode) self.shadeNode = ASDisplayNode() @@ -405,13 +429,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.innerShadeNode = ASDisplayNode() self.innerShadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.bottomEdgeEffectView = EdgeEffectView() + self.bottomEdgeEffectView.isUserInteractionEnabled = false + super.init() self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.addSubnode(self.listNode) self.addSubnode(self.headerNode) - self.addSubnode(self.optionsNode) + //self.addSubnode(self.optionsNode) self.listNode.addSubnode(self.emptyResultsTextNode) self.shadeNode.addSubnode(self.innerShadeNode) self.addSubnode(self.shadeNode) @@ -780,36 +807,34 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM if annotations != previousAnnotations { strongSelf.headerNode.mapNode.annotations = annotations } + + var updateLayout = false + if [.denied, .restricted].contains(access) { + if !strongSelf.locationAccessDenied { + strongSelf.locationAccessDenied = true + strongSelf.locationAccessDeniedUpdated(true) + updateLayout = true + } + } else { + if strongSelf.locationAccessDenied { + strongSelf.locationAccessDenied = false + strongSelf.locationAccessDeniedUpdated(false) + updateLayout = true + } + } - if let (layout, navigationBarHeight) = strongSelf.validLayout { - var updateLayout = false - let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) + if case let .location(_, previousAddress, _) = previousState.selectedLocation, case let .location(_, newAddress, _) = state.selectedLocation, previousAddress != newAddress { + updateLayout = true + } else if previousState.displayingMapModeOptions != state.displayingMapModeOptions { + updateLayout = true + } else if previousState.selectedLocation.isCustom != state.selectedLocation.isCustom { + updateLayout = true + } else if previousState.searchingVenuesAround != state.searchingVenuesAround { + updateLayout = true + } - if [.denied, .restricted].contains(access) { - if !strongSelf.locationAccessDenied { - strongSelf.locationAccessDenied = true - strongSelf.locationAccessDeniedUpdated(true) - updateLayout = true - } - } else { - if strongSelf.locationAccessDenied { - strongSelf.locationAccessDenied = false - strongSelf.locationAccessDeniedUpdated(false) - updateLayout = true - } - } - - if previousState.displayingMapModeOptions != state.displayingMapModeOptions { - updateLayout = true - } else if previousState.selectedLocation.isCustom != state.selectedLocation.isCustom { - updateLayout = true - } else if previousState.searchingVenuesAround != state.searchingVenuesAround { - updateLayout = true - } - - if updateLayout { - strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationBarHeight, transition: transition) - } + if updateLayout { + strongSelf.requestLayout(transition: .animated(duration: 0.45, curve: .spring)) } let locale = localeWithStrings(presentationData.strings) @@ -907,7 +932,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM setupGeocoding(coordinate, false, { [weak self] geoAddress, _, address, cityName, streetName, countryCode, isStreet in self?.updateState { state in var state = state - state.selectedLocation = .location(coordinate, address, global) + + var shortAddress = "" + if let streetName { + shortAddress.append(streetName) + } + + state.selectedLocation = .location(coordinate, shortAddress, global) state.geoAddress = geoAddress state.city = cityName state.street = streetName @@ -950,15 +981,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in - guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout, strongSelf.listNode.scrollEnabled else { + guard let self, let (layout, navigationBarHeight) = self.validLayout, self.listNode.scrollEnabled else { return } - let overlap: CGFloat = 6.0 - strongSelf.listOffset = max(0.0, offset) + let overlap: CGFloat = 0.0 //6.0 + self.listOffset = max(0.0, offset) let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap))) - listTransition.updateFrame(node: strongSelf.headerNode, frame: headerFrame) - strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition) - strongSelf.layoutEmptyResultsPlaceholder(transition: listTransition) + listTransition.updateFrame(node: self.headerNode, frame: headerFrame) + self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: self.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? 38.0 : 0.0, controlsBottomPadding: self.isPickingLocation ? 94.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition) + self.layoutEmptyResultsPlaceholder(transition: listTransition) } self.listNode.beganInteractiveDragging = { [weak self] _ in @@ -973,17 +1004,19 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } self.headerNode.mapNode.beganInteractiveDragging = { [weak self] in - guard let strongSelf = self else { + guard let self, let controller = self.controller else { return } - strongSelf.beganInteractiveDragging() - strongSelf.updateState { state in + self.beganInteractiveDragging() + self.updateState { state in var state = state state.displayingMapModeOptions = false state.selectedLocation = .selecting state.searchingVenuesAround = false return state } + + controller.updateTabBarVisibility(false, .animated(duration: 0.4, curve: .spring)) } self.headerNode.mapNode.endedInteractiveDragging = { [weak self] coordinate in @@ -1081,7 +1114,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM if let strongSelf = self { strongSelf.emptyResultsTextNode.isHidden = transition.isLoading || !transition.isEmpty - strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_NoPlacesNearby, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) + strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_NoPlacesNearby, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.itemSecondaryTextColor) strongSelf.layoutEmptyResultsPlaceholder(transition: .immediate) } @@ -1089,17 +1122,17 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } func activateSearch(navigationBar: NavigationBar) -> Signal { - guard let (layout, navigationBarHeight) = self.validLayout, self.searchContainerNode == nil, let coordinate = self.headerNode.mapNode.mapCenterCoordinate else { + guard self.searchContainerNode == nil, let coordinate = self.headerNode.mapNode.mapCenterCoordinate else { return .complete() } - + let searchContainerNode = LocationSearchContainerNode(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, coordinate: coordinate, interaction: self.interaction, story: self.source == .story) self.insertSubnode(searchContainerNode, belowSubnode: navigationBar) self.searchContainerNode = searchContainerNode searchContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.containerLayoutUpdated(layout, navigationHeight: navigationBarHeight, transition: .immediate) + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) return searchContainerNode.isSearching } @@ -1112,6 +1145,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM searchContainerNode?.removeFromSupernode() }) self.searchContainerNode = nil + + self.deactivateInput() + + self.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + } + + func deactivateInput() { + if let searchInputView = self.searchInput?.view as? SearchInputPanelComponent.View { + let _ = searchInputView.deactivateInput() + } } func scrollToTop() { @@ -1140,12 +1183,27 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM let emptyTextSize = self.emptyResultsTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude)) transition.updateFrame(node: self.emptyResultsTextNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - emptyTextSize.width) / 2.0), y: headerHeight + actionsInset + floor((layout.size.height - headerHeight - actionsInset - emptyTextSize.height - layout.intrinsicInsets.bottom - layout.additionalInsets.bottom) / 2.0)), size: emptyTextSize)) } + + private var isPickingLocation: Bool { + return (self.state.selectedLocation.isCustom || self.state.forceSelection) && !self.state.searchingVenuesAround + } + + func requestLayout(transition: ContainedViewLayoutTransition) { + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition) + } + } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { let isFirstLayout = self.validLayout == nil self.validLayout = (layout, navigationHeight) - let isPickingLocation = (self.state.selectedLocation.isCustom || self.state.forceSelection) && !self.state.searchingVenuesAround + var glass = false + if let controller = self.controller, controller._hasGlassStyle { + glass = true + } + + let isPickingLocation = self.isPickingLocation let optionsHeight: CGFloat = 38.0 var actionHeight: CGFloat? self.listNode.forEachItemNode { itemNode in @@ -1155,14 +1213,17 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } } } - -// let topInset: CGFloat = floor((layout.size.height - navigationHeight) / 2.0 + navigationHeight) - let topInset: CGFloat = 240.0 - let overlap: CGFloat = 6.0 + + let topInset: CGFloat = glass ? 300.0 : 240.0 + let overlap: CGFloat = glass ? 0.0 : 6.0 let headerHeight: CGFloat if isPickingLocation, let actionHeight = actionHeight { self.listOffset = topInset - headerHeight = layout.size.height - actionHeight - layout.intrinsicInsets.bottom + overlap - 2.0 + if glass { + headerHeight = layout.size.height + } else { + headerHeight = layout.size.height - actionHeight - layout.intrinsicInsets.bottom + overlap - 2.0 + } } else if let listOffset = self.listOffset { headerHeight = max(0.0, listOffset + overlap) } else { @@ -1171,7 +1232,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight)) transition.updateFrame(node: self.headerNode, frame: headerFrame) - self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, offset: 0.0, size: headerFrame.size, transition: transition) + self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions && !glass ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions && !glass ? optionsHeight : 0.0, controlsBottomPadding: isPickingLocation ? 94.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let scrollToItem: ListViewScrollToItem? @@ -1267,6 +1328,248 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.controller?.updateTabBarAlpha(1.0, .immediate) } + if glass { + let titleSize = self.title.update( + transition: ComponentTransition(transition), + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: self.presentationData.strings.Map_ChooseLocationTitle, + font: Font.semibold(17.0), + textColor: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.rootController.navigationBar.primaryTextColor : .white + ) + ) + ) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 40.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleSize.width) / 2.0), y: floorToScreenPixels((navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.view.addSubview(titleView) + } + transition.updateFrame(view: titleView, frame: titleFrame) + } + + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let cancelButtonSize = self.cancelButton.update( + transition: ComponentTransition(transition), + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: isPickingLocation ? "back" : "close", component: AnyComponent( + BundleIconComponent( + name: isPickingLocation ? "Navigation/Back" : "Navigation/Close", + tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self else { + return + } + if isPickingLocation { + self.goToUserLocation() + } else { + self.controller?.dismiss() + } + } + )), + environment: {}, + containerSize: barButtonSize + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.view.addSubview(cancelButtonView) + } + transition.updateFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + + let searchButtonSize = self.searchButton.update( + transition: ComponentTransition(transition), + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "search", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Search", + tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.controller?.searchPressed() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let searchButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - 16.0 - searchButtonSize.width, y: 16.0), size: searchButtonSize) + if let searchButtonView = self.searchButton.view { + if searchButtonView.superview == nil { + self.view.addSubview(searchButtonView) + } + transition.updateFrameAsPositionAndBounds(layer: searchButtonView.layer, frame: searchButtonFrame) + + if let _ = self.searchContainerNode { + transition.updateAlpha(layer: searchButtonView.layer, alpha: 0.0) + transition.updateTransformScale(layer: searchButtonView.layer, scale: 0.01) + } else { + transition.updateAlpha(layer: searchButtonView.layer, alpha: 1.0) + transition.updateTransformScale(layer: searchButtonView.layer, scale: 1.0) + } + } + + if let _ = self.searchContainerNode { + let searchInput: ComponentView + if let current = self.searchInput { + searchInput = current + } else { + searchInput = ComponentView() + self.searchInput = searchInput + } + + let searchInputTransition: ComponentTransition = self.searchInput?.view == nil ? .immediate : ComponentTransition(transition) + let searchInputSize = searchInput.update( + transition: searchInputTransition, + component: AnyComponent( + SearchInputPanelComponent( + theme: self.presentationData.theme, + strings: self.presentationData.strings, + placeholder: self.presentationData.strings.Map_Search, + resetText: nil, + updated: { [weak self] query in + guard let self, let controller = self.controller else { + return + } + controller.updateSearchQuery(query) + }, + cancel: { [weak self] in + guard let self, let controller = self.controller else { + return + } + controller.dismissSearchPressed() + } + ) + ), + environment: {}, + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + ) + + let bottomInset: CGFloat = layout.insets(options: .input).bottom + let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + if let searchInputView = searchInput.view as? SearchInputPanelComponent.View { + if searchInputView.superview == nil { + self.view.addSubview(searchInputView) + searchInputView.frame = CGRect(origin: CGPoint(x: searchInputFrame.minX, y: layout.size.height), size: searchInputFrame.size) + + searchInputView.activateInput() + } + transition.updateFrame(view: searchInputView, frame: searchInputFrame) + } + } else if let searchInput = self.searchInput { + self.searchInput = nil + if let searchInputView = searchInput.view { + transition.updateFrame(view: searchInputView, frame: CGRect(origin: CGPoint(x: searchInputView.frame.minX, y: layout.size.height), size: searchInputView.frame.size), completion: { _ in + searchInputView.removeFromSuperview() + }) + } + } + + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 88.0 - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: 88.0)) + transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + self.bottomEdgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) + if self.bottomEdgeEffectView.superview == nil { + self.view.addSubview(self.bottomEdgeEffectView) + } + + if isPickingLocation { + let sendButton: ComponentView + if let current = self.sendButton { + sendButton = current + } else { + sendButton = ComponentView() + self.sendButton = sendButton + } + + let buttonBackground = ButtonComponent.Background( + style: .glass, + color: self.presentationData.theme.list.itemCheckColors.fillColor, + foreground: self.presentationData.theme.list.itemCheckColors.foregroundColor, + pressedColor: self.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + ) + //TODO:localize + var buttonContents: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity( + id: AnyHashable("label"), + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Send Location", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) + ) + ] + + var address: String? + if case let .location(_, addressValue, _) = self.state.selectedLocation { + address = addressValue + } + + buttonContents.append( + AnyComponentWithIdentity( + id: AnyHashable(address ?? "locating"), + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: address ?? self.presentationData.strings.Map_Locating, font: Font.medium(13.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7), paragraphAlignment: .center)))) + ) + ) + + let sendButtonSize = sendButton.update( + transition: ComponentTransition(transition), + component: AnyComponent( + ButtonComponent( + background: buttonBackground, + content: AnyComponentWithIdentity( + id: AnyHashable("send"), + component: AnyComponent( + VStack(buttonContents, spacing: 1.0) + ) + ), + action: { [weak self] in + guard let self else { + return + } + if case let .location(coordinate, _, _) = self.state.selectedLocation { + self.interaction.sendLocation(coordinate, nil, nil) + } + } + ) + ), + environment: {}, + containerSize: CGSize(width: layout.size.width - 36.0 * 2.0, height: 52.0) + ) + let sendButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - sendButtonSize.width) / 2.0), y: layout.size.height - insets.bottom - sendButtonSize.height - 14.0), size: sendButtonSize) + if let sendButtonView = sendButton.view { + if sendButtonView.superview == nil { + self.view.addSubview(sendButtonView) + + sendButtonView.alpha = 0.0 + sendButtonView.transform = .identity + + transition.animateTransformScale(view: sendButtonView, from: 0.01) + transition.updateAlpha(layer: sendButtonView.layer, alpha: 1.0) + } + transition.updateFrameAsPositionAndBounds(layer: sendButtonView.layer, frame: sendButtonFrame) + } + } else if let sendButton = self.sendButton { + self.sendButton = nil + if let sendButtonView = sendButton.view { + transition.updateAlpha(layer: sendButtonView.layer, alpha: 0.0, completion: { _ in + sendButtonView.removeFromSuperview() + }) + transition.updateTransformScale(layer: sendButtonView.layer, scale: 0.01) + } + } + } } func updateSendActionHighlight(_ highlighted: Bool) { @@ -1275,6 +1578,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM } func goToUserLocation() { + guard let controller = self.controller else { + return + } + self.searchVenuesPromise.set(.single(nil)) self.updateState { state in var state = state @@ -1283,6 +1590,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM state.searchingVenuesAround = false return state } + + controller.updateTabBarVisibility(true, .animated(duration: 0.4, curve: .spring)) } func requestPlacesAtSelectedLocation() { diff --git a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift index 0749c3fff0..f3ca8873ed 100644 --- a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift +++ b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift @@ -136,10 +136,11 @@ final class LocationSearchContainerNode: ASDisplayNode { self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear // UIColor.black.withAlphaComponent(0.5) + self.listNode = ListView() self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.listNode.isHidden = true + self.listNode.alpha = 0.0 self.listNode.accessibilityPageScrolledString = { row, count in return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } @@ -164,9 +165,7 @@ final class LocationSearchContainerNode: ASDisplayNode { self.addSubnode(self.emptyResultsTitleNode) self.addSubnode(self.emptyResultsTextNode) - - self.listNode.isHidden = true - + let currentLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) let themeAndStringsPromise = self.themeAndStringsPromise @@ -278,7 +277,7 @@ final class LocationSearchContainerNode: ASDisplayNode { } func scrollToTop() { - if !self.listNode.isHidden { + if self.listNode.alpha > 0.0 { self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } @@ -354,14 +353,16 @@ final class LocationSearchContainerNode: ASDisplayNode { guard let strongSelf = self else { return } - strongSelf.listNode.isHidden = !transition.isSearching + + let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) + containerTransition.updateAlpha(node: strongSelf.listNode, alpha: transition.isSearching ? 1.0 : 0.0) strongSelf.dimNode.isHidden = transition.isSearching strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.Map_SearchNoResultsDescription(transition.query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) let emptyResults = transition.isSearching && transition.isEmpty - strongSelf.emptyResultsTitleNode.isHidden = !emptyResults - strongSelf.emptyResultsTextNode.isHidden = !emptyResults + containerTransition.updateAlpha(node: strongSelf.emptyResultsTitleNode, alpha: emptyResults ? 1.0 : 0.0) + containerTransition.updateAlpha(node: strongSelf.emptyResultsTextNode, alpha: emptyResults ? 1.0 : 0.0) if let (layout, navigationBarHeight) = strongSelf.validLayout { strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) diff --git a/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift b/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift index b82ac19664..42f8bd296a 100644 --- a/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift +++ b/submodules/LocationUI/Sources/LocationSectionHeaderItem.swift @@ -9,10 +9,12 @@ import ItemListUI class LocationSectionHeaderItem: ListViewItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let title: String - public init(presentationData: ItemListPresentationData, title: String) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String) { self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title } @@ -102,7 +104,7 @@ private class LocationSectionHeaderItemNode: ListViewItemNode { } headerNode.frame = CGRect(origin: CGPoint(), size: contentSize) - headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset) + headerNode.updateLayout(size: contentSize, leftInset: params.leftInset, rightInset: params.rightInset, showBackground: item.systemStyle == .legacy) } }) }) diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index d8593c0b55..636eabfc99 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -318,7 +318,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan } var setupProximityNotificationImpl: ((Bool) -> Void)? - self.headerNode = LocationMapHeaderNode(presentationData: presentationData, toggleMapModeSelection: interaction.toggleMapModeSelection, goToUserLocation: interaction.toggleTrackingMode, setupProximityNotification: { reset in + self.headerNode = LocationMapHeaderNode( + presentationData: presentationData, + glass: false, + toggleMapModeSelection: interaction.toggleMapModeSelection, + updateMapMode: interaction.updateMapMode, + goToUserLocation: interaction.toggleTrackingMode, + setupProximityNotification: { reset in setupProximityNotificationImpl?(reset) }) //self.headerNode.mapNode.isRotateEnabled = false @@ -714,7 +720,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan strongSelf.listOffset = max(0.0, offset) let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(0.0, offset + overlap))) listTransition.updateFrame(node: strongSelf.headerNode, frame: headerFrame) - strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition) + strongSelf.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsTopPadding: strongSelf.state.displayingMapModeOptions ? 38.0 : 0.0, controlsBottomPadding: 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition) } self.listNode.beganInteractiveDragging = { [weak self] _ in @@ -952,7 +958,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: headerHeight)) transition.updateFrame(node: self.headerNode, frame: headerFrame) - self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, offset: 0.0, size: headerFrame.size, transition: transition) + self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsBottomPadding: 0.0, offset: 0.0, size: headerFrame.size, transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index ce1515accc..debd37981b 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -54,6 +54,10 @@ swift_library( "//submodules/TelegramUI/Components/MediaAssetsContext", "//submodules/TelegramUI/Components/AvatarBackground", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/LottieComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index ec7db2e9b7..c6d47c3e77 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -28,6 +28,12 @@ import ImageObjectSeparation import ChatSendMessageActionUI import AnimatedCountLabelNode import MediaAssetsContext +import GlassBackgroundComponent +import EdgeEffect +import ComponentFlow +import BundleIconComponent +import LottieComponent +import GlassBarButtonComponent final class MediaPickerInteraction { let downloadManager: AssetDownloadManager @@ -131,6 +137,11 @@ struct Month: Equatable { private var savedStoriesContentOffset: CGFloat? public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, AttachmentContainable { + public enum Style { + case glass + case legacy + } + public enum Subject { public enum Media: Equatable { case image(UIImage) @@ -173,6 +184,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att private var presentationData: PresentationData private var presentationDataDisposable: Disposable? private let updatedPresentationData: (initial: PresentationData, signal: Signal)? + private let style: Style fileprivate var interaction: MediaPickerInteraction? @@ -193,6 +205,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att private let titleView: MediaPickerTitleView private let cancelButtonNode: WebAppCancelButtonNode + + private var cancelButton: ComponentView? + private var rightButton: ComponentView? + private let moreButtonPlayOnce = ActionSlot() + private let moreButtonNode: MoreButtonNode private let selectedButtonNode: SelectedButtonNode @@ -258,8 +275,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att private var requestedCameraAccess = false private let containerNode: ASDisplayNode + private let backgroundView: GlassBackgroundView? private let backgroundNode: NavigationBackgroundNode fileprivate let gridNode: GridNode + fileprivate let topEdgeEffectView: EdgeEffectView + fileprivate let bottomEdgeEffectView: EdgeEffectView fileprivate let cameraWrapperView: UIView fileprivate var cameraView: TGAttachmentCameraView? @@ -297,6 +317,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att private var fastScrollContentOffset = ValuePromise(ignoreRepeated: true) private var fastScrollDisposable: Disposable? + fileprivate var scrolledToTop = true + fileprivate var scrolledExactlyToTop = true + private var didSetReady = false private let _ready = Promise() var ready: Promise { @@ -323,6 +346,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.containerNode = ASDisplayNode() self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor) self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + + if case .glass = controller.style, !"".isEmpty { + self.backgroundView = GlassBackgroundView() + } else { + self.backgroundView = nil + } + self.gridNode = GridNode() self.scrollingArea = SparseItemGridScrollingArea() @@ -333,6 +363,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.cameraActivateAreaNode.accessibilityLabel = "Camera" self.cameraActivateAreaNode.accessibilityTraits = [.button] + self.topEdgeEffectView = EdgeEffectView() + self.topEdgeEffectView.isUserInteractionEnabled = false + + self.bottomEdgeEffectView = EdgeEffectView() + self.bottomEdgeEffectView.isUserInteractionEnabled = false + super.init() if case .assets(nil, .default) = controller.subject { @@ -342,10 +378,23 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } self.addSubnode(self.containerNode) - self.containerNode.addSubnode(self.backgroundNode) + + if let backgroundView = self.backgroundView { + self.containerNode.view.addSubview(backgroundView) + } else { + self.containerNode.addSubnode(self.backgroundNode) + } self.containerNode.addSubnode(self.gridNode) self.containerNode.addSubnode(self.scrollingArea) + if case .glass = controller.style { + self.containerNode.view.addSubview(self.topEdgeEffectView) + + if case let .assets(_, mode) = controller.subject, case .default = mode { + self.containerNode.view.addSubview(self.bottomEdgeEffectView) + } + } + self.gridNode.scrollView.addSubview(self.cameraWrapperView) let selectedCollection = controller.selectedCollection.get() @@ -399,8 +448,37 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self?.dismissInput() } - self.gridNode.visibleContentOffsetChanged = { [weak self] _ in - self?.updateNavigation(transition: .immediate) + self.gridNode.visibleContentOffsetChanged = { [weak self] offset in + guard let self else { + return + } + self.updateNavigation(transition: .immediate) + + var scrolledToTop = false + var scrolledExactlyToTop = false + if case let .known(contentOffset) = offset { + if contentOffset < 30.0 { + scrolledToTop = true + } + if contentOffset < 5.0 { + scrolledExactlyToTop = true + } + } + + var updated = false + var transition: ContainedViewLayoutTransition = .animated(duration: 0.5, curve: .easeInOut) + if self.scrolledToTop != scrolledToTop { + self.scrolledToTop = scrolledToTop + updated = true + } + if self.scrolledExactlyToTop != scrolledExactlyToTop { + self.scrolledExactlyToTop = scrolledExactlyToTop + updated = true + transition = .animated(duration: 0.25, curve: .easeInOut) + } + if updated { + self.controller?.updateNavigationButtons(transition: transition) + } } self.hiddenMediaDisposable = (self.hiddenMediaId.get() @@ -1018,7 +1096,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } - private func updateSelectionState(animated: Bool = false) { + private func updateSelectionState(animated: Bool = false, updateLayout: Bool = true) { self.gridNode.forEachItemNode { itemNode in if let itemNode = itemNode as? MediaPickerGridItemNode { itemNode.updateSelectionState(animated: animated) @@ -1029,7 +1107,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0) self.controller?.updateSelectionState(count: count) - if let (layout, navigationBarHeight) = self.validLayout { + if updateLayout, let (layout, navigationBarHeight) = self.validLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .spring)) } } @@ -1039,6 +1117,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.tabBar.backgroundColor, transition: .immediate) + + self.updateSelectionState(animated: true) } private(set) var currentDisplayMode: DisplayMode = .all { @@ -1085,17 +1165,16 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att return nil } } - self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode) self.selectionNode = selectionNode - - if let (layout, navigationBarHeight) = self.validLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } + } + + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.25, curve: .easeInOut)) } self.gridNode.isUserInteractionEnabled = displayMode == .all self.selectionNode?.isUserInteractionEnabled = displayMode == .selected - + var completion: () -> Void = {} if updated && displayMode == .all { completion = { @@ -1538,6 +1617,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let firstTime = self.validLayout == nil self.validLayout = (layout, navigationBarHeight) + if firstTime { + self.updateSelectionState(animated: false, updateLayout: false) + } + var insets = layout.insets(options: []) insets.top += navigationBarHeight @@ -1656,8 +1739,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att transition.updateFrame(node: self.gridNode, frame: innerBounds) self.scrollingArea.frame = innerBounds - transition.updateFrame(node: self.backgroundNode, frame: innerBounds) - self.backgroundNode.update(size: bounds.size, transition: transition) + if let backgroundView = self.backgroundView { + backgroundView.update(size: bounds.size, cornerRadius: 0.0, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: self.presentationData.theme.list.plainBackgroundColor), transition: ComponentTransition(transition)) + transition.updateFrame(view: backgroundView, frame: innerBounds) + } else { + transition.updateFrame(node: self.backgroundNode, frame: innerBounds) + self.backgroundNode.update(size: bounds.size, transition: transition) + } transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: bounds.height))) @@ -1699,9 +1787,14 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } if let selectionNode = self.selectionNode, let controller = self.controller { + let selectionTransition = selectionNode.supernode == nil ? .immediate : transition + if selectionNode.supernode == nil { + self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode) + } + let selectedItems = controller.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? [] let updateSelectionNode = { - selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition) + selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: selectionTransition) } if selectedItems.count < 1 && self.currentDisplayMode == .selected { @@ -1710,10 +1803,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } else { updateSelectionNode() } - transition.updateFrame(node: selectionNode, frame: innerBounds) + selectionTransition.updateFrame(node: selectionNode, frame: innerBounds) } - var cameraView: UIView? if let view = self.cameraView { cameraView = view @@ -1801,6 +1893,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.placeholderNode = nil placeholderNode.removeFromSupernode() } + + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 88.0 - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: 88.0)) + transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + self.bottomEdgeEffectView.update(content: self.currentDisplayMode == .all ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) } } @@ -1840,6 +1936,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + style: Style = .legacy, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, @@ -1864,6 +1961,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.presentationData = presentationData self.updatedPresentationData = updatedPresentationData + self.style = style self.peer = peer self.threadTitle = threadTitle self.chatLocation = chatLocation @@ -1882,7 +1980,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let selectionContext = selectionContext ?? TGMediaSelectionContext(groupingAllowed: false, selectionLimit: enableMultiselection ? 100 : 1)! - self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) + self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, glass: style == .glass, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) if case let .assets(collection, mode) = subject { if let collection = collection { @@ -1916,15 +2014,26 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings) + if case .glass = style { + self.cancelButton = ComponentView() + self.rightButton = ComponentView() + } self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme) self.moreButtonNode.iconNode.enqueueState(.more, animated: false) - self.selectedButtonNode = SelectedButtonNode(theme: self.presentationData.theme) + self.selectedButtonNode = SelectedButtonNode(theme: self.presentationData.theme, glass: self.style == .glass) self.selectedButtonNode.alpha = 0.0 self.selectedButtonNode.transform = CATransform3DMakeScale(0.01, 0.01, 1.0) - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData)) + var navigationBarPresentationData: NavigationBarPresentationData? + if case .glass = style { + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: true, hideSeparator: true) + } else { + navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData) + } + + super.init(navigationBarPresentationData: navigationBarPresentationData) self.statusBar.statusBarStyle = .Ignore @@ -2011,7 +2120,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if case .wallpaper = mode { self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) } else if collection == nil { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + if let _ = self.cancelButton { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + } else { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + } var hasSelect = false if forCollage { @@ -2024,12 +2137,19 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } if hasSelect { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Select, target: self, action: #selector(self.selectPressed)) + if let _ = self.rightButton { + } else { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Select, target: self, action: #selector(self.selectPressed)) + } } else { if [.createSticker].contains(mode) { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) - self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) - self.navigationItem.rightBarButtonItem?.target = self + if let _ = self.rightButton { + + } else { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + } } } } else { @@ -2039,24 +2159,32 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if case let .assets(collection, _) = self.subject, collection != nil { self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) } else { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode) - self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) - self.navigationItem.leftBarButtonItem?.target = self + if let _ = self.cancelButton { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) + } else { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode) + self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) + self.navigationItem.leftBarButtonItem?.target = self + } } if self.bannedSendPhotos != nil && self.bannedSendVideos != nil { } else { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) - self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) - self.navigationItem.rightBarButtonItem?.target = self + if let _ = self.rightButton { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: UIView()) + } else { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.rightButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + } } } - self.moreButtonNode.action = { [weak self] _, gesture in - if let strongSelf = self { - strongSelf.searchOrMorePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture) - } - } +// self.moreButtonNode.action = { [weak self] _, gesture in +// if let strongSelf = self { +// strongSelf.searchOrMorePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture) +// } +// } self.selectedButtonNode.addTarget(self, action: #selector(self.selectedPressed), forControlEvents: .touchUpInside) @@ -2199,8 +2327,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att guard let self else { return } - let count = Int32(self.interaction?.selectionState?.count() ?? 0) - self.updateSelectionState(count: count) + self.updateNavigationButtons() } if case .media = self.subject { self.controllerNode.updateDisplayMode(.selected, animated: false) @@ -2213,6 +2340,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.controllerNode.closeGalleryController() } + private func updateNavigationButtons(transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)) { + let count = Int32(self.interaction?.selectionState?.count() ?? 0) + self.updateSelectionState(count: count, transition: transition) + } + public var groupsPresented: () -> Void = {} private var didSetupGroups = false @@ -2252,7 +2384,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.titleView.isHighlighted = true let contextController = ContextController( presentationData: self.presentationData, - source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceNode: self.titleView.contextSourceNode)), + source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: self.titleView)), items: .single(ContextController.Items(content: .custom(content))), gesture: nil ) @@ -2360,21 +2492,20 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } fileprivate var selectionCount: Int32 = 0 - fileprivate func updateSelectionState(count: Int32) { + fileprivate func updateSelectionState(count: Int32, transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)) { self.selectionCount = count guard let layout = self.validLayout else { return } - let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) var moreIsVisible = false + var isBack = false if case let .assets(_, mode) = self.subject, [.story, .createSticker].contains(mode) { moreIsVisible = true } else if case let .media(media) = self.subject { self.titleView.title = media.count == 1 ? self.presentationData.strings.Attachment_Pasteboard : self.presentationData.strings.Attachment_SelectedMedia(count) self.titleView.segmentsHidden = true moreIsVisible = true -// self.moreButtonNode.iconNode.enqueueState(.more, animated: false) } else { let title: String let isEnabled: Bool @@ -2387,6 +2518,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } self.titleView.updateTitle(title: title, isEnabled: isEnabled, animated: true) self.cancelButtonNode.setState(isEnabled ? .cancel : .back, animated: true) + isBack = !isEnabled let selectedSize = self.selectedButtonNode.update(count: count) @@ -2394,8 +2526,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att if layout.safeInsets.right > 0.0 { safeInset += layout.safeInsets.right + 16.0 } + let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0 let navigationHeight = navigationLayout(layout: layout).navigationFrame.height - self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - 54.0 - selectedSize.width - safeInset, y: floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize) + self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - selectedSize.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize) let isSelectionButtonVisible = count > 0 && self.controllerNode.currentDisplayMode == .all transition.updateAlpha(node: self.selectedButtonNode, alpha: isSelectionButtonVisible ? 1.0 : 0.0) @@ -2409,8 +2542,109 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att moreIsVisible = count > 0 } - transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0) - transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1) + let useGlassButtons = isBack || !self.controllerNode.scrolledToTop + + let barButtonSideInset: CGFloat = 16.0 + let barButtonSize = CGSize(width: 40.0, height: 40.0) + + var buttonTransition = ComponentTransition.easeInOut(duration: 0.25) + if case let .animated(duration, _) = transition, duration > 0.25 { + buttonTransition = .easeInOut(duration: duration) + } + + self.titleView.updateIsDark(isDark: self.controllerNode.currentDisplayMode == .all && useGlassButtons, animated: true) + + let topEdgeColor: UIColor + if self.controllerNode.currentDisplayMode == .all { + if useGlassButtons { + topEdgeColor = UIColor(rgb: 0x000000, alpha: 0.45) + } else { + topEdgeColor = self.presentationData.theme.overallDarkAppearance ? self.presentationData.theme.list.modalBlocksBackgroundColor : self.presentationData.theme.list.plainBackgroundColor + } + } else { + topEdgeColor = .clear + } + let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 100.0)) + transition.updateFrame(view: self.controllerNode.topEdgeEffectView, frame: topEdgeEffectFrame) + transition.updateAlpha(layer: self.controllerNode.topEdgeEffectView.layer, alpha: self.controllerNode.scrolledExactlyToTop && self.controllerNode.currentDisplayMode == .all ? 0.0 : 1.0) + self.controllerNode.topEdgeEffectView.update(content: topEdgeColor, blur: true, alpha: 0.8, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + + if let cancelButton = self.cancelButton { + if cancelButton.view == nil { + buttonTransition = .immediate + } + let cancelButtonSize = cancelButton.update( + transition: buttonTransition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: useGlassButtons ? .glass : .generic, + component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent( + BundleIconComponent( + name: isBack ? "Navigation/Back" : "Navigation/Close", + tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.cancelPressed() + } + )), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset, y: barButtonSideInset), size: cancelButtonSize) + if let view = cancelButton.view { + if view.superview == nil { + self.view.addSubview(view) + } + view.bounds = CGRect(origin: .zero, size: cancelButtonFrame.size) + view.center = cancelButtonFrame.center + } + } + + if let moreButton = self.rightButton { + let moreButtonSize = moreButton.update( + transition: buttonTransition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: useGlassButtons ? .glass : .generic, + component: AnyComponentWithIdentity(id: "more", component: AnyComponent( + LottieComponent( + content: LottieComponent.AppBundleContent( + name: "anim_morewide" + ), + color: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor, + size: CGSize(width: 34.0, height: 34.0), + playOnce: self.moreButtonPlayOnce + ) + )), + action: { [weak self] view in + self?.searchOrMorePressed(view: view, gesture: nil) + self?.moreButtonPlayOnce.invoke(Void()) + })), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset, y: barButtonSideInset), size: moreButtonSize) + if let view = moreButton.view { + if view.superview == nil { + self.view.addSubview(view) + } + view.bounds = CGRect(origin: .zero, size: moreButtonFrame.size) + view.center = moreButtonFrame.center + } + } + + if let moreButtonView = self.rightButton?.view { + transition.updateAlpha(layer: moreButtonView.layer, alpha: moreIsVisible ? 1.0 : 0.0) + transition.updateTransformScale(layer: moreButtonView.layer, scale: moreIsVisible ? 1.0 : 0.1) + } else { + transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0) + transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1) + } if case .assets(_, .story) = self.subject, self.selectionCount > 0 { let text = self.presentationData.strings.MediaPicker_CreateStory(self.selectionCount) @@ -2428,7 +2662,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att } private func updateThemeAndStrings() { - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + var navigationBarPresentationData: NavigationBarPresentationData + if case .glass = style { + navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, hideBackground: true, hideBadge: true, hideSeparator: true) + } else { + navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData) + } + self.navigationBar?.updatePresentationData(navigationBarPresentationData) self.titleView.theme = self.presentationData.theme self.cancelButtonNode.theme = self.presentationData.theme self.moreButtonNode.theme = self.presentationData.theme @@ -2644,7 +2884,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.controllerNode.updateDisplayMode(.selected, animated: true) } - @objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) { + @objc private func searchOrMorePressed(view: UIView, gesture: ContextGesture?) { guard self.moreButtonNode.iconNode.alpha > 0.0 else { return } @@ -2669,7 +2909,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self?.presentFilePicker() }))) - let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) self.presentInGlobalOverlay(contextController) return @@ -2830,7 +3070,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att return ContextController.Items(content: .list(items)) } - let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceNode: node)), items: items, gesture: gesture) + let contextController = ContextController(presentationData: self.presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: self, sourceView: view)), items: items, gesture: gesture) self.presentInGlobalOverlay(contextController) } } @@ -2866,7 +3106,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att safeInset += layout.safeInsets.right + 16.0 } let navigationHeight = navigationLayout(layout: layout).navigationFrame.height - self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - 54.0 - self.selectedButtonNode.frame.width - safeInset, y: floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size) + + let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0 + self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - self.selectedButtonNode.frame.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size) } public func dismissAnimated() { @@ -3051,15 +3293,15 @@ final class MediaPickerContext: AttachmentMediaPickerContext { private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController - private let sourceNode: ContextReferenceContentNode + private let sourceView: UIView - init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + init(controller: ViewController, sourceView: UIView) { self.controller = controller - self.sourceNode = sourceNode + self.sourceView = sourceView } func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) } } @@ -3167,7 +3409,15 @@ public func wallpaperMediaPickerController( completion: @escaping (MediaPickerScreenImpl, Any) -> Void = { _, _ in }, openColors: @escaping () -> Void ) -> ViewController { - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { + let controller = AttachmentController( + context: context, + updatedPresentationData: updatedPresentationData, + chatLocation: nil, + buttons: [.standalone], + initialButton: .standalone, + fromMenu: false, + hasTextInput: false, + makeEntityInputView: { return nil }) controller.animateAppearance = animateAppearance @@ -3256,6 +3506,7 @@ public func storyMediaPickerController( let controller = AttachmentController( context: context, updatedPresentationData: updatedPresentationData, + style: .glass, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, @@ -3271,6 +3522,7 @@ public func storyMediaPickerController( let mediaPickerController = MediaPickerScreenImpl( context: context, updatedPresentationData: updatedPresentationData, + style: .glass, peer: nil, threadTitle: nil, chatLocation: nil, @@ -3370,13 +3622,34 @@ public func stickerMediaPickerController( ) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { + let controller = AttachmentController( + context: context, + updatedPresentationData: updatedPresentationData, + style: .glass, + chatLocation: nil, + buttons: [.standalone], + initialButton: .standalone, + fromMenu: false, + hasTextInput: false, + makeEntityInputView: { return nil }) controller.forceSourceRect = true controller.getSourceRect = getSourceRect controller.requestController = { [weak controller] _, present in - let mediaPickerController = MediaPickerScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .createSticker), mainButtonState: nil, mainButtonAction: nil) + let mediaPickerController = MediaPickerScreenImpl( + context: context, + updatedPresentationData: updatedPresentationData, + style: .glass, + peer: nil, + threadTitle: nil, + chatLocation: nil, + bannedSendPhotos: nil, + bannedSendVideos: nil, + subject: .assets(nil, .createSticker), + mainButtonState: nil, + mainButtonAction: nil + ) mediaPickerController.customSelection = { controller, result in if let result = result as? PHAsset { controller.updateHiddenMediaId(result.localIdentifier) @@ -3500,13 +3773,21 @@ public func avatarMediaPickerController( ) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { + let controller = AttachmentController( + context: context, + updatedPresentationData: updatedPresentationData, + style: .glass, + chatLocation: nil, + buttons: [.standalone], + initialButton: .standalone, + fromMenu: false, + hasTextInput: false, + makeEntityInputView: { return nil }) controller.forceSourceRect = true controller.getSourceRect = getSourceRect controller.requestController = { [weak controller] _, present in - var mainButtonState: AttachmentMainButtonState? if canDelete { @@ -3516,6 +3797,7 @@ public func avatarMediaPickerController( let mediaPickerController = MediaPickerScreenImpl( context: context, updatedPresentationData: updatedPresentationData, + style: .glass, peer: nil, threadTitle: nil, chatLocation: nil, @@ -3670,43 +3952,89 @@ public func coverMediaPickerController( return controller } -private class SelectedButtonNode: HighlightableButtonNode { - private let background = ASImageNode() +private class SelectedButtonNode: HighlightTrackingButtonNode { + private let containerView: UIView + private let backgroundView: GlassBackgroundView? + private let background: ASImageNode? private let icon = ASImageNode() private let label = ImmediateAnimatedCountLabelNode() + private let glass: Bool + var theme: PresentationTheme { didSet { - self.icon.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor) - self.background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor) + self.icon.image = generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Media Gallery/Check" : "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor) + if let background = self.background { + background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor) + } let _ = self.update(count: self.count) } } private var count: Int32 = 0 - init(theme: PresentationTheme) { + init(theme: PresentationTheme, glass: Bool) { self.theme = theme + self.glass = glass + + self.containerView = UIView() + + if glass { + self.backgroundView = GlassBackgroundView() + self.background = nil + } else { + let background = ASImageNode() + background.displaysAsynchronously = false + self.background = background + self.backgroundView = nil + } super.init() - - self.background.displaysAsynchronously = false + self.icon.displaysAsynchronously = false self.label.displaysAsynchronously = false - self.icon.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor) - self.background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor) + self.icon.image = generateTintedImage(image: UIImage(bundleImageName: self.glass ? "Media Gallery/Check" : "Media Gallery/SelectedIcon"), color: self.theme.list.itemCheckColors.foregroundColor) - self.addSubnode(self.background) - self.addSubnode(self.icon) - self.addSubnode(self.label) + self.view.addSubview(self.containerView) + if let backgroundView = self.backgroundView { + self.containerView.addSubview(backgroundView) + } + if let background = self.background { + background.image = generateStretchableFilledCircleImage(radius: 21.0 / 2.0, color: self.theme.list.itemCheckColors.fillColor) + self.containerView.addSubnode(background) + } + + self.containerView.addSubnode(self.icon) + self.containerView.addSubnode(self.label) + + self.highligthedChanged = { [weak self] highlighted in + if let self { + if glass { + let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring)) + if highlighted { + transition.setScale(view: self.containerView, scale: 1.2) + } else { + transition.setScale(view: self.containerView, scale: 1.0) + } + } else { + if highlighted { + self.containerView.layer.removeAnimation(forKey: "opacity") + self.containerView.alpha = 0.4 + } else { + self.containerView.alpha = 1.0 + self.containerView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } } func update(count: Int32) -> CGSize { self.count = count - let diameter: CGFloat = 21.0 - let font = Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]) + let diameter: CGFloat = self.glass ? 40.0 : 21.0 + let font = self.glass ? Font.with(size: 17.0, weight: .medium, traits: [.monospacedNumbers]) : Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]) let stringValue = "\(max(1, count))" var segments: [AnimatedCountLabelNode.Segment] = [] @@ -3717,18 +4045,36 @@ private class SelectedButtonNode: HighlightableButtonNode { } self.label.segments = segments - let textSize = self.label.updateLayout(size: CGSize(width: 100.0, height: diameter), animated: true) - let size = CGSize(width: textSize.width + 28.0, height: diameter) - - if let _ = self.icon.image { - let iconSize = CGSize(width: 14.0, height: 11.0) - let iconFrame = CGRect(origin: CGPoint(x: 5.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize) - self.icon.frame = iconFrame + let textSize = self.label.updateLayout(size: CGSize(width: 120.0, height: diameter), animated: true) + var size = CGSize(width: textSize.width + 28.0, height: diameter) + if self.glass { + size.width += 10.0 } - self.label.frame = CGRect(origin: CGPoint(x: 21.0, y: floor((size.height - textSize.height) / 2.0) - UIScreenPixel), size: textSize) - self.background.frame = CGRect(origin: .zero, size: size) + if let _ = self.icon.image { + if self.glass { + let iconSize = CGSize(width: 24.0, height: 24.0) + let iconFrame = CGRect(origin: CGPoint(x: 6.0 + UIScreenPixel, y: floor((size.height - iconSize.height) / 2.0) - UIScreenPixel), size: iconSize) + self.icon.frame = iconFrame + } else { + let iconSize = CGSize(width: 14.0, height: 11.0) + let iconFrame = CGRect(origin: CGPoint(x: 5.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize) + self.icon.frame = iconFrame + } + } + self.label.frame = CGRect(origin: CGPoint(x: self.glass ? 27.0 - UIScreenPixel : 21.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + + let backgroundFrame = CGRect(origin: .zero, size: size) + self.containerView.frame = backgroundFrame + if let backgroundView = self.backgroundView { + backgroundView.frame = backgroundFrame + backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.size.height * 0.5, isDark: false, tintColor: .init(kind: .custom, color: self.theme.list.itemCheckColors.fillColor), transition: .immediate) + } + if let background = self.background { + background.frame = backgroundFrame + } + return size } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift index a9d360915b..aaaba5dce4 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift @@ -14,18 +14,37 @@ final class MediaPickerTitleView: UIView { private let arrowNode: ASImageNode private let segmentedControlNode: SegmentedControlNode + private let glass: Bool + public var theme: PresentationTheme { didSet { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor) - self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) + self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor) self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme)) + if self.glass { + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/TitleExpand"), color: self.isDark ? UIColor.white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.4)) + } + self.setNeedsLayout() + } + } + + public var isDark: Bool = false { + didSet { + if self.isDark != oldValue { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) + self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor) + if self.glass { + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/TitleExpand"), color: self.isDark ? UIColor.white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.4)) + } + self.setNeedsLayout() + } } } public var title: String = "" { didSet { if self.title != oldValue { - self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: self.isDark ? .white : self.theme.rootController.navigationBar.primaryTextColor) self.setNeedsLayout() } } @@ -34,7 +53,7 @@ final class MediaPickerTitleView: UIView { public var subtitle: String = "" { didSet { if self.subtitle != oldValue { - self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor) + self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle, font: Font.regular(12.0), textColor: self.isDark ? .white.withAlphaComponent(0.5) : self.theme.rootController.navigationBar.secondaryTextColor) self.setNeedsLayout() } } @@ -47,6 +66,41 @@ final class MediaPickerTitleView: UIView { } } + public func updateIsDark(isDark: Bool, animated: Bool) { + if animated { + if self.isDark != isDark { + if let snapshotView = self.titleNode.view.snapshotContentTree() { + snapshotView.frame = self.titleNode.frame + self.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + self.titleNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.5) + } + if let snapshotView = self.subtitleNode.view.snapshotContentTree() { + snapshotView.frame = self.subtitleNode.frame + self.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + self.subtitleNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.5) + } + if let snapshotView = self.arrowNode.view.snapshotContentTree() { + snapshotView.frame = self.arrowNode.frame + self.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.5, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + self.arrowNode.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.5) + } + } + } + self.isDark = isDark + } + public func updateTitle(title: String, subtitle: String = "", isEnabled: Bool, animated: Bool) { if animated { if self.title != title { @@ -129,8 +183,9 @@ final class MediaPickerTitleView: UIView { public var indexUpdated: ((Int) -> Void)? public var action: () -> Void = {} - public init(theme: PresentationTheme, segments: [String], selectedIndex: Int) { + public init(theme: PresentationTheme, glass: Bool, segments: [String], selectedIndex: Int) { self.theme = theme + self.glass = glass self.segments = segments self.contextSourceNode = ContextReferenceContentNode() @@ -144,7 +199,11 @@ final class MediaPickerTitleView: UIView { self.arrowNode = ASImageNode() self.arrowNode.displaysAsynchronously = false - self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/DownArrow"), color: theme.rootController.navigationBar.secondaryTextColor) + if glass { + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/TitleExpand"), color: theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.4)) + } else { + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/DownArrow"), color: theme.rootController.navigationBar.secondaryTextColor) + } self.arrowNode.isHidden = true self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segments.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedIndex) @@ -201,11 +260,19 @@ final class MediaPickerTitleView: UIView { totalHeight += subtitleSize.height } - self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0)), size: titleSize) - self.subtitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0) + subtitleSize.height + 7.0), size: subtitleSize) + let verticalOffset: CGFloat = self.glass ? 3.0 : 0.0 + let arrowOffset: CGFloat = self.glass ? 1.0 : 5.0 + + var totalWidth = titleSize.width + if let arrowSize = self.arrowNode.image?.size, !self.arrowNode.isHidden { + totalWidth += arrowOffset + arrowSize.width + } + + self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0) + verticalOffset), size: titleSize) + self.subtitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((size.height - totalHeight) / 2.0) + subtitleSize.height + 7.0 + verticalOffset), size: subtitleSize) if let arrowSize = self.arrowNode.image?.size { - self.arrowNode.frame = CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + 5.0, y: floorToScreenPixels((size.height - totalHeight) / 2.0) + titleSize.height / 2.0 - arrowSize.height / 2.0 + 1.0 - UIScreenPixel), size: arrowSize) + self.arrowNode.frame = CGRect(origin: CGPoint(x: self.titleNode.frame.maxX + arrowOffset, y: floorToScreenPixels((size.height - totalHeight) / 2.0) + titleSize.height / 2.0 - arrowSize.height / 2.0 + 1.0 - UIScreenPixel + verticalOffset), size: arrowSize) } self.buttonNode.frame = CGRect(origin: .zero, size: size) } diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift index 5d0aab4433..4e9432cc77 100644 --- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift +++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift @@ -183,7 +183,7 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .uploadSound(text): let icon = PresentationResourcesItemList.uploadToneIcon(presentationData.theme) - return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.upload() }) case let .cloudInfo(text): @@ -193,15 +193,15 @@ private enum NotificationSoundSelectionEntry: ItemListNodeEntry { case let .classicHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .none(_, _, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: true, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: true, sectionId: self.section, action: { arguments.selectSound(.none) }) case let .default(_, _, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectSound(.default) }) case let .sound(_, _, _, text, sound, selected, canBeDeleted): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectSound(sound) }, deleteAction: canBeDeleted ? { arguments.deleteSound(sound, text) diff --git a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift index fd12587163..d5d78af6e1 100644 --- a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift +++ b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift @@ -72,7 +72,7 @@ private enum ResetPasswordEntry: ItemListNodeEntry, Equatable { let arguments = arguments as! ResetPasswordControllerArguments switch self { case let .code(theme, _, text, value): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .number, spacing: 10.0, tag: ResetPasswordEntryTag.code, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .number, spacing: 10.0, tag: ResetPasswordEntryTag.code, sectionId: self.section, textUpdated: { updatedText in arguments.updateCodeText(updatedText) }, action: { }) diff --git a/submodules/PaymentMethodUI/Sources/PaymentMethodListScreen.swift b/submodules/PaymentMethodUI/Sources/PaymentMethodListScreen.swift index 2a23bed3ce..072d6e0360 100644 --- a/submodules/PaymentMethodUI/Sources/PaymentMethodListScreen.swift +++ b/submodules/PaymentMethodUI/Sources/PaymentMethodListScreen.swift @@ -117,12 +117,13 @@ private enum InviteLinksListEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .addMethod(text): let icon = PresentationResourcesItemList.plusIconImage(presentationData.theme) - return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.addMethod() }) case let .item(_, info, isSelected): return ItemListCheckboxItem( presentationData: presentationData, + systemStyle: .glass, icon: STPPaymentCardTextField.brandImage(for: .masterCard), iconSize: nil, iconPlacement: .default, title: "•••• " + info.number.suffix(4), diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index bb9b7a38b1..6d2ebebd3c 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -674,10 +674,10 @@ private final class VariableBlurView: UIVisualEffectView { variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius") variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage") variableBlur.setValue(true, forKey: "inputNormalizeEdges") - variableBlur.setValue(UIScreenScale, forKey: "scale") let backdropLayer = self.subviews.first?.layer backdropLayer?.filters = [variableBlur] + backdropLayer?.setValue(UIScreenScale, forKey: "scale") } } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index feea2bc7c1..98e256beca 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -265,11 +265,11 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { let arguments = arguments as! ChannelAdminsControllerArguments switch self { case let .recentActions(_, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Chat/Info/RecentActionsIcon")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openRecentActions() }) case let .antiSpam(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Chat/Info/AntiSpam")?.precomposed(), title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Chat/Info/AntiSpam")?.precomposed(), title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateAntiSpamEnabled(value) }) case let .antiSpamInfo(_, text): @@ -302,23 +302,23 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { arguments.openAdmin(participant.participant) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: .text(peerText, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.removeAdmin(peerId) }) case let .addAdmin(theme, text, editing): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: editing, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: editing, action: { arguments.addAdmin() }) case let .adminsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .signMessages(_, text, value, profiles): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateSignaturesAndProfilesEnabled(value, profiles) }) case let .showAuthorProfiles(_, text, value, signatures): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateSignaturesAndProfilesEnabled(signatures, value) }) case let .signMessagesInfo(_, text): diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index 2963312711..86dd2e6d09 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -150,7 +150,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { let arguments = arguments as! ChannelBlacklistControllerArguments switch self { case let .add(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.addPeer() }) case let .addInfo(_, text): @@ -167,7 +167,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { default: break } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { arguments.openPeer(participant) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift index 85c54f41a8..6f78a09e33 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift @@ -145,7 +145,7 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { } return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animation: .discussionGroupSetup, sectionId: self.section) case let .create(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: { arguments.createGroup() }) case let .group(_, _, strings, peer, nameOrder): @@ -157,13 +157,13 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { } else { text = strings.Channel_DiscussionGroup_PrivateGroup } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.selectGroup(peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) case let .groupsInfo(_, title): return ItemListTextItem(presentationData: presentationData, text: .plain(title), sectionId: self.section) case let .unlink(_, title): - return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.unlinkGroup() }) } diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift index e631a36ed9..d0e057565e 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift @@ -39,7 +39,7 @@ final class ChannelDiscussionGroupSetupSearchItem: ItemListControllerSearch { } } - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if let current = current as? ChannelDiscussionSearchNavigationContentNode { current.updateTheme(presentationData.theme) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index bd55fc4126..2985fd3f70 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -254,7 +254,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { let arguments = arguments as! ChannelMembersControllerArguments switch self { case let .hideMembers(text, disabledReason, isInteractive, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !value && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: isInteractive, enabled: true, displayLocked: !value && disabledReason != nil, sectionId: self.section, style: .blocks, updated: { value in if let disabledReason { arguments.displayHideMembersTip(disabledReason) } else { @@ -268,11 +268,11 @@ private enum ChannelMembersEntry: ItemListNodeEntry { case let .hideMembersInfo(text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .addMember(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { arguments.addMember() }) case let .inviteLink(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.linkIcon(theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { arguments.inviteViaLink() }) case let .addMemberInfo(_, text): @@ -286,7 +286,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { } else { text = .presence } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: { arguments.openPeer(EnginePeer(participant.peer)) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index b31b399445..624df973e1 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1148,7 +1148,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, switch subject { case let .create(peer, _, share, shareViaException, _): if share, filteredPhoneNumbers.count <= 1, let peer = peer { - addContactDisposable.set((context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) + addContactDisposable.set((context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", noteText: "", noteEntities: [], addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) |> deliverOnMainQueue).start(error: { _ in presentControllerImpl?(textAlertController(context: context, updatedPresentationData: (environment.presentationData, updatedPresentationData), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) }, completed: { @@ -1182,7 +1182,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext, switch subject { case let .create(peer, _, share, shareViaException, _): if share, let peer = peer { - return context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) + return context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", noteText: "", noteEntities: [], addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) |> mapToSignal { _ -> Signal<(DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)?, AddContactError> in } |> then( diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift index 020bb7bda2..6f63653114 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift @@ -60,7 +60,7 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { } } - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if let current = current as? GroupInfoSearchNavigationContentNode { current.updateTheme(presentationData.theme) diff --git a/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift b/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift index 8bf2deb94e..4e5a223763 100644 --- a/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift +++ b/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift @@ -84,11 +84,11 @@ private enum GroupPreHistorySetupEntry: ItemListNodeEntry { case let .header(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .visible(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.toggle(true) }) case let .hidden(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.toggle(false) }) case let .info(_, text): diff --git a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift index e788aa5c98..5bb4a86ac6 100644 --- a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift +++ b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift @@ -59,7 +59,7 @@ private enum PhoneLabelEntry: ItemListNodeEntry { let arguments = arguments as! PhoneLabelArguments switch self { case let .label(_, _, value, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectLabel(value) }) } diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index b65f458d11..7564d28dfb 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -2261,6 +2261,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let perksSection = perksSection.update( component: ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: strings.Premium_WhatsIncluded.uppercased(), @@ -2504,6 +2505,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let businessSection = businessSection.update( component: ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: perksItems @@ -2531,6 +2533,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if let accountContext = context.component.screenContext.context { perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -2573,6 +2576,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -2607,6 +2611,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -2642,6 +2647,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let moreBusinessSection = moreBusinessSection.update( component: ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: strings.Business_MoreFeaturesTitle.uppercased(), @@ -2689,6 +2695,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { var adsSettingsItems: [AnyComponentWithIdentity] = [] adsSettingsItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -2726,6 +2733,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let adsSettingsSection = adsSettingsSection.update( component: ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: strings.Business_AdsTitle.uppercased(), @@ -2805,7 +2813,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } else { layoutPerks() - let textPadding: CGFloat = 13.0 + let textPadding: CGFloat = 17.0 let infoTitle = infoTitle.update( component: MultilineTextComponent( @@ -2844,7 +2852,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let infoBackground = infoBackground.update( component: RoundedRectangle( color: environment.theme.list.itemBlocksBackgroundColor, - cornerRadius: 10.0 + cornerRadius: 26.0 ), environment: {}, availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0), diff --git a/submodules/PresentationDataUtils/Sources/ItemListController.swift b/submodules/PresentationDataUtils/Sources/ItemListController.swift index 0da76bfc1c..1b29f60082 100644 --- a/submodules/PresentationDataUtils/Sources/ItemListController.swift +++ b/submodules/PresentationDataUtils/Sources/ItemListController.swift @@ -6,12 +6,12 @@ import SwiftSignalKit import ItemListUI public extension ItemListController { - convenience init(context: AccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal? = nil) { - self.init(sharedContext: context.sharedContext, state: state, tabBarItem: tabBarItem) + convenience init(context: AccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal? = nil, hideNavigationBarBackground: Bool = false) { + self.init(sharedContext: context.sharedContext, state: state, tabBarItem: tabBarItem, hideNavigationBarBackground: hideNavigationBarBackground) } - convenience init(sharedContext: SharedAccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal? = nil) { + convenience init(sharedContext: SharedAccountContext, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal? = nil, hideNavigationBarBackground: Bool = false) { let presentationData = sharedContext.currentPresentationData.with { $0 } - self.init(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: sharedContext.presentationData |> map(ItemListPresentationData.init(_:)), state: state, tabBarItem: tabBarItem) + self.init(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: sharedContext.presentationData |> map(ItemListPresentationData.init(_:)), state: state, tabBarItem: tabBarItem, hideNavigationBarBackground: hideNavigationBarBackground) } } diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index bfb017f963..ed6a5c6f47 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -127,6 +127,9 @@ swift_library( "//submodules/TelegramUI/Components/Settings/GenerateThemeName", "//submodules/TelegramUI/Components/Settings/PeerNameColorItem", "//submodules/TelegramUI/Components/FaceScanScreen", + "//submodules/ComponentFlow", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/ButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift index 6cd24015eb..1daffea084 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift @@ -91,7 +91,7 @@ private enum ChangePhoneNumberCodeEntry: ItemListNodeEntry { let arguments = arguments as! ChangePhoneNumberCodeControllerArguments switch self { case let .codeEntry(theme, _, title, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: title, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ChangePhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: title, textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ChangePhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in arguments.updateEntryText(updatedText) }, action: { arguments.next() diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift index 2b1c339bf4..9b6dd2d5c2 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberController.swift @@ -183,10 +183,10 @@ public func ChangePhoneNumberController(context: AccountContext) -> ViewControll if let (_, countryCode) = lookupCountryIdByNumber(phone, configuration: context.currentCountriesConfiguration.with { $0 }), let codeValue = Int32(countryCode.code) { initialCountryCode = codeValue } else { - initialCountryCode = AuthorizationSequenceController.defaultCountryCode() + initialCountryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() } } else { - initialCountryCode = AuthorizationSequenceController.defaultCountryCode() + initialCountryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() } controller.updateData(countryCode: initialCountryCode, countryName: nil, number: "") controller.updateCountryCode() diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadConnectionTypeController.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadConnectionTypeController.swift index b8d635f6be..e9cf1a1e98 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadConnectionTypeController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadConnectionTypeController.swift @@ -153,31 +153,31 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry { let arguments = arguments as! AutodownloadMediaConnectionTypeControllerArguments switch self { case let .master(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleMaster(value) }) case let .dataUsageHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .dataUsageItem(theme, strings, value, customPosition, enabled): - return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, value: value, customPosition: customPosition, enabled: enabled, sectionId: self.section, updated: { preset in + return AutodownloadDataUsagePickerItem(theme: theme, strings: strings, systemStyle: .glass, value: value, customPosition: customPosition, enabled: enabled, sectionId: self.section, updated: { preset in arguments.changePreset(preset) }) case let .typesHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .photos(_, text, value, enabled): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Photos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { arguments.customize(.photo) }) case let .stories(_, text, value, enabled): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Stories")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Stories")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { arguments.customize(.story) }) case let .videos(_, text, value, enabled): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Videos")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { arguments.customize(.video) }) case let .files(_, text, value, enabled): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Files")?.precomposed(), title: text, enabled: enabled, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { arguments.customize(.file) }) case let .voiceMessagesInfo(_, text): diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift index d0ad752a2f..f49b774870 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadDataUsagePickerItem.swift @@ -33,15 +33,17 @@ enum AutomaticDownloadDataUsage: Int { final class AutodownloadDataUsagePickerItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let value: AutomaticDownloadDataUsage let customPosition: Int? let enabled: Bool let sectionId: ItemListSectionId let updated: (AutomaticDownloadDataUsage) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, value: AutomaticDownloadDataUsage, customPosition: Int?, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, value: AutomaticDownloadDataUsage, customPosition: Int?, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (AutomaticDownloadDataUsage) -> Void) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.value = value self.customPosition = customPosition self.enabled = enabled @@ -296,7 +298,7 @@ private final class AutodownloadDataUsagePickerItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift index edbb24cc55..44e2124a9a 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadMediaCategoryController.swift @@ -189,29 +189,29 @@ private enum AutodownloadMediaCategoryEntry: ItemListNodeEntry { case let .peerHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .peerContacts(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.togglePeer(.contact) }) case let .peerOtherPrivate(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.togglePeer(.otherPrivate) }) case let .peerGroups(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.togglePeer(.group) }) case let .peerChannels(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.togglePeer(.channel) }) case let .sizeHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .sizeItem(theme, strings, decimalSeparator, text, value): - return AutodownloadSizeLimitItem(theme: theme, strings: strings, decimalSeparator: decimalSeparator, text: text, value: value, range: nil, sectionId: self.section, updated: { value in + return AutodownloadSizeLimitItem(theme: theme, strings: strings, systemStyle: .glass, decimalSeparator: decimalSeparator, text: text, value: value, range: nil, sectionId: self.section, updated: { value in arguments.adjustSize(value) }) case let .sizePreload(_, text, value, enabled): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value && enabled, enableInteractiveChanges: true, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value && enabled, enableInteractiveChanges: true, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleVideoPreload() }) case let .sizePreloadInfo(_, text): diff --git a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift index a0ea01a3c0..affc44c9a6 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/AutodownloadSizeLimitItem.swift @@ -51,6 +51,7 @@ private func sizeValue(for sliderValue: CGFloat) -> Int64 { final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let decimalSeparator: String let text: String let value: Int64 @@ -58,9 +59,10 @@ final class AutodownloadSizeLimitItem: ListViewItem, ItemListItem { let sectionId: ItemListSectionId let updated: (Int64) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, decimalSeparator: String, text: String, value: Int64, range: Range?, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, decimalSeparator: String, text: String, value: Int64, range: Range?, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.decimalSeparator = decimalSeparator self.text = text self.value = value @@ -192,6 +194,7 @@ private final class AutodownloadSizeLimitItemNode: ListViewItemNode { let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) @@ -255,12 +258,12 @@ private final class AutodownloadSizeLimitItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - separatorRightInset, height: separatorHeight)) let _ = textApply() strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.size.width) / 2.0), y: 12.0), size: textLayout.size) diff --git a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift index 3554501b8c..717d16872d 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift @@ -362,21 +362,21 @@ private enum DataAndStorageEntry: ItemListNodeEntry { let arguments = arguments as! DataAndStorageControllerArguments switch self { case let .storageUsage(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Storage")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Storage")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openStorageUsage() }) case let .networkUsage(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Network")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Network")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openNetworkUsage() }) case let .automaticDownloadHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .automaticDownloadCellular(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Cellular")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { arguments.openAutomaticDownloadConnectionType(.cellular) }) case let .automaticDownloadWifi(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/WiFi")?.precomposed(), title: text, label: value, labelStyle: .detailText, sectionId: self.section, style: .blocks, action: { arguments.openAutomaticDownloadConnectionType(.wifi) }) case let .automaticDownloadReset(theme, text, enabled): @@ -384,7 +384,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry { if !enabled { icon = generateTintedImage(image: icon, color: theme.list.itemDisabledTextColor) } - return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: text, sectionId: self.section, height: .generic, color: enabled ? .accent : .disabled, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: icon, title: text, sectionId: self.section, height: .generic, color: enabled ? .accent : .disabled, editing: false, action: { if enabled { arguments.resetAutomaticDownload() } @@ -401,13 +401,13 @@ private enum DataAndStorageEntry: ItemListNodeEntry { case .channels: iconName = "Settings/Menu/Channels" } - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: iconName)?.precomposed(), title: title, label: value, labelStyle: .text, additionalDetailLabel: label.isEmpty ? nil : label, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: iconName)?.precomposed(), title: title, label: value, labelStyle: .text, additionalDetailLabel: label.isEmpty ? nil : label, sectionId: self.section, style: .blocks, action: { arguments.openSaveIncoming(type) }) case let .autoSaveInfo(text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .useLessVoiceData(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleVoiceUseLessData(value) }, tag: nil) case let .useLessVoiceDataInfo(_, text): @@ -415,35 +415,35 @@ private enum DataAndStorageEntry: ItemListNodeEntry { case let .otherHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .openLinksIn(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openBrowserSelection() }) case let .shareSheet(_, text): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openIntents() }) case let .saveEditedPhotos(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleSaveEditedPhotos(value) }, tag: DataAndStorageEntryTag.saveEditedPhotos) case let .pauseMusicOnRecording(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.togglePauseMusicOnRecording(value) }, tag: DataAndStorageEntryTag.pauseMusicOnRecording) case let .raiseToListen(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleRaiseToListen(value) }, tag: DataAndStorageEntryTag.raiseToListen) case let .raiseToListenInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .sensitiveContent(text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: false, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: false, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleSensitiveContent(value) }, tag: DataAndStorageEntryTag.sensitiveContent) case let .sensitiveContentInfo(text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .downloadInBackground(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleDownloadInBackground(value) }, tag: DataAndStorageEntryTag.downloadInBackground) case let .downloadInBackgroundInfo(_, text): @@ -451,7 +451,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry { case let .connectionHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .connectionProxy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openProxy() }) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/EnergySavingSettingsScreen.swift b/submodules/SettingsUI/Sources/Data and Storage/EnergySavingSettingsScreen.swift index 2c00d85b1f..255af4d950 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/EnergySavingSettingsScreen.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/EnergySavingSettingsScreen.swift @@ -178,6 +178,7 @@ private enum EnergeSavingSettingsScreenEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .all(value): return EnergyUsageBatteryLevelItem( + systemStyle: .glass, theme: presentationData.theme, strings: presentationData.strings, value: value, @@ -192,7 +193,7 @@ private enum EnergeSavingSettingsScreenEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.PowerSavingScreen_OptionsHeader, sectionId: self.section) case let .item(_, type, value, enabled): let (iconName, title, text) = type.title(strings: presentationData.strings) - return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: iconName)?.precomposed(), title: title, text: text, value: value, enableInteractiveChanges: true, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: iconName)?.precomposed(), title: title, text: text, value: value, enableInteractiveChanges: true, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleItem(type) }, activatedWhileDisabled: { arguments.displayDisabledTooltip() diff --git a/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift b/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift index f9b62b234e..537dc09ae5 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/EnergyUsageBatteryLevelItem.swift @@ -11,13 +11,15 @@ import PresentationDataUtils import AppBundle class EnergyUsageBatteryLevelItem: ListViewItem, ItemListItem { + let systemStyle: ItemListSystemStyle let theme: PresentationTheme let strings: PresentationStrings let value: Int32 let sectionId: ItemListSectionId let updated: (Int32) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, value: Int32, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { + init(systemStyle: ItemListSystemStyle = .legacy, theme: PresentationTheme, strings: PresentationStrings, value: Int32, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { + self.systemStyle = systemStyle self.theme = theme self.strings = strings self.value = value @@ -157,8 +159,14 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode { let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 - contentSize = CGSize(width: params.width, height: 88.0) + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset = 4.0 + } + + contentSize = CGSize(width: params.width, height: 88.0 + verticalInset * 2.0) insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -210,12 +218,12 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) strongSelf.leftTextNode.attributedText = NSAttributedString(string: item.strings.PowerSaving_BatteryLevelLimit_Off, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) strongSelf.rightTextNode.attributedText = NSAttributedString(string: item.strings.PowerSaving_BatteryLevelLimit_On, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) @@ -246,10 +254,10 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode { let sideInset: CGFloat = 18.0 - strongSelf.leftTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + sideInset, y: 15.0), size: leftTextSize) - strongSelf.rightTextNode.frame = CGRect(origin: CGPoint(x: params.width - params.leftInset - sideInset - rightTextSize.width, y: 15.0), size: rightTextSize) + strongSelf.leftTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + sideInset, y: 15.0 + verticalInset), size: leftTextSize) + strongSelf.rightTextNode.frame = CGRect(origin: CGPoint(x: params.width - params.leftInset - sideInset - rightTextSize.width, y: 15.0 + verticalInset), size: rightTextSize) - var centerFrame = CGRect(origin: CGPoint(x: floor((params.width - centerMeasureTextSize.width) / 2.0), y: 11.0), size: centerTextSize) + var centerFrame = CGRect(origin: CGPoint(x: floor((params.width - centerMeasureTextSize.width) / 2.0), y: 11.0 + verticalInset), size: centerTextSize) if !strongSelf.batteryBackgroundNode.isHidden { centerFrame.origin.x -= 12.0 } @@ -312,7 +320,7 @@ class EnergyUsageBatteryLevelItemNode: ListViewItemNode { sliderView.knobImage = PresentationResourcesItemList.knobImage(item.theme) } - sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 36.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 18.0 * 2.0, height: 44.0)) + sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 36.0 + verticalInset), size: CGSize(width: params.width - params.leftInset - params.rightInset - 18.0 * 2.0, height: 44.0)) } } }) diff --git a/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift index d193eb1427..1aa9295dac 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/IntentsSettingsController.swift @@ -187,7 +187,7 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry { case let .accountHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .account(_, peer, selected, _): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context.sharedContext.makeTempAccountContext(account: arguments.context.account), peer: peer, height: .generic, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: false), revealOptions: nil, switchValue: ItemListPeerItemSwitch(value: selected, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context.sharedContext.makeTempAccountContext(account: arguments.context.account), peer: peer, height: .generic, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: false), revealOptions: nil, switchValue: ItemListPeerItemSwitch(value: selected, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { arguments.updateSettings { $0.withUpdatedAccount(peer.id) } }, setPeerIdWithRevealedOptions: { _, _ in}, removePeer: { _ in }) case let .accountInfo(_, text): @@ -195,19 +195,19 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry { case let .chatsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .contacts(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.updateSettings { $0.withUpdatedContacts(value) } }) case let .savedMessages(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.updateSettings { $0.withUpdatedSavedMessages(value) } }) case let .privateChats(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.updateSettings { $0.withUpdatedPrivateChats(value) } }) case let .groups(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.updateSettings { $0.withUpdatedGroups(value) } }) case let .chatsInfo(_, text): @@ -215,16 +215,16 @@ private enum IntentsSettingsControllerEntry: ItemListNodeEntry { case let .suggestHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .suggestAll(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateSettings { $0.withUpdatedOnlyShared(false) } }) case let .suggestOnlyShared(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateSettings { $0.withUpdatedOnlyShared(true) } }) case let .resetAll(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.resetAll() }) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift index 48ea6da9db..1ecf2768de 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift @@ -201,7 +201,7 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { let arguments = arguments as! ProxySettingsControllerArguments switch self { case let .enabled(_, text, value, createsNew): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !createsNew, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !createsNew, enabled: true, sectionId: self.section, style: .blocks, updated: { value in if createsNew { arguments.addNewServer() } else { @@ -211,11 +211,11 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { case let .serversHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .addServer(_, text, _): - return ProxySettingsActionItem(presentationData: presentationData, title: text, icon: .add, sectionId: self.section, editing: false, action: { + return ProxySettingsActionItem(presentationData: presentationData, systemStyle: .glass, title: text, icon: .add, sectionId: self.section, editing: false, action: { arguments.addNewServer() }) case let .server(_, theme, strings, settings, active, status, editing, enabled): - return ProxySettingsServerItem(theme: theme, strings: strings, server: settings, activity: status.activity, active: active, color: enabled ? .accent : .secondary, label: status.text, labelAccent: status.textActive, editing: editing, sectionId: self.section, action: { + return ProxySettingsServerItem(theme: theme, strings: strings, systemStyle: .glass, server: settings, activity: status.activity, active: active, color: enabled ? .accent : .secondary, label: status.text, labelAccent: status.textActive, editing: editing, sectionId: self.section, action: { arguments.activateServer(settings) }, infoAction: { arguments.editServer(settings) @@ -225,11 +225,11 @@ private enum ProxySettingsControllerEntry: ItemListNodeEntry { arguments.removeServer(settings) }) case let .shareProxyList(_, text): - return ProxySettingsActionItem(presentationData: presentationData, title: text, sectionId: self.section, editing: false, action: { + return ProxySettingsActionItem(presentationData: presentationData, systemStyle: .glass, title: text, sectionId: self.section, editing: false, action: { arguments.shareProxyList() }) case let .useForCalls(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleUseForCalls(value) }) case let .useForCallsInfo(_, text): diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift index aa3aa8cd7c..5f35c78e37 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift @@ -116,13 +116,13 @@ private enum ProxySettingsEntry: ItemListNodeEntry { let arguments = arguments as! ProxyServerSettingsControllerArguments switch self { case let .usePasteboardSettings(_, title): - return ItemListActionItem(presentationData: presentationData, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.usePasteboardSettings() }) case let .usePasteboardInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .modeSocks5(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateState { state in var state = state state.mode = .socks5 @@ -130,7 +130,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry { } }) case let .modeMtp(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateState { state in var state = state state.mode = .mtp @@ -140,7 +140,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry { case let .connectionHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .connectionServer(_, _, placeholder, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.host = value @@ -148,7 +148,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry { } }, action: {}) case let .connectionPort(_, _, placeholder, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: placeholder, type: .number, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: text, placeholder: placeholder, type: .number, sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.port = value @@ -158,7 +158,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry { case let .credentialsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .credentialsUsername(_, _, placeholder, text): - return ItemListSingleLineInputItem(context: nil, presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: text, placeholder: placeholder, sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.username = value @@ -166,7 +166,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry { } }, action: {}) case let .credentialsPassword(_, _, placeholder, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: placeholder, type: .password, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: text, placeholder: placeholder, type: .password, sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.password = value @@ -174,7 +174,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry { } }, action: {}) case let .credentialsSecret(_, _, placeholder, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: text, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.secret = value @@ -182,7 +182,7 @@ private enum ProxySettingsEntry: ItemListNodeEntry { } }, action: {}) case let .share(_, text, enabled): - return ItemListActionItem(presentationData: presentationData, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.share() }) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift index 83aedaff2c..6b3e78494f 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsActionItem.swift @@ -14,14 +14,16 @@ enum ProxySettingsActionIcon { final class ProxySettingsActionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let title: String let icon: ProxySettingsActionIcon let editing: Bool let sectionId: ItemListSectionId let action: () -> Void - init(presentationData: ItemListPresentationData, title: String, icon: ProxySettingsActionIcon = .none, sectionId: ItemListSectionId, editing: Bool, action: @escaping () -> Void) { + init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, title: String, icon: ProxySettingsActionIcon = .none, sectionId: ItemListSectionId, editing: Bool, action: @escaping () -> Void) { self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title self.icon = icon self.editing = editing @@ -140,9 +142,18 @@ private final class ProxySettingsActionItemNode: ListViewItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - editingOffset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: 22.0 + titleLayout.size.height) + let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -214,14 +225,14 @@ private final class ProxySettingsActionItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: 11.0), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel)) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift index 0002bc6155..da7512ddab 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift @@ -21,6 +21,7 @@ struct ProxySettingsServerItemEditing: Equatable { final class ProxySettingsServerItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let server: ProxyServerSettings let activity: Bool let active: Bool @@ -34,9 +35,10 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem { let setServerWithRevealedOptions: (ProxyServerSettings?, ProxyServerSettings?) -> Void let removeServer: (ProxyServerSettings) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, server: ProxyServerSettings, activity: Bool, active: Bool, color: ItemListCheckboxItemColor, label: String, labelAccent: Bool, editing: ProxySettingsServerItemEditing, sectionId: ItemListSectionId, action: @escaping () -> Void, infoAction: @escaping () -> Void, setServerWithRevealedOptions: @escaping (ProxyServerSettings?, ProxyServerSettings?) -> Void, removeServer: @escaping (ProxyServerSettings) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, server: ProxyServerSettings, activity: Bool, active: Bool, color: ItemListCheckboxItemColor, label: String, labelAccent: Bool, editing: ProxySettingsServerItemEditing, sectionId: ItemListSectionId, action: @escaping () -> Void, infoAction: @escaping () -> Void, setServerWithRevealedOptions: @escaping (ProxyServerSettings?, ProxyServerSettings?) -> Void, removeServer: @escaping (ProxyServerSettings) -> Void) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.server = server self.activity = activity self.active = active @@ -264,9 +266,15 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset = 4.0 + } + let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: 64.0) + let contentSize = CGSize(width: params.width, height: 64.0 + verticalInset * 2.0) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -396,15 +404,15 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 12.0), size: titleLayout.size)) - transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 37.0), size: statusLayout.size)) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 12.0 + verticalInset), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 37.0 + verticalInset), size: statusLayout.size)) strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height)) diff --git a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift index b361db75b4..84f7b552cd 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift @@ -169,6 +169,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry { return ItemListAvatarAndNameInfoItem( itemContext: .accountContext(arguments.context), presentationData: presentationData, + systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), mode: .generic, peer: peer, @@ -185,11 +186,11 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry { case let .typesHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .typePhotos(title, value): - return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/DataPhotos"), title: title, value: value, sectionId: self.section, style: .blocks, updated: { _ in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/DataPhotos"), title: title, value: value, sectionId: self.section, style: .blocks, updated: { _ in arguments.toggle(.photo) }) case let .typeVideos(title, value): - return ItemListSwitchItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/DataVideo"), title: title, value: value, sectionId: self.section, style: .blocks, updated: { _ in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/DataVideo"), title: title, value: value, sectionId: self.section, style: .blocks, updated: { _ in arguments.toggle(.video) }) case let .typesInfo(text): @@ -197,7 +198,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry { case let .videoSizeHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .videoSize(decimalSeparator, text, size): - return AutodownloadSizeLimitItem(theme: presentationData.theme, strings: presentationData.strings, decimalSeparator: decimalSeparator, text: text, value: size, range: nil/*2 * 1024 * 1024 ..< (4 * 1024 * 1024 * 1024)*/, sectionId: self.section, updated: { value in + return AutodownloadSizeLimitItem(theme: presentationData.theme, strings: presentationData.strings, systemStyle: .glass, decimalSeparator: decimalSeparator, text: text, value: size, range: nil/*2 * 1024 * 1024 ..< (4 * 1024 * 1024 * 1024)*/, sectionId: self.section, updated: { value in arguments.updateMaximumVideoSize(value) }) case let .videoInfo(text): @@ -206,12 +207,13 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .addException(title): let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme) - return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: title, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: icon, title: title, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { arguments.openAddException() }) case let .exceptionItem(_, peer, label): return ItemListPeerItem( presentationData: presentationData, + systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, @@ -244,7 +246,7 @@ private enum SaveIncomingMediaEntry: ItemListNodeEntry { arguments.openPeerMenu(peer) }, tag: nil)*/ case let .deleteAllExceptions(title): - return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.deleteAllExceptions() }) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift b/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift index 2fd37c6d3e..2c85919d39 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/VoiceCallDataSavingController.swift @@ -82,15 +82,15 @@ private enum VoiceCallDataSavingEntry: ItemListNodeEntry { let arguments = arguments as! VoiceCallDataSavingControllerArguments switch self { case let .never(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateSelection(.never) }) case let .cellular(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateSelection(.cellular) }) case let .always(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateSelection(.always) }) case let .info(_, text): diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift index 869532b3a8..2ba7108446 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift @@ -15,6 +15,7 @@ private enum RevealOptionKey: Int32 { final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let context: AccountContext let title: String let label: String @@ -25,6 +26,7 @@ final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem { init( presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle, context: AccountContext, title: String, label: String, @@ -34,6 +36,7 @@ final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem { deleted: (() -> Void)? ) { self.presentationData = presentationData + self.systemStyle = systemStyle self.context = context self.title = title self.label = label @@ -153,6 +156,8 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -170,7 +175,13 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.label, font: labelFont, textColor: labelColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let verticalInset: CGFloat = 11.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 11.0 + case .legacy: + verticalInset = 11.0 + } let titleSpacing: CGFloat = 1.0 let height: CGFloat = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height @@ -267,12 +278,12 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } var centralContentHeight: CGFloat = titleLayout.size.height diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift index 5dcd3e2263..410254c412 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift @@ -14,15 +14,17 @@ import AppBundle class WebBrowserItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let title: String let application: OpenInApplication? let checked: Bool public let sectionId: ItemListSectionId let action: () -> Void - public init(context: AccountContext, presentationData: ItemListPresentationData, title: String, application: OpenInApplication?, checked: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) { + public init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, title: String, application: OpenInApplication?, checked: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.title = title self.application = application self.checked = checked @@ -170,9 +172,18 @@ private final class WebBrowserItemNode: ListViewItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: 22.0 + titleLayout.size.height) + let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -254,14 +265,14 @@ private final class WebBrowserItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0) + 1.0), size: titleLayout.size) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift index faf85ab0ea..c66c0a9916 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift @@ -202,15 +202,15 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { case let .browserHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .browser(_, title, application, identifier, selected, _): - return WebBrowserItem(context: arguments.context, presentationData: presentationData, title: title, application: application, checked: selected, sectionId: self.section) { + return WebBrowserItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, title: title, application: application, checked: selected, sectionId: self.section) { arguments.updateDefaultBrowser(identifier) } case let .clearCookies(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.clearCookies() }) case let .clearCache(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.clearCache() }) case let .clearCookiesInfo(_, text): @@ -218,15 +218,15 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry { case let .exceptionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .exception(_, _, exception): - return WebBrowserDomainExceptionItem(presentationData: presentationData, context: arguments.context, title: exception.title, label: exception.domain, icon: exception.icon, sectionId: self.section, style: .blocks, deleted: { + return WebBrowserDomainExceptionItem(presentationData: presentationData, systemStyle: .glass, context: arguments.context, title: exception.title, label: exception.domain, icon: exception.icon, sectionId: self.section, style: .blocks, deleted: { arguments.removeException(exception.domain) }) case let .exceptionsAdd(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.addException() }) case let .exceptionsClear(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.deleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { arguments.clearExceptions() }) case let .exceptionsInfo(_, text): diff --git a/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift b/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift index d15613ecbc..11c8ad4905 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountPhoneItem.swift @@ -75,7 +75,7 @@ private func generatePhoneInputBackground(color: UIColor, strokeColor: UIColor) } -class DeleteAccountPhoneItem: ListViewItem, ItemListItem { +final class DeleteAccountPhoneItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings let value: (Int32?, String?, String) @@ -83,7 +83,7 @@ class DeleteAccountPhoneItem: ListViewItem, ItemListItem { let selectCountryCode: () -> Void let updated: (Int) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, value: (Int32?, String?, String), sectionId: ItemListSectionId, selectCountryCode: @escaping () -> Void, updated: @escaping (Int) -> Void) { + public init(theme: PresentationTheme, strings: PresentationStrings, value: (Int32?, String?, String), sectionId: ItemListSectionId, selectCountryCode: @escaping () -> Void, updated: @escaping (Int) -> Void) { self.theme = theme self.strings = strings self.value = value @@ -126,7 +126,7 @@ class DeleteAccountPhoneItem: ListViewItem, ItemListItem { } } -class DeleteAccountPhoneItemNode: ListViewItemNode, ItemListItemNode { +final class DeleteAccountPhoneItemNode: ListViewItemNode, ItemListItemNode { private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode diff --git a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift index fcb305ae46..0f79fad0ca 100644 --- a/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift +++ b/submodules/SettingsUI/Sources/Language Selection/LocalizationListControllerNode.swift @@ -93,11 +93,11 @@ private enum LanguageListEntry: Comparable, Identifiable { case let .translateTitle(text): return ItemListSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), text: text, sectionId: LanguageListSection.translate.rawValue) case let .translate(text, value): - return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), title: text, value: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, value: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in toggleShowTranslate(value) }) case let .translateEntire(text, value, locked): - return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), title: text, value: value, enableInteractiveChanges: !locked, displayLocked: locked, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !locked, displayLocked: locked, sectionId: LanguageListSection.translate.rawValue, style: .blocks, updated: { value in if !locked { toggleTranslateChats(value) } @@ -105,7 +105,7 @@ private enum LanguageListEntry: Comparable, Identifiable { showPremiumInfo() }) case let .doNotTranslate(text, value): - return ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), title: text, label: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: text, label: value, sectionId: LanguageListSection.translate.rawValue, style: .blocks, action: { openDoNotTranslate() }) case let .translateInfo(text): @@ -113,7 +113,7 @@ private enum LanguageListEntry: Comparable, Identifiable { case let .localizationTitle(text, section): return ItemListSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), text: text, sectionId: section) case let .localization(_, info, type, selected, activity, revealed, editing): - return LocalizationListItem(presentationData: ItemListPresentationData(presentationData), id: info?.languageCode ?? "", title: info?.title ?? " ", subtitle: info?.localizedTitle ?? " ", checked: selected, activity: activity, loading: info == nil, editing: LocalizationListItemEditing(editable: !selected && !searchMode && !(info?.isOfficial ?? true), editing: editing, revealed: !selected && revealed, reorderable: false), sectionId: type == .official ? LanguageListSection.official.rawValue : LanguageListSection.unofficial.rawValue, alwaysPlain: searchMode, action: { + return LocalizationListItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, id: info?.languageCode ?? "", title: info?.title ?? " ", subtitle: info?.localizedTitle ?? " ", checked: selected, activity: activity, loading: info == nil, editing: LocalizationListItemEditing(editable: !selected && !searchMode && !(info?.isOfficial ?? true), editing: editing, revealed: !selected && revealed, reorderable: false), sectionId: type == .official ? LanguageListSection.official.rawValue : LanguageListSection.unofficial.rawValue, alwaysPlain: searchMode, action: { if let info = info { selectLocalization(info) } diff --git a/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift b/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift index 88351caec7..5da4449a77 100644 --- a/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift +++ b/submodules/SettingsUI/Sources/Language Selection/TranslatonSettingsController.swift @@ -62,7 +62,7 @@ private enum TranslationSettingsControllerEntry: ItemListNodeEntry { let arguments = arguments as! TranslationSettingsControllerArguments switch self { case let .language(_, _, title, subtitle, value, code): - return LocalizationListItem(presentationData: presentationData, id: code, title: title, subtitle: subtitle, checked: value, activity: false, loading: false, editing: LocalizationListItemEditing(editable: false, editing: false, revealed: false, reorderable: false), sectionId: self.section, alwaysPlain: false, action: { + return LocalizationListItem(presentationData: presentationData, systemStyle: .glass, id: code, title: title, subtitle: subtitle, checked: value, activity: false, loading: false, editing: LocalizationListItemEditing(editable: false, editing: false, revealed: false, reorderable: false), sectionId: self.section, alwaysPlain: false, action: { arguments.updateLanguageSelected(code, !value) }, setItemWithRevealedOptions: { _, _ in }, removeItem: { _ in }) } diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift index fe492cc6af..fe38fae279 100644 --- a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift +++ b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift @@ -434,57 +434,57 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry { case let .accountsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .allAccounts(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateNotificationsFromAllAccounts(updatedValue) }, tag: self.tag) case let .accountsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .permissionInfo(_, title, text, suppressed): - return ItemListInfoItem(presentationData: presentationData, title: title, text: .plain(text), style: .blocks, sectionId: self.section, closeAction: suppressed ? nil : { + return ItemListInfoItem(presentationData: presentationData, systemStyle: .glass, title: title, text: .plain(text), style: .blocks, sectionId: self.section, closeAction: suppressed ? nil : { arguments.suppressWarning() }) case let .permissionEnable(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.authorizeNotifications() }) case let .categoriesHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .privateChats(_, title, subtitle, label): - return NotificationsCategoryItemListItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/EditProfile"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { + return NotificationsCategoryItemListItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/EditProfile"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { arguments.openPeerCategory(.privateChat) }) case let .groupChats(_, title, subtitle, label): - return NotificationsCategoryItemListItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/GroupChats"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { + return NotificationsCategoryItemListItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/GroupChats"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { arguments.openPeerCategory(.group) }) case let .channels(_, title, subtitle, label): - return NotificationsCategoryItemListItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Channels"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { + return NotificationsCategoryItemListItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Channels"), title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { arguments.openPeerCategory(.channel) }) case let .stories(_, title, subtitle, label): - return NotificationsCategoryItemListItem(presentationData: presentationData, icon: PresentationResourcesSettings.stories, title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { + return NotificationsCategoryItemListItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.stories, title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { arguments.openPeerCategory(.stories) }) case let .reactions(_, title, subtitle, label): - return NotificationsCategoryItemListItem(presentationData: presentationData, icon: PresentationResourcesSettings.reactions, title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { + return NotificationsCategoryItemListItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.reactions, title: title, subtitle: subtitle, label: label, sectionId: self.section, style: .blocks, action: { arguments.openReactions() }) case let .inAppHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .inAppSounds(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateInAppSounds(updatedValue) }, tag: self.tag) case let .inAppVibrate(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateInAppVibration(updatedValue) }, tag: self.tag) case let .inAppPreviews(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateInAppPreviews(updatedValue) }, tag: self.tag) case let .displayNamesOnLockscreen(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateDisplayNameOnLockscreen(updatedValue) }, tag: self.tag) case let .displayNamesOnLockscreenInfo(_, text): @@ -494,23 +494,23 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry { case let .badgeHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .includeChannels(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateIncludeTag(.channels, updatedValue) }, tag: self.tag) case let .unreadCountCategory(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateTotalUnreadCountCategory(updatedValue) }, tag: self.tag) case let .unreadCountCategoryInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .joinedNotifications(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateJoinedNotifications(updatedValue) }, tag: self.tag) case let .joinedNotificationsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .reset(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.resetNotifications() }, tag: self.tag) case let .resetNotice(_, text): diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift b/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift index d948c1fada..9e0f727d66 100644 --- a/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift +++ b/submodules/SettingsUI/Sources/Notifications/NotificationsCategoryItemListItem.swift @@ -9,6 +9,7 @@ import ItemListUI public class NotificationsCategoryItemListItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let icon: UIImage? let title: String let subtitle: String @@ -20,8 +21,9 @@ public class NotificationsCategoryItemListItem: ListViewItem, ItemListItem { public let tag: ItemListItemTag? public let shimmeringIndex: Int? - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, subtitle: String, enabled: Bool = true, label: String, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .glass, icon: UIImage? = nil, title: String, subtitle: String, enabled: Bool = true, label: String, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) { self.presentationData = presentationData + self.systemStyle = systemStyle self.icon = icon self.title = title self.subtitle = subtitle @@ -191,6 +193,8 @@ public class NotificationsCategoryItemListItemNode: ListViewItemNode, ItemListIt let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -215,7 +219,14 @@ public class NotificationsCategoryItemListItemNode: ListViewItemNode, ItemListIt let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.subtitle, font: detailFont, textColor: detailColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrain, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let verticalInset: CGFloat = 11.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } + let titleSpacing: CGFloat = 1.0 let height: CGFloat @@ -332,15 +343,15 @@ public class NotificationsCategoryItemListItemNode: ListViewItemNode, ItemListIt strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame let subtitleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: subtitleLayout.size) diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index dda03df3b3..a0c2b9e619 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -278,11 +278,11 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry { case let .enableHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .enable(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateEnabled(updatedValue) }, tag: self.tag) case let .enableImportant(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateEnabledImportant(updatedValue) }, tag: self.tag) case let .importantInfo(_, text): @@ -290,21 +290,21 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry { case let .optionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .previews(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updatePreviews(value) }) case let .sound(_, text, value, sound): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openSound(sound) }, tag: self.tag) case let .exceptionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .addException(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, height: .peerList, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, height: .peerList, color: .accent, editing: false, action: { arguments.addException() }) case let .exception(_, _, _, dateTimeFormat, nameDisplayOrder, peer, description, _, editing, revealed, canRemove): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(description, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: canRemove, editing: canRemove && editing, revealed: canRemove && revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(description, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: canRemove, editing: canRemove && editing, revealed: canRemove && revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.openException(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.updateRevealedPeerId(peerId) @@ -312,7 +312,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry { arguments.removePeer(peer) }, hasTopStripe: false, hasTopGroupInset: false, noInsets: false) case let .removeAllExceptions(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { arguments.removeAllExceptions() }) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift index a553d73a08..f9669b2aa9 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/BlockedPeersController.swift @@ -124,7 +124,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry { let arguments = arguments as! BlockedPeersControllerArguments switch self { case let .add(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.blockAccentIcon(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.blockAccentIcon(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: { arguments.addPeer() }) case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): @@ -132,7 +132,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry { arguments.removePeer(peer.id) })]) - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .none, label: .none, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .none, label: .none, editing: editing, revealOptions: revealOptions, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift index 2d45a6f58b..bbc704e546 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift @@ -87,7 +87,7 @@ private enum ConfirmPhoneNumberCodeEntry: ItemListNodeEntry { let arguments = arguments as! ConfirmPhoneNumberCodeControllerArguments switch self { case let .codeEntry(_, _, title, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: title, textColor: .black), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ConfirmPhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: title, textColor: .black), text: text, placeholder: "", type: .number, spacing: 10.0, tag: ConfirmPhoneNumberCodeTag.input, sectionId: self.section, textUpdated: { updatedText in arguments.updateEntryText(updatedText) }, action: { arguments.next() diff --git a/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift b/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift index e085ebf7e6..d0b5427be0 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift @@ -124,13 +124,13 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable { case let .passwordHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .password(_, _, text, value): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.password, sectionId: self.section, textUpdated: { updatedText in arguments.updateFieldText(.password, updatedText) }, action: { arguments.selectNextInputItem(CreatePasswordEntryTag.password) }) case let .passwordConfirmation(_, _, text, value): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.passwordConfirmation, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: value, placeholder: text, type: .password, returnKeyType: .next, spacing: 0.0, tag: CreatePasswordEntryTag.passwordConfirmation, sectionId: self.section, textUpdated: { updatedText in arguments.updateFieldText(.passwordConfirmation, updatedText) }, action: { arguments.selectNextInputItem(CreatePasswordEntryTag.passwordConfirmation) @@ -140,7 +140,7 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable { case let .hintHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .hint(_, _, text, value, last): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: text, type: .regular(capitalization: true, autocorrection: false), returnKeyType: last ? .done : .next, spacing: 0.0, tag: CreatePasswordEntryTag.hint, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: value, placeholder: text, type: .regular(capitalization: true, autocorrection: false), returnKeyType: last ? .done : .next, spacing: 0.0, tag: CreatePasswordEntryTag.hint, sectionId: self.section, textUpdated: { updatedText in arguments.updateFieldText(.hint, updatedText) }, action: { if last { @@ -154,7 +154,7 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable { case let .emailHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .email(_, _, text, value): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: value, placeholder: text, type: .email, returnKeyType: .done, spacing: 0.0, tag: CreatePasswordEntryTag.email, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: value, placeholder: text, type: .email, returnKeyType: .done, spacing: 0.0, tag: CreatePasswordEntryTag.email, sectionId: self.section, textUpdated: { updatedText in arguments.updateFieldText(.email, updatedText) }, action: { arguments.save() @@ -164,7 +164,7 @@ private enum CreatePasswordEntry: ItemListNodeEntry, Equatable { case let .emailConfirmation(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .emailCancel(_, text, enabled): - return ItemListActionItem(presentationData: presentationData, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.cancelEmailConfirmation() }) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift index 42c891e5e3..cbde827577 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/DataPrivacySettingsController.swift @@ -229,17 +229,17 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case let .contactsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .deleteContacts(_, text, value): - return ItemListActionItem(presentationData: presentationData, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.deleteContacts() }) case let .syncContacts(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateSyncContacts(updatedValue) }) case let .syncContactsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .frequentContacts(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateSuggestFrequentContacts(updatedValue) }) case let .frequentContactsInfo(_, text): @@ -247,13 +247,13 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case let .chatsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .deleteCloudDrafts(_, text, value): - return ItemListActionItem(presentationData: presentationData, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: value ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.deleteCloudDrafts() }) case let .paymentHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .clearPaymentInfo(_, text, enabled): - return ItemListActionItem(presentationData: presentationData, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: enabled ? .generic : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.clearPaymentInfo() }) case let .paymentInfo(_, text): @@ -261,7 +261,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case let .secretChatLinkPreviewsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .secretChatLinkPreviews(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in arguments.updateSecretChatLinkPreviews(updatedValue) }) case let .secretChatLinkPreviewsInfo(_, text): @@ -269,6 +269,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case .botList: return ItemListDisclosureItem( presentationData: presentationData, + systemStyle: .glass, title: presentationData.strings.Settings_BotListSettings, label: "", sectionId: self.section, diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index 4475f0c0af..f522f3826f 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -16,6 +16,7 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let sectionId: ItemListSectionId let fontSize: PresentationFontSize let chatBubbleCorners: PresentationChatBubbleCorners @@ -26,10 +27,11 @@ class ForwardPrivacyChatPreviewItem: ListViewItem, ItemListItem { let linkEnabled: Bool let tooltipText: String - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peerName: String, linkEnabled: Bool, tooltipText: String) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, peerName: String, linkEnabled: Bool, tooltipText: String) { self.context = context self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.sectionId = sectionId self.fontSize = fontSize self.chatBubbleCorners = chatBubbleCorners @@ -247,7 +249,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift index 203bc2f9c3..a10a405f7f 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift @@ -120,11 +120,11 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { case let .sectionHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .timerOption(value, text, isSelected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateValue(value) }) case let .customAction(text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openCustomValue() }) case let .info(text): diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift index 784a9b396d..f5344b55f0 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PasscodeOptionsController.swift @@ -110,23 +110,23 @@ private enum PasscodeOptionsEntry: ItemListNodeEntry { let arguments = arguments as! PasscodeOptionsControllerArguments switch self { case let .togglePasscode(_, title, value): - return ItemListActionItem(presentationData: presentationData, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { if value { arguments.turnPasscodeOff() } }) case let .changePasscode(_, title): - return ItemListActionItem(presentationData: presentationData, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.changePasscode() }) case let .settingInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .autoLock(_, title, value): - return ItemListDisclosureItem(presentationData: presentationData, title: title, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: { arguments.changePasscodeTimeout() }) case let .touchId(_, title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.changeTouchId(value) }) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 4af1f31303..1e28d88d12 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -401,33 +401,33 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case let .privacyHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .blockedPeers(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Blocked")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Blocked")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openBlockedUsers() }) case let .phoneNumberPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openPhoneNumberPrivacy() }) case let .lastSeenPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openLastSeenPrivacy() }) case let .profilePhotoPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openProfilePhotoPrivacy() }) case let .forwardPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openForwardPrivacy() }) case let .groupPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openGroupsPrivacy() }) case .groupPrivacyFooter: return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.PrivacySettings_InviteSectionFooter), sectionId: self.section) case let .voiceMessagePrivacy(theme, text, value, hasPremium): - return ItemListDisclosureItem(presentationData: presentationData, title: text, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, action: { arguments.openVoiceMessagePrivacy() }) case let .messagePrivacy(theme, value, hasPremium): @@ -444,55 +444,55 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case .paidMessages: label = presentationData.strings.Settings_Privacy_Messages_ValuePaid } - return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.Settings_Privacy_Messages, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: label, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.Settings_Privacy_Messages, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: label, sectionId: self.section, style: .blocks, action: { arguments.openMessagePrivacy() }) case let .bioPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openBioPrivacy() }) case let .birthdayPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openBirthdayPrivacy() }) case let .giftsAutoSavePrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openGiftsPrivacy() }) case let .selectivePrivacyInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .voiceCallPrivacy(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openVoiceCallPrivacy() }) case let .passcode(_, text, hasFaceId, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: hasFaceId ? "Settings/Menu/FaceId" : "Settings/Menu/TouchId")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: hasFaceId ? "Settings/Menu/FaceId" : "Settings/Menu/TouchId")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openPasscode() }) case let .twoStepVerification(_, text, value, data): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openTwoStepVerification(data) }) case let .messageAutoremoveTimeout(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Timer")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Timer")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.setupMessageAutoremove() }, tag: PrivacyAndSecurityEntryTag.messageAutoremoveTimeout) case let .messageAutoremoveInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .loginEmail(_, text, emailPattern): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/LoginEmail")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/LoginEmail")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openEmailSettings(emailPattern) }) case let .loginEmailInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .activeSessions(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openActiveSessions() }) case let .autoArchiveHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .autoArchive(text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleArchiveAndMuteNonContacts(value) }, tag: PrivacyAndSecurityEntryTag.autoArchive) case let .autoArchiveInfo(text): @@ -500,13 +500,13 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case let .accountHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .accountTimeout(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.setupAccountAutoremove() }, tag: PrivacyAndSecurityEntryTag.accountTimeout) case let .accountInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .dataSettings(_, text): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openDataSettings() }) case let .dataSettingsInfo(_, text): diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift index 4265629d2c..e8847e7d57 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift @@ -11,11 +11,11 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode private func generateButtonImage(backgroundColor: UIColor, highlightColor: UIColor?) -> UIImage? { - return generateImage(CGSize(width: 24.0, height: 44.0), contextGenerator: { size, context in + return generateImage(CGSize(width: 66.0, height: 52.0), contextGenerator: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) - let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 11.0, height: 11.0)) + let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 26.0, height: 26.0)) context.addPath(path.cgPath) context.clip() @@ -26,7 +26,7 @@ private func generateButtonImage(backgroundColor: UIColor, highlightColor: UICol context.setFillColor(backgroundColor.cgColor) context.fill(bounds) } - }, opaque: false)?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) + }, opaque: false)?.stretchableImage(withLeftCapWidth: 26, topCapHeight: 26) } private let titleFont = Font.bold(17.0) @@ -177,7 +177,7 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 20.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.textNode, size: textSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 16.0, maxValue: 16.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), - AuthorizationLayoutItem(node: self.buttonNode, size: CGSize(width: layout.size.width - buttonInset * 2.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 40.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), + AuthorizationLayoutItem(node: self.buttonNode, size: CGSize(width: layout.size.width - buttonInset * 2.0, height: 52.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 40.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 44.0, maxValue: 44.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 20.0, maxValue: 40.0)) ] diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift index bbe89b8789..77684f5bc9 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift @@ -36,6 +36,7 @@ enum ItemListRecentSessionItemText { final class ItemListRecentSessionItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let dateTimeFormat: PresentationDateTimeFormat let session: RecentAccountSession let enabled: Bool @@ -47,8 +48,9 @@ final class ItemListRecentSessionItem: ListViewItem, ItemListItem { let removeSession: (Int64) -> Void let action: (() -> Void)? - init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editable: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, action: (() -> Void)?) { + init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editable: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, action: (() -> Void)?) { self.presentationData = presentationData + self.systemStyle = systemStyle self.dateTimeFormat = dateTimeFormat self.session = session self.enabled = enabled @@ -266,7 +268,14 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0)) let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) - let verticalInset: CGFloat = 10.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 14.0 + case .legacy: + verticalInset = 10.0 + } + let titleSpacing: CGFloat = 1.0 let textSpacing: CGFloat = 3.0 @@ -345,6 +354,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { let insets = itemListNeighborsGroupedInsets(neighbors, params) let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + appLayout.size.height + textSpacing + locationLayout.size.height) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -498,13 +508,13 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 12.0), size: CGSize(width: 30.0, height: 30.0))) transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size)) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift index 78127a08f5..cd902c96f8 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListWebsiteItem.swift @@ -32,6 +32,7 @@ struct ItemListWebsiteItemEditing: Equatable { final class ItemListWebsiteItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let dateTimeFormat: PresentationDateTimeFormat let nameDisplayOrder: PresentationPersonNameOrder let website: WebAuthorization @@ -44,9 +45,10 @@ final class ItemListWebsiteItem: ListViewItem, ItemListItem { let removeSession: (Int64) -> Void let action: (() -> Void)? - init(context: AccountContext, presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, action: (() -> Void)?) { + init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool, sectionId: ItemListSectionId, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, action: (() -> Void)?) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.dateTimeFormat = dateTimeFormat self.nameDisplayOrder = nameDisplayOrder self.website = website @@ -274,9 +276,15 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { let (appLayout, appApply) = makeAppLayout(TextNodeLayoutArguments(attributedString: appAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (locationLayout, locationApply) = makeLocationLayout(TextNodeLayoutArguments(attributedString: locationAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset = 4.0 + } + let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: 75.0) + let contentSize = CGSize(width: params.width, height: 75.0 + verticalInset * 2.0) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -424,20 +432,20 @@ class ItemListWebsiteItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: 12.0), size: CGSize(width: 30.0, height: 30.0))) + transition.updateFrame(node: strongSelf.avatarNode, frame: CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 16.0, y: verticalInset + 12.0), size: CGSize(width: 30.0, height: 30.0))) - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 10.0), size: titleLayout.size)) - transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 30.0), size: appLayout.size)) - transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 50.0), size: locationLayout.size)) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 10.0), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.appNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 30.0), size: appLayout.size)) + transition.updateFrame(node: strongSelf.locationNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 50.0), size: locationLayout.size)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift index 1bc481e1dd..7ff33d6e67 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift @@ -333,21 +333,21 @@ private enum RecentSessionsEntry: ItemListNodeEntry { case let .currentSessionHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .currentSession(_, _, dateTimeFormat, session): - return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: true, editable: false, editing: false, revealed: false, sectionId: self.section, setSessionIdWithRevealedOptions: { _, _ in + return ItemListRecentSessionItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, session: session, enabled: true, editable: false, editing: false, revealed: false, sectionId: self.section, setSessionIdWithRevealedOptions: { _, _ in }, removeSession: { _ in }, action: { arguments.openSession(session) }) case let .terminateOtherSessions(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { arguments.terminateOtherSessions() }) case let .terminateAllWebSessions(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.blockDestructiveIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { arguments.terminateAllWebSessions() }) case let .currentAddDevice(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addDeviceIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.addDevice() }) case let .currentSessionInfo(_, text): @@ -360,7 +360,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry { case let .pendingSessionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .pendingSession(_, _, _, dateTimeFormat, session, enabled, editing, revealed): - return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in + return ItemListRecentSessionItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in arguments.setSessionIdWithRevealedOptions(previousId, id) }, removeSession: { id in arguments.removeSession(id) @@ -372,11 +372,11 @@ private enum RecentSessionsEntry: ItemListNodeEntry { case let .otherSessionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .addDevice(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addDeviceIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addDeviceIcon(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.addDevice() }) case let .session(_, _, _, dateTimeFormat, session, enabled, editing, revealed): - return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in + return ItemListRecentSessionItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in arguments.setSessionIdWithRevealedOptions(previousId, id) }, removeSession: { id in arguments.removeSession(id) @@ -384,7 +384,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry { arguments.openSession(session) }) case let .website(_, _, _, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed): - return ItemListWebsiteItem(context: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in + return ItemListWebsiteItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in arguments.setSessionIdWithRevealedOptions(previousId, id) }, removeSession: { id in arguments.removeWebSession(id) @@ -401,7 +401,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry { case let .ttlHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .ttlTimeout(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.setupAuthorizationTTL() }, tag: PrivacyAndSecurityEntryTag.accountTimeout) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift index d7ce3be983..2bd1a4d163 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsHeaderItem.swift @@ -12,6 +12,9 @@ import AccountContext import Markdown import TextFormat import SolidRoundedButtonNode +import ComponentFlow +import ButtonComponent +import BundleIconComponent class RecentSessionsHeaderItem: ListViewItem, ItemListItem { let context: AccountContext @@ -75,6 +78,7 @@ class RecentSessionsHeaderItemNode: ListViewItemNode { private let titleNode: TextNode private var animationNode: AnimatedStickerNode private let buttonNode: SolidRoundedButtonNode + private let button = ComponentView() private var item: RecentSessionsHeaderItem? @@ -92,7 +96,7 @@ class RecentSessionsHeaderItemNode: ListViewItemNode { self.addSubnode(self.titleNode) self.addSubnode(self.animationNode) - self.addSubnode(self.buttonNode) + //self.addSubnode(self.buttonNode) } override public func didLoad() { @@ -154,12 +158,46 @@ class RecentSessionsHeaderItemNode: ListViewItemNode { strongSelf.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: item.theme)) } - let inset = max(16.0, params.leftInset) - let buttonWidth = min(375, contentSize.width - inset - inset) - let buttonHeight = strongSelf.buttonNode.updateLayout(width: buttonWidth, transition: .immediate) + let buttonSideInset: CGFloat = 36.0 + let buttonWidth = min(375, contentSize.width - buttonSideInset * 2.0) + let buttonHeight = 52.0 let buttonFrame = CGRect(x: floorToScreenPixels((params.width - buttonWidth) / 2.0), y: contentSize.height - buttonHeight + 4.0, width: buttonWidth, height: buttonHeight) strongSelf.buttonNode.frame = buttonFrame + let _ = strongSelf.button.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: item.theme.list.itemCheckColors.fillColor, + foreground: item.theme.list.itemCheckColors.foregroundColor, + pressedColor: item.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: "button", + component: AnyComponent( + HStack([ + AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Settings/QrButtonIcon", tintColor: item.theme.list.itemCheckColors.foregroundColor))), + AnyComponentWithIdentity(id: "label", component: AnyComponent( + Text(text: item.context.sharedContext.currentPresentationData.with { $0 }.strings.AuthSessions_LinkDesktopDevice, font: Font.semibold(17.0), color: item.theme.list.itemCheckColors.foregroundColor) + )) + ], spacing: 6.0) + ) + ), + action: { + item.buttonAction() + } + )), + environment: {}, + containerSize: CGSize(width: buttonWidth, height: buttonHeight) + ) + if let buttonView = strongSelf.button.view { + if buttonView.superview == nil { + strongSelf.view.addSubview(buttonView) + } + buttonView.frame = buttonFrame + } + strongSelf.accessibilityLabel = attributedText.string let iconSize = CGSize(width: 128.0, height: 128.0) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index a79f736255..c024fa6cfa 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -558,7 +558,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .forwardsPreviewHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) case let .forwardsPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, peerName, linkEnabled, tooltipText): - return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText) + return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, systemStyle: .glass, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText) case let .birthdayHeader(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in arguments.openProfileEdit() @@ -566,14 +566,14 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .settingHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) case let .everybody(_, text, value, isLocked, isEnabled): - return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, enabled: isEnabled, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, enabled: isEnabled, zeroSeparatorInsets: false, sectionId: self.section, action: { if isLocked { } else { arguments.updateType(.everybody) } }) case let .contacts(_, text, value, isLocked, isEnabled): - return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, enabled: isEnabled, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, enabled: isEnabled, zeroSeparatorInsets: false, sectionId: self.section, action: { if isLocked { arguments.displayLockedInfo() } else { @@ -581,7 +581,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } }) case let .nobody(_, text, value, isLocked, isEnabled): - return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, enabled: isEnabled, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, enabled: isEnabled, zeroSeparatorInsets: false, sectionId: self.section, action: { if isLocked { arguments.displayLockedInfo() } else { @@ -595,11 +595,11 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .exceptionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .disableFor(_, title, value, isEnabled): - return ItemListDisclosureItem(presentationData: presentationData, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSelective(.main, false) }) case let .enableFor(_, title, value, isEnabled): - return ItemListDisclosureItem(presentationData: presentationData, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, enabled: isEnabled, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSelective(.main, true) }) case let .peersInfo(_, text): @@ -607,31 +607,31 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .callsP2PHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .callsP2PAlways(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateCallP2PMode?(.everybody) }) case let .callsP2PContacts(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateCallP2PMode?(.contacts) }) case let .callsP2PNever(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateCallP2PMode?(.nobody) }) case let .callsP2PInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .callsP2PDisableFor(_, title, value): - return ItemListDisclosureItem(presentationData: presentationData, title: title, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSelective(.callP2P, false) }) case let .callsP2PEnableFor(_, title, value): - return ItemListDisclosureItem(presentationData: presentationData, title: title, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: title, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSelective(.callP2P, true) }) case let .callsP2PPeersInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .callsIntegrationEnabled(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateCallIntegrationEnabled?(value) }) case let .callsIntegrationInfo(_, text): @@ -639,11 +639,11 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .phoneDiscoveryHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .phoneDiscoveryEverybody(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updatePhoneDiscovery?(true) }) case let .phoneDiscoveryMyContacts(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updatePhoneDiscovery?(false) }) case let .phoneDiscoveryInfo(_, text, link): @@ -651,24 +651,24 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { arguments.copyPhoneLink?(link) }) case let .setPublicPhoto(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPhotoIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPhotoIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.setPublicPhoto?() }) case let .removePublicPhoto(_, text, peer, image, completeImage): - return ItemListPeerActionItem(presentationData: presentationData, icon: completeImage, iconSignal: completeImage == nil ? peerAvatarCompleteImage(account: arguments.context.account, peer: peer, forceProvidedRepresentation: true, representation: image?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28)), size: CGSize(width: 28.0, height: 28.0)) : nil, title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: completeImage, iconSignal: completeImage == nil ? peerAvatarCompleteImage(account: arguments.context.account, peer: peer, forceProvidedRepresentation: true, representation: image?.representationForDisplayAtSize(PixelDimensions(width: 28, height: 28)), size: CGSize(width: 28.0, height: 28.0)) : nil, title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: { arguments.removePublicPhoto?() }) case let .publicPhotoInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in }) case let .hideReadTime(_, text, enabled, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in arguments.updateHideReadTime?(value) }) case let .hideReadTimeInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .subscribeToPremium(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: nil, title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: { arguments.openPremiumIntro() }) case let .subscribeToPremiumInfo(_, text): @@ -676,7 +676,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .disallowedGiftsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .disallowedGiftsUnlimited(_, text, isLocked, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in if !isLocked { arguments.updateDisallowedGifts?(.unlimited, !updatedValue) } else { @@ -686,7 +686,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { arguments.displayLockedGiftsInfo() }) case let .disallowedGiftsLimited(_, text, isLocked, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in if !isLocked { arguments.updateDisallowedGifts?(.limited, !updatedValue) } else { @@ -696,7 +696,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { arguments.displayLockedGiftsInfo() }) case let .disallowedGiftsUnique(_, text, isLocked, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in if !isLocked { arguments.updateDisallowedGifts?(.unique, !updatedValue) } else { @@ -706,7 +706,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { arguments.displayLockedGiftsInfo() }) case let .disallowedGiftsPremium(_, text, isLocked, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in if !isLocked { arguments.updateDisallowedGifts?(.premium, !updatedValue) } else { @@ -718,7 +718,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case let .disallowedGiftsInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .showGiftButton(_, text, isLocked, value, available): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: available, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: available, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in if !isLocked { arguments.updateShowGiftButton?(updatedValue) } else if available { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index 7b724ca498..6a758582ad 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -217,7 +217,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { case let .premiumUsersItem(editing, enabled): let peer: EnginePeer = .user(TelegramUser( id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)), accessHash: nil, firstName: presentationData.strings.PrivacySettings_CategoryPremiumUsers, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, customAvatarIcon: premiumAvatarIcon, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, customAvatarIcon: premiumAvatarIcon, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in @@ -226,7 +226,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { case let .botsItem(editing, enabled): let peer: EnginePeer = .user(TelegramUser( id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(2)), accessHash: nil, firstName: presentationData.strings.PrivacySettings_CategoryBots, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil, verificationIconFileId: nil)) - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, customAvatarIcon: botsIcon, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, customAvatarIcon: botsIcon, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in @@ -248,7 +248,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { } } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { arguments.openPeer(EnginePeer(peer.peer)) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) @@ -256,7 +256,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { arguments.removePeer(peerId) }) case let .addItem(text, editing): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .compactPeerList, editing: editing, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .compactPeerList, editing: editing, action: { arguments.addPeer() }) case let .headerItem(text): @@ -264,7 +264,7 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { case let .footerItem(text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .deleteItem(text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.deleteAll() }) } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift index f8efe84b9d..59b1af8a77 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/TwoStepVerificationUnlockController.swift @@ -132,7 +132,7 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry { let arguments = arguments as! TwoStepVerificationUnlockSettingsControllerArguments switch self { case let .passwordEntry(theme, _, text, value): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationUnlockSettingsEntryTag.password, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: text, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: "", type: .password, spacing: 10.0, tag: TwoStepVerificationUnlockSettingsEntryTag.password, sectionId: self.section, textUpdated: { updatedText in arguments.updatePasswordText(updatedText) }, action: { arguments.checkPassword() @@ -151,21 +151,21 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry { } }) case let .passwordSetup(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openSetupPassword() }) case let .passwordSetupInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) case let .changePassword(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openSetupPassword() }) case let .turnPasswordOff(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openDisablePassword() }) case let .setupRecoveryEmail(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openSetupEmail() }) case let .passwordInfo(_, text): @@ -173,7 +173,7 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry { case let .pendingEmailConfirmInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .pendingEmailConfirmCode(_, _, title, text): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: ""), text: text, placeholder: title, type: .number, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: ""), text: text, placeholder: title, type: .number, sectionId: self.section, textUpdated: { value in arguments.updateEmailCode(value) }, action: {}) case let .pendingEmailInfo(_, text): @@ -184,7 +184,7 @@ private enum TwoStepVerificationUnlockSettingsEntry: ItemListNodeEntry { } }) case let .pendingEmailOpenConfirm(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openConfirmEmail() }) } diff --git a/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift b/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift index 4e48dfb494..87c6c60f32 100644 --- a/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift +++ b/submodules/SettingsUI/Sources/ReactionNotificationSettingsController.swift @@ -165,13 +165,13 @@ private enum ReactionNotificationSettingsEntry: ItemListNodeEntry { case let .categoriesHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .messages(title, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, text: text, textColor: .accent, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, text: text, textColor: .accent, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleMessages(value) }, action: { arguments.openMessages() }) case let .stories(title, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, text: text, textColor: .accent, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, text: text, textColor: .accent, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleStories(value) }, action: { arguments.openStories() @@ -179,11 +179,11 @@ private enum ReactionNotificationSettingsEntry: ItemListNodeEntry { case let .optionsHeader(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .previews(text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updatePreviews(value) }) case let .sound(text, value, sound): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openSound(sound) }, tag: self.tag) } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift index 651320a3e5..595c4adbeb 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchItem.swift @@ -124,7 +124,7 @@ final class SettingsSearchItem: ItemListControllerSearch { } } - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { let updateActivated: (Bool) -> Void = self.updateActivated if let current = current as? NavigationBarSearchContentNode { current.updateThemeAndPlaceholder(theme: self.theme, placeholder: self.placeholder) diff --git a/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift index 4d143040ca..be30b24f6b 100644 --- a/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/ArchivedStickerPacksController.swift @@ -141,7 +141,7 @@ private enum ArchivedStickerPacksEntry: ItemListNodeEntry { case let .info(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .pack(_, _, _, info, topItem, count, animatedStickers, enabled, editing, selected): - return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .installation(installed: false), editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: { + return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, systemStyle: .glass, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .installation(installed: false), editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: { arguments.openStickerPack(info) }, setPackIdWithRevealedOptions: { current, previous in arguments.setPackIdWithRevealedOptions(current, previous) diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index f737897e8f..c03f10d74a 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -391,41 +391,41 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { case let .info(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .suggestOptions(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openSuggestOptions() }, tag: InstalledStickerPacksEntryTag.suggestOptions) case let .largeEmoji(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleLargeEmoji(value) }) case let .trending(theme, text, count): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Trending")?.precomposed(), title: text, label: count == 0 ? "" : "\(count)", labelStyle: .badge(theme.list.itemAccentColor), sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Trending")?.precomposed(), title: text, label: count == 0 ? "" : "\(count)", labelStyle: .badge(theme.list.itemAccentColor), sectionId: self.section, style: .blocks, action: { arguments.openFeatured() }) case let .masks(_, text): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openMasks() }) case let .emoji(_, text, count): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Emoji")?.precomposed(), title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Emoji")?.precomposed(), title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: { arguments.openEmoji() }) case let .quickReaction(title, reaction, availableReactions): - return ItemListReactionItem(context: arguments.context, presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Reactions")?.precomposed(), title: title, arrowStyle: .arrow, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: { + return ItemListReactionItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Reactions")?.precomposed(), title: title, arrowStyle: .arrow, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: { arguments.openQuickReaction() }) case let .archived(_, text, count, archived): - return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Archived")?.precomposed(), title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: UIImage(bundleImageName: "Settings/Menu/Archived")?.precomposed(), title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: { arguments.openArchived(archived) }) case let .packOrder(_, text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleDynamicPackOrder(value) }) case let .packOrderInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .suggestAnimatedEmoji(text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleSuggestAnimatedEmoji(value) }) case let .suggestAnimatedEmojiInfo(_, text): @@ -433,7 +433,7 @@ private indirect enum InstalledStickerPacksEntry: ItemListNodeEntry { case let .packsTitle(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .pack(_, _, _, info, topItem, count, animatedStickers, enabled, editing, selected): - return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .none, editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: { + return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, systemStyle: .glass, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: editing.editing ? .check(checked: selected ?? false) : .none, editing: editing, enabled: enabled, playAnimatedStickers: animatedStickers, sectionId: self.section, action: { arguments.openStickerPack(info) }, setPackIdWithRevealedOptions: { current, previous in arguments.setPackIdWithRevealedOptions(current, previous) diff --git a/submodules/SettingsUI/Sources/ThemePickerController.swift b/submodules/SettingsUI/Sources/ThemePickerController.swift index 527ffc8475..61b06a951b 100644 --- a/submodules/SettingsUI/Sources/ThemePickerController.swift +++ b/submodules/SettingsUI/Sources/ThemePickerController.swift @@ -167,7 +167,7 @@ private enum ThemePickerControllerEntry: ItemListNodeEntry { case let .customHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items): - return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) + return ThemeSettingsChatPreviewItem(context: arguments.context, systemStyle: .glass, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) case let .theme(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers, _, themePreferredBaseTheme): return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: true, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, themePreferredBaseTheme: themePreferredBaseTheme, currentTheme: currentTheme, updatedTheme: { theme in if case let .cloud(theme) = theme, theme.theme.file == nil && theme.theme.settings == nil { @@ -273,11 +273,11 @@ private enum ThemePickerControllerEntry: ItemListNodeEntry { arguments.openAccentColorPicker(currentTheme, create) }, tag: ThemeSettingsEntryTag.accentColor) case let .editTheme(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.editThemeIcon(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.editThemeIcon(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: { arguments.editCurrentTheme() }) case let .createTheme(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, height: .generic, editing: false, action: { arguments.createNewTheme() }) } diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index 530f7e3844..e00bdb0fee 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -172,7 +172,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry { let arguments = arguments as! EditThemeControllerArguments switch self { case let .title(_, _, title, text, _): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(), text: text, placeholder: title, type: .regular(capitalization: true, autocorrection: false), returnKeyType: .default, clearType: .onFocus, tag: EditThemeEntryTag.title, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(), text: text, placeholder: title, type: .regular(capitalization: true, autocorrection: false), returnKeyType: .default, clearType: .onFocus, tag: EditThemeEntryTag.title, sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.title = value @@ -182,7 +182,7 @@ private enum EditThemeControllerEntry: ItemListNodeEntry { }) case let .slug(_, _, title, text, enabled): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/addtheme/", textColor: presentationData.theme.list.itemPrimaryTextColor), text: text, placeholder: title, type: .username, clearType: .onFocus, enabled: enabled, tag: EditThemeEntryTag.slug, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/addtheme/", textColor: presentationData.theme.list.itemPrimaryTextColor), text: text, placeholder: title, type: .username, clearType: .onFocus, enabled: enabled, tag: EditThemeEntryTag.slug, sectionId: self.section, textUpdated: { value in arguments.updateState { current in var state = current state.slug = value @@ -196,21 +196,21 @@ private enum EditThemeControllerEntry: ItemListNodeEntry { case let .chatPreviewHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .chatPreview(theme, componentTheme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items): - return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) + return ThemeSettingsChatPreviewItem(context: arguments.context, systemStyle: .glass, theme: theme, componentTheme: componentTheme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) case let .changeColors(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openColors() }) case let .toggleDark(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.toggleDark() }) case let .convertToPresetTheme(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.convertToPresetTheme() }) case let .uploadTheme(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.openFile() }) case let .uploadInfo(_, text): diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift index 2f8ca0cdab..1502b2a9a8 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift @@ -204,37 +204,37 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry { let arguments = arguments as! ThemeAutoNightSettingsControllerArguments switch self { case let .modeSystem(_, title, value): - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.system) }) case let .modeDisabled(_, title, value): - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.none) }) case let .modeTimeBased(_, title, value): - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.timeBased) }) case let .modeBrightness(_, title, value): - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.brightness) }) case let .settingsHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .timeBasedAutomaticLocation(_, title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateTimeBasedAutomatic(value) }) case let .timeBasedAutomaticLocationValue(_, title, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, titleColor: .accent, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: title, titleColor: .accent, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { arguments.updateTimeBasedAutomaticLocation() }) case let .timeBasedManualFrom(_, title, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openTimeBasedManual(.from) }) case let .timeBasedManualTo(_, title, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openTimeBasedManual(.to) }) case let .brightnessValue(theme, value): @@ -246,7 +246,7 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry { case let .themeHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers): - return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: false, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, themePreferredBaseTheme: [:], currentTheme: currentTheme, updatedTheme: { theme in + return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: false, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, themePreferredBaseTheme: [:], currentTheme: currentTheme, updatedTheme: { theme in arguments.updateTheme(theme) }, contextAction: nil) } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift index 946e81f609..421d3d4179 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift @@ -44,15 +44,17 @@ class ThemeSettingsAppIconItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let icons: [PresentationAppIcon] let isPremium: Bool let currentIconName: String? let updated: (PresentationAppIcon) -> Void let tag: ItemListItemTag? - init(theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, icons: [PresentationAppIcon], isPremium: Bool, currentIconName: String?, updated: @escaping (PresentationAppIcon) -> Void, tag: ItemListItemTag? = nil) { + init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle, sectionId: ItemListSectionId, icons: [PresentationAppIcon], isPremium: Bool, currentIconName: String?, updated: @escaping (PresentationAppIcon) -> Void, tag: ItemListItemTag? = nil) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.icons = icons self.isPremium = isPremium self.currentIconName = currentIconName @@ -316,7 +318,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift index 0359d23db1..ac07ff62b2 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsChatPreviewItem.swift @@ -43,6 +43,7 @@ struct ChatPreviewMessageItem: Equatable { class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem { let context: AccountContext + let systemStyle: ItemListSystemStyle let theme: PresentationTheme let componentTheme: PresentationTheme let strings: PresentationStrings @@ -54,8 +55,9 @@ class ThemeSettingsChatPreviewItem: ListViewItem, ItemListItem { let nameDisplayOrder: PresentationPersonNameOrder let messageItems: [ChatPreviewMessageItem] - init(context: AccountContext, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [ChatPreviewMessageItem]) { + init(context: AccountContext, systemStyle: ItemListSystemStyle = .legacy, theme: PresentationTheme, componentTheme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, messageItems: [ChatPreviewMessageItem]) { self.context = context + self.systemStyle = systemStyle self.theme = theme self.componentTheme = componentTheme self.strings = strings @@ -270,7 +272,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.componentTheme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 1c87e16ce5..4aa227f8d8 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -284,7 +284,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { let arguments = arguments as! ThemeSettingsControllerArguments switch self { case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items): - return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) + return ThemeSettingsChatPreviewItem(context: arguments.context, systemStyle: .glass, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) case let .themes(theme, strings, chatThemes, currentTheme, nightMode, animatedEmojiStickers, themeSpecificAccentColors, themeSpecificChatWallpapers): return ThemeCarouselThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: chatThemes, hasNoTheme: false, animatedEmojiStickers: animatedEmojiStickers, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, nightMode: nightMode, currentTheme: currentTheme, updatedTheme: { theme in if let theme { @@ -294,11 +294,11 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { arguments.themeContextAction(false, theme, node, gesture) }, tag: ThemeSettingsEntryTag.theme) case let .chatTheme(_, text): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openThemeSettings() }) case let .wallpaper(_, text): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openWallpaperSettings() }) case let .nameColor(_, text, _, nameColor, profileColor): @@ -312,23 +312,23 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { let colorImage = generateSettingsMenuPeerColorsLabelIcon(colors: colors) - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", labelStyle: .image(image: colorImage, size: colorImage.size), sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: "", labelStyle: .image(image: colorImage, size: colorImage.size), sectionId: self.section, style: .blocks, action: { arguments.openNameColorSettings() }) case let .autoNight(_, title, value, enabled): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleNightTheme(value) }, tag: nil) case let .autoNightTheme(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openAutoNightTheme() }) case let .textSize(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openTextSize() }) case let .bubbleSettings(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: text, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openBubbleSettings() }) case let .themeListHeader(_, text): @@ -336,21 +336,21 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case let .iconHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .iconItem(theme, strings, icons, isPremium, value): - return ThemeSettingsAppIconItem(theme: theme, strings: strings, sectionId: self.section, icons: icons, isPremium: isPremium, currentIconName: value, updated: { icon in + return ThemeSettingsAppIconItem(theme: theme, strings: strings, systemStyle: .glass, sectionId: self.section, icons: icons, isPremium: isPremium, currentIconName: value, updated: { icon in arguments.selectAppIcon(icon) }, tag: ThemeSettingsEntryTag.icon) case .powerSaving: - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: presentationData.strings.AppearanceSettings_Animations, label: "", labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: presentationData.strings.AppearanceSettings_Animations, label: "", labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openPowerSavingSettings() }) case .stickersAndEmoji: - return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: presentationData.strings.ChatSettings_StickersAndReactions, label: "", labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, title: presentationData.strings.ChatSettings_StickersAndReactions, label: "", labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openStickersAndEmoji() }) case let .otherHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .showNextMediaOnTap(_, title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleShowNextMediaOnTap(value) }, tag: ThemeSettingsEntryTag.animations) case let .showNextMediaOnTapInfo(_, text): diff --git a/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift b/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift index 481e5d5320..f94d01a250 100644 --- a/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift +++ b/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift @@ -79,7 +79,7 @@ private enum WatchSettingsControllerEntry: ItemListNodeEntry { case let .replyPresetsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .replyPreset(_, _, identifier, placeholder, value, _): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: ""), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: true), spacing: 0.0, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: ""), text: value, placeholder: placeholder, type: .regular(capitalization: true, autocorrection: true), spacing: 0.0, sectionId: self.section, textUpdated: { updatedText in arguments.updatePreset(identifier, updatedText.trimmingCharacters(in: .whitespacesAndNewlines)) }, action: {}) case let .replyPresetsInfo(_, text): diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index 906c96938c..b90c6a4987 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -128,6 +128,8 @@ swift_library( "//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/ReactionSelectionNode", "//submodules/TelegramUI/Components/EntityKeyboard", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/PremiumUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift index 0d198d009d..9646f8c7a3 100644 --- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift @@ -10,6 +10,8 @@ import TelegramUIPreferences import AccountContext import AnimatedCountLabelNode import VoiceChatActionButton +import ComponentFlow +import ReactionSelectionNode private let blue = UIColor(rgb: 0x007fff) private let lightBlue = UIColor(rgb: 0x00affe) @@ -191,6 +193,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateAnimatedCountLabelNode private let speakerNode: ImmediateTextNode + private var messageView: ComponentView? private let audioLevelDisposable = MetaDisposable() private let stateDisposable = MetaDisposable() @@ -212,6 +215,11 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { private var currentMembers: PresentationGroupCallMembers? private var currentIsConnected = true + private var reactionItems: [ReactionItem]? + private var messagesState: GroupCallMessagesContext.State? + private let messagesStateDisposable = MetaDisposable() + private var currentMessageId: Int64? + private let hierarchyTrackingNode: HierarchyTrackingNode private var isCurrentlyInHierarchy = true @@ -250,6 +258,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { self.presentationDataDisposable.dispose() self.audioLevelDisposable.dispose() self.stateDisposable.dispose() + self.messagesStateDisposable.dispose() self.currentCallTimer?.invalidate() } @@ -401,6 +410,27 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { effectiveLevel = audioLevels.map { $0.2 }.max() ?? 0.0 strongSelf.backgroundNode.audioLevel = effectiveLevel })) + + if let groupCall = call as? PresentationGroupCallImpl { + let _ = (allowedStoryReactions(account: account) + |> deliverOnMainQueue).start(next: { [weak self] reactionItems in + self?.reactionItems = reactionItems + }) + + self.messagesStateDisposable.set((groupCall.messagesState + |> deliverOnMainQueue).start(next: { [weak self] messagesState in + guard let self else { + return + } + if self.messagesState != messagesState { + self.messagesState = messagesState + + if self.isCurrentlyInHierarchy { + self.update() + } + } + })) + } } } @@ -529,9 +559,66 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { self.subtitleNode.segments = segments } + let contentHeight: CGFloat = 24.0 + let verticalOrigin: CGFloat = size.height - contentHeight + + var isDisplayingMessage = false + let componentTransition: ComponentTransition = .easeInOut(duration: 0.25) + if case let .groupCall(_, account, call) = self.currentContent, let message = self.messagesState?.messages.last(where: { $0.author?.id != account.peerId }), let author = message.author, let groupCall = call as? PresentationGroupCallImpl { + if self.currentMessageId != message.id { + self.currentMessageId = message.id + + if let messageView = self.messageView?.view { + self.messageView = nil + componentTransition.setAlpha(view: messageView, alpha: 0.0, completion: { _ in + messageView.removeFromSuperview() + }) + } + } + let messageView: ComponentView + if let current = self.messageView { + messageView = current + } else { + messageView = ComponentView() + self.messageView = messageView + } + + let messageSize = messageView.update( + transition: .immediate, + component: AnyComponent( + MessageItemComponent( + context: groupCall.accountContext, + icon: .peer(author), + style: .status, + text: message.text, + entities: message.entities, + availableReactions: self.reactionItems, + openPeer: nil + ) + ), + environment: {}, + containerSize: CGSize(width: size.width - 140.0, height: contentHeight) + ) + if let view = messageView.view { + if view.superview == nil { + view.transform = CGAffineTransformMakeScale(1.0, -1.0) + self.view.addSubview(view) + componentTransition.animateAlpha(view: view, from: 0.0, to: 1.0) + } + view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - messageSize.width) / 2.0), y: verticalOrigin + floor((contentHeight - messageSize.height) / 2.0) + 1.0), size: messageSize) + } + isDisplayingMessage = true + } else if let messageView = self.messageView?.view { + self.messageView = nil + componentTransition.setAlpha(view: messageView, alpha: 0.0, completion: { _ in + messageView.removeFromSuperview() + }) + } + let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) - alphaTransition.updateAlpha(node: self.subtitleNode, alpha: displaySpeakerSubtitle ? 0.0 : 1.0) - alphaTransition.updateAlpha(node: self.speakerNode, alpha: displaySpeakerSubtitle ? 1.0 : 0.0) + alphaTransition.updateAlpha(node: self.titleNode, alpha: isDisplayingMessage ? 0.0 : 1.0) + alphaTransition.updateAlpha(node: self.subtitleNode, alpha: displaySpeakerSubtitle || isDisplayingMessage ? 0.0 : 1.0) + alphaTransition.updateAlpha(node: self.speakerNode, alpha: displaySpeakerSubtitle && !isDisplayingMessage ? 1.0 : 0.0) self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white) @@ -551,9 +638,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode { totalWidth += subtitleSize.width let horizontalOrigin: CGFloat = floor((size.width - totalWidth) / 2.0) - let contentHeight: CGFloat = 24.0 - let verticalOrigin: CGFloat = size.height - contentHeight - let sizeChanged = self.titleNode.frame.size.width != titleSize.width let transition: ContainedViewLayoutTransition = wasEmpty || sizeChanged ? .immediate : .animated(duration: 0.2, curve: .easeInOut) diff --git a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift index b0002c35e9..5843bdf191 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift @@ -26,9 +26,15 @@ final class MessageItemComponent: Component { case animation(String) } + public enum Style { + case generic + case notification + case status + } + private let context: AccountContext private let icon: Icon - private let isNotification: Bool + private let style: Style private let text: String private let entities: [MessageTextEntity] private let availableReactions: [ReactionItem]? @@ -37,7 +43,7 @@ final class MessageItemComponent: Component { init( context: AccountContext, icon: Icon, - isNotification: Bool, + style: Style, text: String, entities: [MessageTextEntity], availableReactions: [ReactionItem]?, @@ -45,7 +51,7 @@ final class MessageItemComponent: Component { ) { self.context = context self.icon = icon - self.isNotification = isNotification + self.style = style self.text = text self.entities = entities self.availableReactions = availableReactions @@ -59,6 +65,9 @@ final class MessageItemComponent: Component { if lhs.icon != rhs.icon { return false } + if lhs.style != rhs.style { + return false + } if lhs.text != rhs.text { return false } @@ -173,27 +182,60 @@ final class MessageItemComponent: Component { let theme = defaultDarkPresentationTheme - let textFont = Font.regular(14.0) - let boldTextFont = Font.semibold(14.0) - let italicFont = Font.italic(14.0) - let boldItalicTextFont = Font.semiboldItalic(14.0) - let monospaceFont = Font.monospace(14.0) + var fontSize: CGFloat = 14.0 + + let minimalHeight: CGFloat + let avatarSpacing: CGFloat + let avatarInset: CGFloat + let avatarSize: CGSize + let rightInset: CGFloat + var maximumNumberOfLines: Int = 0 + var hasBackground = true + var textVerticalMargin: CGFloat = 15.0 + switch component.style { + case .generic: + minimalHeight = 36.0 + avatarSpacing = 10.0 + avatarInset = 4.0 + avatarSize = CGSize(width: 28.0, height: 28.0) + rightInset = 13.0 + case .notification: + minimalHeight = 50.0 + avatarSpacing = 10.0 + avatarInset = 10.0 + avatarSize = CGSize(width: 30.0, height: 30.0) + rightInset = 15.0 + case .status: + minimalHeight = 24.0 + avatarSpacing = 4.0 + avatarInset = 4.0 + avatarSize = CGSize(width: 16.0, height: 16.0) + rightInset = 0.0 + maximumNumberOfLines = 1 + hasBackground = false + fontSize = 13.0 + textVerticalMargin = 0.0 + } + + let textFont = Font.regular(fontSize) + let boldTextFont = Font.semibold(fontSize) + let italicFont = Font.italic(fontSize) + let boldItalicTextFont = Font.semiboldItalic(fontSize) + let monospaceFont = Font.monospace(fontSize) let textColor: UIColor = .white - let linkColor: UIColor = UIColor(rgb: 0x59b6fa) - - let minimalHeight: CGFloat = component.isNotification ? 50.0 : 36.0 + let linkColor: UIColor = component.style == .status ? textColor : UIColor(rgb: 0x59b6fa) + let cornerRadius = minimalHeight * 0.5 - let avatarInset: CGFloat = component.isNotification ? 10.0 : 4.0 - let avatarSize = CGSize(width: component.isNotification ? 30.0 : 28.0, height: component.isNotification ? 30.0 : 28.0) - let avatarSpacing: CGFloat = 10.0 let iconSpacing: CGFloat = 10.0 - let rightInset: CGFloat = component.isNotification ? 15.0 : 13.0 var peerName = "" - if !component.isNotification, case let .peer(peer) = component.icon { - peerName = peer.compactDisplayTitle - if peerName.count > 40 { - peerName = "\(peerName.prefix(40))…" + if case let .peer(peer) = component.icon { + if case .notification = component.style { + } else { + peerName = peer.compactDisplayTitle + if peerName.count > 40 { + peerName = "\(peerName.prefix(40))…" + } } } @@ -234,7 +276,7 @@ final class MessageItemComponent: Component { } let attributedText: NSAttributedString - if component.isNotification { + if case .notification = component.style { attributedText = parseMarkdownIntoAttributedString( text, attributes: MarkdownAttributes( @@ -270,7 +312,7 @@ final class MessageItemComponent: Component { animationRenderer: component.context.animationRenderer, placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.3), text: .plain(attributedText), - maximumNumberOfLines: 0, + maximumNumberOfLines: maximumNumberOfLines, lineSpacing: 0.1, spoilerColor: .white, handleSpoilers: true @@ -283,7 +325,7 @@ final class MessageItemComponent: Component { let size = CGSize( width: avatarInset + avatarSize.width + spacing + textSize.width + rightInset, - height: max(minimalHeight, textSize.height + 15.0) + height: max(minimalHeight, textSize.height + textVerticalMargin) ) switch component.icon { @@ -393,6 +435,7 @@ final class MessageItemComponent: Component { transition.setFrame(view: self.container, frame: CGRect(origin: CGPoint(), size: size)) + self.background.isHidden = !hasBackground self.background.update(size: size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .custom, color: glassColor), transition: transition) transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift index 8e715cfc62..53a3824634 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MessageListComponent.swift @@ -176,7 +176,7 @@ final class MessageListComponent: Component { component: AnyComponent(MessageItemComponent( context: component.context, icon: item.icon, - isNotification: item.isNotification, + style: item.isNotification ? .notification : .generic, text: item.text, entities: item.entities, availableReactions: component.availableReactions, diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 65ba2ce6bd..366ab0071b 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -1395,7 +1395,7 @@ final class VideoChatScreenComponent: Component { ) self.inputMediaInteraction?.forceTheme = defaultDarkColorPresentationTheme - let _ = (allowedStoryReactions(context: context) + let _ = (allowedStoryReactions(account: context.account) |> deliverOnMainQueue).start(next: { [weak self] reactionItems in self?.reactionItems = reactionItems }) @@ -1880,6 +1880,7 @@ final class VideoChatScreenComponent: Component { } } } else { + //TODO:localized if event.joined { self.lastTitleEvent = "\(event.peer.compactDisplayTitle) joined" } else { @@ -2350,7 +2351,7 @@ final class VideoChatScreenComponent: Component { name: "Call/PanelIcon", tintColor: .white )), - background: AnyComponent(GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter + 10.0, height: navigationButtonDiameter), cornerRadius: navigationButtonDiameter * 0.5, isDark: true, tintColor: .init(kind: .panel, color: panelColor))), + background: AnyComponent(GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter + 10.0, height: navigationButtonDiameter), cornerRadius: navigationButtonDiameter * 0.5, isDark: true, tintColor: .init(kind: .custom, color: panelColor))), effectAlignment: .center, minSize: CGSize(width: navigationButtonDiameter + 10.0, height: navigationButtonDiameter), action: { [weak self] in @@ -4239,9 +4240,9 @@ private func hasFirstResponder(_ view: UIView) -> Bool { return false } -private func allowedStoryReactions(context: AccountContext) -> Signal<[ReactionItem], NoError> { +func allowedStoryReactions(account: Account) -> Signal<[ReactionItem], NoError> { let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions) - let topReactions = context.account.postbox.combinedView(keys: [viewKey]) + let topReactions = account.postbox.combinedView(keys: [viewKey]) |> map { views -> [RecentReactionItem] in guard let view = views.views[viewKey] as? OrderedItemListView else { return [] @@ -4252,7 +4253,7 @@ private func allowedStoryReactions(context: AccountContext) -> Signal<[ReactionI } return combineLatest( - context.engine.stickers.availableReactions(), + TelegramEngine(account: account).stickers.availableReactions(), topReactions ) |> take(1) diff --git a/submodules/TelegramCore/Sources/Account/AccountManager.swift b/submodules/TelegramCore/Sources/Account/AccountManager.swift index 21b5cad557..d20312586a 100644 --- a/submodules/TelegramCore/Sources/Account/AccountManager.swift +++ b/submodules/TelegramCore/Sources/Account/AccountManager.swift @@ -239,6 +239,7 @@ private var declaredEncodables: Void = { declareEncodable(SuggestedPostMessageAttribute.self, f: { SuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(PublishedSuggestedPostMessageAttribute.self, f: { PublishedSuggestedPostMessageAttribute(decoder: $0) }) declareEncodable(TelegramMediaLiveStream.self, f: { TelegramMediaLiveStream(decoder: $0) }) + declareEncodable(ScheduledRepeatAttribute.self, f: { ScheduledRepeatAttribute(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 5ddc45e0e0..465d32ad9e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -697,7 +697,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, _): + case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod): var attributes: [MessageAttribute] = [] if (flags2 & (1 << 4)) != 0 { @@ -952,6 +952,10 @@ extension StoreMessage { attributes.append(PaidStarsMessageAttribute(stars: StarsAmount(value: paidMessageStars, nanos: 0), postponeSending: false)) } + if let scheduledRepeatPeriod { + attributes.append(ScheduledRepeatAttribute(repeatPeriod: scheduledRepeatPeriod)) + } + var entitiesAttribute: TextEntitiesMessageAttribute? if let entities = entities, !entities.isEmpty { let attribute = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)) diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift index ba3623cb25..a2f1fafa5a 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingUpdateMessageManager.swift @@ -75,7 +75,7 @@ private final class PendingUpdateMessageManagerImpl { self.contexts[messageId] = context let queue = self.queue - disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: nil, invertMediaAttribute: invertMediaAttribute) + disposable.set((requestEditMessage(accountPeerId: self.stateManager.accountPeerId, postbox: self.postbox, network: self.network, stateManager: self.stateManager, transformOutgoingMessageMedia: self.transformOutgoingMessageMedia, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: nil, invertMediaAttribute: invertMediaAttribute) |> deliverOn(self.queue)).start(next: { [weak self, weak context] value in queue.async { guard let strongSelf = self, let initialContext = context else { diff --git a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift index 1bc4cd9091..fe6816a6f8 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/RequestEditMessage.swift @@ -27,15 +27,15 @@ public enum RequestEditMessageError { case invalidGrouping } -func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { - return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, invertMediaAttribute: invertMediaAttribute) +func _internal_requestEditMessage(account: Account, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { + return requestEditMessage(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, stateManager: account.stateManager, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, invertMediaAttribute: invertMediaAttribute) } -func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { - return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: false) +func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?) -> Signal { + return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: false) |> `catch` { error -> Signal in if case .invalidReference = error { - return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, forceReupload: true) + return requestEditMessageInternal(accountPeerId: accountPeerId, postbox: postbox, network: network, stateManager: stateManager, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, mediaReferenceRevalidationContext: mediaReferenceRevalidationContext, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, invertMediaAttribute: invertMediaAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, forceReupload: true) } else { return .fail(error) } @@ -50,7 +50,7 @@ func requestEditMessage(accountPeerId: PeerId, postbox: Postbox, network: Networ } } -private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool, scheduleTime: Int32?, forceReupload: Bool) -> Signal { +private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, mediaReferenceRevalidationContext: MediaReferenceRevalidationContext, messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute?, invertMediaAttribute: InvertMediaMessageAttribute?, disableUrlPreview: Bool, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute?, forceReupload: Bool) -> Signal { let uploadedMedia: Signal switch media { case .keep: @@ -124,7 +124,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, case _ as TelegramMediaImage, _ as TelegramMediaFile, _ as TelegramMediaTodo: break default: - if let _ = scheduleTime { + if let _ = scheduleInfoAttribute { break } else { return (nil, nil, SimpleDictionary()) @@ -173,13 +173,19 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, } var effectiveScheduleTime: Int32? + var effectiveScheduleRepeatPeriod: Int32? if messageId.namespace == Namespaces.Message.ScheduledCloud { - if let scheduleTime = scheduleTime { + if let scheduleTime = scheduleInfoAttribute?.scheduleTime { effectiveScheduleTime = scheduleTime } else { effectiveScheduleTime = message.timestamp } flags |= Int32(1 << 15) + + if let scheduleInfoAttribute { + effectiveScheduleRepeatPeriod = scheduleInfoAttribute.repeatPeriod + flags |= Int32(1 << 18) + } } if let _ = invertMediaAttribute { @@ -192,7 +198,7 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox, flags |= Int32(1 << 17) } - return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, scheduleRepeatPeriod: nil, quickReplyShortcutId: quickReplyShortcutId)) + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, scheduleRepeatPeriod: effectiveScheduleRepeatPeriod, quickReplyShortcutId: quickReplyShortcutId)) |> map { result -> Api.Updates? in return result } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index c9cc63be58..387b022a43 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -340,6 +340,7 @@ private func sendUploadedMessageContent( var replyToStoryId: StoryId? var replyTodoItemId: Int32? var scheduleTime: Int32? + var scheduleRepeatPeriod: Int32? var videoTimestamp: Int32? var sendAsPeerId: PeerId? var bubbleUpEmojiOrStickersets = false @@ -378,6 +379,10 @@ private func sendUploadedMessageContent( } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { flags |= Int32(1 << 10) scheduleTime = attribute.scheduleTime + if let repeatPeriod = attribute.repeatPeriod { + flags |= Int32(1 << 24) + scheduleRepeatPeriod = repeatPeriod + } } else if let attribute = attribute as? SendAsMessageAttribute { sendAsPeerId = attribute.peerId } else if let attribute = attribute as? ForwardVideoTimestampAttribute { @@ -454,7 +459,7 @@ private func sendUploadedMessageContent( flags |= 1 << 22 } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -486,7 +491,7 @@ private func sendUploadedMessageContent( flags |= 1 << 22 } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): if topMsgId != nil { @@ -494,7 +499,7 @@ private func sendUploadedMessageContent( } if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) @@ -634,6 +639,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M var replyMessageId: Int32? var replyToStoryId: StoryId? var scheduleTime: Int32? + var scheduleRepeatPeriod: Int32? var sendAsPeerId: PeerId? var allowPaidStars: Int64? var suggestedPost: Api.SuggestedPost? @@ -659,6 +665,10 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { flags |= Int32(1 << 10) scheduleTime = attribute.scheduleTime + if let repeatPeriod = attribute.repeatPeriod { + flags |= Int32(1 << 24) + scheduleRepeatPeriod = repeatPeriod + } } else if let attribute = attribute as? SendAsMessageAttribute { sendAsPeerId = attribute.peerId } else if let attribute = attribute as? PaidStarsMessageAttribute { @@ -701,7 +711,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil, monoforumPeerId: nil, todoItemId: nil) } - sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: nil)) |> `catch` { _ -> Signal in return .complete() } @@ -726,7 +736,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M flags |= 1 << 22 } - sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost)) + sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost)) |> `catch` { _ -> Signal in return .complete() } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 15a107418e..42c4920826 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -990,6 +990,7 @@ public final class PendingMessageManager { var replyToStoryId: StoryId? var replyTodoItemId: Int32? var scheduleTime: Int32? + var scheduleRepeatPeriod: Int32? var videoTimestamp: Int32? var sendAsPeerId: PeerId? var quickReply: OutgoingQuickReplyMessageAttribute? @@ -1020,6 +1021,10 @@ public final class PendingMessageManager { } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { flags |= Int32(1 << 10) scheduleTime = attribute.scheduleTime + if let repeatPeriod = attribute.repeatPeriod { + flags |= Int32(1 << 24) + scheduleRepeatPeriod = repeatPeriod + } } else if let attribute = attribute as? ForwardOptionsMessageAttribute { hideSendersNames = attribute.hideNames hideCaptions = attribute.hideCaptions @@ -1128,7 +1133,7 @@ public final class PendingMessageManager { } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id) - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) } else { assertionFailure() sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) @@ -1491,6 +1496,7 @@ public final class PendingMessageManager { var replyToStoryId: StoryId? var replyTodoItemId: Int32? var scheduleTime: Int32? + var scheduleRepeatPeriod: Int32? var videoTimestamp: Int32? var sendAsPeerId: PeerId? var bubbleUpEmojiOrStickersets = false @@ -1543,6 +1549,10 @@ public final class PendingMessageManager { } else if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { flags |= Int32(1 << 10) scheduleTime = attribute.scheduleTime + if let repeatPeriod = attribute.repeatPeriod { + flags |= Int32(1 << 24) + scheduleRepeatPeriod = repeatPeriod + } } else if let attribute = attribute as? SendAsMessageAttribute { sendAsPeerId = attribute.peerId } else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute { @@ -1677,7 +1687,7 @@ public final class PendingMessageManager { flags |= 1 << 22 } - sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) + sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), info: .acknowledgement, tag: dependencyTag) case let .media(inputMedia, text): if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) @@ -1776,7 +1786,7 @@ public final class PendingMessageManager { flags |= 1 << 22 } - sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): var topMsgId: Int32? @@ -1821,7 +1831,7 @@ public final class PendingMessageManager { } if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: nil, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) diff --git a/submodules/TelegramCore/Sources/SyncCore/ScheduledRepeatAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/ScheduledRepeatAttribute.swift new file mode 100644 index 0000000000..dd3d4d034b --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/ScheduledRepeatAttribute.swift @@ -0,0 +1,23 @@ +import Foundation +import Postbox +import TelegramApi + +public final class ScheduledRepeatAttribute: Equatable, MessageAttribute { + public let repeatPeriod: Int32 + + public init(repeatPeriod: Int32) { + self.repeatPeriod = repeatPeriod + } + + required public init(decoder: PostboxDecoder) { + self.repeatPeriod = decoder.decodeInt32ForKey("rp", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.repeatPeriod, forKey: "rp") + } + + public static func ==(lhs: ScheduledRepeatAttribute, rhs: ScheduledRepeatAttribute) -> Bool { + return lhs.repeatPeriod == rhs.repeatPeriod + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingScheduleInfoMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingScheduleInfoMessageAttribute.swift index ee9f21c5cc..d9c0cd5a3b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingScheduleInfoMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_OutgoingScheduleInfoMessageAttribute.swift @@ -5,21 +5,33 @@ public let scheduleWhenOnlineTimestamp: Int32 = 0x7ffffffe public class OutgoingScheduleInfoMessageAttribute: MessageAttribute { public let scheduleTime: Int32 + public let repeatPeriod: Int32? - public init(scheduleTime: Int32) { + public init(scheduleTime: Int32, repeatPeriod: Int32?) { self.scheduleTime = scheduleTime + self.repeatPeriod = repeatPeriod } required public init(decoder: PostboxDecoder) { self.scheduleTime = decoder.decodeInt32ForKey("t", orElse: 0) + self.repeatPeriod = decoder.decodeOptionalInt32ForKey("rp") } public func encode(_ encoder: PostboxEncoder) { - encoder.encodeInt32(scheduleTime, forKey: "t") + encoder.encodeInt32(self.scheduleTime, forKey: "t") + if let repeatPeriod = self.repeatPeriod { + encoder.encodeInt32(repeatPeriod, forKey: "rp") + } else { + encoder.encodeNil(forKey: "rp") + } } public func withUpdatedScheduleTime(_ scheduleTime: Int32) -> OutgoingScheduleInfoMessageAttribute { - return OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime) + return OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: self.repeatPeriod) + } + + public func withUpdatedRepeatPeriod(_ repeatPeriod: Int32?) -> OutgoingScheduleInfoMessageAttribute { + return OutgoingScheduleInfoMessageAttribute(scheduleTime: self.scheduleTime, repeatPeriod: repeatPeriod) } } @@ -32,4 +44,15 @@ public extension Message { } return nil } + + var scheduleRepeatPeriod: Int32? { + for attribute in self.attributes { + if let attribute = attribute as? OutgoingScheduleInfoMessageAttribute { + return attribute.repeatPeriod + } else if let attribute = attribute as? ScheduledRepeatAttribute { + return attribute.repeatPeriod + } + } + return nil + } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift index 21e4820453..1148b91c2e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift @@ -41,7 +41,7 @@ public enum AddContactError { case generic } -func _internal_addContactInteractively(account: Account, peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, addToPrivacyExceptions: Bool) -> Signal { +func _internal_addContactInteractively(account: Account, peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, noteText: String, noteEntities: [MessageTextEntity], addToPrivacyExceptions: Bool) -> Signal { let accountPeerId = account.peerId return account.postbox.transaction { transaction -> (Api.InputUser, String)? in @@ -60,7 +60,12 @@ func _internal_addContactInteractively(account: Account, peerId: PeerId, firstNa if addToPrivacyExceptions { flags |= (1 << 0) } - return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone, note: nil)) + var note: Api.TextWithEntities? + if !noteText.isEmpty { + flags |= (1 << 1) + note = .textWithEntities(text: noteText, entities: apiEntitiesFromMessageTextEntities(noteEntities, associatedPeers: SimpleDictionary())) + } + return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone, note: note)) |> mapError { _ -> AddContactError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift index 72f9156b76..a4eabbbd5c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/TelegramEngineContacts.swift @@ -46,8 +46,8 @@ public extension TelegramEngine { return _internal_importContact(account: self.account, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber) } - public func addContactInteractively(peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, addToPrivacyExceptions: Bool) -> Signal { - return _internal_addContactInteractively(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, addToPrivacyExceptions: addToPrivacyExceptions) + public func addContactInteractively(peerId: PeerId, firstName: String, lastName: String, phoneNumber: String, noteText: String, noteEntities: [MessageTextEntity], addToPrivacyExceptions: Bool) -> Signal { + return _internal_addContactInteractively(account: self.account, peerId: peerId, firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, noteText: noteText, noteEntities: noteEntities, addToPrivacyExceptions: addToPrivacyExceptions) } public func acceptAndShareContact(peerId: PeerId) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index fe290487dd..0d8e6dc1c4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -27,7 +27,7 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: attributes.append(InlineBotMessageAttribute(peerId: botId, title: nil)) } if let scheduleTime = scheduleTime { - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil)) } if silentPosting { attributes.append(NotificationInfoMessageAttribute(flags: .muted)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 92954a0412..c4618dfed6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -165,8 +165,8 @@ public extension TelegramEngine { return _internal_clearAuthorHistory(account: self.account, peerId: peerId, memberId: memberId) } - public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleTime: Int32? = nil) -> Signal { - return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleTime: scheduleTime, invertMediaAttribute: invertMediaAttribute) + public func requestEditMessage(messageId: MessageId, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, inlineStickers: [MediaId: Media], webpagePreviewAttribute: WebpagePreviewMessageAttribute? = nil, invertMediaAttribute: InvertMediaMessageAttribute? = nil, disableUrlPreview: Bool = false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute? = nil) -> Signal { + return _internal_requestEditMessage(account: self.account, messageId: messageId, text: text, media: media, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview, scheduleInfoAttribute: scheduleInfoAttribute, invertMediaAttribute: invertMediaAttribute) } public func requestEditLiveLocation(messageId: MessageId, stop: Bool, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?, proximityNotificationRadius: Int32?, extendPeriod: Int32?) -> Signal { @@ -299,7 +299,7 @@ public extension TelegramEngine { attributes.append(NotificationInfoMessageAttribute(flags: .muted)) } if let scheduleTime = scheduleTime { - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil)) } if let sendPaidMessageStars { attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false)) diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index f7cdcc3d64..56384410f5 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -159,6 +159,18 @@ public final class PresentationThemeRootNavigationBar { let resolvedClearButtonForegroundColor = clearButtonForegroundColor ?? self.clearButtonForegroundColor return PresentationThemeRootNavigationBar(buttonColor: buttonColor ?? self.buttonColor, disabledButtonColor: disabledButtonColor ?? self.disabledButtonColor, primaryTextColor: primaryTextColor ?? self.primaryTextColor, secondaryTextColor: secondaryTextColor ?? self.secondaryTextColor, controlColor: controlColor ?? self.controlColor, accentTextColor: accentTextColor ?? self.accentTextColor, blurredBackgroundColor: blurredBackgroundColor ?? self.blurredBackgroundColor, opaqueBackgroundColor: opaqueBackgroundColor ?? self.opaqueBackgroundColor, separatorColor: separatorColor ?? self.separatorColor, badgeBackgroundColor: badgeBackgroundColor ?? self.badgeBackgroundColor, badgeStrokeColor: badgeStrokeColor ?? self.badgeStrokeColor, badgeTextColor: badgeTextColor ?? self.badgeTextColor, segmentedBackgroundColor: segmentedBackgroundColor ?? self.segmentedBackgroundColor, segmentedForegroundColor: segmentedForegroundColor ?? self.segmentedForegroundColor, segmentedTextColor: segmentedTextColor ?? self.segmentedTextColor, segmentedDividerColor: segmentedDividerColor ?? self.segmentedDividerColor, clearButtonBackgroundColor: resolvedClearButtonBackgroundColor, clearButtonForegroundColor: resolvedClearButtonForegroundColor) } + + public var glassBarButtonBackgroundColor: UIColor { + return self.opaqueBackgroundColor.mixedWith(self.primaryTextColor, alpha: 0.1).withAlphaComponent(0.85) + } + + public var glassBarButtonForegroundColor: UIColor { + if self.primaryTextColor.lightness > 0.8 { + return self.primaryTextColor.withAlphaComponent(0.8) + } else { + return self.primaryTextColor.withAlphaComponent(0.57) + } + } } public final class PresentationThemeNavigationSearchBar { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index b5f32e6612..1aa3532958 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -68,6 +68,9 @@ public enum PresentationResourceKey: Int32 { case itemListCornersTop case itemListCornersBottom case itemListCornersBoth + case itemListCornersTopGlass + case itemListCornersBottomGlass + case itemListCornersBothGlass case itemListKnob case itemListBlockAccentIcon case itemListBlockDestructiveIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 9788809b1f..a8f287270c 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -345,20 +345,20 @@ public struct PresentationResourcesItemList { }) } - public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? { + public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool, glass: Bool = false) -> UIImage? { if !top && !bottom { return nil } let key: PresentationResourceKey if top && bottom { - key = PresentationResourceKey.itemListCornersBoth + key = glass ? PresentationResourceKey.itemListCornersBothGlass : PresentationResourceKey.itemListCornersBoth } else if top { - key = PresentationResourceKey.itemListCornersTop + key = glass ? PresentationResourceKey.itemListCornersTopGlass : PresentationResourceKey.itemListCornersTop } else { - key = PresentationResourceKey.itemListCornersBottom + key = glass ? PresentationResourceKey.itemListCornersBottomGlass : PresentationResourceKey.itemListCornersBottom } return theme.image(key.rawValue, { theme in - return generateImage(CGSize(width: 50.0, height: 50.0), rotatedContext: { (size, context) in + return generateImage(CGSize(width: 56.0, height: 56.0), rotatedContext: { (size, context) in let bounds = CGRect(origin: CGPoint(), size: size) context.setFillColor(theme.list.blocksBackgroundColor.cgColor) context.fill(bounds) @@ -374,10 +374,11 @@ public struct PresentationResourcesItemList { corners.insert(.bottomLeft) corners.insert(.bottomRight) } - let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: 11.0, height: 11.0)) + let cornerRadius: CGFloat = glass ? 26.0 : 11.0 + let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) context.addPath(path.cgPath) context.fillPath() - })?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25) + })?.stretchableImage(withLeftCapWidth: 28, topCapHeight: 28) }) } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 46b169144b..545728eda1 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -492,6 +492,8 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel", "//submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode", "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/AttachmentFileController", + "//submodules/TelegramUI/Components/Contacts/NewContactScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/AttachmentFileController/BUILD b/submodules/TelegramUI/Components/AttachmentFileController/BUILD new file mode 100644 index 0000000000..6258fbdb77 --- /dev/null +++ b/submodules/TelegramUI/Components/AttachmentFileController/BUILD @@ -0,0 +1,47 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AttachmentFileController", + module_name = "AttachmentFileController", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramStringFormatting", + "//submodules/TelegramUIPreferences", + "//submodules/LegacyComponents", + "//submodules/SolidRoundedButtonNode", + "//submodules/PresentationDataUtils", + "//submodules/UIKitRuntimeUtils", + "//submodules/ComponentFlow", + "//submodules/ItemListPeerActionItem", + "//submodules/ListMessageItem", + "//submodules/AttachmentUI", + "//submodules/SearchBarNode", + "//submodules/MergeLists", + "//submodules/ChatListSearchItemHeader", + "//submodules/ItemListUI", + "//submodules/SearchUI", + "//submodules/ContextUI", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/SearchInputPanelComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + ], + visibility = [ + "//visibility:public", + ], +) + diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift similarity index 65% rename from submodules/TelegramUI/Sources/AttachmentFileController.swift rename to submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index 3a4d0bdd01..dbd39e4088 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -14,6 +14,10 @@ import ItemListPeerActionItem import AttachmentUI import TelegramStringFormatting import ListMessageItem +import ComponentFlow +import GlassBarButtonComponent +import BundleIconComponent +import EdgeEffect private final class AttachmentFileControllerArguments { let context: AccountContext @@ -113,11 +117,11 @@ private enum AttachmentFileEntry: ItemListNodeEntry { let arguments = arguments as! AttachmentFileControllerArguments switch self { case let .selectFromGallery(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.imageIcon(presentationData.theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.imageIcon(presentationData.theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { arguments.openGallery() }) case let .selectFromFiles(_, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.cloudIcon(presentationData.theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.cloudIcon(presentationData.theme), title: text, alwaysPlain: false, sectionId: self.section, height: .generic, editing: false, action: { arguments.openFiles() }) case let .recentHeader(_, text): @@ -131,7 +135,7 @@ private enum AttachmentFileEntry: ItemListNodeEntry { let dateTimeFormat = arguments.context.sharedContext.currentPresentationData.with({$0}).dateTimeFormat let chatPresentationData = ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .color(0)), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, disableAnimations: false, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0, auxiliaryRadius: 0, mergeBubbleCorners: false)) - return ListMessageItem(presentationData: chatPresentationData, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: .none, displayHeader: false, displayFileInfo: false, displayBackground: true, style: .blocks) + return ListMessageItem(presentationData: chatPresentationData, systemStyle: .glass, context: arguments.context, chatLocation: .peer(id: PeerId(0)), interaction: interaction, message: message, selection: .none, displayHeader: false, displayFileInfo: false, displayBackground: true, style: .blocks) } } } @@ -166,7 +170,7 @@ private func attachmentFileControllerEntries(presentationData: PresentationData, private final class AttachmentFileContext: AttachmentMediaPickerContext { } -class AttachmentFileControllerImpl: ItemListController, AttachmentFileController, AttachmentContainable { +public class AttachmentFileControllerImpl: ItemListController, AttachmentFileController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } public var parentController: () -> ViewController? = { @@ -182,12 +186,12 @@ class AttachmentFileControllerImpl: ItemListController, AttachmentFileController var delayDisappear = false var resetForReuseImpl: () -> Void = {} - func resetForReuse() { + public func resetForReuse() { self.resetForReuseImpl() self.scrollToTop?() } - func prepareForReuse() { + public func prepareForReuse() { self.delayDisappear = true self.visibleBottomContentOffsetChanged?(self.visibleBottomContentOffset) self.delayDisappear = false @@ -196,13 +200,49 @@ class AttachmentFileControllerImpl: ItemListController, AttachmentFileController public var mediaPickerContext: AttachmentMediaPickerContext? { return AttachmentFileContext() } + + private var topEdgeEffectView: EdgeEffectView? + private var bottomEdgeEffectView: EdgeEffectView? + + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + let topEdgeEffectView: EdgeEffectView + if let current = self.topEdgeEffectView { + topEdgeEffectView = current + } else { + topEdgeEffectView = EdgeEffectView() + if let navigationBar = self.navigationBar { + self.view.insertSubview(topEdgeEffectView, belowSubview: navigationBar.view) + } + self.topEdgeEffectView = topEdgeEffectView + } + + let edgeEffectHeight: CGFloat = 88.0 + let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) + transition.updateFrame(view: topEdgeEffectView, frame: topEdgeEffectFrame) + topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + + let bottomEdgeEffectView: EdgeEffectView + if let current = self.bottomEdgeEffectView { + bottomEdgeEffectView = current + } else { + bottomEdgeEffectView = EdgeEffectView() + self.view.addSubview(bottomEdgeEffectView) + self.bottomEdgeEffectView = bottomEdgeEffectView + } + + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) + transition.updateFrame(view: bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + bottomEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) + } } private struct AttachmentFileControllerState: Equatable { var searching: Bool } -func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController { +public func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController { let actionsDisposable = DisposableSet() let statePromise = ValuePromise(AttachmentFileControllerState(searching: false), ignoreRepeated: true) @@ -211,6 +251,7 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati statePromise.set(stateValue.modify { f($0) }) } + var updateTabBarVisibilityImpl: ((Bool) -> Void)? var expandImpl: (() -> Void)? var dismissImpl: (() -> Void)? var dismissInputImpl: (() -> Void)? @@ -251,40 +292,110 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati ) let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData - + + let existingCloseButton = Atomic(value: nil) + let existingSearchButton = Atomic(value: nil) + let previousRecentDocuments = Atomic<[Message]?>(value: nil) let signal = combineLatest(queue: Queue.mainQueue(), presentationData, recentDocuments, statePromise.get() ) - |> map { presentationData, recentDocuments, state -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { + presentationData, + recentDocuments, + state -> (ItemListControllerState, (ItemListNodeState, Any)) in var presentationData = presentationData let updatedTheme = presentationData.theme.withModalBlocksBackground() presentationData = presentationData.withUpdated(theme: updatedTheme) - let previousRecentDocuments = previousRecentDocuments.swap(recentDocuments) - let crossfade = previousRecentDocuments == nil && recentDocuments != nil - var animateChanges = false - if let previousRecentDocuments = previousRecentDocuments, let recentDocuments = recentDocuments, !previousRecentDocuments.isEmpty && !recentDocuments.isEmpty, !crossfade { - animateChanges = true + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let closeButton = GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: presentationData.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { _ in + dismissImpl?() + } + ) + let closeButtonComponent = AnyComponentWithIdentity(id: "close", component: AnyComponent(closeButton)) + let closeButtonNode = existingCloseButton.modify { current in + let buttonNode: BarComponentHostNode + if let current { + buttonNode = current + buttonNode.component = closeButtonComponent + } else { + buttonNode = BarComponentHostNode(component: closeButtonComponent, size: barButtonSize) + } + return buttonNode } - - var rightNavigationButton: ItemListNavigationButton? - if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10) { - rightNavigationButton = ItemListNavigationButton(content: .icon(.search), style: .regular, enabled: true, action: { + + let searchButton = GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: presentationData.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "search", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Search", + tintColor: presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { _ in updateState { state in var updatedState = state updatedState.searching = true return updatedState } - }) + updateTabBarVisibilityImpl?(false) + } + ) + let searchButtonComponent = state.searching ? nil : AnyComponentWithIdentity(id: "search", component: AnyComponent(searchButton)) + let searchButtonNode = existingSearchButton.modify { current in + let buttonNode: BarComponentHostNode + if let current { + buttonNode = current + buttonNode.component = searchButtonComponent + } else { + buttonNode = BarComponentHostNode(component: searchButtonComponent, size: barButtonSize) + } + return buttonNode + } + + let previousRecentDocuments = previousRecentDocuments.swap(recentDocuments) + let crossfade = previousRecentDocuments == nil && recentDocuments != nil + var animateChanges = false + if let previousRecentDocuments = previousRecentDocuments, + let recentDocuments = recentDocuments, + !previousRecentDocuments.isEmpty && !recentDocuments.isEmpty, + !crossfade { + animateChanges = true } - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Attachment_File), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: { - dismissImpl?() - }), rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) + let leftNavigationButton = closeButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) } + + var rightNavigationButton: ItemListNavigationButton? + if bannedSendMedia == nil && (recentDocuments == nil || (recentDocuments?.count ?? 0) > 10) { + rightNavigationButton = searchButtonNode.flatMap { ItemListNavigationButton(content: .node($0), style: .regular, enabled: true, action: {}) } + } + + let controllerState = ItemListControllerState( + presentationData: ItemListPresentationData(presentationData), + title: .text(presentationData.strings.Attachment_File), + leftNavigationButton: leftNavigationButton, + rightNavigationButton: rightNavigationButton, + backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), + animateChanges: true + ) var emptyItem: AttachmentFileEmptyStateItem? if let (untilDate, personal) = bannedSendMedia { @@ -297,7 +408,8 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati banDescription = presentationData.strings.Conversation_DefaultRestrictedMedia } emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .bannedSendMedia(text: banDescription, canBoost: false)) - } else if let recentDocuments = recentDocuments, recentDocuments.isEmpty { + } else if let recentDocuments = recentDocuments, + recentDocuments.isEmpty { emptyItem = AttachmentFileEmptyStateItem(context: context, theme: presentationData.theme, strings: presentationData.strings, content: .intro) } @@ -311,6 +423,7 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati updatedState.searching = false return updatedState } + updateTabBarVisibilityImpl?(true) }, send: { message in arguments.send(message) }, dismissInput: { @@ -325,7 +438,7 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati actionsDisposable.dispose() } - let controller = AttachmentFileControllerImpl(context: context, state: signal) + let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true) controller.delayDisappear = true controller.visibleBottomContentOffsetChanged = { [weak controller] offset in switch offset { @@ -359,5 +472,8 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati expandImpl = { [weak controller] in controller?.requestAttachmentMenuExpansion() } + updateTabBarVisibilityImpl = { [weak controller] isVisible in + controller?.updateTabBarVisibility(isVisible, .animated(duration: 0.4, curve: .spring)) + } return controller } diff --git a/submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift similarity index 100% rename from submodules/TelegramUI/Sources/AttachmentFileEmptyItem.swift rename to submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift diff --git a/submodules/TelegramUI/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift similarity index 82% rename from submodules/TelegramUI/Sources/AttachmentFileSearchItem.swift rename to submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index a27c6612ca..88c1a22a89 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -17,6 +17,8 @@ import ItemListUI import SearchUI import ContextUI import ListMessageItem +import ComponentFlow +import SearchInputPanelComponent private let searchBarFont = Font.regular(17.0) @@ -41,12 +43,12 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont self.focus = focus self.cancel = cancel - + self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasSeparator: false), strings: strings, fieldStyle: .modern, displayBackground: false) super.init() - - self.addSubnode(self.searchBar) + + //self.addSubnode(self.searchBar) self.searchBar.cancel = { [weak self] in self?.searchBar.deactivate(clear: false) @@ -95,11 +97,11 @@ private final class AttachmentFileSearchNavigationContentNode: NavigationBarCont } func activate() { - self.searchBar.activate() + //self.searchBar.activate() } func deactivate() { - self.searchBar.deactivate(clear: false) + //self.searchBar.deactivate(clear: false) } } @@ -149,40 +151,60 @@ final class AttachmentFileSearchItem: ItemListControllerSearch { } } - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { - let presentationData = self.presentationData - if let current = current as? AttachmentFileSearchNavigationContentNode { - current.updateTheme(presentationData.theme) - return current - } else { - return AttachmentFileSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, focus: self.focus, cancel: self.cancel, updateActivity: { [weak self] value in - self?.updateActivity = value - }) - } + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { + return nil +// let presentationData = self.presentationData +// if let current = current as? AttachmentFileSearchNavigationContentNode { +// current.updateTheme(presentationData.theme) +// return current +// } else { +// return AttachmentFileSearchNavigationContentNode(theme: presentationData.theme, strings: presentationData.strings, focus: self.focus, cancel: self.cancel, updateActivity: { [weak self] value in +// self?.updateActivity = value +// }) +// } } func node(current: ItemListControllerSearchNode?, titleContentNode: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> ItemListControllerSearchNode { - return AttachmentFileSearchItemNode(context: self.context, send: self.send, cancel: self.cancel, updateActivity: { [weak self] value in + return AttachmentFileSearchItemNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, focus: self.focus, send: self.send, cancel: self.cancel, updateActivity: { [weak self] value in self?.activity.set(value) }, dismissInput: self.dismissInput) } } private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { + private let context: AccountContext + private let theme: PresentationTheme + private let strings: PresentationStrings + private let focus: () -> Void + private let cancel: () -> Void + private let containerNode: AttachmentFileSearchContainerNode - init(context: AccountContext, send: @escaping (Message) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, dismissInput: @escaping () -> Void) { + private let searchInput = ComponentView() + + private var validLayout: ContainerViewLayout? + + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, focus: @escaping () -> Void, send: @escaping (Message) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, dismissInput: @escaping () -> Void) { + self.context = context + self.theme = theme + self.strings = strings + self.focus = focus + self.cancel = cancel + self.containerNode = AttachmentFileSearchContainerNode(context: context, forceTheme: nil, send: { message in send(message) }, updateActivity: updateActivity) - self.containerNode.cancel = { - cancel() - } + super.init() self.addSubnode(self.containerNode) + self.containerNode.cancel = { [weak self] in + dismissInput() + cancel() + self?.deactivateInput() + } self.containerNode.dismissInput = { dismissInput() } @@ -196,12 +218,66 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { self.containerNode.scrollToTop() } + private func deactivateInput() { + if let layout = self.validLayout, let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { + let transition = ComponentTransition.spring(duration: 0.4) + transition.setFrame(view: searchInputView, frame: CGRect(origin: CGPoint(x: searchInputView.frame.minX, y: layout.size.height), size: searchInputView.frame.size)) + } + } + override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight))) self.containerNode.containerLayoutUpdated(layout.withUpdatedSize(CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight)), navigationBarHeight: 0.0, transition: transition) + + let searchInputSize = self.searchInput.update( + transition: .immediate, + component: AnyComponent( + SearchInputPanelComponent( + theme: self.theme, + strings: self.strings, + placeholder: self.strings.Attachment_FilesSearchPlaceholder, + resetText: nil, + updated: { [weak self] query in + guard let self else { + return + } + self.queryUpdated(query) + }, + cancel: { [weak self] in + guard let self else { + return + } + self.cancel() + self.deactivateInput() + } + ) + ), + environment: {}, + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height) + ) + + let bottomInset: CGFloat = layout.insets(options: .input).bottom + let searchInputFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.size.height - bottomInset - searchInputSize.height), size: searchInputSize) + if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { + if searchInputView.superview == nil { + self.view.addSubview(searchInputView) + searchInputView.frame = CGRect(origin: CGPoint(x: searchInputFrame.minX, y: layout.size.height), size: searchInputFrame.size) + + self.focus() + searchInputView.activateInput() + } + transition.updateFrame(view: searchInputView, frame: searchInputFrame) + } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let searchInputView = self.searchInput.view as? SearchInputPanelComponent.View { + if let result = searchInputView.hitTest(self.view.convert(point, to: searchInputView), with: event) { + return result + } + } if let result = self.containerNode.hitTest(self.view.convert(point, to: self.containerNode.view), with: event) { return result } @@ -315,7 +391,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon private var presentationDataDisposable: Disposable? private let presentationDataPromise: Promise - + private var _hasDim: Bool = false override public var hasDim: Bool { return _hasDim @@ -335,7 +411,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon self.presentationDataPromise = Promise(self.presentationData) self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear // UIColor.black.withAlphaComponent(0.5) self.listNode = ListView() self.listNode.accessibilityPageScrolledString = { row, count in @@ -357,7 +433,8 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon super.init() self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor - self.listNode.isHidden = true + self.listNode.alpha = 0.0 + //self.listNode.isHidden = true self._hasDim = true @@ -503,7 +580,8 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon return } - strongSelf.listNode.isHidden = !isSearching + let containerTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) + containerTransition.updateAlpha(node: strongSelf.listNode, alpha: isSearching ? 1.0 : 0.0) strongSelf.dimNode.isHidden = transition.isSearching strongSelf.emptyResultsTextNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.ChatList_Search_NoResultsQueryDescription(transition.query).string, font: Font.regular(15.0), textColor: strongSelf.presentationData.theme.list.freeTextColor) @@ -555,7 +633,9 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon } override public func scrollToTop() { - self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + if self.listNode.alpha > 0.0 { + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } } @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { diff --git a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift index f8588251df..b6124f5cba 100644 --- a/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift +++ b/submodules/TelegramUI/Components/ButtonComponent/Sources/ButtonComponent.swift @@ -536,7 +536,7 @@ public final class ButtonComponent: Component { transition: contentItemTransition, component: component.content.component, environment: {}, - containerSize: availableSize + containerSize: CGSize(width: availableSize.width - cornerRadius * 2.0, height: availableSize.height) ) if let contentView = contentItem.view.view { var animateIn = false diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeFrameView.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeFrameView.swift index 513c3a7feb..f54ad09112 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeFrameView.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraCodeFrameView.swift @@ -136,7 +136,7 @@ final class CameraCodeFrameView: UIView { layer.path = path.cgPath } layer.opacity = focused ? 1.0 : 0.0 - layer.strokeColor = focused ? UIColor(rgb: 0xf8d74a).cgColor : UIColor.white.cgColor + layer.strokeColor = focused ? UIColor(rgb: 0xffd300).cgColor : UIColor.white.cgColor layer.lineWidth = focused ? 5.0 : 2.0 layer.shadowOffset = .zero layer.shadowRadius = 1.0 diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index c647baa75a..edeaa6e288 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -1832,7 +1832,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { self.previewContainerView = UIView() self.previewContainerView.clipsToBounds = true - self.previewContainerView.layer.cornerRadius = 12.0 + self.previewContainerView.layer.cornerRadius = 30.0 if #available(iOS 13.0, *) { self.previewContainerView.layer.cornerCurve = .continuous } @@ -2457,7 +2457,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { self.mainPreviewContainerView.layer.animate( from: self.additionalPreviewContainerView.layer.cornerRadius as NSNumber, - to: 12.0 as NSNumber, + to: self.previewContainerView.layer.cornerRadius as NSNumber, keyPath: "cornerRadius", timingFunction: timingFunction, duration: duration diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/ZoomComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/ZoomComponent.swift index 6c5b759e3b..ea3261746f 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/ZoomComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/ZoomComponent.swift @@ -45,7 +45,7 @@ final class ZoomComponent: Component { } func update(value: String, selected: Bool) { - self.setAttributedTitle(NSAttributedString(string: value, font: Font.with(size: 13.0, design: .round, weight: .semibold), textColor: selected ? UIColor(rgb: 0xf8d74a) : .white, paragraphAlignment: .center), for: .normal) + self.setAttributedTitle(NSAttributedString(string: value, font: Font.with(size: 13.0, design: .round, weight: .semibold), textColor: selected ? UIColor(rgb: 0xffd300) : .white, paragraphAlignment: .center), for: .normal) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift index a751806322..5464d4daaf 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift @@ -95,6 +95,34 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess dateText = " " } + if let repeatPeriod = message.scheduleRepeatPeriod { + //TODO:localize + let repeatString: String + switch repeatPeriod { + case 60: + repeatString = "1 min" + case 300: + repeatString = "5 min" + case 86400: + repeatString = "daily" + case 7 * 86400: + repeatString = "weekly" + case 14 * 86400: + repeatString = "biweekly" + case 30 * 86400: + repeatString = "monthly" + case 91 * 86400: + repeatString = "every 3 months" + case 182 * 86400: + repeatString = "every 6 months" + case 365 * 86400: + repeatString = "yearly" + default: + repeatString = "" + } + dateText = "\(repeatString) at \(dateText)" + } + if message.id.namespace == Namespaces.Message.ScheduledCloud, let _ = message.pendingProcessingAttribute { return "appx. \(dateText)" } diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 5757aaec60..ebc2697c6b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -4766,6 +4766,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg return nil } + public func getAttachmentButton() -> UIView { + return self.attachmentButton + } + public func frameForAttachmentButton() -> CGRect? { if !self.attachmentButtonBackground.alpha.isZero { return self.attachmentButtonBackground.frame.insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: 0.0, dy: 0.0) diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD b/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD index b2fa7bf50e..ccaa2d7040 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/BUILD @@ -24,9 +24,18 @@ swift_library( "//submodules/ComponentFlow", "//submodules/TelegramUI/Components/ToastComponent", "//submodules/Markdown", + "//submodules/UndoUI", "//submodules/TelegramUI/Components/LottieComponent", "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/SheetComponent", + "//submodules/DatePickerNode", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift new file mode 100644 index 0000000000..0dd817a9d6 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeScreen.swift @@ -0,0 +1,1241 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramCore +import ViewControllerComponent +import TelegramPresentationData +import TelegramStringFormatting +import AccountContext +import SheetComponent +import ButtonComponent +import PlainButtonComponent +import BundleIconComponent +import GlassBackgroundComponent +import GlassBarButtonComponent +import DatePickerNode +import UndoUI + +private let calendar = Calendar(identifier: .gregorian) + +private final class ChatScheduleTimeSheetContentComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let mode: ChatScheduleTimeScreen.Mode + let dismiss: () -> Void + + init( + context: AccountContext, + mode: ChatScheduleTimeScreen.Mode, + dismiss: @escaping () -> Void + ) { + self.context = context + self.mode = mode + self.dismiss = dismiss + } + + static func ==(lhs: ChatScheduleTimeSheetContentComponent, rhs: ChatScheduleTimeSheetContentComponent) -> Bool { + return true + } + + final class View: UIView { + private let cancel = ComponentView() + private let title = ComponentView() + private let button = ComponentView() + + private var datePicker: DatePickerNode? + + private let topSeparator = SimpleLayer() + + private let timeTitle = ComponentView() + private let timeValue = ComponentView() + + private let bottomSeparator = SimpleLayer() + + private let repeatTitle = ComponentView() + private let repeatValue = ComponentView() + + private let timePicker = ComponentView() + private let repeatPicker = ComponentView() + + private var component: ChatScheduleTimeSheetContentComponent? + private(set) weak var state: EmptyComponentState? + private var environment: EnvironmentType? + + private var date: Date? + private var minDate: Date? + private var maxDate: Date? + + private var isPickingTime = false + private var isPickingRepeatPeriod = false + + private var repeatPeriod: Int32? + + private let dateFormatter: DateFormatter + + override init(frame: CGRect) { + self.dateFormatter = DateFormatter() + self.dateFormatter.timeStyle = .none + self.dateFormatter.dateStyle = .short + self.dateFormatter.timeZone = TimeZone.current + + super.init(frame: frame) + + self.layer.addSublayer(self.topSeparator) + self.layer.addSublayer(self.bottomSeparator) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateMinimumDate(currentTime: Int32? = nil) { + let timeZone = TimeZone(secondsFromGMT: 0)! + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = timeZone + let currentDate = Date() + var components = calendar.dateComponents(Set([.era, .year, .month, .day, .hour, .minute, .second]), from: currentDate) + components.second = 0 + let minute = (components.minute ?? 0) % 5 + + let next1MinDate = calendar.date(byAdding: .minute, value: 1, to: calendar.date(from: components)!) + let next5MinDate = calendar.date(byAdding: .minute, value: 5 - minute, to: calendar.date(from: components)!) + + if let date = calendar.date(byAdding: .day, value: 365, to: currentDate) { + self.maxDate = date + } + + if let next1MinDate = next1MinDate, let next5MinDate = next5MinDate { + let minimalTime: Double = 0 //self.minimalTime.flatMap(Double.init) ?? 0.0 + self.minDate = max(next1MinDate, Date(timeIntervalSince1970: minimalTime)) + if let currentTime = currentTime, Double(currentTime) > max(currentDate.timeIntervalSince1970, minimalTime) { + self.date = Date(timeIntervalSince1970: Double(currentTime)) + } else { + self.date = next5MinDate + } + } + } + + func update(component: ChatScheduleTimeSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[EnvironmentType.self].value + self.environment = environment + + if self.component == nil { + self.updateMinimumDate(currentTime: nil) + } + + self.component = component + self.state = state + + let sideInset: CGFloat = 39.0 + + var contentHeight: CGFloat = 0.0 + contentHeight += 30.0 + + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let cancelSize = self.cancel.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self, let component = self.component else { + return + } + component.dismiss() + } + ) + ), + environment: {}, + containerSize: barButtonSize + ) + let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize) + if let cancelView = self.cancel.view { + if cancelView.superview == nil { + self.addSubview(cancelView) + } + transition.setFrame(view: cancelView, frame: cancelFrame) + } + + let title: String + switch component.mode { + case .scheduledMessages: + title = environment.strings.Conversation_ScheduleMessage_Title + case .reminders: + title = environment.strings.Conversation_SetReminder_Title + } + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + Text(text: title, font: Font.semibold(17.0), color: environment.theme.actionSheet.primaryTextColor) + ), + environment: {}, + containerSize: availableSize + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: 27.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + contentHeight += 62.0 + + let datePicker: DatePickerNode + if let current = self.datePicker { + datePicker = current + } else { + datePicker = DatePickerNode( + theme: DatePickerTheme(theme: environment.theme), + strings: environment.strings, + dateTimeFormat: environment.dateTimeFormat, + hasValueRow: false + ) + datePicker.date = self.date + datePicker.valueUpdated = { [weak self] date in + if let self { + self.date = date + self.state?.updated() + } + } + self.addSubview(datePicker.view) + self.datePicker = datePicker + } + datePicker.displayDateSelection = true + + if let minDate = self.minDate { + datePicker.minimumDate = minDate + } else { + datePicker.minimumDate = Date() + } + if let maxDate = self.maxDate { + datePicker.maximumDate = maxDate + } + + let constrainedWidth = min(390.0, availableSize.width) + let cellSize = floor((constrainedWidth - 12.0 * 2.0) / 7.0) + let pickerHeight = 59.0 + cellSize * 5.0 + + let datePickerSize = CGSize(width: availableSize.width - 22.0, height: pickerHeight) + datePicker.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - datePickerSize.width) / 2.0), y: contentHeight), size: datePickerSize) + datePicker.updateLayout(size: datePickerSize, transition: .immediate) + contentHeight += pickerHeight + + self.topSeparator.frame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)) + self.topSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor + + let timeTitleSize = self.timeTitle.update( + transition: transition, + component: AnyComponent( + Text(text: "Time", font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor) + ), + environment: {}, + containerSize: availableSize + ) + let timeTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 16.0), size: timeTitleSize) + if let timeTitleView = self.timeTitle.view { + if timeTitleView.superview == nil { + self.addSubview(timeTitleView) + } + transition.setFrame(view: timeTitleView, frame: timeTitleFrame) + } + + + let date = self.date ?? Date() + + var t: time_t = Int(date.timeIntervalSince1970) + var timeinfo = tm() + localtime_r(&t, &timeinfo); + + let timeString = stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: environment.dateTimeFormat) + let timeValueSize = self.timeValue.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + ButtonContentComponent( + theme: environment.theme, + text: timeString, + isActive: self.isPickingTime, + isLocked: false + ) + ), + action: { [weak self] in + guard let self else { + return + } + if self.isPickingRepeatPeriod { + self.isPickingRepeatPeriod = false + } else { + self.isPickingTime = !self.isPickingTime + } + self.state?.updated() + }, + animateScale: false + ) + ), + environment: { + }, + containerSize: availableSize + ) + let timeValueFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - timeValueSize.width, y: contentHeight + 10.0), size: timeValueSize) + if let timeValueView = self.timeValue.view { + if timeValueView.superview == nil { + self.addSubview(timeValueView) + } + transition.setFrame(view: timeValueView, frame: timeValueFrame) + } + + if self.isPickingTime { + let timePickerSize = self.timePicker.update( + transition: transition, + component: AnyComponent( + MenuComponent(component: AnyComponent(TimeMenuComponent( + value: self.date ?? Date(), + valueUpdated: { [weak self] value in + guard let self else { + return + } + self.date = value + self.state?.updated() + } + ))) + ), + environment: {}, + containerSize: availableSize + ) + let timePickerFrame = CGRect(origin: CGPoint(x: timeValueFrame.maxX - timePickerSize.width + 80.0, y: timeValueFrame.minY - 20.0 - timePickerSize.height + 80.0), size: timePickerSize) + if let timePickerView = self.timePicker.view as? MenuComponent.View { + if timePickerView.superview == nil { + self.addSubview(timePickerView) + + timePickerView.animateIn() + } + transition.setFrame(view: timePickerView, frame: timePickerFrame) + } + } else if let timePicker = self.timePicker.view as? MenuComponent.View, timePicker.superview != nil { + timePicker.animateOut(completion: { + timePicker.removeFromSuperview() + }) + } + + contentHeight += 56.0 + + self.bottomSeparator.frame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: UIScreenPixel)) + self.bottomSeparator.backgroundColor = environment.theme.list.itemBlocksSeparatorColor.cgColor + + let repeatTitleSize = self.repeatTitle.update( + transition: transition, + component: AnyComponent( + Text(text: "Repeat", font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor) + ), + environment: {}, + containerSize: availableSize + ) + let repeatTitleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 16.0), size: repeatTitleSize) + if let timeTitleView = self.repeatTitle.view { + if timeTitleView.superview == nil { + self.addSubview(timeTitleView) + } + transition.setFrame(view: timeTitleView, frame: repeatTitleFrame) + } + + let repeatString: String + if let repeatPeriod = self.repeatPeriod { + switch repeatPeriod { + case 60: + repeatString = "Every Minute" + case 300: + repeatString = "Every 5 Minutes" + case 86400: + repeatString = "Daily" + case 7 * 86400: + repeatString = "Weekly" + case 14 * 86400: + repeatString = "Biweekly" + case 30 * 86400: + repeatString = "Monthly" + case 91 * 86400: + repeatString = "Every 3 Months" + case 182 * 86400: + repeatString = "Every 6 Months" + case 365 * 86400: + repeatString = "Yearly" + default: + repeatString = "\(repeatPeriod)" + } + } else { + repeatString = "Never" + } + + let repeatValueSize = self.repeatValue.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + ButtonContentComponent( + theme: environment.theme, + text: repeatString, + isActive: self.isPickingRepeatPeriod, + isLocked: !component.context.isPremium + ) + ), + action: { [weak self] in + guard let self else { + return + } + if self.isPickingTime { + self.isPickingTime = false + } else { + self.isPickingRepeatPeriod = !self.isPickingRepeatPeriod + } + self.state?.updated() + } + ) + ), + environment: { + }, + containerSize: availableSize + ) + let repeatValueFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - repeatValueSize.width, y: contentHeight + 10.0), size: repeatValueSize) + if let repeatValueView = self.repeatValue.view { + if repeatValueView.superview == nil { + self.addSubview(repeatValueView) + } + transition.setFrame(view: repeatValueView, frame: repeatValueFrame) + } + + if self.isPickingRepeatPeriod { + let repeatPickerSize = self.repeatPicker.update( + transition: transition, + component: AnyComponent( + MenuComponent(component: AnyComponent(RepeatMenuComponent( + value: self.repeatPeriod, + valueUpdated: { [weak self] value in + guard let self, let component = self.component, let environment = self.environment else { + return + } + self.isPickingRepeatPeriod = false + if component.context.isPremium { + self.repeatPeriod = value + } else { + let toastController = UndoOverlayController( + presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, + content: .premiumPaywall( + title: "Premium Required", + text: "Subscribe to **Telegram Premium** to schedule repeating messages.", + customUndoText: nil, + timeout: nil, + linkAction: nil + ), + elevatedLayout: true, + action: { [weak environment] action in + if case .info = action { + var replaceImpl: ((ViewController) -> Void)? + let controller = component.context.sharedContext.makePremiumDemoController(context: component.context, subject: .colors, forceDark: false, action: { + let controller = component.context.sharedContext.makePremiumIntroController(context: component.context, source: .nameColor, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + environment?.controller()?.push(controller) + } + return true + } + ) + environment.controller()?.present(toastController, in: .current) + } + self.state?.updated() + } + ))) + ), + environment: {}, + containerSize: availableSize + ) + let repeatPickerFrame = CGRect(origin: CGPoint(x: repeatValueFrame.maxX - repeatPickerSize.width + 80.0, y: repeatValueFrame.minY - 20.0 - repeatPickerSize.height + 80.0), size: repeatPickerSize) + if let repeatPickerView = self.repeatPicker.view as? MenuComponent.View { + if repeatPickerView.superview == nil { + self.addSubview(repeatPickerView) + + repeatPickerView.animateIn() + } + transition.setFrame(view: repeatPickerView, frame: repeatPickerFrame) + } + } else if let repeatPicker = self.repeatPicker.view as? MenuComponent.View, repeatPicker.superview != nil { + repeatPicker.animateOut(completion: { + repeatPicker.removeFromSuperview() + }) + } + + contentHeight += 70.0 + + let time = stringForMessageTimestamp(timestamp: Int32(date.timeIntervalSince1970), dateTimeFormat: environment.dateTimeFormat) + let buttonTitle: String + switch component.mode { + case .scheduledMessages: + if calendar.isDateInToday(date) { + buttonTitle = environment.strings.Conversation_ScheduleMessage_SendToday(time).string + } else if calendar.isDateInTomorrow(date) { + buttonTitle = environment.strings.Conversation_ScheduleMessage_SendTomorrow(time).string + } else { + buttonTitle = environment.strings.Conversation_ScheduleMessage_SendOn(self.dateFormatter.string(from: date), time).string + } + case .reminders: + if calendar.isDateInToday(date) { + buttonTitle = environment.strings.Conversation_SetReminder_RemindToday(time).string + } else if calendar.isDateInTomorrow(date) { + buttonTitle = environment.strings.Conversation_SetReminder_RemindTomorrow(time).string + } else { + buttonTitle = environment.strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string + } + } + + let buttonSideInset: CGFloat = 30.0 + let buttonSize = self.button.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: environment.theme.list.itemCheckColors.fillColor, + foreground: environment.theme.list.itemCheckColors.foregroundColor, + pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8), + ), + content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( + Text(text: buttonTitle, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) + )), + isEnabled: true, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component, let controller = self.environment?.controller() as? ChatScheduleTimeScreen else { + return + } + controller.completion( + ChatScheduleTimeScreen.Result( + time: Int32(self.date?.timeIntervalSince1970 ?? 0), + repeatPeriod: self.repeatPeriod + ) + ) + component.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) + ) + let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: buttonSize) + if let buttonView = self.button.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + transition.setFrame(view: buttonView, frame: buttonFrame) + } + contentHeight += buttonSize.height + + let bottomPanelPadding: CGFloat = 15.0 + let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding + contentHeight += bottomInset + + +// if let controller = environment.controller(), !controller.automaticallyControlPresentationContextLayout { +// let sideInset: CGFloat = 0.0 +// let bottomInset: CGFloat = max(environment.safeInsets.bottom, contentHeight) +//// if case .regular = environment.metrics.widthClass { +//// sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0 +//// bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight +//// } +// +// let layout = ContainerViewLayout( +// size: availableSize, +// metrics: environment.metrics, +// deviceMetrics: environment.deviceMetrics, +// intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0), +// safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)), +// additionalInsets: .zero, +// statusBarHeight: environment.statusBarHeight, +// inputHeight: nil, +// inputHeightIsInteractivellyChanging: false, +// inVoiceOver: false +// ) +// controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition) +// } + + + return CGSize(width: availableSize.width, height: contentHeight) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ChatScheduleTimeScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let mode: ChatScheduleTimeScreen.Mode + + init( + context: AccountContext, + mode: ChatScheduleTimeScreen.Mode + ) { + self.context = context + self.mode = mode + } + + static func ==(lhs: ChatScheduleTimeScreenComponent, rhs: ChatScheduleTimeScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.mode != rhs.mode { + return false + } + return true + } + + final class View: UIView { + private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>() + private let sheetAnimateOut = ActionSlot>() + + private var component: ChatScheduleTimeScreenComponent? + private var environment: EnvironmentType? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ChatScheduleTimeScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environment + + let sheetEnvironment = SheetComponentEnvironment( + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, + hasInputHeight: !environment.inputHeight.isZero, + regularMetricsSize: CGSize(width: 430.0, height: 900.0), + dismiss: { [weak self] _ in + guard let self, let environment = self.environment else { + return + } + self.sheetAnimateOut.invoke(Action { _ in + if let controller = environment.controller() { + controller.dismiss(completion: nil) + } + }) + } + ) + let _ = self.sheet.update( + transition: transition, + component: AnyComponent(SheetComponent( + content: AnyComponent(ChatScheduleTimeSheetContentComponent( + context: component.context, + mode: component.mode, + dismiss: { [weak self] in + guard let self else { + return + } + self.sheetAnimateOut.invoke(Action { _ in + if let controller = environment.controller() { + controller.dismiss(completion: nil) + } + }) + } + )), + style: .glass, + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), + animateOut: self.sheetAnimateOut + )), + environment: { + environment + sheetEnvironment + }, + containerSize: availableSize + ) + if let sheetView = self.sheet.view { + if sheetView.superview == nil { + self.addSubview(sheetView) + } + transition.setFrame(view: sheetView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class ChatScheduleTimeScreen: ViewControllerComponentContainer { + public enum Mode: Equatable { + case scheduledMessages(sendWhenOnlineAvailable: Bool) + case reminders + } + + public struct Result { + public let time: Int32 + public let repeatPeriod: Int32? + } + + fileprivate let completion: (Result) -> Void + + public init( + context: AccountContext, + mode: Mode, + completion: @escaping (Result) -> Void + ) { + self.completion = completion + + super.init(context: context, component: ChatScheduleTimeScreenComponent( + context: context, + mode: mode + ), navigationBarAppearance: .none) + + self.statusBar.statusBarStyle = .Ignore + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + + //self.automaticallyControlPresentationContextLayout = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + } +} + +private final class ButtonContentComponent: Component { + let theme: PresentationTheme + let text: String + let isActive: Bool + let isLocked: Bool + + init( + theme: PresentationTheme, + text: String, + isActive: Bool, + isLocked: Bool + ) { + self.theme = theme + self.text = text + self.isActive = isActive + self.isLocked = isLocked + } + + static func ==(lhs: ButtonContentComponent, rhs: ButtonContentComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.isActive != rhs.isActive { + return false + } + if lhs.isLocked != rhs.isLocked { + return false + } + return true + } + + final class View: UIView { + private var component: ButtonContentComponent? + private weak var componentState: EmptyComponentState? + + private let backgroundLayer = SimpleLayer() + private let title = ComponentView() + private let icon = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.layer.addSublayer(self.backgroundLayer) + self.backgroundLayer.masksToBounds = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ButtonContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + self.componentState = state + + let backgroundColor: UIColor = component.isActive ? component.theme.actionSheet.controlAccentColor.withMultipliedAlpha(0.1) : component.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.07) + let textColor: UIColor = component.isActive ? component.theme.actionSheet.controlAccentColor : component.theme.actionSheet.primaryTextColor + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + Text(text: component.text, font: Font.regular(17.0), color: textColor) + ), + environment: {}, + containerSize: availableSize + ) + + var totalWidth = titleSize.width + + var iconSize = CGSize() + if component.isLocked { + iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent( + BundleIconComponent( + name: "Media Grid/Lock", + tintColor: textColor + ) + ), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + totalWidth += iconSize.width + 2.0 + } + + let padding: CGFloat = 12.0 + let size = CGSize(width: totalWidth + padding * 2.0, height: 36.0) + + let titleFrame = CGRect(origin: CGPoint(x: padding, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + let iconFrame = CGRect(origin: CGPoint(x: size.width - padding - iconSize.width, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + + self.backgroundLayer.backgroundColor = backgroundColor.cgColor + transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size)) + self.backgroundLayer.cornerRadius = size.height / 2.0 + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + + + +private final class MenuComponent: Component { + public let component: AnyComponent + + public init( + component: AnyComponent + ) { + self.component = component + } + + public static func ==(lhs: MenuComponent, rhs: MenuComponent) -> Bool { + if lhs.component != rhs.component { + return false + } + return true + } + + public final class View: UIView { + private let backgroundView: GlassBackgroundView + private var componentView: ComponentView? + + private var component: MenuComponent? + + public override init(frame: CGRect) { + self.backgroundView = GlassBackgroundView() + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func animateIn() { + let duration: Double = 0.35 + + self.layer.removeAllAnimations() + self.alpha = 0.0 + self.transform = CGAffineTransform(scaleX: 0.001, y: 0.001) + self.layer.anchorPoint = CGPoint(x: 1.0, y: 1.0) + + UIView.animate( + withDuration: duration, + delay: 0.0, + usingSpringWithDamping: 0.75, + initialSpringVelocity: 0.6, + options: [.curveEaseOut], + animations: { + self.transform = .identity + self.alpha = 1.0 + }, + completion: nil + ) + } + + public func animateOut(duration: TimeInterval = 0.15, completion: (() -> Void)? = nil) { + self.layer.removeAllAnimations() + self.layer.anchorPoint = CGPoint(x: 1.0, y: 1.0) + + UIView.animate( + withDuration: duration, + delay: 0.0, + options: [.curveEaseInOut], + animations: { + self.transform = CGAffineTransform(scaleX: 0.001, y: 0.001) + }, + completion: { _ in + completion?() + } + ) + } + + func update(component: MenuComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + var componentView: ComponentView + var componentTransition = transition + if let current = self.componentView { + componentView = current + } else { + componentTransition = .immediate + componentView = ComponentView() + self.componentView = componentView + } + + let componentSize = componentView.update( + transition: componentTransition, + component: component.component, + environment: {}, + containerSize: availableSize + ) + let componentFrame = CGRect(origin: CGPoint(x: 80.0, y: 80.0), size: componentSize) + if let view = componentView.view { + if view.superview == nil { + self.addSubview(view) + } + componentTransition.setFrame(view: view, frame: componentFrame) + } + + let tintColor = GlassBackgroundView.TintColor(kind: .custom, color: UIColor(rgb: 0xf6f7f8)) + + let backgroundFrame = CGRect(origin: CGPoint(x: 80.0, y: 80.0), size: componentSize) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 30.0, isDark: false, tintColor: tintColor, transition: transition) + self.backgroundView.frame = backgroundFrame + + return CGSize(width: componentSize.width + 80.0 * 2.0, height: componentSize.height + 80.0 * 2.0) + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return self.backgroundView.frame.contains(point) + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class RepeatMenuComponent: Component { + let value: Int32? + let valueUpdated: (Int32?) -> Void + + init( + value: Int32?, + valueUpdated: @escaping (Int32?) -> Void + ) { + self.value = value + self.valueUpdated = valueUpdated + } + + public static func ==(lhs: RepeatMenuComponent, rhs: RepeatMenuComponent) -> Bool { + if lhs.value != rhs.value { + return false + } + return true + } + + public final class View: UIView { + private let backgroundView: GlassBackgroundView + private let never = ComponentView() + private let separator = SimpleLayer() + private var itemViews: [Int32: ComponentView] = [:] + private let checkIcon = UIImageView() + + private var component: RepeatMenuComponent? + + private let values: [Int32] = [ + 86400, + 7 * 86400, + 14 * 86400, + 30 * 86400, + 91 * 86400, + 182 * 86400, + 365 * 86400 + ] + + public override init(frame: CGRect) { + self.backgroundView = GlassBackgroundView() + self.checkIcon.image = UIImage(bundleImageName: "Media Gallery/Check")?.withRenderingMode(.alwaysTemplate) + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + self.layer.addSublayer(self.separator) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: RepeatMenuComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.component = component + + let sideInset: CGFloat = 18.0 + let itemInset: CGFloat = 60.0 + + self.checkIcon.tintColor = .black + + let neverSize = self.never.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + Text(text: "Never", font: Font.regular(17.0), color: .black) + ), + action: { [weak self] in + guard let self else { + return + } + self.component?.valueUpdated(nil) + } + ) + ), + environment: {}, + containerSize: availableSize + ) + let neverFrame = CGRect(origin: CGPoint(x: itemInset, y: 21.0), size: neverSize) + if let neverView = self.never.view { + if neverView.superview == nil { + self.addSubview(neverView) + } + transition.setFrame(view: neverView, frame: neverFrame) + + if component.value == nil { + neverView.addSubview(self.checkIcon) + } + } + + var maxWidth: CGFloat = 0.0 + var originY: CGFloat = 83.0 + for value in self.values { + let itemView: ComponentView + if let current = self.itemViews[value] { + itemView = current + } else { + itemView = ComponentView() + self.itemViews[value] = itemView + } + + let repeatString: String + switch value { + case 60: + repeatString = "Every Minute" + case 300: + repeatString = "Every 5 Minutes" + case 86400: + repeatString = "Daily" + case 7 * 86400: + repeatString = "Weekly" + case 14 * 86400: + repeatString = "Biweekly" + case 30 * 86400: + repeatString = "Monthly" + case 91 * 86400: + repeatString = "Every 3 Months" + case 182 * 86400: + repeatString = "Every 6 Months" + case 365 * 86400: + repeatString = "Yearly" + default: + repeatString = "\(value)" + } + + let itemSize = itemView.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + Text(text: repeatString, font: Font.regular(17.0), color: .black) + ), + action: { [weak self] in + guard let self else { + return + } + self.component?.valueUpdated(value) + } + ) + ), + environment: {}, + containerSize: availableSize + ) + maxWidth = max(maxWidth, itemSize.width) + let itemFrame = CGRect(origin: CGPoint(x: itemInset, y: originY), size: itemSize) + if let itemView = itemView.view { + if itemView.superview == nil { + self.addSubview(itemView) + } + transition.setFrame(view: itemView, frame: itemFrame) + + if component.value == value { + itemView.addSubview(self.checkIcon) + } + } + originY += 42.0 + } + + if let image = self.checkIcon.image { + self.checkIcon.frame = CGRect(origin: CGPoint(x: -35.0, y: floorToScreenPixels((12.0 - image.size.height) / 2.0) + 5.0), size: image.size) + } + + let size = CGSize(width: itemInset + maxWidth + 40.0, height: originY) + + self.separator.backgroundColor = UIColor(rgb: 0xdddddd).cgColor + self.separator.frame = CGRect(origin: CGPoint(x: sideInset, y: 62.0), size: CGSize(width: size.width - sideInset * 2.0, height: UIScreenPixel)) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class TimeMenuComponent: Component { + let value: Date + let valueUpdated: (Date) -> Void + + init( + value: Date, + valueUpdated: @escaping (Date) -> Void + ) { + self.value = value + self.valueUpdated = valueUpdated + } + + public static func == (lhs: TimeMenuComponent, rhs: TimeMenuComponent) -> Bool { + return lhs.value == rhs.value + } + + public final class View: UIView { + private let picker = UIDatePicker() + private var component: TimeMenuComponent? + + public override init(frame: CGRect) { + super.init(frame: frame) + + self.picker.datePickerMode = .time + if #available(iOS 13.4, *) { + self.picker.preferredDatePickerStyle = .wheels + } + self.picker.addTarget(self, action: #selector(valueChanged), for: .valueChanged) + + self.addSubview(self.picker) + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func valueChanged() { + guard let component = self.component else { + return + } + component.valueUpdated(self.picker.date) + } + + func update(component: TimeMenuComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previous = self.component + self.component = component + + if previous == nil || abs(component.value.timeIntervalSince(self.picker.date)) > 0.5 { + self.picker.setDate(component.value, animated: false) + } + + let pickerSize = self.picker.sizeThatFits(availableSize) + let width = min(availableSize.width, max(pickerSize.width, 230.0)) + let height = pickerSize.height > 0 ? pickerSize.height : 216.0 + + let frame = CGRect(origin: .zero, size: CGSize(width: width, height: height)) + transition.setFrame(view: self.picker, frame: frame) + + return frame.size + } + } + + public func makeView() -> View { + return View(frame: .zero) + } + + public func update( view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD new file mode 100644 index 0000000000..1f30dd2987 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/BUILD @@ -0,0 +1,34 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatScheduleTimeScreen", + module_name = "ChatScheduleTimeScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/TelegramStringFormatting", + "//submodules/SolidRoundedButtonNode", + "//submodules/PresentationDataUtils", + "//submodules/UIKitRuntimeUtils", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/ToastComponent", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/ComponentDisplayAdapters", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift new file mode 100644 index 0000000000..e64e7b12b8 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatScheduleTimeScreen/Sources/ChatScheduleTimeScreen.swift @@ -0,0 +1,122 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData + +public enum ChatScheduleTimeControllerMode { + case scheduledMessages(sendWhenOnlineAvailable: Bool) + case reminders + case suggestPost(needsTime: Bool, isAdmin: Bool, funds: (amount: CurrencyAmount, commissionPermille: Int)?) +} + +public enum ChatScheduleTimeControllerStyle { + case `default` + case media +} + +public final class ChatScheduleTimeController: ViewController { + private var controllerNode: ChatScheduleTimeControllerNode { + return self.displayNode as! ChatScheduleTimeControllerNode + } + + private var animatedIn = false + + private let context: AccountContext + private let mode: ChatScheduleTimeControllerMode + private let style: ChatScheduleTimeControllerStyle + private let currentTime: Int32? + private let minimalTime: Int32? + private let dismissByTapOutside: Bool + private let completion: (Int32) -> Void + + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + + public var dismissed: () -> Void = {} + + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: ChatScheduleTimeControllerMode, style: ChatScheduleTimeControllerStyle, currentTime: Int32? = nil, minimalTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { + self.context = context + self.mode = mode + self.style = style + self.currentTime = currentTime != scheduleWhenOnlineTimestamp ? currentTime : nil + self.minimalTime = minimalTime + self.dismissByTapOutside = dismissByTapOutside + self.completion = completion + + self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } + + super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore + + self.blocksBackgroundWhenInOverlay = true + + self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + strongSelf.presentationData = presentationData + strongSelf.controllerNode.updatePresentationData(presentationData) + } + }) + + self.statusBar.statusBarStyle = .Ignore + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.presentationDataDisposable?.dispose() + } + + override public func loadDisplayNode() { + self.displayNode = ChatScheduleTimeControllerNode(context: self.context, presentationData: self.presentationData, mode: self.mode, style: self.style, currentTime: self.currentTime, minimalTime: self.minimalTime, dismissByTapOutside: self.dismissByTapOutside) + self.controllerNode.completion = { [weak self] time in + guard let strongSelf = self else { + return + } + if time == 0 { + strongSelf.completion(time) + } else { + strongSelf.completion(time == scheduleWhenOnlineTimestamp ? time : time + 5) + } + strongSelf.dismiss() + } + self.controllerNode.dismiss = { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: nil) + } + self.controllerNode.cancel = { [weak self] in + self?.dismiss() + } + } + + override public func loadView() { + super.loadView() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.animatedIn { + self.animatedIn = true + self.controllerNode.animateIn() + } + } + + override public func dismiss(completion: (() -> Void)? = nil) { + self.dismissed() + self.controllerNode.animateOut(completion: completion) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD b/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD index bdd35b09ee..691a18915c 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/BUILD @@ -44,6 +44,8 @@ swift_library( "//submodules/TelegramUI/Components/ListComposePollOptionComponent", "//submodules/ComposePollUI", "//submodules/Markdown", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift index 17082f97b6..8a1da0c876 100644 --- a/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift +++ b/submodules/TelegramUI/Components/ComposeTodoScreen/Sources/ComposeTodoScreen.swift @@ -29,6 +29,8 @@ import TextFieldComponent import ListComposePollOptionComponent import Markdown import PresentationDataUtils +import EdgeEffect +import GlassBarButtonComponent final class ComposeTodoScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -67,7 +69,8 @@ final class ComposeTodoScreenComponent: Component { final class View: UIView, UIScrollViewDelegate { private let scrollView: UIScrollView - + private let edgeEffectView: EdgeEffectView + private let todoTextSection = ComponentView() private let todoItemsSectionHeader = ComponentView() @@ -76,7 +79,10 @@ final class ComposeTodoScreenComponent: Component { private var todoItemsSectionContainer: ListSectionContentView private let todoSettingsSection = ComponentView() - private let actionButton = ComponentView() + + private let title = ComponentView() + private let cancelButton = ComponentView() + private let doneButton = ComponentView() private var isUpdating: Bool = false private var ignoreScrolling: Bool = false @@ -126,6 +132,8 @@ final class ComposeTodoScreenComponent: Component { self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.alwaysBounceVertical = true + self.edgeEffectView = EdgeEffectView() + self.todoItemsSectionContainer = ListSectionContentView(frame: CGRect()) self.todoItemsSectionContainer.automaticallyLayoutExternalContentBackgroundView = false @@ -134,6 +142,8 @@ final class ComposeTodoScreenComponent: Component { self.scrollView.delegate = self self.addSubview(self.scrollView) + self.addSubview(self.edgeEffectView) + let reorderRecognizer = ReorderGestureRecognizer( shouldBegin: { [weak self] point in guard let self, let (id, item) = self.item(at: point) else { @@ -783,6 +793,7 @@ final class ComposeTodoScreenComponent: Component { todoTextSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListComposePollOptionComponent( externalState: self.todoTextInputState, context: component.context, + style: .glass, theme: theme, strings: environment.strings, isEnabled: canEdit, @@ -827,6 +838,7 @@ final class ComposeTodoScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: theme, + style: .glass, header: nil, footer: nil, items: todoTextSectionItems @@ -871,6 +883,7 @@ final class ComposeTodoScreenComponent: Component { todoItemsSectionItems.append(AnyComponentWithIdentity(id: todoItem.id, component: AnyComponent(ListComposePollOptionComponent( externalState: todoItem.textInputState, context: component.context, + style: .glass, theme: theme, strings: environment.strings, isEnabled: isEnabled, @@ -1062,6 +1075,7 @@ final class ComposeTodoScreenComponent: Component { let todoItemsSectionUpdateResult = self.todoItemsSectionContainer.update( configuration: ListSectionContentView.Configuration( theme: theme, + style: .glass, displaySeparators: true, extendsItemHighlightToSection: false, background: .all @@ -1230,6 +1244,7 @@ final class ComposeTodoScreenComponent: Component { if canEdit { todoSettingsSectionItems.append(AnyComponentWithIdentity(id: "completable", component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1253,6 +1268,7 @@ final class ComposeTodoScreenComponent: Component { if self.isCompletableByOthers { todoSettingsSectionItems.append(AnyComponentWithIdentity(id: "editable", component: AnyComponent(ListActionItemComponent( theme: theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1280,6 +1296,7 @@ final class ComposeTodoScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: theme, + style: .glass, header: nil, footer: nil, items: todoSettingsSectionItems @@ -1542,11 +1559,116 @@ final class ComposeTodoScreenComponent: Component { } } } + + let edgeEffectHeight: CGFloat = 66.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + + let title: String + if !component.initialData.canEdit && component.initialData.existingTodo != nil { + title = environment.strings.CreateTodo_AddTitle + } else { + title = component.initialData.existingTodo != nil ? environment.strings.CreateTodo_EditTitle : environment.strings.CreateTodo_Title + } + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: title, + font: Font.semibold(17.0), + textColor: environment.theme.rootController.navigationBar.primaryTextColor + ) + ) + ) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 40.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? ComposeTodoScreen else { + return + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } let isValid = self.validatedInput() != nil - if let controller = environment.controller() as? ComposeTodoScreen, let sendButtonItem = controller.sendButtonItem { - if sendButtonItem.isEnabled != isValid { - sendButtonItem.isEnabled = isValid + let doneButtonSize = self.doneButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: isValid ? environment.theme.list.itemCheckColors.fillColor : environment.theme.list.itemCheckColors.fillColor.desaturated().withMultipliedAlpha(0.5), + isDark: environment.theme.overallDarkAppearance, + state: .tintedGlass, + isEnabled: isValid, + component: AnyComponentWithIdentity(id: "done", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Done", + tintColor: environment.theme.list.itemCheckColors.foregroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? ComposeTodoScreen else { + return + } + if let input = self.validatedInput() { + controller.completion(input) + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) + if let doneButtonView = self.doneButton.view { + if doneButtonView.superview == nil { + self.addSubview(doneButtonView) + } + transition.setFrame(view: doneButtonView, frame: doneButtonFrame) + + if isValid { + doneButtonView.layer.filters = [] + } else { + if (doneButtonView.layer.filters ?? []).isEmpty, let monochrome = CALayer.monochrome() { + doneButtonView.layer.filters = [monochrome] + } } } @@ -1607,7 +1729,7 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont } private let context: AccountContext - private let completion: (TelegramMediaTodo) -> Void + fileprivate let completion: (TelegramMediaTodo) -> Void private var isDismissed: Bool = false fileprivate private(set) var sendButtonItem: UIBarButtonItem? @@ -1658,22 +1780,26 @@ public class ComposeTodoScreen: ViewControllerComponentContainer, AttachmentCont peer: peer, initialData: initialData, completion: completion - ), navigationBarAppearance: .default, theme: .default) + ), navigationBarAppearance: .transparent, theme: .default) + + self._hasGlassStyle = true let presentationData = context.sharedContext.currentPresentationData.with { $0 } - if !initialData.canEdit && initialData.existingTodo != nil { - self.title = presentationData.strings.CreateTodo_AddTitle + if self._hasGlassStyle { + self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: UIView()), animated: false) } else { - self.title = initialData.existingTodo != nil ? presentationData.strings.CreateTodo_EditTitle : presentationData.strings.CreateTodo_Title + self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) } - self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) - let sendButtonItem = UIBarButtonItem(title: initialData.existingTodo != nil ? presentationData.strings.CreateTodo_Save : presentationData.strings.CreateTodo_Send, style: .done, target: self, action: #selector(self.sendPressed)) self.sendButtonItem = sendButtonItem - self.navigationItem.setRightBarButton(sendButtonItem, animated: false) - sendButtonItem.isEnabled = false + if self._hasGlassStyle { + + } else { + self.navigationItem.setRightBarButton(sendButtonItem, animated: false) + sendButtonItem.isEnabled = false + } self.scrollToTop = { [weak self] in guard let self, let componentView = self.node.hostView.componentView as? ComposeTodoScreenComponent.View else { diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/BUILD b/submodules/TelegramUI/Components/Contacts/NewContactScreen/BUILD new file mode 100644 index 0000000000..42ca757a73 --- /dev/null +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/BUILD @@ -0,0 +1,51 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "NewContactScreen", + module_name = "NewContactScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/ItemListUI", + "//submodules/AccountContext", + "//submodules/AlertUI", + "//submodules/PresentationDataUtils", + "//submodules/TextFormat", + "//submodules/ObjCRuntimeUtils", + "//submodules/AttachmentUI", + "//submodules/TextInputMenu", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/AppBundle", + "//submodules/UndoUI", + "//submodules/Markdown", + "//submodules/CountrySelectionUI", + "//submodules/PhoneInputNode", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/ListTextFieldItemComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/TextFieldComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/TelegramUI/Components/ListComposePollOptionComponent", + "//submodules/TelegramUI/Components/ListItemComponentAdaptor", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/PhoneNumberFormat", + "//submodules/QrCodeUI", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift new file mode 100644 index 0000000000..41c4807997 --- /dev/null +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift @@ -0,0 +1,922 @@ +import Foundation +import UIKit +import Display +import AccountContext +import TelegramCore +import Postbox +import SwiftSignalKit +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import AppBundle +import ViewControllerComponent +import MultilineTextComponent +import BundleIconComponent +import ListSectionComponent +import ListTextFieldItemComponent +import ListActionItemComponent +import TextFormat +import TextFieldComponent +import ListComposePollOptionComponent +import ListItemComponentAdaptor +import PresentationDataUtils +import EdgeEffect +import GlassBarButtonComponent +import Markdown +import CountrySelectionUI +import PhoneNumberFormat +import QrCodeUI +import MessageUI + +final class NewContactScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + struct Result { + let peer: EnginePeer? + let firstName: String + let lastName: String + let phoneNumber: String + let syncContactToPhone: Bool + let note: NSAttributedString + } + + let context: AccountContext + let initialData: NewContactScreen.InitialData + let completion: (TelegramMediaTodo) -> Void + + init( + context: AccountContext, + initialData: NewContactScreen.InitialData, + completion: @escaping (TelegramMediaTodo) -> Void + ) { + self.context = context + self.initialData = initialData + self.completion = completion + } + + static func ==(lhs: NewContactScreenComponent, rhs: NewContactScreenComponent) -> Bool { + return true + } + + enum ResolvedPeer: Equatable { + case resolving + case peer(peer: EnginePeer, isContact: Bool) + case notFound + } + + final class View: UIView, UIScrollViewDelegate, MFMessageComposeViewControllerDelegate { + private let scrollView: UIScrollView + private let edgeEffectView: EdgeEffectView + + private let nameSection = ComponentView() + private let phoneSection = ComponentView() + private let syncContactSection = ComponentView() + private let noteSection = ComponentView() + private let qrSection = ComponentView() + + private let title = ComponentView() + private let cancelButton = ComponentView() + private let doneButton = ComponentView() + + private var isUpdating: Bool = false + private var ignoreScrolling: Bool = false + private var previousHadInputHeight: Bool = false + + private var component: NewContactScreenComponent? + private(set) weak var state: EmptyComponentState? + private var environment: EnvironmentType? + + private var resolvedPeer: NewContactScreenComponent.ResolvedPeer? + private var resolvedPeerDisposable = MetaDisposable() + + private let firstNameTag = NSObject() + private let lastNameTag = NSObject() + private let phoneTag = NSObject() + private let noteTag = NSObject() + + private var syncContactToPhone = true + + private var cachedChevronImage: (UIImage, PresentationTheme)? + + private var composer: MFMessageComposeViewController? + + override init(frame: CGRect) { + self.scrollView = UIScrollView() + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + self.scrollView.alwaysBounceVertical = true + + self.edgeEffectView = EdgeEffectView() + + super.init(frame: frame) + + self.scrollView.delegate = self + self.addSubview(self.scrollView) + + self.addSubview(self.edgeEffectView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.resolvedPeerDisposable.dispose() + } + + func scrollToTop() { + self.scrollView.setContentOffset(CGPoint(), animated: true) + } + + func validatedInput() -> NewContactScreenComponent.Result? { + var peer: EnginePeer? + var firstName = "" + var lastName = "" + var phoneNumber = "" + var note = NSAttributedString() + if case let .peer(resolvedPeer, _) = self.resolvedPeer { + peer = resolvedPeer + } + if let view = self.nameSection.findTaggedView(tag: self.firstNameTag) as? ListTextFieldItemComponent.View { + firstName = view.currentText.trimmingCharacters(in: .whitespacesAndNewlines) + if firstName.isEmpty { + return nil + } + } + if let view = self.nameSection.findTaggedView(tag: self.lastNameTag) as? ListTextFieldItemComponent.View { + lastName = view.currentText.trimmingCharacters(in: .whitespacesAndNewlines) + } + if let view = self.phoneSection.findTaggedView(tag: self.phoneTag) as? ListItemComponentAdaptor.View { + if let itemNode = view.itemNode as? PhoneInputItemNode { + if itemNode.codeNumberAndFullNumber.0.isEmpty || itemNode.codeNumberAndFullNumber.1.isEmpty { + return nil + } + phoneNumber = itemNode.phoneNumber + } + } + if let view = self.noteSection.findTaggedView(tag: self.noteTag) as? ListComposePollOptionComponent.View { + note = view.currentAttributedText + } + return Result( + peer: peer, + firstName: firstName, + lastName: lastName, + phoneNumber: phoneNumber, + syncContactToPhone: self.syncContactToPhone, + note: note + ) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + + } + + func updateCountryCode(code: Int32, name: String) { + if let view = self.phoneSection.findTaggedView(tag: self.phoneTag) as? ListItemComponentAdaptor.View { + if let itemNode = view.itemNode as? PhoneInputItemNode { + itemNode.updateCountryCode(code: code, name: name) + } + } + } + + private var currentPhoneNumber: String { + if let view = self.phoneSection.findTaggedView(tag: tag) as? ListItemComponentAdaptor.View { + if let itemNode = view.itemNode as? PhoneInputItemNode { + return itemNode.phoneNumber + } + } + return "" + } + + func activateInput(tag: Any) { + if let view = self.phoneSection.findTaggedView(tag: tag) as? ListItemComponentAdaptor.View { + if let itemNode = view.itemNode as? PhoneInputItemNode { + itemNode.activateInput() + } + } + if let view = self.nameSection.findTaggedView(tag: tag) as? ListTextFieldItemComponent.View { + view.activateInput() + } + } + + func deactivateInput() { + self.endEditing(true) + } + + func sendInvite() { + guard MFMessageComposeViewController.canSendText(), let environment = self.environment else { + return + } + let composer = MFMessageComposeViewController() + composer.messageComposeDelegate = self + composer.recipients = [self.currentPhoneNumber] + let url = environment.strings.InviteText_URL + let body = environment.strings.InviteText_SingleContact(url).string + composer.body = body + self.composer = composer + if let window = self.window { + window.rootViewController?.present(composer, animated: true) + } + } + + @objc public func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) { + self.composer = nil + + controller.dismiss(animated: true, completion: nil) + } + + func update(component: NewContactScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + var alphaTransition = transition + if !transition.animation.isImmediate { + alphaTransition = alphaTransition.withAnimation(.curve(duration: 0.25, curve: .easeInOut)) + } + + let environment = environment[EnvironmentType.self].value + let themeUpdated = self.environment?.theme !== environment.theme + self.environment = environment + + let theme = environment.theme + + var initialCountryCode: Int32? + var initialFocusTag: Any? + if self.component == nil { + let countryCode: Int32 + if let phone = component.initialData.phoneNumber { + if let (_, code) = lookupCountryIdByNumber(phone, configuration: component.context.currentCountriesConfiguration.with { $0 }), let codeValue = Int32(code.code) { + countryCode = codeValue + } else { + countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() + } + initialFocusTag = self.firstNameTag + } else { + countryCode = AuthorizationSequenceCountrySelectionController.defaultCountryCode() + initialFocusTag = self.phoneTag + } + initialCountryCode = countryCode + } + + self.component = component + self.state = state + + let topInset: CGFloat = 24.0 + let bottomInset: CGFloat = 8.0 + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let sectionSpacing: CGFloat = 24.0 + + let footerAttributes = MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor), + linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + } + ) + + if themeUpdated { + self.backgroundColor = theme.list.blocksBackgroundColor + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + var contentHeight: CGFloat = 0.0 + contentHeight += environment.navigationHeight + contentHeight += topInset + + let nameSectionItems: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity(id: "firstName", component: AnyComponent(ListTextFieldItemComponent( + style: .glass, + theme: theme, + initialText: component.initialData.firstName ?? "", + resetText: nil, + placeholder: "First Name", + autocapitalizationType: .sentences, + autocorrectionType: .default, + updated: { value in + + }, + tag: self.firstNameTag + ))), + AnyComponentWithIdentity(id: "lastName", component: AnyComponent(ListTextFieldItemComponent( + style: .glass, + theme: theme, + initialText: component.initialData.lastName ?? "", + resetText: nil, + placeholder: "Last Name", + autocapitalizationType: .sentences, + autocorrectionType: .default, + updated: { value in + + }, + tag: self.lastNameTag + ))) + ] + let nameSectionSize = self.nameSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: nil, + items: nameSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let nameSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: nameSectionSize) + if let nameSectionView = self.nameSection.view as? ListSectionComponent.View { + if nameSectionView.superview == nil { + self.scrollView.addSubview(nameSectionView) + self.nameSection.parentState = state + } + transition.setFrame(view: nameSectionView, frame: nameSectionFrame) + } + contentHeight += nameSectionSize.height + contentHeight += sectionSpacing + + var phoneAccesory: PhoneInputItem.Accessory? + switch self.resolvedPeer { + case .resolving: + phoneAccesory = .activity + case .peer: + phoneAccesory = .check + default: + phoneAccesory = nil + } + + let phoneSectionItems: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity(id: "phone", component: AnyComponent( + ListItemComponentAdaptor( + itemGenerator: PhoneInputItem( + theme: theme, + strings: environment.strings, + value: (initialCountryCode, nil, ""), + accessory: phoneAccesory, + selectCountryCode: { [weak self] in + guard let self, let environment = self.environment, let controller = environment.controller() else { + return + } + let countryController = AuthorizationSequenceCountrySelectionController(strings: environment.strings, theme: environment.theme, glass: true) + countryController.completeWithCountryCode = { [weak self] code, name in + guard let self else { + return + } + self.updateCountryCode(code: Int32(code), name: name) + self.activateInput(tag: self.phoneTag) + } + self.deactivateInput() + controller.push(countryController) + }, + updated: { [weak self] number, mask in + guard let self, let component = self.component else { + return + } + self.resolvedPeerDisposable.set(nil) + self.resolvedPeer = nil + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + + let cleanNumber = number.replacingOccurrences(of: "+", with: "") + var scheduleResolve = false + if !mask.isEmpty && abs(cleanNumber.count - mask.count) < 3 { + scheduleResolve = true + } else if mask.isEmpty && cleanNumber.count > 4 { + scheduleResolve = true + } + let _ = component + + if scheduleResolve { + self.resolvedPeerDisposable.set( + ((Signal.complete() |> delay(2.5, queue: Queue.mainQueue())) + |> then( + component.context.engine.peers.resolvePeerByPhone(phone: number) + |> beforeStarted({ [weak self] in + guard let self else { + return + } + self.resolvedPeer = .resolving + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + }) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self else { + return + } + if let peer { + self.resolvedPeer = .peer(peer: peer, isContact: false) + } else { + self.resolvedPeer = .notFound + } + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + }) + ) + } + } + ), + params: ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true), + tag: self.phoneTag + ) + )) + ] + + var phoneFooterComponent: AnyComponent? + if let resolvedPeer = self.resolvedPeer { + if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme { + self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme) + } + + let phoneFooterRawText: String + switch resolvedPeer { + case .resolving: + phoneFooterRawText = "" + case let .peer(_, isContact): + if isContact { + phoneFooterRawText = "This phone number is already in your contacts. [View >]()" + } else { + phoneFooterRawText = "This phone number is on Telegram." + } + case .notFound: + phoneFooterRawText = "This phone number is not on Telegram. [Invite >]()" + } + let phoneFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(phoneFooterRawText, attributes: footerAttributes)) + if let range = phoneFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 { + phoneFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: phoneFooterText.string)) + } + phoneFooterComponent = AnyComponent(MultilineTextComponent( + text: .plain(phoneFooterText), + maximumNumberOfLines: 0, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.1), + highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) + } else { + return nil + } + }, + tapAction: { [weak self] _, _ in + guard let self else { + return + } + if case let .peer(peer, _) = self.resolvedPeer { + let _ = peer + } else { + self.sendInvite() + } + } + )) + } + + let phoneSectionSize = self.phoneSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: phoneFooterComponent, + items: phoneSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let phoneSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: phoneSectionSize) + if let phoneSectionView = self.phoneSection.view as? ListSectionComponent.View { + if phoneSectionView.superview == nil { + self.scrollView.addSubview(phoneSectionView) + self.phoneSection.parentState = state + } + transition.setFrame(view: phoneSectionView, frame: phoneSectionFrame) + } + contentHeight += phoneSectionSize.height + contentHeight += sectionSpacing + + if let initialCountryCode { + self.updateCountryCode(code: initialCountryCode, name: "") + } + + let syncContactSectionItems: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity(id: "syncContact", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Sync Contact to Phone", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + )), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.syncContactToPhone, action: { [weak self] _ in + guard let self else { + return + } + self.syncContactToPhone = !self.syncContactToPhone + self.state?.updated(transition: .spring(duration: 0.4)) + })), + action: nil + ))) + ] + let syncContactSectionSize = self.syncContactSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: nil, + items: syncContactSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let syncContactSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: syncContactSectionSize) + if let syncContactSectionView = self.syncContactSection.view { + if syncContactSectionView.superview == nil { + self.scrollView.addSubview(syncContactSectionView) + self.syncContactSection.parentState = state + } + transition.setFrame(view: syncContactSectionView, frame: syncContactSectionFrame) + } + contentHeight += syncContactSectionSize.height + contentHeight += sectionSpacing + + if case .peer = self.resolvedPeer { + if let qrSectionView = self.qrSection.view, qrSectionView.superview != nil { + transition.setAlpha(view: qrSectionView, alpha: 0.0, completion: { _ in + qrSectionView.removeFromSuperview() + }) + } + + var characterLimit: Int = 128 + if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["contact_note_length_limit"] as? Double { + characterLimit = Int(value) + } + let noteSectionItems: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity( + id: "note", + component: AnyComponent( + ListComposePollOptionComponent( + externalState: nil, + context: component.context, + style: .glass, + theme: theme, + strings: environment.strings, + placeholder: NSAttributedString(string: "Add notes only visible to you", font: Font.regular(17.0), textColor: theme.list.itemPlaceholderTextColor), + characterLimit: characterLimit, + emptyLineHandling: .allowed, + returnKeyAction: nil, + backspaceKeyAction: nil, + selection: nil, + inputMode: nil, + toggleInputMode: nil, + tag: self.noteTag + ) + ) + ) + ] + var noteSectionTransition = transition + if self.noteSection.view == nil { + noteSectionTransition = .immediate + } + let noteSectionSize = self.noteSection.update( + transition: noteSectionTransition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: nil, + items: noteSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let noteSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: noteSectionSize) + if let noteSectionView = self.noteSection.view { + if noteSectionView.superview == nil { + self.scrollView.addSubview(noteSectionView) + self.syncContactSection.parentState = state + + noteSectionTransition = .immediate + transition.setAlpha(view: noteSectionView, alpha: 1.0) + } + noteSectionTransition.setFrame(view: noteSectionView, frame: noteSectionFrame) + } + contentHeight += noteSectionSize.height + contentHeight += sectionSpacing + } else { + if let noteSectionView = self.noteSection.view, noteSectionView.superview != nil { + transition.setAlpha(view: noteSectionView, alpha: 0.0, completion: { _ in + noteSectionView.removeFromSuperview() + }) + } + + let qrSectionItems: [AnyComponentWithIdentity] = [ + AnyComponentWithIdentity(id: "syncContact", component: AnyComponent(ListActionItemComponent( + theme: theme, + style: .glass, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Add via QR Code", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: theme.list.itemAccentColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + leftIcon: .custom( + AnyComponentWithIdentity( + id: "icon", + component: AnyComponent(BundleIconComponent(name: "Settings/QrIcon", tintColor: theme.list.itemAccentColor)) + ), + false + ), + accessory: .none, + action: { [weak self] _ in + guard let self, let component = self.component, let environment = self.environment, let controller = environment.controller() else { + return + } + let scanController = QrCodeScanScreen(context: component.context, subject: .peer) + controller.push(scanController) + } + ))) + ] + let qrSectionSize = self.qrSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: theme, + style: .glass, + header: nil, + footer: nil, + items: qrSectionItems + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let qrSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: qrSectionSize) + if let qrSectionView = self.qrSection.view { + var qrSectionTransition = transition + if qrSectionView.superview == nil { + self.scrollView.addSubview(qrSectionView) + self.syncContactSection.parentState = state + + qrSectionTransition = .immediate + transition.setAlpha(view: qrSectionView, alpha: 1.0) + } + qrSectionTransition.setFrame(view: qrSectionView, frame: qrSectionFrame) + } + contentHeight += qrSectionSize.height + contentHeight += sectionSpacing + } + + + let inputHeight = environment.inputHeight + + let combinedBottomInset: CGFloat + combinedBottomInset = bottomInset + max(environment.safeInsets.bottom, 8.0 + inputHeight) + contentHeight += combinedBottomInset + + self.ignoreScrolling = true + let previousBounds = self.scrollView.bounds + let contentSize = CGSize(width: availableSize.width, height: contentHeight) + if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { + self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) + } + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: environment.safeInsets.bottom, right: 0.0) + if self.scrollView.verticalScrollIndicatorInsets != scrollInsets { + self.scrollView.verticalScrollIndicatorInsets = scrollInsets + } + + if !previousBounds.isEmpty, !transition.animation.isImmediate { + let bounds = self.scrollView.bounds + if bounds.maxY != previousBounds.maxY { + let offsetY = previousBounds.maxY - bounds.maxY + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) + } + } + self.ignoreScrolling = false + + + let isValid = self.validatedInput() != nil + + let edgeEffectHeight: CGFloat = 66.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + + let titleSize = self.title.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent( + text: .plain( + NSAttributedString( + string: "New Contact", + font: Font.semibold(17.0), + textColor: environment.theme.rootController.navigationBar.primaryTextColor + ) + ) + ) + ), + environment: {}, + containerSize: CGSize(width: 200.0, height: 40.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: floorToScreenPixels((environment.navigationHeight - titleSize.height) / 2.0) + 3.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: environment.theme.rootController.navigationBar.opaqueBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? NewContactScreen else { + return + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + + let doneButtonSize = self.doneButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: isValid ? environment.theme.list.itemCheckColors.fillColor : environment.theme.list.itemCheckColors.fillColor.desaturated().withMultipliedAlpha(0.5), + isDark: environment.theme.overallDarkAppearance, + state: .tintedGlass, + isEnabled: isValid, + component: AnyComponentWithIdentity(id: "done", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Done", + tintColor: environment.theme.list.itemCheckColors.foregroundColor + ) + )), + action: { [weak self] _ in + guard let self, let controller = self.environment?.controller() as? NewContactScreen else { + return + } + if let input = self.validatedInput() { + controller.complete(result: input) + } + controller.dismiss() + } + )), + environment: {}, + containerSize: barButtonSize + ) + let doneButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - doneButtonSize.width, y: 16.0), size: doneButtonSize) + if let doneButtonView = self.doneButton.view { + if doneButtonView.superview == nil { + self.addSubview(doneButtonView) + } + transition.setFrame(view: doneButtonView, frame: doneButtonFrame) + } + + if let initialFocusTag { + self.activateInput(tag: initialFocusTag) + } + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class NewContactScreen: ViewControllerComponentContainer { + public final class InitialData { + fileprivate let firstName: String? + fileprivate let lastName: String? + fileprivate let phoneNumber: String? + + fileprivate init( + firstName: String?, + lastName: String?, + phoneNumber: String? + ) { + self.firstName = firstName + self.lastName = lastName + self.phoneNumber = phoneNumber + } + } + + private let context: AccountContext + fileprivate let completion: (TelegramMediaTodo) -> Void + private var isDismissed: Bool = false + + public init( + context: AccountContext, + initialData: InitialData, + completion: @escaping (TelegramMediaTodo) -> Void + ) { + self.context = context + self.completion = completion + + let countriesConfiguration = context.currentCountriesConfiguration.with { $0 } + AuthorizationSequenceCountrySelectionController.setupCountryCodes(countries: countriesConfiguration.countries, codesByPrefix: countriesConfiguration.countriesByPrefix) + + super.init(context: context, component: NewContactScreenComponent( + context: context, + initialData: initialData, + completion: completion + ), navigationBarAppearance: .none, theme: .default) + + self._hasGlassStyle = true + self.navigationPresentation = .modal + + self.scrollToTop = { [weak self] in + guard let self, let componentView = self.node.hostView.componentView as? NewContactScreenComponent.View else { + return + } + componentView.scrollToTop() + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + public static func initialData( + firstName: String? = nil, + lastName: String? = nil, + phoneNumber: String? = nil + ) -> InitialData { + return InitialData( + firstName: firstName, + lastName: lastName, + phoneNumber: phoneNumber + ) + } + + fileprivate func complete(result: NewContactScreenComponent.Result) { + if let peer = result.peer { + let entities = generateChatInputTextEntities(result.note) + let _ = self.context.engine.contacts.addContactInteractively( + peerId: peer.id, + firstName: result.firstName, + lastName: result.lastName, + phoneNumber: result.phoneNumber, + noteText: result.note.string, + noteEntities: entities, + addToPrivacyExceptions: false + ).startStandalone() + } else { + let _ = self.context.engine.contacts.importContact( + firstName: result.firstName, + lastName: result.lastName, + phoneNumber: result.phoneNumber + ).startStandalone() + } + } +} diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift new file mode 100644 index 0000000000..bfb74ff6a2 --- /dev/null +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/PhoneInputItem.swift @@ -0,0 +1,436 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import PhoneInputNode +import CountrySelectionUI +import ListItemComponentAdaptor +import ComponentFlow + +private func generateCountryButtonBackground(strokeColor: UIColor) -> UIImage? { + return generateImage(CGSize(width: 75.0, height: 52.0 + 6.0), rotatedContext: { size, context in + let arrowSize: CGFloat = 7.0 + let lineWidth = 1.0 - UIScreenPixel + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(strokeColor.cgColor) + context.setLineWidth(lineWidth) + context.move(to: CGPoint(x: 16.0, y: size.height - arrowSize - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: 16.0 + 21.0, y: size.height - arrowSize - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: 16.0 + 21.0 + arrowSize, y: size.height - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: 16.0 + 21.0 + arrowSize + arrowSize, y: size.height - arrowSize - lineWidth / 2.0)) + context.addLine(to: CGPoint(x: size.width - 16.0, y: size.height - arrowSize - lineWidth / 2.0)) + context.strokePath() + })?.resizableImage(withCapInsets: UIEdgeInsets(top: 1.0, left: 55.0, bottom: 1.0, right: 17.0), resizingMode: .stretch) +} + +private func generateCountryButtonHighlightedBackground(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 52.0, height: 52.0 + 6.0), rotatedContext: { size, context in + let arrowSize: CGFloat = 7.0 + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.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: 51, topCapHeight: 2) +} + +private func generatePhoneInputBackground(strokeColor: UIColor) -> UIImage? { + return generateImage(CGSize(width: 79.0, height: 52.0), rotatedContext: { size, context in + let lineWidth = 1.0 - UIScreenPixel + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(strokeColor.cgColor) + context.setLineWidth(lineWidth) + context.move(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: size.height - 16.0)) + context.addLine(to: CGPoint(x: size.width - 2.0 + lineWidth / 2.0, y: 16.0)) + context.strokePath() + })?.stretchableImage(withLeftCapWidth: 78, topCapHeight: 2) +} + +final class PhoneInputItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { + public enum Accessory { + case check + case activity + } + + let theme: PresentationTheme + let strings: PresentationStrings + let value: (Int32?, String?, String) + let accessory: Accessory? + let selectCountryCode: () -> Void + let updated: (String, String) -> Void + + public init(theme: PresentationTheme, strings: PresentationStrings, value: (Int32?, String?, String), accessory: Accessory?, selectCountryCode: @escaping () -> Void, updated: @escaping (String, String) -> Void) { + self.theme = theme + self.strings = strings + self.value = value + self.accessory = accessory + self.selectCountryCode = selectCountryCode + self.updated = updated + } + + let sectionId: ItemListSectionId = 0 + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = PhoneInputItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + if let nodeValue = node() as? PhoneInputItemNode { + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } + } + + func item() -> ListViewItem { + return self + } + + static func ==(lhs: PhoneInputItem, rhs: PhoneInputItem) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.value.0 != rhs.value.0 { + return false + } + if lhs.value.1 != rhs.value.1 { + return false + } + if lhs.value.2 != rhs.value.2 { + return false + } + if lhs.accessory != rhs.accessory { + return false + } + return true + } +} + +final class PhoneInputItemNode: ListViewItemNode, ItemListItemNode { + private let countryButton: ASButtonNode + private let arrowNode: ASImageNode + private let phoneBackground: ASImageNode + private let phoneInputNode: PhoneInputNode + + private var item: PhoneInputItem? + private var layoutParams: ListViewItemLayoutParams? + + var preferredCountryIdForCode: [String: String] = [:] + + private let checkNode: ASImageNode + private var activityIndicatorView: UIActivityIndicatorView? + + var tag: ItemListItemTag? { + return self.item?.tag + } + + init() { + self.countryButton = ASButtonNode() + self.arrowNode = ASImageNode() + self.arrowNode.displaysAsynchronously = false + self.arrowNode.isLayerBacked = true + + self.phoneBackground = ASImageNode() + self.phoneBackground.displaysAsynchronously = false + self.phoneBackground.displayWithoutProcessing = true + self.phoneBackground.isLayerBacked = true + + self.phoneInputNode = PhoneInputNode(fontSize: 17.0) + + self.checkNode = ASImageNode() + self.checkNode.displaysAsynchronously = false + self.checkNode.isLayerBacked = true + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.phoneBackground) + self.addSubnode(self.countryButton) + self.addSubnode(self.arrowNode) + self.addSubnode(self.phoneInputNode) + self.addSubnode(self.checkNode) + + self.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15.0, bottom: 4.0, right: 0.0) + self.countryButton.contentHorizontalAlignment = .left + + self.countryButton.addTarget(self, action: #selector(self.countryPressed), forControlEvents: .touchUpInside) + + self.phoneInputNode.numberTextUpdated = { [weak self] number in + if let self { + let _ = self.processNumberChange(self.phoneInputNode.number) + } + } + + self.phoneInputNode.countryCodeUpdated = { [weak self] code, name in + guard let self, let item = self.item else { + return + } + if let name = name { + self.preferredCountryIdForCode[code] = name + } + + if self.processNumberChange(self.phoneInputNode.number) { + } else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] { + let flagString = emojiFlagForISOCountryCode(name) + var localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: item.strings) ?? countryName + if name == "FT" { + localizedName = item.strings.Login_AnonymousNumbers + } + self.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: []) + } else if let code = Int(code), let (countryId, countryName) = countryCodeToIdAndName[code] { + let flagString = emojiFlagForISOCountryCode(countryId) + var localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: item.strings) ?? countryName + if countryId == "FT" { + localizedName = item.strings.Login_AnonymousNumbers + } + self.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: []) + } else { + self.countryButton.setTitle(item.strings.Login_SelectCountry, with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: []) + } + } + + self.phoneInputNode.customFormatter = { number in + if let (_, code) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: [:]) { + return code.code + } else { + return nil + } + } + + let countryId = (Locale.current as NSLocale).object(forKey: .countryCode) as? String + + var countryCodeAndId: (Int32, String) = (1, "US") + if let countryId = countryId { + let normalizedId = countryId.uppercased() + for (code, idAndName) in countryCodeToIdAndName { + if idAndName.0 == normalizedId { + countryCodeAndId = (Int32(code), idAndName.0.uppercased()) + break + } + } + } + + self.phoneInputNode.number = "+\(countryCodeAndId.0)" + } + + func processNumberChange(_ number: String) -> Bool { + guard let item = self.item else { + return false + } + if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(number, preferredCountries: self.preferredCountryIdForCode) { + let flagString = emojiFlagForISOCountryCode(country.id) + let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: item.strings) ?? country.name + self.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(17.0), with: item.theme.list.itemPrimaryTextColor, for: []) + + let maskFont = Font.with(size: 17.0, design: .regular, traits: [.monospacedNumbers]) + var rawMask = "" + if let mask = AuthorizationSequenceCountrySelectionController.lookupPatternByNumber(number, preferredCountries: self.preferredCountryIdForCode) { + self.phoneInputNode.numberField.textField.attributedPlaceholder = nil + self.phoneInputNode.mask = NSAttributedString(string: mask, font: maskFont, textColor: item.theme.list.itemPlaceholderTextColor) + + let rawCountryCode = self.codeNumberAndFullNumber.0.replacingOccurrences(of: "+", with: "") + rawMask = mask.replacingOccurrences(of: " ", with: "") + for _ in 0 ..< rawCountryCode.count { + rawMask.insert("X", at: rawMask.startIndex) + } + } else { + self.phoneInputNode.mask = nil + self.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: item.strings.Login_PhonePlaceholder, font: Font.regular(17.0), textColor: item.theme.list.itemPlaceholderTextColor) + } + item.updated(number, rawMask) + + return true + } else { + return false + } + } + + @objc private func countryPressed() { + if let item = self.item { + item.selectCountryCode() + } + } + + var phoneNumber: String { + return self.phoneInputNode.number + } + + var codeNumberAndFullNumber: (String, String, String) { + return self.phoneInputNode.codeNumberAndFullNumber + } + + func updateCountryCode() { + self.phoneInputNode.codeAndNumber = self.phoneInputNode.codeAndNumber + } + + func updateCountryCode(code: Int32, name: String) { + self.phoneInputNode.codeAndNumber = (code, name, self.phoneInputNode.codeAndNumber.2) + let _ = self.processNumberChange(self.phoneInputNode.number) + } + + func activateInput() { + self.phoneInputNode.numberField.textField.becomeFirstResponder() + } + + func animateError() { + self.phoneInputNode.countryCodeField.layer.addShakeAnimation() + self.phoneInputNode.numberField.layer.addShakeAnimation() + } + + func asyncLayout() -> (_ item: PhoneInputItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let currentItem = self.item + + return { item, params, neighbors in + var updatedCountryButtonBackground: UIImage? + var updatedCountryButtonHighlightedBackground: UIImage? + var updatedPhoneBackground: UIImage? + var updatedArrowImage: UIImage? + var updatedCheckImage: UIImage? + + if currentItem?.theme !== item.theme { + updatedCountryButtonBackground = generateCountryButtonBackground(strokeColor: item.theme.list.itemBlocksSeparatorColor.withMultipliedAlpha(0.5)) + updatedCountryButtonHighlightedBackground = generateCountryButtonHighlightedBackground(color: item.theme.list.itemHighlightedBackgroundColor) + updatedPhoneBackground = generatePhoneInputBackground(strokeColor: item.theme.list.itemBlocksSeparatorColor.withMultipliedAlpha(0.5)) + updatedArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.theme) + updatedCheckImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Check"), color: item.theme.list.itemAccentColor) + } + + let contentSize: CGSize + var insets: UIEdgeInsets + + let countryButtonHeight: CGFloat = 52.0 + let inputFieldHeight: CGFloat = 52.0 + + contentSize = CGSize(width: params.width, height: countryButtonHeight + inputFieldHeight) + insets = itemListNeighborsGroupedInsets(neighbors, params) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + strongSelf.layoutParams = params + + if let updatedCountryButtonBackground = updatedCountryButtonBackground { + strongSelf.countryButton.setBackgroundImage(updatedCountryButtonBackground, for: []) + } + if let updatedCountryButtonHighlightedBackground = updatedCountryButtonHighlightedBackground { + strongSelf.countryButton.setBackgroundImage(updatedCountryButtonHighlightedBackground, for: .highlighted) + } + if let updatedPhoneBackground = updatedPhoneBackground { + strongSelf.phoneBackground.image = updatedPhoneBackground + } + if let updatedArrowImage { + strongSelf.arrowNode.image = updatedArrowImage + } + if let updatedCheckImage { + strongSelf.checkNode.image = updatedCheckImage + } + + strongSelf.phoneInputNode.countryCodeField.textField.textColor = item.theme.list.itemPrimaryTextColor + strongSelf.phoneInputNode.countryCodeField.textField.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance + strongSelf.phoneInputNode.countryCodeField.textField.tintColor = item.theme.list.itemAccentColor + strongSelf.phoneInputNode.numberField.textField.textColor = item.theme.list.itemPrimaryTextColor + strongSelf.phoneInputNode.numberField.textField.keyboardAppearance = item.theme.rootController.keyboardColor.keyboardAppearance + strongSelf.phoneInputNode.numberField.textField.tintColor = item.theme.list.itemAccentColor + + strongSelf.countryButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: params.leftInset + 15.0, bottom: 4.0, right: 0.0) + + strongSelf.countryButton.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: 52.0 + 6.0)) + + if let arrowImage = strongSelf.arrowNode.image { + strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - arrowImage.size.width - 8.0, y: floorToScreenPixels((countryButtonHeight - arrowImage.size.height) / 2.0)), size: arrowImage.size) + } + + strongSelf.phoneBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: 52.0), size: CGSize(width: params.width, height: 52.0)) + + let countryCodeFrame = CGRect(origin: CGPoint(x: 7.0, y: 52.0), size: CGSize(width: 67.0, height: 52.0)) + let numberFrame = CGRect(origin: CGPoint(x: 88.0, y: 52.0), size: CGSize(width: layout.size.width - 70.0 - 8.0, height: 52.0)) + let placeholderFrame = numberFrame.offsetBy(dx: 0.0, dy: 8.0) + + let phoneInputFrame = countryCodeFrame.union(numberFrame) + + strongSelf.phoneInputNode.frame = phoneInputFrame + strongSelf.phoneInputNode.countryCodeField.frame = countryCodeFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY) + strongSelf.phoneInputNode.numberField.frame = numberFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY) + strongSelf.phoneInputNode.placeholderNode.frame = placeholderFrame.offsetBy(dx: -phoneInputFrame.minX, dy: -phoneInputFrame.minY + 8.0 + UIScreenPixel) + + if case .check = item.accessory { + strongSelf.checkNode.isHidden = false + } else { + strongSelf.checkNode.isHidden = true + } + if let checkImage = strongSelf.checkNode.image { + strongSelf.checkNode.frame = CGRect(origin: CGPoint(x: params.width - checkImage.size.width - 10.0, y: countryButtonHeight + floorToScreenPixels((inputFieldHeight - checkImage.size.height) / 2.0)), size: checkImage.size) + } + + if case .activity = item.accessory { + let activityIndicatorView: UIActivityIndicatorView + let activityIndicatorTransition = ComponentTransition.immediate + if let current = strongSelf.activityIndicatorView { + activityIndicatorView = current + } else { + if #available(iOS 13.0, *) { + activityIndicatorView = UIActivityIndicatorView(style: .medium) + } else { + activityIndicatorView = UIActivityIndicatorView(style: .gray) + } + strongSelf.activityIndicatorView = activityIndicatorView + strongSelf.view.addSubview(activityIndicatorView) + activityIndicatorView.sizeToFit() + } + + let activityIndicatorSize = activityIndicatorView.bounds.size + let activityIndicatorFrame = CGRect(origin: CGPoint(x: params.width - 16.0 - activityIndicatorSize.width, y: countryButtonHeight + floor((inputFieldHeight - activityIndicatorSize.height) * 0.5)), size: activityIndicatorSize) + + activityIndicatorView.tintColor = item.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.5) + + activityIndicatorTransition.setFrame(view: activityIndicatorView, frame: activityIndicatorFrame) + + if !activityIndicatorView.isAnimating { + activityIndicatorView.startAnimating() + } + } else { + if let activityIndicatorView = strongSelf.activityIndicatorView { + strongSelf.activityIndicatorView = nil + activityIndicatorView.removeFromSuperview() + } + } + } + }) + } + } +} diff --git a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift index d2e812a783..9487bd960f 100644 --- a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift +++ b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift @@ -11,6 +11,7 @@ public final class EdgeEffectView: UIView { private let contentView: UIView private let contentMaskView: UIImageView + private var blurView: VariableBlurView? public override init(frame: CGRect) { self.contentView = UIView() @@ -26,8 +27,8 @@ public final class EdgeEffectView: UIView { fatalError("init(coder:) has not been implemented") } - public func update(content: UIColor, alpha: CGFloat = 0.65, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) { - self.contentView.backgroundColor = content + public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.65, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) { + transition.setBackgroundColor(view: self.contentView, color: content) switch edge { case .top: @@ -36,8 +37,9 @@ public final class EdgeEffectView: UIView { self.contentMaskView.transform = .identity } - transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: rect.size)) - transition.setFrame(view: self.contentMaskView, frame: CGRect(origin: CGPoint(), size: rect.size)) + let bounds = CGRect(origin: CGPoint(), size: rect.size) + transition.setFrame(view: self.contentView, frame: bounds) + transition.setFrame(view: self.contentMaskView, frame: bounds) if self.contentMaskView.image?.size.height != edgeSize { let baseGradientAlpha: CGFloat = alpha @@ -72,5 +74,138 @@ public final class EdgeEffectView: UIView { self.contentMaskView.image = nil } } + + if blur { + let gradientMaskLayer = SimpleGradientLayer() + let baseGradientAlpha: CGFloat = 1.0 + let numSteps = 8 + let firstStep = 1 + let firstLocation = 0.8 + gradientMaskLayer.colors = (0 ..< numSteps).map { i in + if i < firstStep { + return UIColor(white: 1.0, alpha: 1.0).cgColor + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step) + return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor + } + } + gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in + if i < firstStep { + return 0.0 as NSNumber + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + return (firstLocation + (1.0 - firstLocation) * step) as NSNumber + } + } + + let blurView: VariableBlurView + if let current = self.blurView { + blurView = current + } else { + blurView = VariableBlurView(gradientMask: self.contentMaskView.image ?? UIImage(), maxBlurRadius: 8.0) + blurView.layer.mask = gradientMaskLayer + self.insertSubview(blurView, at: 0) + self.blurView = blurView + } + transition.setFrame(view: blurView, frame: bounds) + if let maskLayer = blurView.layer.mask { + transition.setFrame(layer: maskLayer, frame: bounds) + maskLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0) + } + blurView.transform = self.contentMaskView.transform + } else if let blurView = self.blurView { + self.blurView = nil + blurView.removeFromSuperview() + } + } +} + +public final class VariableBlurView: UIVisualEffectView { + public let maxBlurRadius: CGFloat + + public var gradientMask: UIImage { + didSet { + if self.gradientMask !== oldValue { + self.resetEffect() + } + } + } + + public init(gradientMask: UIImage, maxBlurRadius: CGFloat = 20.0) { + self.gradientMask = gradientMask + self.maxBlurRadius = maxBlurRadius + + super.init(effect: UIBlurEffect(style: .regular)) + + self.resetEffect() + + if self.subviews.indices.contains(1) { + let tintOverlayView = subviews[1] + tintOverlayView.alpha = 0 + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + self.resetEffect() + } + } + + private func resetEffect() { + let filterClassStringEncoded = "Q0FGaWx0ZXI=" + let filterClassString: String = { + if + let data = Data(base64Encoded: filterClassStringEncoded), + let string = String(data: data, encoding: .utf8) + { + return string + } + + return "" + }() + let filterWithTypeStringEncoded = "ZmlsdGVyV2l0aFR5cGU6" + let filterWithTypeString: String = { + if + let data = Data(base64Encoded: filterWithTypeStringEncoded), + let string = String(data: data, encoding: .utf8) + { + return string + } + + return "" + }() + + let filterWithTypeSelector = Selector(filterWithTypeString) + + guard let filterClass = NSClassFromString(filterClassString) as AnyObject as? NSObjectProtocol else { + return + } + + guard filterClass.responds(to: filterWithTypeSelector) else { + return + } + + let variableBlur = filterClass.perform(filterWithTypeSelector, with: "variableBlur").takeUnretainedValue() + + guard let variableBlur = variableBlur as? NSObject else { + return + } + + guard let gradientImageRef = self.gradientMask.cgImage else { + return + } + + variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius") + variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage") + variableBlur.setValue(true, forKey: "inputNormalizeEdges") + + let backdropLayer = self.subviews.first?.layer + backdropLayer?.filters = [variableBlur] + backdropLayer?.setValue(UIScreenScale, forKey: "scale") } } diff --git a/submodules/TelegramUI/Components/FaceScanScreen/BUILD b/submodules/TelegramUI/Components/FaceScanScreen/BUILD index e024731f40..a5c49dd48c 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/BUILD +++ b/submodules/TelegramUI/Components/FaceScanScreen/BUILD @@ -34,6 +34,7 @@ swift_library( "//submodules/TelegramUI/Components/PlainButtonComponent", "//submodules/Components/SheetComponent", "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift index 90adffae9b..067ab62016 100644 --- a/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift +++ b/submodules/TelegramUI/Components/FaceScanScreen/Sources/AgeVerificationScreen.swift @@ -13,6 +13,7 @@ import BalancedTextComponent import MultilineTextComponent import BundleIconComponent import ButtonComponent +import GlassBarButtonComponent import AccountContext import PresentationDataUtils import TelegramUIPreferences @@ -61,7 +62,6 @@ private final class SheetContent: CombinedComponent { } final class State: ComponentState { - var cachedCloseImage: (UIImage, PresentationTheme)? } func makeState() -> State { @@ -69,9 +69,8 @@ private final class SheetContent: CombinedComponent { } static var body: Body { - let background = Child(RoundedRectangle.self) let icon = Child(ZStack.self) - let closeButton = Child(Button.self) + let closeButton = Child(GlassBarButtonComponent.self) let title = Child(Text.self) let text = Child(BalancedTextComponent.self) @@ -80,7 +79,6 @@ private final class SheetContent: CombinedComponent { return { context in let environment = context.environment[EnvironmentType.self] let component = context.component - let state = context.state let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let theme = presentationData.theme @@ -88,15 +86,6 @@ private final class SheetContent: CombinedComponent { var contentSize = CGSize(width: context.availableSize.width, height: 18.0) - let background = background.update( - component: RoundedRectangle(color: theme.actionSheet.opaqueItemBackgroundColor, cornerRadius: 8.0), - availableSize: CGSize(width: context.availableSize.width, height: 1000.0), - transition: .immediate - ) - context.add(background - .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0)) - ) - let icon = icon.update( component: ZStack([ AnyComponentWithIdentity( @@ -118,25 +107,27 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width / 2.0, y: icon.size.height / 2.0 + 31.0)) ) - let closeImage: UIImage - if let (image, cacheTheme) = state.cachedCloseImage, theme === cacheTheme { - closeImage = image - } else { - closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: theme.actionSheet.inputClearButtonColor)! - state.cachedCloseImage = (closeImage, theme) - } let closeButton = closeButton.update( - component: Button( - content: AnyComponent(Image(image: closeImage)), - action: { + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { _ in component.dismiss() } ), - availableSize: CGSize(width: 30.0, height: 30.0), + availableSize: CGSize(width: 40.0, height: 40.0), transition: .immediate ) context.add(closeButton - .position(CGPoint(x: context.availableSize.width - closeButton.size.width, y: 28.0)) + .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0)) ) let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 @@ -194,10 +185,11 @@ private final class SheetContent: CombinedComponent { let button = button.update( component: ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: theme.list.itemCheckColors.fillColor, foreground: theme.list.itemCheckColors.foregroundColor, pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), - cornerRadius: 10.0 + cornerRadius: 10.0, ), content: AnyComponentWithIdentity( id: AnyHashable(0), @@ -209,17 +201,15 @@ private final class SheetContent: CombinedComponent { controller?.complete(result: true) } ), - availableSize: CGSize(width: context.availableSize.width - 16.0 * 2.0, height: 50), + availableSize: CGSize(width: context.availableSize.width - 30.0 * 2.0, height: 52.0), transition: .immediate ) context.add(button - .clipsToBounds(true) - .cornerRadius(10.0) .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0)) ) contentSize.height += button.size.height - contentSize.height += 48.0 + contentSize.height += 30.0 return contentSize } @@ -266,7 +256,8 @@ private final class AgeVerificationSheetComponent: CombinedComponent { }) } )), - backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor), + style: .glass, + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), followContentSizeChanges: true, clipsContent: true, animateOut: animateOut diff --git a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift index aec7e0f1e8..e458503b03 100644 --- a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift +++ b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift @@ -425,6 +425,7 @@ final class ForumSettingsScreenComponent: Component { var generalSectionItems: [AnyComponentWithIdentity] = [] generalSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -448,6 +449,7 @@ final class ForumSettingsScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: generalSectionItems @@ -471,6 +473,7 @@ final class ForumSettingsScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.PeerInfo_Topics_DisplayAs, diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 3d2bd5ea1b..757ffd9a67 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -19,6 +19,11 @@ import CheckNode import BundleIconComponent public final class GiftItemComponent: Component { + public enum Style { + case glass + case legacy + } + public enum Subject: Equatable { case premium(months: Int32, price: String) case starGift(gift: StarGift.Gift, price: String) @@ -138,6 +143,7 @@ public final class GiftItemComponent: Component { } let context: AccountContext + let style: Style let theme: PresentationTheme let strings: PresentationStrings let peer: GiftItemComponent.Peer? @@ -161,6 +167,7 @@ public final class GiftItemComponent: Component { public init( context: AccountContext, + style: Style = .legacy, theme: PresentationTheme, strings: PresentationStrings, peer: GiftItemComponent.Peer? = nil, @@ -183,6 +190,7 @@ public final class GiftItemComponent: Component { contextAction: ((UIView, ContextGesture) -> Void)? = nil ) { self.context = context + self.style = style self.theme = theme self.strings = strings self.peer = peer @@ -209,6 +217,9 @@ public final class GiftItemComponent: Component { if lhs.context !== rhs.context { return false } + if lhs.style != rhs.style { + return false + } if lhs.theme !== rhs.theme { return false } @@ -365,7 +376,12 @@ public final class GiftItemComponent: Component { size.height += 23.0 } iconSize = CGSize(width: 88.0, height: 88.0) - cornerRadius = 10.0 + switch component.style { + case .glass: + cornerRadius = 16.0 + case .legacy: + cornerRadius = 10.0 + } case .profile, .select: size = availableSize let side = floor(88.0 * availableSize.height / 116.0) @@ -1108,8 +1124,8 @@ public final class GiftItemComponent: Component { let image = generateImage(outlineFrame.size, rotatedContext: { size, context in context.clear(CGRect(origin: .zero, size: size)) - context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: outlineFrame.size), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)) - context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: outlineFrame.size).insetBy(dx: lineWidth, dy: lineWidth), cornerWidth: 8.0, cornerHeight: 8.0, transform: nil)) + context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: outlineFrame.size), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)) + context.addPath(CGPath(roundedRect: CGRect(origin: .zero, size: outlineFrame.size).insetBy(dx: lineWidth, dy: lineWidth), cornerWidth: cornerRadius - 2.0, cornerHeight: cornerRadius - 2.0, transform: nil)) context.clip(using: .evenOdd) diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD index 854b81c822..6d7769c329 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/BUILD @@ -43,6 +43,8 @@ swift_library( "//submodules/TelegramUI/Components/TabSelectorComponent", "//submodules/TelegramUI/Components/Gifts/GiftSetupScreen", "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", + "//submodules/TelegramUI/Components/EdgeEffect", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index e3dd5550db..d08746e994 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -26,6 +26,8 @@ import TabSelectorComponent import GiftSetupScreen import GiftViewScreen import UndoUI +import EdgeEffect +import GlassBarButtonComponent final class GiftOptionsScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -122,7 +124,8 @@ final class GiftOptionsScreenComponent: Component { private let topOverscrollLayer = SimpleLayer() private let scrollView: ScrollView - private let topPanel = ComponentView() + private let topEdgeEffectView: EdgeEffectView + private let bottomEdgeEffectView: EdgeEffectView private let topSeparator = ComponentView() private let cancelButton = ComponentView() @@ -254,6 +257,9 @@ final class GiftOptionsScreenComponent: Component { } self.scrollView.alwaysBounceVertical = true + self.topEdgeEffectView = EdgeEffectView() + self.bottomEdgeEffectView = EdgeEffectView() + super.init(frame: frame) self.scrollView.delegate = self @@ -394,20 +400,30 @@ final class GiftOptionsScreenComponent: Component { } } } + + private var isInAttachmentMenu: Bool { + if let controller = self.environment?.controller() { + return type(of: controller) != GiftOptionsScreen.self + } else { + return false + } + } private func updateScrolling(interactive: Bool = false, transition: ComponentTransition) { guard let environment = self.environment, let component = self.component else { return } - + + let theme = self.isInAttachmentMenu ? environment.theme.withModalBlocksBackground() : environment.theme + let availableWidth = self.scrollView.bounds.width let contentOffset = self.scrollView.contentOffset.y - - let topPanelAlpha = min(20.0, max(0.0, contentOffset - 95.0)) / 20.0 - if let topPanelView = self.topPanel.view, let topSeparator = self.topSeparator.view { - transition.setAlpha(view: topPanelView, alpha: topPanelAlpha) - transition.setAlpha(view: topSeparator, alpha: topPanelAlpha) - } +// +// let topPanelAlpha = min(20.0, max(0.0, contentOffset - 95.0)) / 20.0 +// if let topPanelView = self.topPanel.view, let topSeparator = self.topSeparator.view { +// transition.setAlpha(view: topPanelView, alpha: topPanelAlpha) +// transition.setAlpha(view: topSeparator, alpha: topPanelAlpha) +// } let topInset: CGFloat = 0.0 let headerTopInset: CGFloat = environment.navigationHeight - 56.0 @@ -575,7 +591,8 @@ final class GiftOptionsScreenComponent: Component { content: AnyComponent( GiftItemComponent( context: component.context, - theme: environment.theme, + style: .glass, + theme: theme, strings: environment.strings, peer: nil, subject: subject, @@ -644,45 +661,62 @@ final class GiftOptionsScreenComponent: Component { topPanelHeight += 39.0 } - if let tabSelectorView = self.tabSelector.view { - let tabSelectorSize = tabSelectorView.bounds.size - transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableWidth - tabSelectorSize.width) / 2.0), y: max(56.0, self.tabSelectorOrigin - contentOffset)), size: tabSelectorSize)) - } +// if let tabSelectorView = self.tabSelector.view { +// let tabSelectorSize = tabSelectorView.bounds.size +// transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableWidth - tabSelectorSize.width) / 2.0), y: max(56.0, self.tabSelectorOrigin - contentOffset)), size: tabSelectorSize)) +// } - var panelTransition = transition - if self.topPanel.view?.superview != nil && !self.switchingFilter { - panelTransition = .spring(duration: 0.3) - } - let topPanelSize = self.topPanel.update( - transition: panelTransition, - component: AnyComponent(BlurredBackgroundComponent( - color: environment.theme.rootController.navigationBar.blurredBackgroundColor - )), - environment: {}, - containerSize: CGSize(width: availableWidth, height: topPanelHeight) - ) - - let topSeparatorSize = self.topSeparator.update( - transition: panelTransition, - component: AnyComponent(Rectangle( - color: environment.theme.rootController.navigationBar.separatorColor - )), - environment: {}, - containerSize: CGSize(width: availableWidth, height: UIScreenPixel) - ) - let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableWidth, height: topPanelSize.height)) - let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height)) - if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view { - if topPanelView.superview == nil { - if let headerView = self.header.view { - self.insertSubview(topSeparatorView, aboveSubview: headerView) - self.insertSubview(topPanelView, aboveSubview: headerView) - } + let edgeEffectHeight: CGFloat = 88.0 + let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableWidth, height: edgeEffectHeight)) + transition.setFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) + self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition) + if self.topEdgeEffectView.superview == nil { + if let headerView = self.header.view { + self.insertSubview(self.topEdgeEffectView, aboveSubview: headerView) + } + if self.isInAttachmentMenu { + self.addSubview(self.bottomEdgeEffectView) } - panelTransition.setFrame(view: topPanelView, frame: topPanelFrame) - panelTransition.setFrame(view: topSeparatorView, frame: topSeparatorFrame) } + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: self.bounds.height - edgeEffectHeight - environment.additionalInsets.bottom), size: CGSize(width: availableWidth, height: edgeEffectHeight)) + transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + self.bottomEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: transition) + +// var panelTransition = transition +// if self.topPanel.view?.superview != nil && !self.switchingFilter { +// panelTransition = .spring(duration: 0.3) +// } +// let topPanelSize = self.topPanel.update( +// transition: panelTransition, +// component: AnyComponent(BlurredBackgroundComponent( +// color: environment.theme.rootController.navigationBar.blurredBackgroundColor +// )), +// environment: {}, +// containerSize: CGSize(width: availableWidth, height: topPanelHeight) +// ) +// +// let topSeparatorSize = self.topSeparator.update( +// transition: panelTransition, +// component: AnyComponent(Rectangle( +// color: environment.theme.rootController.navigationBar.separatorColor +// )), +// environment: {}, +// containerSize: CGSize(width: availableWidth, height: UIScreenPixel) +// ) +// let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableWidth, height: topPanelSize.height)) +// let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height)) +// if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view { +// if topPanelView.superview == nil { +// if let headerView = self.header.view { +// self.insertSubview(topSeparatorView, aboveSubview: headerView) +// self.insertSubview(topPanelView, aboveSubview: headerView) +// } +// } +// panelTransition.setFrame(view: topPanelView, frame: topPanelFrame) +// panelTransition.setFrame(view: topSeparatorView, frame: topSeparatorFrame) +// } + let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height) if interactive, bottomContentOffset < 320.0, case .transfer = self.starsFilter { self.state?.starGiftsContext.loadMore() @@ -908,7 +942,7 @@ final class GiftOptionsScreenComponent: Component { } self.component = component - let theme = environment.theme + let theme = self.isInAttachmentMenu ? environment.theme.withModalBlocksBackground() : environment.theme let strings = environment.strings if let disallowedGifts = self.state?.disallowedGifts, disallowedGifts == .All, let controller = controller(), !self.dismissed { @@ -927,7 +961,7 @@ final class GiftOptionsScreenComponent: Component { } if themeUpdated { - self.backgroundColor = environment.theme.list.blocksBackgroundColor + self.backgroundColor = theme.list.blocksBackgroundColor } let textColor = theme.list.itemPrimaryTextColor @@ -1015,32 +1049,68 @@ final class GiftOptionsScreenComponent: Component { // transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame) // } - let cancelButtonSize = self.cancelButton.update( - transition: transition, - component: AnyComponent( - PlainButtonComponent( - content: AnyComponent( - MultilineTextComponent( - text: .plain(NSAttributedString(string: strings.Common_Cancel, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)), - horizontalAlignment: .center + var isGlass = false + if let controller = controller(), controller._hasGlassStyle { + isGlass = true + } + + if isGlass { + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor ) - ), - effectAlignment: .center, - action: { + )), + action: { _ in controller()?.dismiss() - }, - animateScale: false - ) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 100.0) - ) - let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0 - cancelButtonSize.height / 2.0), size: cancelButtonSize) - if let cancelButtonView = self.cancelButton.view { - if cancelButtonView.superview == nil { - self.addSubview(cancelButtonView) + } + )), + environment: {}, + containerSize: barButtonSize + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: 16.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + } else { + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + MultilineTextComponent( + text: .plain(NSAttributedString(string: strings.Common_Cancel, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor)), + horizontalAlignment: .center + ) + ), + effectAlignment: .center, + action: { + controller()?.dismiss() + }, + animateScale: false + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let cancelButtonFrame = CGRect(origin: CGPoint(x: environment.safeInsets.left + 16.0, y: environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0 - cancelButtonSize.height / 2.0), size: cancelButtonSize) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) } - transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) } let balanceTitleSize = self.balanceTitle.update( @@ -1049,7 +1119,7 @@ final class GiftOptionsScreenComponent: Component { text: .plain(NSAttributedString( string: strings.Stars_Purchase_Balance, font: Font.regular(14.0), - textColor: environment.theme.actionSheet.primaryTextColor + textColor: theme.actionSheet.primaryTextColor )), maximumNumberOfLines: 1 )), @@ -1060,7 +1130,12 @@ final class GiftOptionsScreenComponent: Component { let formattedBalance = formatStarsAmountText(self.starsState?.balance ?? StarsAmount.zero, dateTimeFormat: environment.dateTimeFormat) let smallLabelFont = Font.regular(11.0) let labelFont = Font.semibold(14.0) - let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: environment.theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator) + let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator) + + var balanceInset: CGFloat = 16.0 + if let controller = controller(), controller._hasGlassStyle { + balanceInset += 6.0 + } let balanceValueSize = self.balanceValue.update( transition: .immediate, @@ -1086,11 +1161,11 @@ final class GiftOptionsScreenComponent: Component { } let navigationHeight = environment.navigationHeight - environment.statusBarHeight let topBalanceOriginY = environment.statusBarHeight + (navigationHeight - balanceTitleSize.height - balanceValueSize.height) / 2.0 - balanceTitleView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0) + balanceTitleView.center = CGPoint(x: availableSize.width - balanceInset - environment.safeInsets.right - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0) balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize) - balanceValueView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0) + balanceValueView.center = CGPoint(x: availableSize.width - balanceInset - environment.safeInsets.right - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0) balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize) - balanceIconView.center = CGPoint(x: availableSize.width - 16.0 - environment.safeInsets.right - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel) + balanceIconView.center = CGPoint(x: availableSize.width - balanceInset - environment.safeInsets.right - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel) balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize) } @@ -1262,6 +1337,7 @@ final class GiftOptionsScreenComponent: Component { let giftItemComponent = GiftItemComponent( context: component.context, + style: .glass, theme: theme, strings: environment.strings, peer: nil, @@ -1509,7 +1585,7 @@ final class GiftOptionsScreenComponent: Component { self.tabSelectorOrigin = contentHeight if let tabSelectorView = self.tabSelector.view { if tabSelectorView.superview == nil { - self.addSubview(tabSelectorView) + self.scrollView.addSubview(tabSelectorView) } transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabSelectorSize.width) / 2.0), y: contentHeight), size: tabSelectorSize)) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD index 910119fa75..15fee5ce98 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD @@ -48,6 +48,7 @@ swift_library( "//submodules/ProgressNavigationButtonNode", "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/ConfettiEffect", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index 0d0effd69a..82f9a77534 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -36,6 +36,7 @@ import Markdown import GiftViewScreen import UndoUI import ConfettiEffect +import EdgeEffect final class GiftSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -89,6 +90,7 @@ final class GiftSetupScreenComponent: Component { private let upgradeSection = ComponentView() private let hideSection = ComponentView() + private let edgeEffectView: EdgeEffectView private let buttonBackground = ComponentView() private let buttonSeparator = SimpleLayer() private let button = ComponentView() @@ -159,12 +161,16 @@ final class GiftSetupScreenComponent: Component { } self.scrollView.alwaysBounceVertical = true + self.edgeEffectView = EdgeEffectView() + super.init(frame: frame) self.scrollView.delegate = self self.addSubview(self.scrollView) self.scrollView.layer.addSublayer(self.topOverscrollLayer) + + self.addSubview(self.edgeEffectView) self.disablesInteractiveKeyboardGestureRecognizer = true } @@ -1400,10 +1406,10 @@ final class GiftSetupScreenComponent: Component { self.buttonSeparator.backgroundColor = environment.theme.rootController.tabBar.separatorColor.cgColor if let view = self.buttonBackground.view { - if view.superview == nil { - self.addSubview(view) - self.layer.addSublayer(self.buttonSeparator) - } +// if view.superview == nil { +// self.addSubview(view) +// self.layer.addSublayer(self.buttonSeparator) +// } view.frame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height), size: bottomPanelSize) self.buttonSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelSize.height), size: CGSize(width: availableSize.width, height: UIScreenPixel)) } @@ -1673,6 +1679,11 @@ final class GiftSetupScreenComponent: Component { } } + let edgeEffectHeight: CGFloat = bottomPanelHeight + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) + self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0)) self.ignoreScrolling = false diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index c0ce3d9f34..f96af66151 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -266,11 +266,11 @@ public class GlassBackgroundView: UIView { } } - private struct Params: Equatable { - let cornerRadius: CGFloat - let isDark: Bool - let tintColor: TintColor - let isInteractive: Bool + public struct Params: Equatable { + public let cornerRadius: CGFloat + public let isDark: Bool + public let tintColor: TintColor + public let isInteractive: Bool init(cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool) { self.cornerRadius = cornerRadius @@ -301,8 +301,8 @@ public class GlassBackgroundView: UIView { } } - private var params: Params? - + public private(set) var params: Params? + public static var useCustomGlassImpl: Bool = false public override init(frame: CGRect) { @@ -375,7 +375,7 @@ public class GlassBackgroundView: UIView { } return nil } - + public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, isInteractive: Bool = false, transition: ComponentTransition) { if let nativeView = self.nativeView, nativeView.bounds.size != size { @@ -383,7 +383,8 @@ public class GlassBackgroundView: UIView { nativeView.layer.cornerRadius = cornerRadius nativeView.frame = CGRect(origin: CGPoint(), size: size) } else { - nativeView.layer.cornerRadius = cornerRadius + //nativeView.layer.cornerRadius = cornerRadius + transition.setCornerRadius(layer: nativeView.layer, cornerRadius: cornerRadius) let nativeFrame = CGRect(origin: CGPoint(), size: size) @@ -556,95 +557,6 @@ public final class GlassBackgroundContainerView: UIView { } } -public final class VariableBlurView: UIVisualEffectView { - public let maxBlurRadius: CGFloat - - public var gradientMask: UIImage { - didSet { - if self.gradientMask !== oldValue { - self.resetEffect() - } - } - } - - public init(gradientMask: UIImage, maxBlurRadius: CGFloat = 20.0) { - self.gradientMask = gradientMask - self.maxBlurRadius = maxBlurRadius - - super.init(effect: UIBlurEffect(style: .regular)) - - self.resetEffect() - - if self.subviews.indices.contains(1) { - let tintOverlayView = subviews[1] - tintOverlayView.alpha = 0 - } - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { - self.resetEffect() - } - } - - private func resetEffect() { - let filterClassStringEncoded = "Q0FGaWx0ZXI=" - let filterClassString: String = { - if - let data = Data(base64Encoded: filterClassStringEncoded), - let string = String(data: data, encoding: .utf8) - { - return string - } - - return "" - }() - let filterWithTypeStringEncoded = "ZmlsdGVyV2l0aFR5cGU6" - let filterWithTypeString: String = { - if - let data = Data(base64Encoded: filterWithTypeStringEncoded), - let string = String(data: data, encoding: .utf8) - { - return string - } - - return "" - }() - - let filterWithTypeSelector = Selector(filterWithTypeString) - - guard let filterClass = NSClassFromString(filterClassString) as AnyObject as? NSObjectProtocol else { - return - } - - guard filterClass.responds(to: filterWithTypeSelector) else { - return - } - - let variableBlur = filterClass.perform(filterWithTypeSelector, with: "variableBlur").takeUnretainedValue() - - guard let variableBlur = variableBlur as? NSObject else { - return - } - - guard let gradientImageRef = self.gradientMask.cgImage else { - return - } - - variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius") - variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage") - variableBlur.setValue(true, forKey: "inputNormalizeEdges") - variableBlur.setValue(UIScreenScale, forKey: "scale") - - let backdropLayer = self.subviews.first?.layer - backdropLayer?.filters = [variableBlur] - } -} - private extension CGContext { func addBadgePath(in rect: CGRect) { saveGState() diff --git a/submodules/TelegramUI/Components/GlassBarButtonComponent/BUILD b/submodules/TelegramUI/Components/GlassBarButtonComponent/BUILD new file mode 100644 index 0000000000..f47de8645f --- /dev/null +++ b/submodules/TelegramUI/Components/GlassBarButtonComponent/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GlassBarButtonComponent", + module_name = "GlassBarButtonComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift new file mode 100644 index 0000000000..9acc11188b --- /dev/null +++ b/submodules/TelegramUI/Components/GlassBarButtonComponent/Sources/GlassBarButtonComponent.swift @@ -0,0 +1,346 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import GlassBackgroundComponent + +public final class GlassBarButtonComponent: Component { + public enum DisplayState: Equatable { + case generic + case glass + case tintedGlass + } + + public let size: CGSize + public let backgroundColor: UIColor + public let isDark: Bool + public let state: DisplayState? + public let isEnabled: Bool + public let component: AnyComponentWithIdentity + public let action: ((UIView) -> Void)? + + public init( + size: CGSize, + backgroundColor: UIColor, + isDark: Bool, + state: DisplayState? = nil, + isEnabled: Bool = true, + component: AnyComponentWithIdentity, + action: ((UIView) -> Void)? + ) { + self.size = size + self.backgroundColor = backgroundColor + self.isDark = isDark + self.state = state + self.isEnabled = isEnabled + self.component = component + self.action = action + } + + public static func ==(lhs: GlassBarButtonComponent, rhs: GlassBarButtonComponent) -> Bool { + if lhs.size != rhs.size { + return false + } + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.isDark != rhs.isDark { + return false + } + if lhs.state != rhs.state { + return false + } + if lhs.isEnabled != rhs.isEnabled { + return false + } + if lhs.component != rhs.component { + return false + } + return true + } + + public final class View: HighlightTrackingButton { + private let containerView: UIView + private let genericContainerView: UIView + private let genericBackgroundView: SimpleGlassView + private let glassContainerView: UIView + private let glassBackgroundView: GlassBackgroundView + private var componentView: ComponentView? + + private var component: GlassBarButtonComponent? + + public override init(frame: CGRect) { + self.containerView = UIView() + self.genericContainerView = UIView() + self.genericBackgroundView = SimpleGlassView() + self.glassContainerView = UIView() + self.glassBackgroundView = GlassBackgroundView() + + super.init(frame: frame) + + self.glassBackgroundView.isUserInteractionEnabled = false + self.containerView.isUserInteractionEnabled = false + + self.addSubview(self.containerView) + self.containerView.addSubview(self.genericContainerView) + self.containerView.addSubview(self.glassContainerView) + + self.genericContainerView.addSubview(self.genericBackgroundView) + self.glassContainerView.addSubview(self.glassBackgroundView) + + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + //let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring)) + if highlighted { + self.containerView.layer.animateSpring(from: CGFloat((self.containerView.layer.presentation()?.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0) as NSNumber, to: 1.3636 as NSNumber, keyPath: "transform.scale", duration: 0.5, removeOnCompletion: false) + } else { + self.containerView.layer.animateSpring(from: CGFloat((self.containerView.layer.presentation()?.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.3636) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6) + } + } + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func pressed() { + guard let component = self.component, let action = component.action else { + return + } + action(self) + } + + func update(component: GlassBarButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component + self.component = component + + self.isEnabled = component.isEnabled + + var componentView: ComponentView + var animateAppearance = false + if previousComponent?.component.id != component.component.id { + if let componentView = self.componentView { + animateAppearance = true + self.componentView = nil + if let view = componentView.view { + transition.setScale(view: view, scale: 0.01) + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + } + } + } + + var componentTransition = transition + if let current = self.componentView { + componentView = current + } else { + componentTransition = .immediate + componentView = ComponentView() + self.componentView = componentView + } + + let componentSize = componentView.update( + transition: componentTransition, + component: component.component.component, + environment: {}, + containerSize: component.size + ) + let componentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((component.size.width - componentSize.width) / 2.0), y: floorToScreenPixels((component.size.height - componentSize.height) / 2.0)), size: componentSize) + if let view = componentView.view { + if view.superview == nil { + self.containerView.addSubview(view) + if animateAppearance { + transition.animateScale(view: view, from: 0.01, to: 1.0) + transition.animateAlpha(view: view, from: 0.0, to: 1.0) + } + } + componentTransition.setFrame(view: view, frame: componentFrame) + } + + let effectiveState = component.state ?? .glass + var genericAlpha: CGFloat = 1.0 + var glassAlpha: CGFloat = 1.0 + switch effectiveState { + case .generic: + genericAlpha = 1.0 + glassAlpha = 0.0 + case .glass, .tintedGlass: + glassAlpha = 1.0 + genericAlpha = 0.0 + } + + let cornerRadius = component.size.height * 0.5 + self.genericBackgroundView.update(size: component.size, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: component.backgroundColor), transition: transition) + self.glassBackgroundView.update(size: component.size, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: component.backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), transition: transition) + + let bounds = CGRect(origin: .zero, size: component.size) + transition.setFrame(view: self.containerView, frame: bounds) + + transition.setAlpha(view: self.genericContainerView, alpha: genericAlpha) + transition.setFrame(view: self.genericContainerView, frame: bounds) + + transition.setAlpha(view: self.glassContainerView, alpha: glassAlpha) + transition.setFrame(view: self.glassContainerView, frame: bounds) + + transition.setFrame(view: self.genericBackgroundView, frame: bounds) + transition.setFrame(view: self.glassBackgroundView, frame: bounds) + + return component.size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + + + +public final class BarComponentHostView: UIView { + private let hostView = ComponentHostView() + + public func update(component: AnyComponent, transition: ComponentTransition) { + let _ = self.hostView.update( + transition: transition, + component: component, + environment: {}, + containerSize: CGSize(width: 120.0, height: 40.0) + ) + } + + public override func sizeThatFits(_ size: CGSize) -> CGSize { + return CGSize() + } +} + +public final class BarComponentHostNode: ASDisplayNode { + public var component: AnyComponentWithIdentity? { + didSet { + self.updateComponent(previousComponent: oldValue, transition: .spring(duration: 0.4)) + } + } + private var componentView: ComponentView? + + private let size: CGSize + + public init(component: AnyComponentWithIdentity?, size: CGSize) { + self.component = component + self.size = size + + super.init() + self.clipsToBounds = false + + self.updateComponent(previousComponent: nil, transition: .immediate) + } + + private func updateComponent(previousComponent: AnyComponentWithIdentity?, transition: ComponentTransition) { + if previousComponent?.id != self.component?.id { + if let componentView = self.componentView { + self.componentView = nil + if let view = componentView.view { + transition.setAlpha(view: view, alpha: 0.0, completion: { _ in + view.removeFromSuperview() + }) + transition.setScale(view: view, scale: 0.01) + } + } + } + + if let component = self.component { + var componentTransition = transition + let componentView: ComponentView + if let current = self.componentView { + componentView = current + } else { + componentTransition = .immediate + componentView = ComponentView() + self.componentView = componentView + } + + let _ = componentView.update( + transition: componentTransition, + component: component.component, + environment: {}, + containerSize: self.size + ) + if let view = componentView.view { + if view.superview == nil { + self.view.addSubview(view) + + if !transition.animation.isImmediate { + transition.animateAlpha(view: view, from: 0.0, to: 1.0) + transition.animateScale(view: view, from: 0.01, to: 1.0) + } + } + view.frame = CGRect(origin: CGPoint(x: 0.0, y: 3.0), size: self.size) + } + } + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return self.size + } +} + +private class SimpleGlassView: UIView { + private let backgroundNode: NavigationBackgroundNode + private let foregroundView: UIImageView + + private struct Params: Equatable { + let size: CGSize + let cornerRadius: CGFloat + let isDark: Bool + let tintColor: GlassBackgroundView.TintColor + + init(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: GlassBackgroundView.TintColor) { + self.size = size + self.cornerRadius = cornerRadius + self.isDark = isDark + self.tintColor = tintColor + } + } + + private var params: Params? + + public override init(frame: CGRect) { + self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 8.0) + self.foregroundView = UIImageView() + + super.init(frame: frame) + + self.addSubview(self.backgroundNode.view) + self.addSubview(self.foregroundView) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return nil + } + + public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: GlassBackgroundView.TintColor, isInteractive: Bool = false, transition: ComponentTransition) { + self.backgroundNode.updateColor(color: tintColor.color, forceKeepBlur: tintColor.color.alpha != 1.0, transition: transition.containedViewLayoutTransition) + self.backgroundNode.update(size: size, cornerRadius: cornerRadius, transition: transition.containedViewLayoutTransition) + transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size)) + + transition.setFrame(view: self.foregroundView, frame: CGRect(origin: CGPoint(), size: size)) + + let params = Params(size: size, cornerRadius: cornerRadius, isDark: isDark, tintColor: tintColor) + if self.params != params { + self.params = params + self.foregroundView.image = GlassBackgroundView.generateForegroundImage(size: size, isDark: isDark, fillColor: .clear) + } + } +} diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift index e29543ef28..a536dab892 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackCurrentItem.swift @@ -24,15 +24,17 @@ enum GroupStickerPackCurrentItemContent: Equatable { final class GroupStickerPackCurrentItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let account: Account let content: GroupStickerPackCurrentItemContent let sectionId: ItemListSectionId let action: (() -> Void)? let remove: (() -> Void)? - init(theme: PresentationTheme, strings: PresentationStrings, account: Account, content: GroupStickerPackCurrentItemContent, sectionId: ItemListSectionId, action: (() -> Void)?, remove: (() -> Void)?) { + init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, account: Account, content: GroupStickerPackCurrentItemContent, sectionId: ItemListSectionId, action: (() -> Void)?, remove: (() -> Void)?) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.account = account self.content = content self.sectionId = sectionId @@ -293,8 +295,16 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = 65.0 + params.leftInset let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: 59.0) + + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset += 4.0 + } + + let contentSize = CGSize(width: params.width, height: 59.0 + verticalInset * 2.0) + let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) let layoutSize = layout.size @@ -404,13 +414,13 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.removeButtonIcon.image = PresentationResourcesItemList.itemListRemoveIconImage(item.theme) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) - transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) + transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))) let titleVerticalOffset: CGFloat if statusLayout.size.width.isZero { @@ -418,24 +428,24 @@ class GroupStickerPackCurrentItemNode: ItemListRevealOptionsItemNode { } else { titleVerticalOffset = 11.0 } - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: titleVerticalOffset), size: titleLayout.size)) - transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 32.0), size: statusLayout.size)) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleVerticalOffset), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset, y: verticalInset + 32.0), size: statusLayout.size)) let boundingSize = CGSize(width: 34.0, height: 34.0) let indicatorSize = CGSize(width: 22.0, height: 22.0) - transition.updateFrame(node: strongSelf.activityIndicator, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0 + floor((boundingSize.width - indicatorSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - indicatorSize.height) / 2.0)), size: indicatorSize)) + transition.updateFrame(node: strongSelf.activityIndicator, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0 + floor((boundingSize.width - indicatorSize.width) / 2.0), y: verticalInset + 11.0 + floor((boundingSize.height - indicatorSize.height) / 2.0)), size: indicatorSize)) if let image = updatedNotFoundImage { strongSelf.notFoundNode.image = image } if let image = strongSelf.notFoundNode.image { - transition.updateFrame(node: strongSelf.notFoundNode, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0 + floor((boundingSize.width - image.size.width) / 2.0), y: 13.0 + floor((boundingSize.height - image.size.height) / 2.0)), size: image.size)) + transition.updateFrame(node: strongSelf.notFoundNode, frame: CGRect(origin: CGPoint(x: params.leftInset + 15.0 + floor((boundingSize.width - image.size.width) / 2.0), y: verticalInset + 13.0 + floor((boundingSize.height - image.size.height) / 2.0)), size: image.size)) } if let updatedImageSignal = updatedImageSignal { strongSelf.imageNode.setSignal(updatedImageSignal) } - let imageFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: 11.0 + floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) + let imageFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: verticalInset + 11.0 + floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame) strongSelf.removeButton.frame = CGRect(origin: CGPoint(x: layoutSize.width - params.rightInset - layoutSize.height, y: 0.0), size: CGSize(width: layoutSize.height, height: layoutSize.height)) diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift index 87b9e28665..9c4eff3f94 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerPackSetupController.swift @@ -204,7 +204,7 @@ private enum GroupStickerPackEntry: ItemListNodeEntry { switch self { case let .search(theme, _, prefix, placeholder, value): let isEmoji = prefix.contains("addemoji") - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), spacing: 0.0, clearType: .none, tag: nil, sectionId: self.section, textUpdated: { value in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: prefix, textColor: theme.list.itemPrimaryTextColor), text: value, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), spacing: 0.0, clearType: .none, tag: nil, sectionId: self.section, textUpdated: { value in arguments.updateSearchText(value) }, processPaste: { text in if let url = (URL(string: text) ?? URL(string: "http://" + text)), url.host == "t.me" || url.host == "telegram.me" { @@ -220,7 +220,7 @@ private enum GroupStickerPackEntry: ItemListNodeEntry { case let .packsTitle(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .pack(_, _, _, info, topItem, count, playAnimatedStickers, selected): - return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: selected ? .selection : .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: playAnimatedStickers, sectionId: self.section, action: { + return ItemListStickerPackItem(presentationData: presentationData, context: arguments.context, systemStyle: .glass, packInfo: StickerPackCollectionInfo.Accessor(info), itemCount: count, topItem: topItem, unread: false, control: selected ? .selection : .none, editing: ItemListStickerPackItemEditing(editable: false, editing: false, revealed: false, reorderable: false, selectable: false), enabled: true, playAnimatedStickers: playAnimatedStickers, sectionId: self.section, action: { if selected { arguments.openStickerPack(info) } else { @@ -232,7 +232,7 @@ private enum GroupStickerPackEntry: ItemListNodeEntry { }, toggleSelected: { }) case let .currentPack(_, theme, strings, content): - return GroupStickerPackCurrentItem(theme: theme, strings: strings, account: arguments.context.account, content: content, sectionId: self.section, action: { + return GroupStickerPackCurrentItem(theme: theme, strings: strings, systemStyle: .glass, account: arguments.context.account, content: content, sectionId: self.section, action: { if case let .found(packInfo, _, _) = content { arguments.openStickerPack(packInfo) } diff --git a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift index e22fd30031..7f275d2601 100644 --- a/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift +++ b/submodules/TelegramUI/Components/GroupStickerPackSetupController/Sources/GroupStickerSearchItem.swift @@ -54,7 +54,7 @@ final class GroupStickerSearchItem: ItemListControllerSearch { } } - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } if let current = current as? GroupStickerSearchNavigationContentNode { current.updateTheme(presentationData.theme) diff --git a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift index b6b6b4ffdf..798088a4d5 100644 --- a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift +++ b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift @@ -249,7 +249,7 @@ public func legacyInstantVideoController(theme: PresentationTheme, forStory: Boo attributes.append(NotificationInfoMessageAttribute(flags: .muted)) } if let scheduleTime = scheduleTime { - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil)) } return attributes } diff --git a/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift b/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift index d30878f224..f0784bd4a2 100644 --- a/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift +++ b/submodules/TelegramUI/Components/ListComposePollOptionComponent/Sources/ListComposePollOptionComponent.swift @@ -15,6 +15,11 @@ import PlainButtonComponent import SwiftSignalKit public final class ListComposePollOptionComponent: Component { + public enum Style { + case glass + case legacy + } + public final class ResetText: Equatable { public let value: NSAttributedString @@ -70,6 +75,7 @@ public final class ListComposePollOptionComponent: Component { public let externalState: TextFieldComponent.ExternalState? public let context: AccountContext + public let style: Style public let theme: PresentationTheme public let strings: PresentationStrings public let placeholder: NSAttributedString? @@ -93,6 +99,7 @@ public final class ListComposePollOptionComponent: Component { public init( externalState: TextFieldComponent.ExternalState?, context: AccountContext, + style: Style = .legacy, theme: PresentationTheme, strings: PresentationStrings, placeholder: NSAttributedString? = nil, @@ -115,6 +122,7 @@ public final class ListComposePollOptionComponent: Component { ) { self.externalState = externalState self.context = context + self.style = style self.theme = theme self.strings = strings self.placeholder = placeholder @@ -143,6 +151,9 @@ public final class ListComposePollOptionComponent: Component { if lhs.context !== rhs.context { return false } + if lhs.style != rhs.style { + return false + } if lhs.theme !== rhs.theme { return false } @@ -550,7 +561,10 @@ public final class ListComposePollOptionComponent: Component { self.component = component self.state = state - let verticalInset: CGFloat = 12.0 + var verticalInset: CGFloat = 12.0 + if case .glass = component.style { + verticalInset = 16.0 + } var leftInset: CGFloat = 16.0 var rightInset: CGFloat = 16.0 let modeSelectorSize = CGSize(width: 32.0, height: 32.0) @@ -859,7 +873,10 @@ public final class ListComposePollOptionComponent: Component { return } - let verticalInset: CGFloat = 12.0 + var verticalInset: CGFloat = 12.0 + if case .glass = component.style { + verticalInset = 16.0 + } var leftInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0 diff --git a/submodules/TelegramUI/Components/ListItemComponentAdaptor/Sources/ListItemComponentAdaptor.swift b/submodules/TelegramUI/Components/ListItemComponentAdaptor/Sources/ListItemComponentAdaptor.swift index 066ed407f8..ebbc50d90f 100644 --- a/submodules/TelegramUI/Components/ListItemComponentAdaptor/Sources/ListItemComponentAdaptor.swift +++ b/submodules/TelegramUI/Components/ListItemComponentAdaptor/Sources/ListItemComponentAdaptor.swift @@ -17,11 +17,13 @@ public final class ListItemComponentAdaptor: Component { private let itemImpl: () -> ListViewItem private let params: ListViewItemLayoutParams private let action: (() -> Void)? + private let tag: AnyObject? public init( itemGenerator: ItemGeneratorType, params: ListViewItemLayoutParams, - action: (() -> Void)? = nil + action: (() -> Void)? = nil, + tag: AnyObject? = nil ) { self.itemGenerator = itemGenerator self.isEqualImpl = { other in @@ -36,6 +38,7 @@ public final class ListItemComponentAdaptor: Component { } self.params = params self.action = action + self.tag = tag } public static func ==(lhs: ListItemComponentAdaptor, rhs: ListItemComponentAdaptor) -> Bool { @@ -48,15 +51,28 @@ public final class ListItemComponentAdaptor: Component { if (lhs.action == nil) != (rhs.action == nil) { return false } + if lhs.tag !== rhs.tag { + return false + } return true } - public final class View: UIView { + public final class View: UIView, ComponentTaggedView { private var button: HighlightTrackingButton? public var itemNode: ListViewItemNode? private var component: ListItemComponentAdaptor? + public func matches(tag: Any) -> Bool { + if let component = self.component, let componentTag = component.tag { + let tag = tag as AnyObject + if componentTag === tag { + return true + } + } + return false + } + @objc private func pressed() { guard let component = self.component else { return diff --git a/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift b/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift index 9a67b39016..e562bcbad8 100644 --- a/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift +++ b/submodules/TelegramUI/Components/ListTextFieldItemComponent/Sources/ListTextFieldItemComponent.swift @@ -9,6 +9,11 @@ import PlainButtonComponent import BundleIconComponent public final class ListTextFieldItemComponent: Component { + public enum Style { + case glass + case legacy + } + public final class ResetText: Equatable { public let value: String @@ -21,6 +26,7 @@ public final class ListTextFieldItemComponent: Component { } } + public let style: Style public let theme: PresentationTheme public let initialText: String public let resetText: ResetText? @@ -31,6 +37,7 @@ public final class ListTextFieldItemComponent: Component { public let tag: AnyObject? public init( + style: Style = .legacy, theme: PresentationTheme, initialText: String, resetText: ResetText? = nil, @@ -40,6 +47,7 @@ public final class ListTextFieldItemComponent: Component { updated: ((String) -> Void)?, tag: AnyObject? = nil ) { + self.style = style self.theme = theme self.initialText = initialText self.resetText = resetText @@ -51,6 +59,9 @@ public final class ListTextFieldItemComponent: Component { } public static func ==(lhs: ListTextFieldItemComponent, rhs: ListTextFieldItemComponent) -> Bool { + if lhs.style != rhs.style { + return false + } if lhs.theme !== rhs.theme { return false } @@ -144,6 +155,10 @@ public final class ListTextFieldItemComponent: Component { return false } + public func activateInput() { + self.textField.becomeFirstResponder() + } + func update(component: ListTextFieldItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -180,7 +195,10 @@ public final class ListTextFieldItemComponent: Component { self.textField.textColor = component.theme.list.itemPrimaryTextColor } - let verticalInset: CGFloat = 12.0 + var verticalInset: CGFloat = 12.0 + if case .glass = component.style { + verticalInset = 16.0 + } let sideInset: CGFloat = 16.0 self.textField.sideInset = sideInset diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 1f77d01911..5d8f928c93 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -286,7 +286,7 @@ public final class MediaEditor { } public var sourceIsVideo: Bool { - self.player != nil + return self.player != nil } public var resultIsVideo: Bool { @@ -2258,13 +2258,11 @@ public final class MediaEditor { Queue.mainQueue().after(delay - (currentTime - previousUpdateTime)) { self.scheduledUpdate = false self.previousUpdateTime = CACurrentMediaTime() - self.renderer.willRenderFrame() - self.renderer.renderFrame() + self.requestRenderFrame() } } else { self.previousUpdateTime = currentTime - self.renderer.willRenderFrame() - self.renderer.renderFrame() + self.requestRenderFrame() } } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift index 69a75e3123..0b23bc037d 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/AdjustmentsComponent.swift @@ -192,7 +192,7 @@ final class AdjustmentSliderComponent: Component { let valueSize = self.value.update( transition: .immediate, component: AnyComponent( - Text(text: valueText, font: Font.with(size: 14.0, traits: .monospacedNumbers), color: UIColor(rgb: 0xf8d74a)) + Text(text: valueText, font: Font.with(size: 14.0, traits: .monospacedNumbers), color: UIColor(rgb: 0xffd300)) ), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift index 6164834f2e..3cf59203b7 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/BlurComponent.swift @@ -61,7 +61,7 @@ private final class BlurModeComponent: Component { component: AnyComponent( Image( image: component.icon, - tintColor: component.isSelected ? UIColor(rgb: 0xf8d74a) : .white, + tintColor: component.isSelected ? UIColor(rgb: 0xffd300) : .white, size: CGSize(width: 30.0, height: 30.0) ) ), @@ -74,7 +74,7 @@ private final class BlurModeComponent: Component { Text( text: component.title, font: Font.regular(14.0), - color: component.isSelected ? UIColor(rgb: 0xf8d74a) : UIColor(rgb: 0x808080) + color: component.isSelected ? UIColor(rgb: 0xffd300) : UIColor(rgb: 0x808080) ) ), environment: {}, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 337987c5cd..6a9f8d871e 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3151,7 +3151,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.previewContainerView = UIView() self.previewContainerView.alpha = 0.0 self.previewContainerView.clipsToBounds = true - self.previewContainerView.layer.cornerRadius = 12.0 + self.previewContainerView.layer.cornerRadius = 30.0 //12.0 if #available(iOS 13.0, *) { self.previewContainerView.layer.cornerCurve = .continuous } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift index 1f94f2ff04..ec53f4d4eb 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift @@ -80,7 +80,7 @@ private final class ToolIconComponent: Component { if component.isSelected { iconColor = .black } else { - iconColor = component.isActive ? UIColor(rgb: 0xf8d74a) : .white + iconColor = component.isActive ? UIColor(rgb: 0xffd300) : .white } let iconSize = self.icon.update( diff --git a/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift b/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift index 1486eacdc8..51bbdf6cf1 100644 --- a/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift +++ b/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift @@ -1866,7 +1866,7 @@ public class TrimView: UIView { fullTrackHeight = trackHeight capsuleOffset = 5.0 - UIScreenPixel color = .white - highlightColor = UIColor(rgb: 0xf8d74a) + highlightColor = UIColor(rgb: 0xffd300) if isFirstTime { self.borderView.image = generateImage(CGSize(width: 1.0, height: fullTrackHeight), rotatedContext: { size, context in diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift index 1306d1275f..38b2e173f5 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift @@ -608,7 +608,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { let arguments = arguments as! NotificationPeerExceptionArguments switch self { case let .remove(_, _, strings): - return ItemListActionItem(presentationData: presentationData, title: strings.Notification_Exceptions_RemoveFromExceptions, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: strings.Notification_Exceptions_RemoveFromExceptions, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.removeFromExceptions() }) case let .switcher(_, _, strings, mode, selected): @@ -619,7 +619,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { case .alwaysOff: title = strings.Notification_Exceptions_AlwaysOff } - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectMode(mode) }) case let .switcherHeader(_, _, text): @@ -632,7 +632,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { case .alwaysOff: title = strings.Notification_Exceptions_MessagePreviewAlwaysOff } - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectDisplayPreviews(value) }) case let .showSender(_, _, strings, value, selected): @@ -643,7 +643,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { case .alwaysOff: title = strings.Notification_Exceptions_MessagePreviewAlwaysOff } - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectHideStoriesSender(value) }) case let .cloudHeader(_, text): @@ -652,7 +652,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .uploadSound(_, text): let icon = PresentationResourcesItemList.uploadToneIcon(presentationData.theme) - return ItemListCheckboxItem(presentationData: presentationData, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: icon, iconSize: nil, iconPlacement: .check, title: text, style: .left, textColor: .accent, checked: false, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.upload() }) case let .displayPreviewsHeader(_, _, text): @@ -665,7 +665,7 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { case .alwaysOff: title = strings.Notification_Exceptions_MessagePreviewAlwaysOff } - return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: title, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectStoriesMuted(value) }) case let .storyNotificationsHeader(_, _, text): @@ -675,15 +675,15 @@ private enum NotificationPeerExceptionEntry: ItemListNodeEntry { case let .soundClassicHeader(_, _, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .none(_, _, _, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: true, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: true, sectionId: self.section, action: { arguments.selectSound(.none) }) case let .default(_, _, _, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectSound(.default) }) case let .sound(_, _, _, text, sound, selected, canBeDeleted): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.selectSound(sound) }, deleteAction: canBeDeleted ? { arguments.deleteSound(sound, text) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 7ced9eef64..a7d4c39c76 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -150,6 +150,7 @@ swift_library( "//submodules/TelegramUI/Components/PeerManagement/OldChannelsController", "//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/UrlHandling", + "//submodules/Pasteboard", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/TelegramUI/Components/MediaEditor", "//submodules/TelegramUI/Components/MediaEditorScreen", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenActionItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenActionItem.swift index c971e48523..64ab4e9b1d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenActionItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenActionItem.swift @@ -127,10 +127,8 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode { self.activateArea.accessibilityLabel = item.text let textSize = self.textNode.updateLayout(CGSize(width: width - (leftInset + rightInset), height: .greatestFiniteMagnitude)) - - let textFrame = CGRect(origin: CGPoint(x: item.alignment == .center ? floorToScreenPixels((width - textSize.width) / 2.0) : leftInset, y: 12.0), size: textSize) - - let height = textSize.height + 24.0 + let height = textSize.height + 32.0 + let textFrame = CGRect(origin: CGPoint(x: item.alignment == .center ? floorToScreenPixels((width - textSize.width) / 2.0) : leftInset, y: floorToScreenPixels((height - textSize.height) / 2.0)), size: textSize) if let icon = item.icon { if self.iconNode.supernode == nil { @@ -167,7 +165,7 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode { let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners @@ -175,7 +173,9 @@ private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode { self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition) transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset))) - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: height - UIScreenPixel), size: CGSize(width: width - separatorInset, height: UIScreenPixel))) + let separatorRightInset: CGFloat = 16.0 + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: height - UIScreenPixel), size: CGSize(width: width - separatorInset - separatorRightInset, height: UIScreenPixel))) transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) self.activateArea.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift index 977ecb3c81..7f4204da32 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenDisclosureItem.swift @@ -212,9 +212,8 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { } let labelSize = self.labelNode.updateLayout(CGSize(width: labelConstrainWidth, height: .greatestFiniteMagnitude)) - let textFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: textSize) - - let height = textSize.height + 24.0 + let height = textSize.height + 32.0 + let textFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((height - textSize.height) / 2.0)), size: textSize) if item.icon != nil || item.iconSignal != nil { if self.iconNode.supernode == nil { @@ -339,7 +338,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { } else if case .labelBadge = item.label { labelFrame = CGRect(origin: CGPoint(x: width - rightInset - badgeWidth + (badgeWidth - labelSize.width) / 2.0, y: floor((height - labelSize.height) / 2.0)), size: labelSize) } else { - labelFrame = CGRect(origin: CGPoint(x: width - rightInset - labelSize.width, y: 12.0), size: labelSize) + labelFrame = CGRect(origin: CGPoint(x: width - rightInset - labelSize.width, y: floorToScreenPixels((height - labelSize.height) / 2.0)), size: labelSize) } if let additionalBadgeLabel = item.additionalBadgeLabel { @@ -398,7 +397,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners @@ -406,7 +405,9 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition) transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset))) - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: height - UIScreenPixel), size: CGSize(width: width - separatorInset, height: UIScreenPixel))) + let separatorRightInset: CGFloat = 16.0 + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: height - UIScreenPixel), size: CGSize(width: width - separatorInset - separatorRightInset, height: UIScreenPixel))) transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) self.activateArea.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift index 71cb89c1e1..4dd64be939 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenInfoItem.swift @@ -67,7 +67,7 @@ private final class PeerInfoScreenInfoItemNode: PeerInfoScreenItemNode { self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - let infoItem = InfoListItem(presentationData: ItemListPresentationData(presentationData), title: item.title, text: item.text, style: .blocks, hasDecorations: false, isWarning: item.isWarning, linkAction: { link in + let infoItem = InfoListItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: item.title, text: item.text, style: .blocks, hasDecorations: false, isWarning: item.isWarning, linkAction: { link in item.linkAction?(link) }, closeAction: nil) let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0) @@ -110,7 +110,7 @@ private final class PeerInfoScreenInfoItemNode: PeerInfoScreenItemNode { let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift index 9f8a03df3d..6d3e856c62 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -631,7 +631,7 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.expandButonNode.isHidden = true } - var topOffset = 11.0 + var topOffset = 15.0 var height = topOffset * 2.0 let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: topOffset), size: labelSize) let rightLabelFrame = CGRect(origin: CGPoint(x: width - sideInset - rightLabelSize.width, y: topOffset), size: rightLabelSize) @@ -801,14 +801,16 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition) transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset))) - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel))) + let separatorRightInset: CGFloat = 16.0 + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset - separatorRightInset, height: UIScreenPixel))) transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) let hasCorners = hasCorners && (topItem == nil || bottomItem == nil) let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift index 33af7c50da..1dff7c738b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenMemberItem.swift @@ -194,7 +194,7 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode { itemText = .presence } - let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: EnginePeer(item.member.peer), height: itemHeight, presence: item.member.presence.flatMap(EnginePeer.Presence.init), text: itemText, label: itemLabel, editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: nil), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: false, animateFirstAvatarTransition: !item.isAccount, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in + let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: EnginePeer(item.member.peer), height: itemHeight, presence: item.member.presence.flatMap(EnginePeer.Presence.init), text: itemText, label: itemLabel, editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: nil), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: false, animateFirstAvatarTransition: !item.isAccount, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in }, removePeer: { _ in @@ -243,7 +243,7 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode { let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners @@ -260,7 +260,9 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode { separatorInset += 49.0 } - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel))) + let separatorRightInset: CGFloat = 16.0 + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: height - UIScreenPixel), size: CGSize(width: width - separatorInset - separatorRightInset, height: UIScreenPixel))) transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) return height diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift index f9231856cf..613542bba6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenNoteListItem.swift @@ -101,8 +101,8 @@ final class PeerInfoScreenNoteListItemNode: PeerInfoScreenItemNode { environment: {}, containerSize: CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude) ) - let textFieldFrame = CGRect(origin: CGPoint(x: sideInset, y: 3.0), size: textFieldSize) - let height: CGFloat = 4.0 + textFieldSize.height + let textFieldFrame = CGRect(origin: CGPoint(x: sideInset, y: 7.0), size: textFieldSize) + let height: CGFloat = 12.0 + textFieldSize.height if let textFieldView = self.textField.view { if textFieldView.superview == nil { self.view.addSubview(textFieldView) @@ -118,7 +118,7 @@ final class PeerInfoScreenNoteListItemNode: PeerInfoScreenItemNode { let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index edad6bc318..4791187924 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -621,7 +621,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod interaction: chatListNodeInteraction ) var itemNode: ListViewItemNode? - let params = ListViewItemLayoutParams(width: width - safeInsets.left - safeInsets.right, leftInset: 0.0, rightInset: 0.0, availableHeight: 1000.0) + let params = ListViewItemLayoutParams(width: width - safeInsets.left - safeInsets.right - 4.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 1000.0) if let current = self.itemNode { itemNode = current chatListItem.updateNode( @@ -657,7 +657,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod itemNode = outItemNode } - let height = itemNode?.contentSize.height ?? 50.0 + var height = itemNode?.contentSize.height ?? 50.0 if self.itemNode !== itemNode { self.itemNode?.removeFromSupernode() @@ -668,7 +668,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod self.contextSourceNode.contentNode.addSubnode(itemNode) } } - let itemFrame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) + let itemFrame = CGRect(origin: CGPoint(x: safeInsets.left, y: 4.0), size: CGSize(width: width - safeInsets.left - safeInsets.right - 4.0, height: height)) if let itemNode = self.itemNode { itemNode.frame = itemFrame } @@ -697,6 +697,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod } } + height += 8.0 + let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition) transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset))) @@ -708,7 +710,7 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenSwitchItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenSwitchItem.swift index c8338793f0..10cff7c74c 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenSwitchItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenSwitchItem.swift @@ -166,9 +166,9 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { self.activateArea.accessibilityHint = presentationData.strings.VoiceOver_Common_SwitchHint let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset, height: .greatestFiniteMagnitude)) - let textFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: leftInset, y: 16.0), size: textSize) - let height = textSize.height + 24.0 + let height = textSize.height + 32.0 if let icon = item.icon { if self.iconNode.supernode == nil { @@ -199,7 +199,7 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { self.lockedButtonNode?.frame = switchFrame if let lockedIconNode = self.lockedIconNode, let icon = lockedIconNode.image { - lockedIconNode.frame = CGRect(origin: CGPoint(x: switchFrame.minX + 10.0 + UIScreenPixel, y: switchFrame.minY + 9.0), size: icon.size) + lockedIconNode.frame = CGRect(origin: CGPoint(x: switchFrame.minX + 16.0 - UIScreenPixel, y: switchFrame.minY + 8.0), size: icon.size) } } @@ -207,7 +207,7 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) self.bottomSeparatorNode.isHidden = hasBottomCorners @@ -215,7 +215,9 @@ private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode { self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition) transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset))) - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel))) + let separatorRightInset: CGFloat = 16.0 + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset - separatorRightInset, height: UIScreenPixel))) transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) self.activateArea.frame = CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift index 4233007ae9..bc3b966244 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderEditingContentNode.swift @@ -54,7 +54,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 22.0), size: CGSize(width: avatarSize, height: avatarSize)) transition.updateFrameAdditiveToCenter(node: self.avatarNode, frame: CGRect(origin: avatarFrame.center, size: CGSize())) - var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0 + var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 28.0 if canEditPeerInfo(context: self.context, peer: peer, chatLocation: chatLocation, threadData: threadData) { if self.avatarButtonNode.supernode == nil { @@ -65,7 +65,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode { let avatarTextSize = self.avatarTextNode.updateLayout(CGSize(width: width, height: 32.0)) transition.updateFrame(node: self.avatarTextNode, frame: CGRect(origin: CGPoint(), size: avatarTextSize)) - transition.updateFrame(node: self.avatarButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - avatarTextSize.width) / 2.0), y: contentHeight - 1.0), size: avatarTextSize)) + transition.updateFrame(node: self.avatarButtonNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - avatarTextSize.width) / 2.0), y: contentHeight - 5.0), size: avatarTextSize)) contentHeight += 32.0 } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderMultiLineTextFieldNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderMultiLineTextFieldNode.swift index 504cf03170..9d253d3f71 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderMultiLineTextFieldNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderMultiLineTextFieldNode.swift @@ -122,8 +122,10 @@ final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderT self.topSeparator.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + let separatorRightInset: CGFloat = 16.0 + let separatorX = safeInset + (hasPrevious ? 16.0 : 0.0) - self.topSeparator.frame = CGRect(origin: CGPoint(x: separatorX, y: 0.0), size: CGSize(width: width - separatorX - safeInset, height: UIScreenPixel)) + self.topSeparator.frame = CGRect(origin: CGPoint(x: separatorX, y: 0.0), size: CGSize(width: width - separatorX - safeInset - separatorRightInset, height: UIScreenPixel)) let attributedPlaceholderText = NSAttributedString(string: placeholder, font: titleFont, textColor: presentationData.theme.list.itemPlaceholderTextColor) if self.textNode.attributedPlaceholderText == nil || !self.textNode.attributedPlaceholderText!.isEqual(to: attributedPlaceholderText) { @@ -145,7 +147,8 @@ final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderT self.measureTextNode.frame = CGRect(origin: CGPoint(), size: measureTextSize) self.currentMeasuredHeight = measureTextSize.height - let height = measureTextSize.height + 22.0 + let verticalInset: CGFloat = 15.0 + let height = measureTextSize.height + verticalInset * 2.0 let buttonSize = CGSize(width: 38.0, height: height) self.clearButtonNode.frame = CGRect(origin: CGPoint(x: width - safeInset - buttonSize.width, y: 0.0), size: buttonSize) @@ -153,7 +156,7 @@ final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderT self.clearIconNode.frame = CGRect(origin: CGPoint(x: width - safeInset - buttonSize.width + floor((buttonSize.width - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size) } - let textNodeFrame = CGRect(origin: CGPoint(x: safeInset + 16.0, y: 10.0), size: CGSize(width: width - safeInset * 2.0 - 16.0 * 2.0 - 38.0, height: max(height, 1000.0))) + let textNodeFrame = CGRect(origin: CGPoint(x: safeInset + 16.0, y: 15.0), size: CGSize(width: width - safeInset * 2.0 - 16.0 * 2.0 - 38.0, height: max(height, 1000.0))) self.textNodeContainer.frame = textNodeFrame self.textNode.frame = CGRect(origin: CGPoint(), size: textNodeFrame.size) @@ -163,7 +166,7 @@ final class PeerInfoHeaderMultiLineTextFieldNode: ASDisplayNode, PeerInfoHeaderT let hasTopCorners = hasCorners && !hasPrevious let hasBottomCorners = hasCorners && !hasNext - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInset, y: 0.0), size: CGSize(width: width - safeInset - safeInset, height: height)) return height diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderSingleLineTextFieldNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderSingleLineTextFieldNode.swift index 95638a8cd3..762bb6b22a 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderSingleLineTextFieldNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderSingleLineTextFieldNode.swift @@ -113,19 +113,22 @@ final class PeerInfoHeaderSingleLineTextFieldNode: ASDisplayNode, PeerInfoHeader self.textNode.textField.text = updateText } + let separatorRightInset: CGFloat = 16.0 + if !hasPrevious { self.topSeparator.isHidden = true } self.topSeparator.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor let separatorX = safeInset + (hasPrevious ? 16.0 : 0.0) - self.topSeparator.frame = CGRect(origin: CGPoint(x: separatorX, y: 0.0), size: CGSize(width: width - separatorX - safeInset, height: UIScreenPixel)) + self.topSeparator.frame = CGRect(origin: CGPoint(x: separatorX, y: 0.0), size: CGSize(width: width - separatorX - safeInset - separatorRightInset, height: UIScreenPixel)) let measureText = "|" let attributedMeasureText = NSAttributedString(string: measureText, font: titleFont, textColor: .black) self.measureTextNode.attributedText = attributedMeasureText let measureTextSize = self.measureTextNode.updateLayout(CGSize(width: width - safeInset * 2.0 - 16.0 * 2.0 - 38.0, height: .greatestFiniteMagnitude)) - let height = measureTextSize.height + 22.0 + let verticalInset: CGFloat = 15.0 + let height = measureTextSize.height + verticalInset * 2.0 let buttonSize = CGSize(width: 38.0, height: height) self.clearButtonNode.frame = CGRect(origin: CGPoint(x: width - safeInset - buttonSize.width, y: 0.0), size: buttonSize) @@ -140,7 +143,7 @@ final class PeerInfoHeaderSingleLineTextFieldNode: ASDisplayNode, PeerInfoHeader let hasTopCorners = hasCorners && !hasPrevious let hasBottomCorners = hasCorners && !hasNext - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil self.maskNode.frame = CGRect(origin: CGPoint(x: safeInset, y: 0.0), size: CGSize(width: width - safeInset - safeInset, height: height)) self.textNode.isUserInteractionEnabled = isEnabled diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMultilineInputtem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMultilineInputtem.swift index 1bf2675c0d..7211899d17 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMultilineInputtem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenMultilineInputtem.swift @@ -67,7 +67,7 @@ final class PeerInfoScreenMultilineInputItemNode: PeerInfoScreenItemNode { self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - let inputItem = ItemListMultilineInputItem(presentationData: ItemListPresentationData(presentationData), text: item.text, placeholder: item.placeholder, maxLength: item.maxLength.flatMap { ItemListMultilineInputItemTextLimit(value: $0, display: true) }, sectionId: 0, style: .blocks, returnKeyType: .done, textUpdated: { updatedText in + let inputItem = ItemListMultilineInputItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, text: item.text, placeholder: item.placeholder, maxLength: item.maxLength.flatMap { ItemListMultilineInputItemTextLimit(value: $0, display: true) }, sectionId: 0, style: .blocks, returnKeyType: .done, textUpdated: { updatedText in item.textUpdated(updatedText) }, action: { item.action() @@ -113,7 +113,7 @@ final class PeerInfoScreenMultilineInputItemNode: PeerInfoScreenItemNode { let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height))) self.bottomSeparatorNode.isHidden = hasBottomCorners diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 9d1c13b307..d6810166f0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -2189,6 +2189,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr if case .location = scope { let mapNode = LocationMapHeaderNode( presentationData: self.presentationData, + glass: false, toggleMapModeSelection: { [weak self] in guard let self else { return @@ -2198,6 +2199,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr state.displayingMapModeOptions = !state.displayingMapModeOptions self.locationViewState = state }, + updateMapMode: { _ in + }, goToUserLocation: { [weak self] in guard let self else { return @@ -3723,6 +3726,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr navigationBarHeight: 0.0, topPadding: mapOverscrollInset + self.additionalNavigationHeight, controlsTopPadding: controlsTopPadding, + controlsBottomPadding: 0.0, offset: mapOffset, size: mapSize, transition: transition diff --git a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift index c26dd80237..5541bace11 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsSearch.swift @@ -79,7 +79,7 @@ final class OldChannelsSearchItem: ItemListControllerSearch { } } - func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> NavigationBarContentNode & ItemListControllerSearchNavigationContentNode { + func titleContentNode(current: (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)?) -> (NavigationBarContentNode & ItemListControllerSearchNavigationContentNode)? { let updateActivated: (Bool) -> Void = self.updateActivated if let current = current as? NavigationBarSearchContentNode { current.updateThemeAndPlaceholder(theme: self.theme, placeholder: self.placeholder) diff --git a/submodules/TelegramUI/Components/SearchInputPanelComponent/BUILD b/submodules/TelegramUI/Components/SearchInputPanelComponent/BUILD new file mode 100644 index 0000000000..f5efe22e23 --- /dev/null +++ b/submodules/TelegramUI/Components/SearchInputPanelComponent/BUILD @@ -0,0 +1,29 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SearchInputPanelComponent", + module_name = "SearchInputPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/AppBundle", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/TelegramUI/Components/EdgeEffect", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift new file mode 100644 index 0000000000..7a93999db6 --- /dev/null +++ b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift @@ -0,0 +1,318 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import TelegramCore +import AppBundle +import BundleIconComponent +import TelegramPresentationData +import MultilineTextComponent +import PlainButtonComponent +import GlassBackgroundComponent +import GlassBarButtonComponent +import EdgeEffect + +public final class SearchInputPanelComponent: Component { + public final class ResetText: Equatable { + public let value: String + + public init(value: String) { + self.value = value + } + + public static func ==(lhs: ResetText, rhs: ResetText) -> Bool { + return lhs === rhs + } + } + + public let theme: PresentationTheme + public let strings: PresentationStrings + public let placeholder: String? + public let resetText: ResetText? + public let updated: ((String) -> Void) + public let cancel: () -> Void + + public init( + theme: PresentationTheme, + strings: PresentationStrings, + placeholder: String? = nil, + resetText: ResetText? = nil, + updated: @escaping ((String) -> Void), + cancel: @escaping () -> Void + ) { + self.theme = theme + self.strings = strings + self.placeholder = placeholder + self.resetText = resetText + self.updated = updated + self.cancel = cancel + } + + public static func ==(lhs: SearchInputPanelComponent, rhs: SearchInputPanelComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.placeholder != rhs.placeholder { + return false + } + if lhs.resetText != rhs.resetText { + return false + } + return true + } + + private final class TextField: UITextField { + var sideInset: CGFloat = 0.0 + + override func textRect(forBounds bounds: CGRect) -> CGRect { + return CGRect(origin: CGPoint(x: self.sideInset, y: 0.0), size: CGSize(width: bounds.width - self.sideInset * 2.0, height: bounds.height)) + } + + override func editingRect(forBounds bounds: CGRect) -> CGRect { + return CGRect(origin: CGPoint(x: self.sideInset, y: 0.0), size: CGSize(width: bounds.width - self.sideInset * 2.0, height: bounds.height)) + } + } + + public final class View: UIView, UITextFieldDelegate { + private let edgeEffectView: EdgeEffectView + private let backgroundView: GlassBackgroundView + + private let icon = ComponentView() + private var placeholder = ComponentView() + + private let textField: TextField + private let clearButton = ComponentView() + + private let cancelButton = ComponentView() + + private var component: SearchInputPanelComponent? + private weak var state: EmptyComponentState? + private var isUpdating: Bool = false + + public var currentText: String { + return self.textField.text ?? "" + } + + override init(frame: CGRect) { + self.edgeEffectView = EdgeEffectView() + + self.backgroundView = GlassBackgroundView() + self.textField = TextField() + + super.init(frame: frame) + + self.addSubview(self.edgeEffectView) + self.addSubview(self.backgroundView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func textDidChange() { + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + self.component?.updated(self.currentText) + } + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + if !self.currentText.isEmpty { + self.textField.resignFirstResponder() + } + + return true + } + + public func setText(text: String, updateState: Bool) { + self.textField.text = text + if updateState { + self.state?.updated(transition: .immediate, isLocal: true) + self.component?.updated(self.currentText) + } else { + self.state?.updated(transition: .immediate, isLocal: true) + } + } + + public func activateInput() { + self.textField.becomeFirstResponder() + } + + public func deactivateInput() -> Bool { + self.textField.resignFirstResponder() + + return self.currentText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + + func update(component: SearchInputPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + let previousComponent = self.component + self.component = component + self.state = state + + if self.textField.superview == nil { + self.addSubview(self.textField) + + self.textField.accessibilityTraits = .searchField + self.textField.autocorrectionType = .no + self.textField.autocapitalizationType = .sentences + self.textField.enablesReturnKeyAutomatically = true + self.textField.returnKeyType = .search + self.textField.delegate = self + self.textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged) + } + + let themeUpdated = component.theme !== previousComponent?.theme + + if themeUpdated { + self.textField.font = Font.regular(17.0) + self.textField.textColor = component.theme.list.itemPrimaryTextColor + self.textField.keyboardAppearance = component.theme.overallDarkAppearance ? .dark : .light + } + + let backgroundColor = component.theme.list.plainBackgroundColor.withMultipliedAlpha(0.75) + + let edgeInsets = UIEdgeInsets(top: 10.0, left: 11.0, bottom: 10.0, right: 11.0) + let fieldHeight: CGFloat = 48.0 + let buttonSpacing: CGFloat = 10.0 + + let fieldFrame = CGRect(origin: CGPoint(x: edgeInsets.left, y: edgeInsets.top), size: CGSize(width: availableSize.width - edgeInsets.left - edgeInsets.right - fieldHeight - buttonSpacing, height: fieldHeight)) + let cancelButtonFrame = CGRect(origin: CGPoint(x: edgeInsets.left + fieldFrame.width + buttonSpacing, y: edgeInsets.top), size: CGSize(width: fieldHeight, height: fieldHeight)) + + self.backgroundView.update(size: fieldFrame.size, cornerRadius: fieldFrame.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: backgroundColor), transition: transition) + transition.setFrame(view: self.backgroundView, frame: fieldFrame) + + let fieldSideInset: CGFloat = 41.0 + self.textField.sideInset = fieldSideInset + + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent(name: "Components/Search Bar/Loupe", tintColor: component.theme.list.itemPrimaryTextColor)), + environment: {}, + containerSize: CGSize(width: availableSize.width - fieldSideInset * 2.0 - 30.0, height: 100.0) + ) + + let iconFrame = CGRect(origin: CGPoint(x: fieldFrame.minX + 11.0, y: fieldFrame.minY + floor((fieldFrame.height - iconSize.height) * 0.5)), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + iconView.layer.anchorPoint = CGPoint() + iconView.isUserInteractionEnabled = false + self.insertSubview(iconView, belowSubview: self.textField) + } + transition.setPosition(view: iconView, position: iconFrame.origin) + iconView.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + } + + let placeholderSize = self.placeholder.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.placeholder ?? component.strings.Common_Search, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.6))) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - fieldSideInset * 2.0 - 30.0, height: 100.0) + ) + + let placeholderFrame = CGRect(origin: CGPoint(x: fieldFrame.minX + fieldSideInset, y: fieldFrame.minY + floor((fieldFrame.height - placeholderSize.height) * 0.5)), size: placeholderSize) + if let placeholderView = self.placeholder.view { + if placeholderView.superview == nil { + placeholderView.layer.anchorPoint = CGPoint() + placeholderView.isUserInteractionEnabled = false + self.insertSubview(placeholderView, belowSubview: self.textField) + } + transition.setPosition(view: placeholderView, position: placeholderFrame.origin) + placeholderView.bounds = CGRect(origin: CGPoint(), size: placeholderFrame.size) + + placeholderView.isHidden = !self.currentText.isEmpty + } + + transition.setFrame(view: self.textField, frame: fieldFrame) + + let clearButtonSize = self.clearButton.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(BundleIconComponent( + name: "Components/Search Bar/Clear", + tintColor: component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4) + )), + effectAlignment: .center, + minSize: CGSize(width: 44.0, height: 44.0), + action: { [weak self] in + guard let self else { + return + } + self.setText(text: "", updateState: true) + }, + animateAlpha: false, + animateScale: true + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + if let clearButtonView = self.clearButton.view { + if clearButtonView.superview == nil { + self.addSubview(clearButtonView) + } + transition.setFrame(view: clearButtonView, frame: CGRect(origin: CGPoint(x: fieldFrame.maxX - clearButtonSize.width, y: fieldFrame.minY + floor((fieldFrame.height - clearButtonSize.height) * 0.5)), size: clearButtonSize)) + clearButtonView.isHidden = self.currentText.isEmpty + } + + let _ = self.cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: cancelButtonFrame.size, + backgroundColor: backgroundColor, + isDark: component.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: component.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + guard let self, let component = self.component else { + return + } + let _ = self.deactivateInput() + component.cancel() + } + )), + environment: {}, + containerSize: cancelButtonFrame.size + ) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + + let size = CGSize(width: availableSize.width, height: edgeInsets.top + fieldHeight + edgeInsets.bottom) + + let edgeColor: UIColor = component.theme.overallDarkAppearance ? .clear : UIColor(rgb: 0x000000, alpha: 0.25) + + let edgeEffectHeight: CGFloat = 88.0 + 30.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight + 30.0), size: CGSize(width: size.width, height: edgeEffectHeight)) + transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame) + self.edgeEffectView.update(content: edgeColor, blur: true, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD index 5ebf49bb3c..671515c2ed 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/BUILD @@ -21,9 +21,9 @@ swift_library( "//submodules/Markdown", "//submodules/ComponentFlow", "//submodules/Components/ViewControllerComponent", - "//submodules/Components/BundleIconComponent", "//submodules/Components/MultilineTextComponent", "//submodules/Components/BalancedTextComponent", + "//submodules/Components/BundleIconComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/BackButtonComponent", @@ -54,6 +54,7 @@ swift_library( "//submodules/UndoUI", "//submodules/ShareController", "//submodules/ContextUI", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift index 18b4d9d709..a263451f0f 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift @@ -780,6 +780,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { var generalSectionItems: [AnyComponentWithIdentity] = [] generalSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -804,6 +805,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: generalSectionItems @@ -844,6 +846,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { } else { messagesSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -871,6 +874,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: component.mode == .greeting ? environment.strings.BusinessMessageSetup_GreetingMessageSectionHeader : environment.strings.BusinessMessageSetup_AwayMessageSectionHeader, @@ -920,6 +924,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { let isSelected = self.schedule == schedule scheduleSectionItems.append(AnyComponentWithIdentity(id: scheduleSectionItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -952,6 +957,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.BusinessMessageSetup_ScheduleSectionHeader, @@ -1061,6 +1067,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { customScheduleSectionItems.append(AnyComponentWithIdentity(id: customScheduleSectionItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1085,6 +1092,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: customScheduleSectionItems @@ -1114,6 +1122,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .markdown( @@ -1132,6 +1141,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1178,6 +1188,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.BusinessMessageSetup_RecipientsSectionHeader, @@ -1190,6 +1201,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1220,6 +1232,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { ))), AnyComponentWithIdentity(id: 1, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1268,6 +1281,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { var excludedSectionItems: [AnyComponentWithIdentity] = [] excludedSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1385,6 +1399,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: self.hasAccessToAllChatsByDefault ? environment.strings.BusinessMessageSetup_Recipients_ExcludedSectionHeader : environment.strings.BusinessMessageSetup_Recipients_IncludedSectionHeader, @@ -1442,6 +1457,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.BusinessMessageSetup_InactivitySectionHeader, diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift index 3c50137c67..6eef6691bb 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/BusinessLinksSetupScreen.swift @@ -404,6 +404,7 @@ final class BusinessLinksSetupScreenComponent: Component { var createLinkSectionItems: [AnyComponentWithIdentity] = [] createLinkSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -441,6 +442,7 @@ final class BusinessLinksSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: footerText.isEmpty ? nil : AnyComponent(MultilineTextComponent( text: .markdown(text: footerText, attributes: MarkdownAttributes( @@ -592,6 +594,7 @@ final class BusinessLinksSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.Business_Links_LinksSectionHeader, diff --git a/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotListSettingsScreen.swift b/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotListSettingsScreen.swift index 5dd8f2cd71..782ac66fa4 100644 --- a/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotListSettingsScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotListSettingsScreen.swift @@ -71,6 +71,7 @@ private enum BotListSettingsEntry: ItemListNodeEntry { case let .botItem(peer): return ItemListPeerItem( presentationData: presentationData, + systemStyle: .glass, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, diff --git a/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotSettingsScreen.swift b/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotSettingsScreen.swift index c11cab76af..cd0a5e2268 100644 --- a/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotSettingsScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BotSettingsScreen/Sources/BotSettingsScreen.swift @@ -61,6 +61,7 @@ private enum BotSettingsEntry: ItemListNodeEntry { case let .biometryAccess(value): return ItemListSwitchItem( presentationData: presentationData, + systemStyle: .glass, title: presentationData.strings.Settings_BotSettings_Biometry, value: value, sectionId: self.section, diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift index ce6e3fa77e..5b453f54f7 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift @@ -510,11 +510,13 @@ final class BusinessHoursSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -615,6 +617,7 @@ final class BusinessHoursSetupScreenComponent: Component { daysSectionItems.append(AnyComponentWithIdentity(id: dayIndex, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -672,6 +675,7 @@ final class BusinessHoursSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.BusinessHoursSetup_DaysSectionTitle, @@ -715,11 +719,13 @@ final class BusinessHoursSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.BusinessHoursSetup_TimeZone, diff --git a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift index 07a79ee025..c8dbe793e8 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift @@ -418,6 +418,7 @@ final class BusinessLocationSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: addressSectionItems @@ -447,6 +448,7 @@ final class BusinessLocationSetupScreenComponent: Component { mapSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -491,6 +493,7 @@ final class BusinessLocationSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: mapSectionItems, @@ -514,6 +517,7 @@ final class BusinessLocationSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [ diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift index 9c967cecd3..17f2df3631 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/BusinessRecipientListScreen.swift @@ -412,6 +412,7 @@ final class BusinessRecipientListScreenComponent: Component { var excludedSectionItems: [AnyComponentWithIdentity] = [] excludedSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -540,6 +541,7 @@ final class BusinessRecipientListScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: excludedSectionItems @@ -561,10 +563,12 @@ final class BusinessRecipientListScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( diff --git a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift index 5fd3236f4a..fa6f049d33 100644 --- a/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/ChatbotSetupScreen/Sources/ChatbotSetupScreen.swift @@ -815,6 +815,7 @@ final class ChatbotSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -843,6 +844,7 @@ final class ChatbotSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.ChatbotSetup_RecipientsSectionHeader, @@ -855,6 +857,7 @@ final class ChatbotSetupScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -885,6 +888,7 @@ final class ChatbotSetupScreenComponent: Component { ))), AnyComponentWithIdentity(id: 1, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -947,6 +951,7 @@ final class ChatbotSetupScreenComponent: Component { var excludedSectionItems: [AnyComponentWithIdentity] = [] excludedSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -979,6 +984,7 @@ final class ChatbotSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .markdown( @@ -1013,6 +1019,7 @@ final class ChatbotSetupScreenComponent: Component { var excludedUsersSectionItems: [AnyComponentWithIdentity] = [] excludedUsersSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1044,6 +1051,7 @@ final class ChatbotSetupScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .markdown( @@ -1139,6 +1147,7 @@ final class ChatbotSetupScreenComponent: Component { permissionsItems.append( AnyComponentWithIdentity(id: permission.id, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(HStack(titleItems, spacing: 6.0)), accessory: .toggle(ListActionItemComponent.Toggle(style: .icons, isOn: value, action: { [weak self] value in guard let self else { @@ -1218,6 +1227,7 @@ final class ChatbotSetupScreenComponent: Component { permissionsItems.append( AnyComponentWithIdentity(id: subpermission.id, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1267,6 +1277,7 @@ final class ChatbotSetupScreenComponent: Component { transition: permissionsTransition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.ChatbotSetup_PermissionsSectionHeader, diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift index f25416016e..c9306e0290 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift @@ -261,6 +261,7 @@ public final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemCompon public var sectionId: ItemListSectionId public let theme: PresentationTheme + public let systemStyle: ItemListSystemStyle public let colors: PeerNameColors public let mode: Mode public let displayEmptyColor: Bool @@ -269,8 +270,9 @@ public final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemCompon public let updated: (PeerNameColor?) -> Void public let tag: ItemListItemTag? - public init(theme: PresentationTheme, colors: PeerNameColors, mode: Mode, displayEmptyColor: Bool = false, currentColor: PeerNameColor?, isLocked: Bool = false, updated: @escaping (PeerNameColor?) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) { + public init(theme: PresentationTheme, systemStyle: ItemListSystemStyle = .legacy, colors: PeerNameColors, mode: Mode, displayEmptyColor: Bool = false, currentColor: PeerNameColor?, isLocked: Bool = false, updated: @escaping (PeerNameColor?) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) { self.theme = theme + self.systemStyle = systemStyle self.colors = colors self.mode = mode self.displayEmptyColor = displayEmptyColor @@ -385,6 +387,12 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset += 4.0 + } let itemsPerRow: Int let displayOrder: [Int32] @@ -407,7 +415,7 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { let rowsCount = ceil(CGFloat(numItems) / CGFloat(itemsPerRow)) - contentSize = CGSize(width: params.width, height: 10.0 + 42.0 * rowsCount) + contentSize = CGSize(width: params.width, height: 10.0 + 42.0 * rowsCount + verticalInset * 2.0) insets = itemListNeighborsGroupedInsets(neighbors, params) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -471,9 +479,9 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { hasBottomCorners = true strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } strongSelf.containerNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height) @@ -515,7 +523,7 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { let spacing = floorToScreenPixels((params.width - sideInset * 2.0 - iconSize.width * CGFloat(itemsPerRow)) / CGFloat(itemsPerRow - 1)) - var origin = CGPoint(x: sideInset, y: 10.0) + var origin = CGPoint(x: sideInset, y: 10.0 + verticalInset) i = 0 var validIds = Set() diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD index e8d5450471..c3b7d8d946 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD @@ -62,6 +62,7 @@ swift_library( "//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView", "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", "//submodules/TelegramUI/Components/Stars/BalanceNeededScreen", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 834d0e7d8c..22d93e0917 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -1054,6 +1054,7 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, background: .none(clipped: false), header: nil, footer: nil, @@ -1117,12 +1118,14 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, background: .all, header: nil, footer: nil, items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(HStack(boostContents, spacing: 12.0)), icon: nil, action: { [weak self] _ in @@ -1165,6 +1168,7 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, background: .all, header: nil, footer: AnyComponent(MultilineTextComponent( @@ -1179,6 +1183,7 @@ final class ChannelAppearanceScreenComponent: Component { AnyComponentWithIdentity(id: 1, component: AnyComponent(ListItemComponentAdaptor( itemGenerator: PeerNameColorItem( theme: environment.theme, + systemStyle: .glass, colors: component.context.peerNameColors, mode: .profile, currentColor: profileColor, @@ -1195,6 +1200,7 @@ final class ChannelAppearanceScreenComponent: Component { ))), AnyComponentWithIdentity(id: 2, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(HStack(profileLogoContents, spacing: 6.0)), icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( context: component.context, @@ -1235,11 +1241,13 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.Channel_Appearance_ResetProfileColor, @@ -1311,6 +1319,7 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1323,6 +1332,7 @@ final class ChannelAppearanceScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(HStack(emojiPackContents, spacing: 6.0)), icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( context: component.context, @@ -1375,6 +1385,7 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1387,6 +1398,7 @@ final class ChannelAppearanceScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(HStack(emojiStatusContents, spacing: 6.0)), icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( context: component.context, @@ -1439,6 +1451,7 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1451,6 +1464,7 @@ final class ChannelAppearanceScreenComponent: Component { items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(HStack(stickerPackContents, spacing: 6.0)), icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( context: component.context, @@ -1559,6 +1573,7 @@ final class ChannelAppearanceScreenComponent: Component { AnyComponentWithIdentity(id: 1, component: AnyComponent(ListItemComponentAdaptor( itemGenerator: PeerNameColorItem( theme: environment.theme, + systemStyle: .glass, colors: component.context.peerNameColors, mode: .name, currentColor: resolvedState.nameColor, @@ -1575,6 +1590,7 @@ final class ChannelAppearanceScreenComponent: Component { ))), AnyComponentWithIdentity(id: 2, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(HStack(replyLogoContents, spacing: 6.0)), icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( context: component.context, @@ -1731,6 +1747,7 @@ final class ChannelAppearanceScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -1774,10 +1791,12 @@ final class ChannelAppearanceScreenComponent: Component { )))) } + let buttonSideInset: CGFloat = 36.0 let buttonSize = self.actionButton.update( transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -1796,7 +1815,7 @@ final class ChannelAppearanceScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) ) contentHeight += buttonSize.height @@ -1805,7 +1824,7 @@ final class ChannelAppearanceScreenComponent: Component { let buttonY = availableSize.height - bottomInset - environment.safeInsets.bottom - buttonSize.height - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: buttonY), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: buttonY), size: buttonSize) if let buttonView = self.actionButton.view { if buttonView.superview == nil { self.addSubview(buttonView) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index a87e44fc4b..dbc72b0f82 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -1277,6 +1277,7 @@ final class UserAppearanceScreenComponent: Component { AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( itemGenerator: PeerNameColorItem( theme: environment.theme, + systemStyle: .glass, colors: component.context.peerNameColors, mode: .profile, currentColor: resolvedState.profileColor, @@ -1609,6 +1610,7 @@ final class UserAppearanceScreenComponent: Component { AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor( itemGenerator: PeerNameColorItem( theme: environment.theme, + systemStyle: .glass, colors: component.context.peerNameColors, mode: .name, currentColor: resolvedState.nameColor.nameColor, diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift index 75659198ad..523d024785 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ItemListReactionItem.swift @@ -19,6 +19,7 @@ public enum ItemListReactionArrowStyle { public class ItemListReactionItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let icon: UIImage? let title: String let arrowStyle: ItemListReactionArrowStyle @@ -29,9 +30,10 @@ public class ItemListReactionItem: ListViewItem, ItemListItem { let action: (() -> Void)? public let tag: ItemListItemTag? - public init(context: AccountContext, presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, arrowStyle: ItemListReactionArrowStyle = .none, reaction: MessageReaction.Reaction, availableReactions: AvailableReactions?, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, tag: ItemListItemTag? = nil) { + public init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, title: String, arrowStyle: ItemListReactionArrowStyle = .none, reaction: MessageReaction.Reaction, availableReactions: AvailableReactions?, sectionId: ItemListSectionId, style: ItemListStyle, action: (() -> Void)?, tag: ItemListItemTag? = nil) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.icon = icon self.title = title self.arrowStyle = arrowStyle @@ -175,6 +177,8 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -207,7 +211,13 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - leftInset - additionalTextRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let verticalInset: CGFloat = 11.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } let height: CGFloat height = verticalInset * 2.0 + titleLayout.size.height @@ -314,15 +324,15 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } - let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 11.0), size: titleLayout.size) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame var animationContent: EmojiStatusComponent.AnimationContent? diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift index fa0225bc2f..7d0375134e 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/QuickReactionSetupController.swift @@ -139,6 +139,7 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry { context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, + systemStyle: .glass, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, @@ -155,7 +156,7 @@ private enum QuickReactionSetupControllerEntry: ItemListNodeEntry { case let .demoDescription(text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .quickReaction(title, reaction, availableReactions): - return ItemListReactionItem(context: arguments.context, presentationData: presentationData, title: title, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: { + return ItemListReactionItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, title: title, reaction: reaction, availableReactions: availableReactions, sectionId: self.section, style: .blocks, action: { arguments.openQuickReaction() }) case let .quickReactionDescription(text): diff --git a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift index f4691ae880..e72575346d 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReactionSetupController/Sources/ReactionChatPreviewItem.swift @@ -18,6 +18,7 @@ class ReactionChatPreviewItem: ListViewItem, ItemListItem { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let sectionId: ItemListSectionId let fontSize: PresentationFontSize let chatBubbleCorners: PresentationChatBubbleCorners @@ -29,10 +30,11 @@ class ReactionChatPreviewItem: ListViewItem, ItemListItem { let accountPeer: Peer? let toggleReaction: () -> Void - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?, accountPeer: Peer?, toggleReaction: @escaping () -> Void) { + init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, sectionId: ItemListSectionId, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, wallpaper: TelegramWallpaper, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction?, accountPeer: Peer?, toggleReaction: @escaping () -> Void) { self.context = context self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.sectionId = sectionId self.fontSize = fontSize self.chatBubbleCorners = chatBubbleCorners @@ -297,6 +299,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode { let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let userPeerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)) let chatPeerId = userPeerId @@ -431,7 +434,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) @@ -460,7 +463,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode { animation.animator.updateFrame(layer: strongSelf.maskNode.layer, frame: backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0), completion: nil) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) if let previousItem = previousItem, previousItem.reaction != item.reaction { strongSelf.beginReactionAnimation() diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift index e61e5e7906..315d529ec3 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift @@ -109,7 +109,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor self.customColorItemNode = ItemListActionItemNode() - self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { + self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { presentColorPicker() }) @@ -265,7 +265,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { self.topBackgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in + self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.presentColorPicker() }) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index 9cc18c2c11..b09d533ea2 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -243,13 +243,13 @@ final class ThemeGridControllerNode: ASDisplayNode { self.maskNode.isUserInteractionEnabled = false self.colorItemNode = ItemListActionItemNode() - self.colorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { + self.colorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.Wallpaper_SetColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { presentColors() }) switch mode { case .generic: - self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { + self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { presentGallery() }) self.galleryItemNode = ItemListActionItemNode() @@ -259,14 +259,14 @@ final class ThemeGridControllerNode: ASDisplayNode { requiredCustomWallpaperLevel = customLevel } - self.galleryItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Image"), color: presentationData.theme.list.itemAccentColor), title: presentationData.strings.Wallpaper_SetCustomBackground, additionalBadgeIcon: requiredCustomWallpaperLevel.flatMap { generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Appearance_BoostLevel("\($0)").string) }, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .accent, editing: false, action: { + self.galleryItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Image"), color: presentationData.theme.list.itemAccentColor), title: presentationData.strings.Wallpaper_SetCustomBackground, additionalBadgeIcon: requiredCustomWallpaperLevel.flatMap { generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Appearance_BoostLevel("\($0)").string) }, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .accent, editing: false, action: { presentGallery() }) self.galleryItemNode = ItemListPeerActionItemNode() } var removeImpl: (() -> Void)? - self.removeItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.list.itemDestructiveColor), title: presentationData.strings.Wallpaper_ChannelRemoveBackground, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .destructive, editing: false, action: { + self.removeItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.list.itemDestructiveColor), title: presentationData.strings.Wallpaper_ChannelRemoveBackground, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .destructive, editing: false, action: { removeImpl?() }) self.removeItemNode = ItemListPeerActionItemNode() @@ -679,13 +679,13 @@ final class ThemeGridControllerNode: ASDisplayNode { self.bottomBackgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - self.colorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in + self.colorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.Wallpaper_SetColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.presentColors() }) switch self.mode { case .generic: - self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in + self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.presentGallery() }) case .peer: @@ -693,16 +693,16 @@ final class ThemeGridControllerNode: ASDisplayNode { if case let .peer(_, _, _, _, customLevel) = mode { requiredCustomWallpaperLevel = customLevel } - self.galleryItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Image"), color: presentationData.theme.list.itemAccentColor), title: presentationData.strings.Wallpaper_SetCustomBackground, additionalBadgeIcon: requiredCustomWallpaperLevel.flatMap { generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Appearance_BoostLevel("\($0)").string) }, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .accent, editing: false, action: { [weak self] in + self.galleryItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Image"), color: presentationData.theme.list.itemAccentColor), title: presentationData.strings.Wallpaper_SetCustomBackground, additionalBadgeIcon: requiredCustomWallpaperLevel.flatMap { generateDisclosureActionBoostLevelBadgeImage(text: presentationData.strings.Channel_Appearance_BoostLevel("\($0)").string) }, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .accent, editing: false, action: { [weak self] in self?.presentGallery() }) - self.removeItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.list.itemDestructiveColor), title: presentationData.strings.Wallpaper_ChannelRemoveBackground, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .destructive, editing: false, action: { [weak self] in + self.removeItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.list.itemDestructiveColor), title: presentationData.strings.Wallpaper_ChannelRemoveBackground, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .destructive, editing: false, action: { [weak self] in self?.controllerInteraction?.removeWallpaper() }) } self.descriptionItem = ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(presentationData.strings.Wallpaper_SetCustomBackgroundInfo), sectionId: 0) - self.resetItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_ResetWallpapers, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in + self.resetItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), systemStyle: .glass, title: presentationData.strings.Wallpaper_ResetWallpapers, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.resetWallpapers() }) self.resetDescriptionItem = ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(presentationData.strings.Wallpaper_ResetWallpapersInfo), sectionId: 0) diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD index 74e98e8cb6..8b6e81b7c6 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/BUILD @@ -46,6 +46,7 @@ swift_library( "//submodules/ContextUI", "//submodules/PromptUI", "//submodules/DirectMediaImageCache", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CoverListItemComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CoverListItemComponent.swift index 364a948ddc..0429469c7a 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CoverListItemComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CoverListItemComponent.swift @@ -83,7 +83,7 @@ final class CoverListItemComponent: Component { self.component = component self.state = state - let height: CGFloat = 44.0 + let height: CGFloat = 52.0 let verticalInset: CGFloat = 0.0 let leftInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0 diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/OptionListItemComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/OptionListItemComponent.swift index f5de0074c1..cad9bbd149 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/OptionListItemComponent.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/OptionListItemComponent.swift @@ -92,7 +92,7 @@ final class OptionListItemComponent: Component { self.component = component self.state = state - let height: CGFloat = 44.0 + let height: CGFloat = 52.0 let verticalInset: CGFloat = 0.0 let leftInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0 diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index b97533ceb3..64f5aa38be 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -29,6 +29,7 @@ import ContextUI import BundleIconComponent import PromptUI import DirectMediaImageCache +import GlassBarButtonComponent final class ShareWithPeersScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -411,7 +412,7 @@ final class ShareWithPeersScreenComponent: Component { self.itemContainerView = UIView() self.itemContainerView.clipsToBounds = true - self.itemContainerView.layer.cornerRadius = 10.0 + self.itemContainerView.layer.cornerRadius = 38.0// 10.0 super.init(frame: frame) @@ -1096,7 +1097,7 @@ final class ShareWithPeersScreenComponent: Component { } sectionBackground = UIView() sectionBackground.backgroundColor = environment.theme.list.itemModalBlocksBackgroundColor - sectionBackground.layer.cornerRadius = 10.0 + sectionBackground.layer.cornerRadius = 26.0 self.visibleSectionBackgrounds[section.id] = sectionBackground } @@ -1868,6 +1869,7 @@ final class ShareWithPeersScreenComponent: Component { transition: itemTransition, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, background: nil, title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( @@ -2401,7 +2403,9 @@ final class ShareWithPeersScreenComponent: Component { self.scrollView.indicatorStyle = environment.theme.overallDarkAppearance ? .white : .black - self.backgroundView.image = generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in + let cornerRadius: CGFloat = 38.0 + + self.backgroundView.image = generateImage(CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) if self.hasBlocksStyle { @@ -2411,7 +2415,7 @@ final class ShareWithPeersScreenComponent: Component { } context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height * 0.5), size: CGSize(width: size.width, height: size.height * 0.5))) - })?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 19) + })?.stretchableImage(withLeftCapWidth: Int(cornerRadius), topCapHeight: Int(cornerRadius * 2.0 - 1.0)) if self.hasBlocksStyle { self.navigationBackgroundView.updateColor(color: environment.theme.list.modalBlocksBackgroundColor, transition: .immediate) @@ -2759,21 +2763,37 @@ final class ShareWithPeersScreenComponent: Component { containerInset += 10.0 } - var navigationHeight: CGFloat = 56.0 + var navigationHeight: CGFloat = 66.0 let navigationSideInset: CGFloat = 16.0 + let buttonSideInset: CGFloat = 36.0 var navigationButtonsWidth: CGFloat = 0.0 let navigationLeftButtonSize = self.navigationLeftButton.update( transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), - action: { [weak self] in - guard let self else { - return - } - self.saveAndDismiss() +// component: AnyComponent(Button( +// content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.rootController.navigationBar.accentTextColor)), +// action: { [weak self] in +// guard let self else { +// return +// } +// self.saveAndDismiss() +// } +// ).minSize(CGSize(width: navigationHeight, height: navigationHeight))), + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.saveAndDismiss() } - ).minSize(CGSize(width: navigationHeight, height: navigationHeight))), + )), environment: {}, containerSize: CGSize(width: availableSize.width, height: navigationHeight) ) @@ -2928,7 +2948,7 @@ final class ShareWithPeersScreenComponent: Component { topInset = max(0.0, availableSize.height - containerInset - inset) } - self.navigationBackgroundView.update(size: CGSize(width: containerWidth, height: navigationHeight), cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition) + self.navigationBackgroundView.update(size: CGSize(width: containerWidth, height: navigationHeight), cornerRadius: 38.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition) transition.setFrame(view: self.navigationBackgroundView, frame: CGRect(origin: CGPoint(x: containerSideInset, y: 0.0), size: CGSize(width: containerWidth, height: navigationHeight))) transition.setFrame(layer: self.navigationSeparatorLayer, frame: CGRect(origin: CGPoint(x: containerSideInset, y: navigationHeight), size: CGSize(width: containerWidth, height: UIScreenPixel))) @@ -2949,6 +2969,7 @@ final class ShareWithPeersScreenComponent: Component { transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) @@ -3166,7 +3187,7 @@ final class ShareWithPeersScreenComponent: Component { } )), environment: {}, - containerSize: CGSize(width: containerWidth - navigationSideInset * 2.0, height: 50.0) + containerSize: CGSize(width: containerWidth - buttonSideInset * 2.0, height: 52.0) ) if environment.inputHeight != 0.0 { @@ -3174,7 +3195,7 @@ final class ShareWithPeersScreenComponent: Component { } else { bottomPanelHeight += 10.0 + environment.safeInsets.bottom + actionButtonSize.height } - let actionButtonFrame = CGRect(origin: CGPoint(x: containerSideInset + navigationSideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) + let actionButtonFrame = CGRect(origin: CGPoint(x: containerSideInset + buttonSideInset, y: availableSize.height - bottomPanelHeight), size: actionButtonSize) if let actionButtonView = self.actionButton.view { if actionButtonView.superview == nil { self.containerView.addSubview(actionButtonView) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift index d028a8d5f6..af4d150393 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift @@ -282,6 +282,7 @@ final class StarsBalanceComponent: Component { transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: component.theme.list.itemCheckColors.fillColor, foreground: component.theme.list.itemCheckColors.foregroundColor, pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -298,7 +299,7 @@ final class StarsBalanceComponent: Component { } )), environment: {}, - containerSize: CGSize(width: withdrawWidth, height: 50.0) + containerSize: CGSize(width: withdrawWidth, height: 52.0) ) if let buttonView = self.button.view { if buttonView.superview == nil { @@ -338,6 +339,7 @@ final class StarsBalanceComponent: Component { transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: component.theme.list.itemCheckColors.fillColor, foreground: component.theme.list.itemCheckColors.foregroundColor, pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -354,7 +356,7 @@ final class StarsBalanceComponent: Component { } )), environment: {}, - containerSize: CGSize(width: withdrawWidth, height: 50.0) + containerSize: CGSize(width: withdrawWidth, height: 52.0) ) if let buttonView = self.secondaryButton.view { if buttonView.superview == nil { diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 487cd63aa5..1358a3341b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -677,6 +677,7 @@ final class StarsTransactionsScreenComponent: Component { transition: .immediate, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.Ton_ProceedsOverview.uppercased(), @@ -738,6 +739,7 @@ final class StarsTransactionsScreenComponent: Component { transition: .immediate, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: component.starsContext.ton ? AnyComponent(MultilineTextComponent( text: .plain(balanceInfoString), @@ -837,6 +839,7 @@ final class StarsTransactionsScreenComponent: Component { transition: .immediate, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [ @@ -966,6 +969,7 @@ final class StarsTransactionsScreenComponent: Component { component: AnyComponent( ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0), leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: .transactionPeer(.peer(subscription.peer)), photo: nil, media: [], uniqueGift: nil, backgroundColor: environment.theme.list.plainBackgroundColor))), false), @@ -987,6 +991,7 @@ final class StarsTransactionsScreenComponent: Component { component: AnyComponent( ListActionItemComponent( theme: environment.theme, + style: .glass, title: AnyComponent(Text( text: environment.strings.Stars_Intro_Subscriptions_ShowMore, font: Font.regular(17.0), @@ -1029,6 +1034,7 @@ final class StarsTransactionsScreenComponent: Component { transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: environment.strings.Stars_Intro_Subscriptions_Title.uppercased(), diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index ae97b0d395..7ee252caca 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -3560,7 +3560,7 @@ public final class StoryItemSetContainerComponent: Component { return false } if case .undo = action { - let _ = component.context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: user.phone ?? "", addToPrivacyExceptions: false).startStandalone() + let _ = component.context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: user.firstName ?? "", lastName: user.lastName ?? "", phoneNumber: user.phone ?? "", noteText: "", noteEntities: [], addToPrivacyExceptions: false).startStandalone() } return false } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 8ebbc654d0..7b3ddb6df5 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -959,7 +959,7 @@ final class StoryItemSetContainerSendMessage { guard let self, let view else { return } - self.presentScheduleTimePicker(view: view, peer: peer, completion: { time in + self.presentScheduleTimePicker(view: view, peer: peer, completion: { time, repeatPeriod in done(time) }) }))) @@ -2086,7 +2086,7 @@ final class StoryItemSetContainerSendMessage { guard let self, let view else { return } - self.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time in + self.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time, repeatPeriod in done(time) }) } @@ -2210,7 +2210,7 @@ final class StoryItemSetContainerSendMessage { view.component?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentSchedulePicker: { [weak view] media, done in if let strongSelf = self, let view { - strongSelf.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time in + strongSelf.presentScheduleTimePicker(view: view, peer: peer, style: media ? .media : .default, completion: { time, repeatPeriod in done(time) }) } @@ -2619,7 +2619,7 @@ final class StoryItemSetContainerSendMessage { guard let self, let view else { return } - self.presentScheduleTimePicker(view: view, peer: peer, style: .media, completion: { time in + self.presentScheduleTimePicker(view: view, peer: peer, style: .media, completion: { time, repeatPeriod in done(time) }) }, presentTimerPicker: { [weak self, weak view] done in @@ -2654,7 +2654,7 @@ final class StoryItemSetContainerSendMessage { style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, - completion: @escaping (Int32) -> Void + completion: @escaping (Int32, Int32?) -> Void ) { guard let component = view.component else { return @@ -2683,7 +2683,7 @@ final class StoryItemSetContainerSendMessage { } let theme = component.theme let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: style, currentTime: selectedTime, minimalTime: nil, dismissByTapOutside: dismissByTapOutside, completion: { time in - completion(time) + completion(time, nil) }) view.endEditing(true) view.component?.controller()?.present(controller, in: .window(.root)) @@ -2736,7 +2736,7 @@ final class StoryItemSetContainerSendMessage { attributes.append(NotificationInfoMessageAttribute(flags: .muted)) } if let scheduleTime = scheduleTime { - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil)) } } var messageAttributes: [MessageAttribute] = [] diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json new file mode 100644 index 0000000000..2468a09a36 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "headerlocation_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/headerlocation_30.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionLocate.imageset/headerlocation_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3ed9097bd4a926272df7eadcc0d4731afae57c4a GIT binary patch literal 6001 zcmb7I2{=@3`==;Pq(w?aM^ag54l{$4EJIDUv1X0gFk)tmp-G64dPSuyF(pY!BtmvY zg(yqOE=5I3St3jM&Wx?}eeeJJpX-`4XYS|T?|Yu}+&^g?Mo$4$RDnqcKo<-I5CDzf z3Il*WdjL3w;z@VIAH&dy<`kwI8DKKJ$bg0hj7%kQm|#kOaN`&>k{6N80Cwq7oZay< zkP_&E&xr1FA>&D8h8vBDXHsZXJ5X6g8Np%sO`Y-|>I}$KXQm5)govgbE>9+djCY5D ziYQP8rJ@Rv5sDxJsiukm6cCC^C{<+y2!M*pN-7{ymHPlHsj2}a5XSL$R(j5Jj@Fqt zQQ&+ysCgnM_*whtMfTx3SPYg*VFGX{UZNS92{=)xBnH`&=EWeA0Rq{XLWO}!0Et3m z&i>-ki0*hgj3aDz%#%rW$5NeWFpe2hNC0x~*lgCIa&Qv|H)cTTqEMX|)6Kl-bT_g) znaTtZFbxfWM0SGI8sh0jcy}@Y*HX|{&{-gwAET22$Qg(UobiT8BauDnc&JWzsxuk3 z2Z7K4_UP$xi@IoSR)XL}bip%bIWgKg2#hWU2`r1ey z{EF3UAKI@m30$L#;j`0Rrn`mCv?8ruW758ipWoc>q#*2>s_H+RWiys;WqvYjymKg{ zvoy8vgx@E2+sCVuiPJpisY@{kMzX+@>y8e_J{XdS_!!@csvtFwJTXew8-Bm?qd~*8 zqZ_jd!`d%b2u-eHnm^t=D!SIXcp5iZEW(-+RLo*;C~LRs=@v-Pj1}FysYW|boM(^P zFLhDBiXssSVV*LJ?I)b4>=eKQmhtk~H9*Zp4_Ly*Ksvf?A`?p?1j~^<>V1Dv#KOkg z`GO1A>~KE#cd6i@%;HL3rnk^t!$>W}u=0071 zb^1uv3)0n*b4!4Pm72o}cQKdLw(3+*x&MtcuAX>NdX#lTVz*73Mfrp!a1Zl2@ zZF}VUkbeche_B(tnJ4$;9vshTz|U)2w(z2^twIJUU*pdX3hRR9@lu0$5rQ`y1&jF1 zy2N7x0fFTvfb5NB6rE+~#Yjs*$wQT!ZIY`x|aoSHd*qt-s1 zkJrNLP$|L%!r_|h>Ky8JZsfm+-;}mub6GTE-DtqlE*xw97U5gv{atn}6}x4J%5QZ! z{n+NaR3aexn?7q3KW_Ua*umvbG(i)2b3hihK6Gs&I&sJ(ImOj{X{u0TP(e`S^&KW# z>#dOM4fiY8y>Qt#0z+=N644o~h>1xuz7}8K(W~COW<+E{?$pWphp~7z!p(qhhs4E@ zs`5(&SI{uFI+nL);|FokFpEc<@Z$IUn-92c^WK}g`nW*b=`W8*@%uAhs%|k7az5qx zi2t3cDyS%YboEi#QQoF=o6G$kUi{X|+FT^80fRsO*rh+Xb5Q(;|CHyHpjM!xCVd;< zT2^pQ@HTCs#Bh2TLN8WQc5V1+#m6|pHV?VXv(1`C2EGS;b+%n#Vk2(6-_A=}dHXCg zjv0m2o;YB+Ek4q^$0aMcDBhRdjx90v*Ga?)$_!lE^-pY8yy^aY%rS#;V1Ly8^Tg`G z9(1)uC93*+b#dg*%FUI>D>I@Ub~?zOu>2K0bz_OvBV1teLzCtkxJ2kBc>r&7A=M>) zV@m9Gs}z>$VBEXBwMJFAD%&%+F5O`o^hGx8TCdxfl$B!l3GFKMUCHb0sbsPG=4^}& z@0Xo6aQB1O*cMAQ12x-x?eU^4bN*yPN|`0rQpQv*NhxKB68K@sIsPgBLQ$lAufw;) zUWuuNvJNPR91}0i?-w`OKetuDx@XvgwMuzq7u=p;e3$=T{wbz$=Y|H+-S8g88V8os z+oeKQ<>pdW#Nwx5c>Jc9aW-)!ag?~_C^*H)^|nj?5oy;b7cUn>7qcAu9R616)(zPX zby0m1BP1WO;&_eZq{Qx9QuVhR`Wi3SqZ;fJ6#7(zsM5{scI__QVYTii zHnK|cV%yDox35r6QbPI^l$&ch4II5I(`vU62V4}98R)Hq^I{Tb4fOUBA8S7$oY9TY z4SOP|?^nI6x;T1ov_tymw8(VVv{UI(X+P6F)7sLev%s9Ut>(?`_89A)`!2)RU+ZSL zKX)6?4R!z8V)7-r^~=5ftVzOGM|c+PUIM-D=xL7>=0-w8tX7To2aEk@lWui8Rd`pN zGa5A_+)_3=v0Isak!`&D92{*^<6w86^-8xXq}%lUHm~Hur+E@yQ3ZW@9_^_eR}1>{ zhue>IBpi=rZTDdhvEEbO%M7^<`PMQB?j$dt#3J9~p<uxnB)6K>%X5FyFGS! zC}-IIYg=FF;L2}p{VI=NlRR6zKFgd~J{|t6gSRK(e85n^Eq#NS)6v-qAu$v1jt3)e zm4e7+WQ9QBym%h`o5^_-oKIawb!K%?^db2ZqHf|vl6gCWH)ZYUAypRQYU{4oyB%2H$Xe&S z!5^GH#@4s5XA+AM`VIY#6-J((|q7sfh1EgJ)`q zkqB(_mDH%XE3Gk3(a|wG438dslb|6DTixiSDRxK6<_WTAKf{b5-=Zj1#eAMtIdy)0 zv=`bIw2k%iOz&&8Ka!9!Y(3WPGvYHaaaU;f=3MH=mfp-v*LB%4-eyfs#n1EOD{_BO z)iR^W=9gdJvuUF?+KYmo9)r;QL zp%YLx8BzZDU zzgK!(nl_fvaw7dz?nou?JKic@N=szc>!69{V_T-|uHrOsZ>{UCZky!a8Tu*NnfR-G zAarwti-e5oP+v+{Z?Wu~)DN!ZpBjkG#LE>|8-p81rYfg0?^+h;UK_3Uc&>`R-$E!tz=^cHi#mJ#F8@ z9>JpwU)`K?N;&2}Y_6{zmW|5F`O2Qozl;8ud(%O|e{bjAmxnzLkNcV|&y;#V94#wY z`SJ?o%Eb-c@CfhpzD*?}9(~lYp@Gh7-k6NTgZYfx6-gh?om@ZOV528kG zMtnvy%2Zrw5v4hPZ76Z%&mZ>B3NMe1Z+)e~{!uyAJS;Qya`^j$v- z)etAgJ$%FemPw-T-tA&@TMp;HbLQPzcR`_ z{lMx3;O6}hLdk*33to@0SJlNYq*a_XzhDJm!CO*B4XXmq=INq6^|eTkrHEH+UX*}* zJcpJ%jJe*@ayhl71zstSE9YSz{KX$GP%p(fbS@f{KxhV@J%4h~k8{UTaAAzM1>jg6 z9c{cPnFMf7tESN0GSBws{LVR(bC2kHAi$ZXejAr`Xl^uy8E0yonWvyZn`2^@1`3gL%-I( zVO#jCW6#+C=xxh?Gc{1m{yMGNIrwpWc<`%aQ}e!$#@*HZgDl^RM}|T}9c~ufOsIud z=zN~6`1sM-HyU(WwmUQ0hYhdT7kao)M#H#X&P~_wUWC5Iwu&k_Q_`pLOiHYPD2sea zNIgf##{WJ;FvAJ`;Fc4?b4@3&t~51H%?+#nI!?OPEiuuhUpfbw+dhKanw}ohZJl~M z|9MR`+pIlz6p$*mey&S0ZBklt-&Q@1-uNoisUN_e%9Rd{em&}bM+tu{B<}^rc30Nq zHuPFETlB7FxQYWb)yD$Ed_whifvPk?S$XgDEd_!ik!$T&bg`Yx33eJ5du(6mkx@z- zt7=+NMT~6JOI@0cMTUjh;K9&T+4EB8WJH96>qC}Q-l%DfE3EIb^{PX9bTNm|q}o}p zle>l@+$4ORT<$dpm*%8}MMcy%hw+HQh<`N8U?fJGWU01kpNa%_5Q>;2)VWBt8 zv6B+CQNqL>DJJp^n{ylWLZWy4no)kJh~+Cke1vw$f%kGr2m8(fH?;THZr8PuLC9i1zfLu)EHN>Vax@pSs}Rf|z(*MIejTAU-FpyxMeE=x zgOUsS^8Pf7@PQ2o^QaY%pQJpg46-_@9i(X`h^CLFH>e+9a+7*it!{gCrgle-ZmH&B zkuyT${>cd~Bgb-t4Z61%Ogy_OMIgO1*nt5&ca`<)0qv#jcQW5F38*)KQiC<%WDeGB?ODdR4Ci=}uw$wN1l% ztbtTVIs)_kpWMB~LL&OojXc{I`RMx7@CT?uXThGT_QYq_ppoes*^dF<%BV@fEYnwn zmlARjRfpuy(5RzzP7ikcqQ+}9-!mMG>WJJ@FCo6MyW+_ynI^Q1lIYi1N z6LjC*{?iWAng0H}Nq!ety?Z2RrGAs8{eACQV|N^*Yp2-8TO;<>ZvFLcynB3Xe57r# zdflU+%T2KEW&dq2S%Ab;(W(o3#Gjy;7L`h4LKw)OKm!gBAxYdB8E!}>QSko@`%s>T zLl&OqG%cVC5O}r#Ht0Z`5(FiA0^FTt4uZhpIrtI)a>2Qo-D7@JTR@LE*p5Dh#DQ!; zF6=Rf*}!#ZUL5gRpJ&h#P7t1pbS+>`99@jn_O z2J9kJ=YUYIfd!wTe9a?f@EvQbqwzLi03ET-+7v z0u2p$wTK2Hp*O`Mni3LK&#jx xRFpu-23qE?-`v)Po6=}dE6oFBa5IVznNv0{fP-f+xrIcbl+<9-(z?ca{{c8VKm-5) literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json new file mode 100644 index 0000000000..b67dccfbe4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "headermap_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/headermap_30.pdf b/submodules/TelegramUI/Images.xcassets/Location/OptionMap.imageset/headermap_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..070e541975ae0ec621a4b3cb5dd8fffe1151f86a GIT binary patch literal 6388 zcmb7I2|QHq*C#|%Q7V$uwY1r01~W_|dnWsmtZBv!#xgTz#*&DXHjyQ}vL$;W*{Muf zqB2Udrsx+f$}YrvXKdy7{{Qdiy`RtAxpSU#&U4Q9oO$l^eD@h^>Bzuk6(Rcq!3zQh zU;qj00s(*{M*yf6fl7A6oY5jVm=kDjcz{On!~?3T5IhmbRD#I;rEN?h;XEDi6yTr^ z!O0zS2y_Bo&_&aO&Ug$CPjSbPZQ=5Y@-XJ;AD-m@@}!R^I?_NSJ~GiunMOSAwAc92~+7b;)`;Xr}pM ziU{~hCWsXwGw-GOk!$9MYNEB!L;?+ff~h)~;c0*)frz8vsU%N|10KNQod`q-Tn@ky z9B50wSTYANYY5ZW> zIRb;J0!MUoSoK^pwq${Iba2K{mNd0AG~rs>3hFHVr4bNRR(2~n zh%^lik`Gf=7RI{%+ryG0DAUXUL!%IwhVcJv_+N&WT`gwBbhs3fIq3wEz$`DoY;x1( zKnWBojR_UlN{gZEW0nN)CBaDoj^<1SnBXs4nJ)D@3)_{X%{;L*X0x%-lwY0zj6e%d zb)XQ)G!o?xu$iH)4lHUfqy5J>RL2eDLHiP*A9E}}LeVsg8^J-H=maiu zOL#1gEq}AJ1#ipC2nvK{b){5yqv0t;3=IT93-3j6z?&Y^SO$W?90Qp7(C}J#W+j$J zmX;Yg1X6KHVhAwd17}(KTlgeBj3F3r>kz0Xc~6GQu(Mm$)?6}(VJo^MlM)o@mc}2+ zhS{)%|FPXxlk;1(wK#3n)@$!3(=2gYwwiRW=i)NA4cY{GuB3ELSh9d)FYSv#Tkgq% z-bXo=0sdc-x<22YOcD@(oWkO`D)>|i%fBDXuN>(C=(*|z31|$dtVqM zdG9Q$4C#)q-#p7pGk+>PDZp=4yXHImqjW9}7=kP}>3# zd*bq#YXiV_MNQxxTS?FnW46h_-zmFyvm;V?6$0f`xQZ`?^g+tli5J*mo9>+6RK;o5 zClYra;Ndm_B=4*zXs!%Aze6`O99lE~;8R36+qzI{gOQV`>D93~0orq8*5PgHOt z{U+80ZV=ZL$KJg29}$6&6Hj(wMDCw^r|Tx>rBbrxEKk>!?@uQ&#|mF7?Ka%(bcy!XqqT$ay!i#NoZz5RwT-T!ga&rToVDn3;R^y%k5y^#YWBGc#QsB@dt&!1K!i*fS% zTzq&@Ok;CuC^-bC6DKOkA9_XhsWDc}L#pulJGCl(KV3gfu?QME?9L|%cEZN&>$C(~ zw1UQru7y})q}70P(Z#Anzoc$-jnO&HRO3yDhHoCc7FU#LdaPXQjQ%g+SoDK%ho+GM zWYdWTMAJl5b>x8t;fAvf1u^yq>?H#%=40mWtW$qtd_Lo`$-6toso+ae7h@ffG3*vO#W3#>yr#XXBFvS%n0(i8MAv26zgTLlh72V|S=eH=e< zY__DEi(5KW*TF**cfC%qPN+#BByc031Vfi>=W^G5F44}O&IZnA5A7aub?)mFEVgfn z9@;aG^A)O2RLw|F?avfvWVa5r#WN7CcBzT!U0JibH?r#AguIqwd~Ho^ZGUT@^GN;C zsfF|GPXYieowBQRwjcBqRT>xmW+RS3};b-44UfvI+&rz1VP}J=gVhR2-gaJi}hq4$}^K zCZ*@!bg-#9MkU5R|65*UzDwSv{OG*j`P96wyoDn8!w;S2@4D@@tOg!9kEOlUE^vS0 z_N(Nw`;QKj?=hX#J0w%H*5TJ;@I`{%znpuulgH? zlZMz#dBcFi@<~xiMu%@ek=D)jwhw&XXtxLVn|>1W%&4p@+v6EsF;wQ!ozru>;%oU> zw`))G**G5w-=tBWPlQj0M%_mJUQw{_I8WcyD!=N{YTxSUs*^skXOB)YCaz9pPsNWu z9JBk;HFSAojs|Ax>BnA)DV7P2oq_h0j(?E5 zfUm{NoCm^1%Ah|@!cB~QTMC*An=ZthlnxMZ6R8p{J8*GV(f$EkL#6Snmb8`+JL;q) zq$+9$=xOvXwY`_m1`A%+6PFf8>U3W*zp-7bMCU_VZJJB^-IKlr-H#~JowA3azT{{| z&bJfo7P$q6r~J=_&h2`c@Bkhc_cJaEo{T!5DhX8`&8-8)l6UIEWOxz5zVe=EbDV?FxE$6#NYIE>&Tssfu93)+x?E~KS!lT z!qD$-Wp=aiHY5B;C=jkvg$s_mNrK9fhFvC5yd(QT@v8wS0D~4sZNqO$*Z_(b=dHV2W z;V|bthm=N#vib(ACEw@#<3j(^>hR7XzW#RG#kTRehPlFf7S$ywlT98UJch3JZR_#m z_4GV|FcfH`PejgKHDhEF7X~KGTshKHb4)S{`xKO9P`^KZj!Wi##hEm8xammO&yXk3 zXoEL*=Nz-nxR06ZX@nFbiXQ$*S}4DV{9JO^Ugn%i@4eTjJWl=cGvO{2FLju#t=Rbb z7U5QuU_Ug>D}QKL&32C=;?(GHZxefL!KsmQN_Ktv=h_pox!>E9?%k?iGTkjqCd^zorisU)}4w-2Fpl56|?>Kzm0jsGnqKheOZfIs7k3t^cuEn-BeC zWH|J_QO&}P`ac)qKgjUG`NsAhIwI+xH&QlpwJ}O3yLx=?N_)eLOi`oD$I8>80k5Wi zk68}OKY!5rx%1W6I`_J+1AhC7k1T&-zu`NZ;v1`uZePgq!_R$*J=(KSh1vl}Oj?io zP8QTEx{$&iJ@oHFh$#G?wtHR~KlN+x8`Y%ghPiiRhvr_7#pk^$dxf{xuPOH5JUv53 z;=Lx7`LItW9U0&AMZSL*LT+-c5Ih0e;F2?kR-X4q1mcrt9na4Ha>bG(deoQ zk6M!#oB3Y95K%}%K=aumlzLw>SHMZ{o#nVwuJxssE(o?}0-Nz!InEI(+=6`YcO?oI z^vlCg#P1K{5<^;zc-#)R|uTY733KT5PrQ-Gxq4KP4fGy!nkCa+h64o?)kb~ zl7YJqg0VHa4H2GC(YKXFBJ%35n@3mzXn3Z$VJmOo^)hWFRZkuFRNUcq^UE4IC)>$& zk7Lt1I^uIWI-m{G#&kB?@p-OL9)>t`)4ArL1P9N+OZ!jO{&9I*3ST|J+W=6srltml zipK%W-Kr^gZdpTxle4zyI&gq_n)>6oq)BokQOua9#>Mj#c+h4}v83_Gh8X_$ z8D8iA+V;X($NRsxyX+Y;)kNE4n{;OVugOG-oz&z-Np$T*N622Okcegz55@sMXE{(w zcJHJ6Uu}7#>*io* zbhO4Bm%*wUGEq8~Q7Qebxxn}4)Hz+=SWCV%_poc!VO&wzK6$LI{n5vg_VjWpd1&Cb zhrU$}zXX9}Onp!8RH+E{ZQSlq@y`$vb%e3w=1!E{{;a3n8H^zf%!$)OCqBhFtXKEp z^3^_$piLCsPtlYznHH^2*mG$^|NZ!fLIh>vtCiC8juv z`~A=oROgC2Y2EEsB(qN1FJVBrj?iy#e(J};W{t5Dvtmd0k{s-5A&=}6mW4*`<|PVt zuDNOFHZ^s42y@$C_2Jv^d2`s=W9H7eKFM`nuwHf1xv(AG{NjvyT{1Q;)U*o5&`q|a z9$P2eP8og2Ghg&r+LnPj$fm5bMavBnO|+0`9pcH}jwR>}NucBMH-|DOy0Z z@4aIMeGNxScHnMB)s;vOp$=$TNm&~tT9kbmtol&fUgae-laeM(;Ot?0^Q$Zh8{^4S z84eHS?;__vJ+dYBX`&AGp|Z5a@pk!K^Yu*VJ%6tqpL|b~i0_Q3lA`8JT)R{()uoaJ zcTX-pQ&~JDuB_tRX83@3R`?QQUig|)<0jL@wqR{^#WqY!)d?}4TSd{%aS=&uyb@ir z&GA?T9v%Ci1rgrXQznwVy1)F#d9AWDj7T=Vycwke!P&^H#|HFr?q{j`0-b~h@K8C| zx+@|_GPG^HZ@oCEGgPL5%8UA}q7s?irhLZQAv1X_@Ym{zTPf-Gs|NKy(|h2=D=hrXw%Ip)BlJ#3Yj=?8uxntn>m-9#Cm|-m;j}@@ zI3t{qV)1;}EwORScx^OR6g{Y9a8|pih0Kw5%*bNP*x^F~o%ivQzTs&(wMkxL$6kG} zVT6k!1j5lD$H^_Y!L*F1GIZ)A&5&$p>EukHd$#f?*9ybRoTm*D_NR9*J zyj#s4Y&`qO3?F$sQI3AN%kX4^w&$1|a!{qzU$MmK^+spt59bHx?Mq+lMU|9Z&`s}v z%jQRmK;C*sqg(HB-`z0e(`q(rc=c5*H1_>MJKxjf&(oK{zu<^=KX$+&Cuo;9 zPO1}$BpS#?{Z057V?aia#Z^EJ@HhhIKXFjkb_XOeS4kgD@Z|tfdQ^b5 z!Y@-n#uz5g2f$e*>EbHB@^V>a@t7Q&9s$Rs&fqNiX_-5NYLYye=1VazvR2F_sB3(! za(_%;76iOE9;Em1E1VqbWwhjl$*h8u=W6KsAkhgN#1ogfXx0R)F@tz5F@TyFkO3k& z{XMr2I1K~_z?C42Odj(NAQ1?81mL)NMaImFH9}mKfpqj*8631)E0a?IgI!}QhXmiF zYh>V@|CT9&$gI&*gn=*bzh!WF`G4stz~sPI`&&-|rtmLY1(?#mZQ+XG&f{-e1mfQq zfGB}`jx~CU%x+mLL;ZmPje;S#;X%R~EHzpSY?^~`7*f#|rYHx8%b^qw{p&ZYb)lvt l64*-2PGNN?)QsSZXO@jcq+uvDRv{56I1;jNpSF?C{{X~I{UQJW literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/Check.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/Check.imageset/Contents.json new file mode 100644 index 0000000000..1623b3443d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/Check.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "checkattach_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/Check.imageset/checkattach_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Gallery/Check.imageset/checkattach_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9c95f16d733b341131db44feaef346d9643ca1ba GIT binary patch literal 5675 zcmb7I2{=^k+o#epB`s1&9kdZfB_h` zjwk@wy&FIiXl$kv`2>MMF{5#uAb`W-LV&tD3ZhdHCY0iD+$0u*%B4UoV23Wv-kB@| zD}f*MwCE0s14KDa=5WXq2P+V(jKv`9zv#mLrpo}L+jATMC75V}aIrZoi0q646_l__ z7!|}WMghcQ@Jcv99;2X$!C{nD08l|u8LNclz2Fp;lt6$Aq7Y|iq-Whmv`zs_BRjg)=UhF%HscNO=LI$xUV*rLou?Bvo)L zO}lPDo*{rUghMnc$AJwX$)A-nndx<2Zs&qF<=S$PX5(cOJ8J;;fB>;6EEE&0CBtnbP@bRPvb^l9VFX|9S^2A{eVmfkgz}M>ty**6dbP3d zY8}END~-iE8<`vn>Z;Yo?TZBj%&bnYKs`}W`A1wfd*NozN5iHYhq7PY%P98t`WV~x zKK@7I6yGKKLIQ@By7W=fF`EM(1gfya=%QuSeyT17qO{J}0$vqve!~p64Y|bu?a`Hj zKUQ+g9*U2Mtg$SeBK;^8cAr?GkQ=+cyxpRwduf73l!&-ktyX~q-)_~PY9d~hCBmD8 z_{tA%@wT6^k_Yw7W8{ddf!Z(^R6>|96JI`_L!{aI?}S_$Jb%(e1HQK}@-JS!&3^xw zo`f+zQo_>KQe8$XqQY=(wTdWt34OtmYRM+RN&WRy>{8JxKIhC#=c$VLspHkpsPSKe z7XS&%HHPJH5-zK5)~=y%&>$FHT>QdEq<-OJAATpFh0MhR_r=HjG*+Rc9ys0?SOy53 z*AQvu%RjxF#5dwId3EDPe%#fSNQ-U-C!;G?p_1WKrwRtx3cu4jHGWJDM%b5Nz@*@(WGcX1uwO6%i-Uim8!B9jkXjcMrkp!Q+)SK;xUXHPfWk0QrnoD3Fi+Z1-Dy5h2}BWM^^ z6UAS<;hls?z`+M%WQkj+n)RHdT=(R!I=Qs%eE-7{^1htsDjSUi?a#6w2)t2I0TqPY zR=J_v_+JK#S9skI`_k$zULvH9LO*=psXw@VP-5)V1bbqIrtdKgrqrS}?*4cDrL+VS z1DOFB-6%=fHG$_99+GUOTz2MMY}P0-@YM6vmI~nzgHqmZ;ioOXevxy96RxB+u4gV4 z6KdJxkn3L(;~CpdEIV*YJCU?PX5jLUf1+|@O!gHLP8fU#_J!ZRM5!6Fy?j zSKbt?F{&n2AGwfr`3A?JH?(obI-REE+;pptct^o+irkB5Q$-t^^9YCe`?nuPJMXt7 zwwS9Ls2(ZQ`d*T2CXi~IUT#h_moZUIR!kqF`Mz5aEHEKZETSa$!RE_RZem8UtPRfQ zjxkr`TbP*j(pL4yJdk?+CPRZC#&dQy<>D=_}Pk zv@}LNH94_6O}gQFV{cP*1Fq3JF($b!{fAy~dgY6N=Q|tv8eDP zKerh7N3{0e>dXCM`}r6;mvJkBS?_kyA&RfJuqC~P_yT4fOwjOF#F{!RTslIF7MZLi9NzD~)^q(SaOy;? zzLiV6dx`!;+1Q0ecl{gviTwH`1^2iQk?vaty-bcZ4yLs9eCc^C?783IiE3gfhS(gJ z5q>4EHPS93B66Fd+y2)H>RVB(n(Q=0Zzvvqq|~#IWoj$eq99t$d74=@acNxy7k>mi z66M9t>TR_?o{&9kIoj><)nj1%rr<8|eENo#4>>uGYx87WO<&rTJ}r!?%paqx=7bmE zXnhP-bJa(xjJszT$7`BuJ3`fpI^0fr#aqSiU^tu`4w6=DP!mu)=_cRGWxjmrdGzPE z1vgGNo~}F7Uf#FYVv00U@oAu-qf}=^=iKPA(WRp+2Doh}lPq%Ftp`7prT)00|6%L* zt&Gv^7VoSV`CqH}-|$!S(^^7v|MeSRGP-fXDxRcH>auLGxNcl{V`x(HRpQTzfpg+P z4x40DhI-RGKa|S8&Uoio@v)K8Oo^_HZ}M;YI#D%|bJM&u|LRDMOP5RUh0e7d+?8DJ zcASw&Q^mK?@e8I6Y4oX{Z>Gl=CMRYXr{;7jsmQBNzIh*&u%vELZ11j`-ECh29-zYw zUtF88OF!W}Y^JXjkcZ2?^Eq~^@FxC!{xuuVmS0=wNKNxwV|7GmWxZ-CeuUHj7b%za$ zV+`-LO#b`g`0F;$nlAG`=gKx`uYe!B#yiH*uLg_QZxwzF1o}b?z5I4A{w$~?MEpEB z5cv8)+0?k^JHO~Ic_C0}c(crIS+C5&g7FL0VY=5#zfPQgS@kqc^1!)$g~@2|y0OV& zivjGD+l}vA>-ru$KW^LZxs86$;=Ao9sI?}#x@52LRJtcL@iB65$5e^xIuJK<_^ZcA zcDb@6Bk0~8uQr^7(&U)+lj7*n@0(w!$BtD^G!M&6JRgqEtShL4Yz)fsyadO_nRv)` zL`}%{!H8W$f0jglzbJmi@uKy6gQSTf&!J~Q;a6g1S2t)ks#vMVX+}nePsJR#uxF`8 zjVqtoX3qdg)D`dAlewyzoe2Wohuk(4QjZ?5yldfz)(e4PD11p=2gNF$fdKL#Zbo`GlXpS=6y?6nk}pW%-HXri{Z7MTrE z0pzx70?#dTY`+ih$eo;bMb`xZWSaVAT+(JZF<7R^)Hpp)fdJ3M4971QV(|AFUibgH z_JX|e{`c!Hf9mDpAYsiFI>Y1ttdzWZ?Ne@FNbYH$6`<&yMD(#-1&-Y|ABOkFKgR z^K1P*mu0W#Q0;NSjbi-gQ?4n}O7)!gNM-Fuozi=4C1dHDmGTa^vV(BDDET<4y+h1I zJ8QrHSz_b^hhqafB@FC~bImETJ-#Lt;Ul@S{wEk(nh)L#+jh_j_Fzh)Lz0#^d=|cD zm$)kPyT-t=ZCO=A`IbpJZ~K#j1KbV@rjniIO)(c9zmwJud}3r%>Q?kVCq&0@k2ezIIgzC090k-m{dy|uqD)G;|u*1c1D(Y#U+ln_weZHH}HmE<&Nh#P{^uAyGf7=MZ;5kaT z`3C;2X1Opfy0(L<3SqP73VBTjqUo)_YHW?n9 z8TP;1c8ATNFaW56nub4+PXLd@VR3++;2aH!i?>3br{Q6*{-D87;a@Zu+WVV^|63m( z_qQxw89oUAu%V0`rN7faEcP!xC5$3mq2KwGVC3;{n#$j@pfa4`KV(%f@PYX!?Qa`6 z%-?)4-ua6S4vS24f?$*mE;W$=H_Z+ZgIBh~C@X>>7K4%b>o>1;(IyNA+)8uk4%(FF a0U>4Mp$}vhhgV3PA_j}vx>e_Z?*9OeLZ?yy literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Back.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/Back.imageset/Contents.json new file mode 100644 index 0000000000..5d2470c05b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/Back.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "headerback_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Back.imageset/headerback_30.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/Back.imageset/headerback_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5284ebd49c1fdd80400ea2c3a9f989bb9ba1f559 GIT binary patch literal 5662 zcmb7I2|QHq*C(MS(jrSzSK1V2t}(+zb|On-Nh)H@TnuK0St3ie7X2-WEFo(PQlj|T zE6NfjO0pHDMcPodl=t4TmEQmF{k-?{F>~iUXZ@b@Jf3ql6ZH%fKt&bQ=0NyCfdB?z zIJ%+$KwTX`>rq)A?xdr746+rK?G6EKCI(9@#ZTp$t!Vmgw@Znhv+1&cwJe^Z71LzM|ccV@c)$}rIa;bO6w5Q&DuC=!(M zN~#2yj8Vj>sNz8^pny>X2})R1Sb?GnMhTBq0YF6%ud0kw0Vp5}@pVRe)@wxTbc{G~ zE)?7x5ea<8K7N6Hw62k!5uM5g&~U6|ONb3PQRx&W#A0xmWC(DCoT+pas02``WcJK2 zE{#khd7uzs%cCqdL^Gm0F;IvZ3s?Yl?&u6+a4NW*}fhle{vgXnAk zgVN9dD3BAZ)|BL7MxsFgT3bO!L3f^Lb<_g_U}s<^aM~L(g95QUNN|owbY}>qj=^XE z>IMefgf3W{k#KY(yO5YOoO(LCN_qwccrO3U3JfYYx;Zb%93~TXkP89VH^c!d9hyVQ zgW-XoKKr*IISAy_$-!drbJH!jZP~!V9?n*It*`w zRT0B|{_SN(5gHLQC9#=QL=gP%g8#BKt7P)a4CZfO zBd*OaOmoko{o6O%z@6mG0+QrM5d8V$w-$7z91&2$c+3?ADnDf{LsGNIYyv4?;3AvodqcDBRzvFeW9O7ozV z`g)6PwFLCnd9ZCLD^{9!3Gnk<*#--t9;mARM{0W>-v;&v)7Go|^IqJ}DGTubklgt$ zv`jG~{&Rs!dndt4fjt>5ByGWm*Aujh9o&OC;-fXt}wxsp^y|xB5qvly7Gi z0jbNhh83>sog-|}ZJ6wKKJPj0k%vTf0>mJDGA@KQ*)L4kt3e?8Cu6OvC0{o#` zV$XSsg4KyUBY{8D)~(~kr7c$u#HR7z3JL2$74yUwx&=X>6k(-}=ByUyZ%j z@)I|mL+uf~uLYXRTLIfqYfi07$EOdOU&?Z|;>!_k4Ji$Y&e&qUq1i@xjp=Ue<3}!< z-%!fp7b0IASJaEo+?$rv-2Gnd{mO5m6FW`@H{VGhC1c!87H?S}bE3ZboTDpfn$VEI z+bHo?S}bhe-L)j?8^@j-x^FzPt7wJqlFrai_eMy&^Pi}$GZS_`$-2w`N>vq96!BW& zh4SKUJ0n%?e<$W^hqqL@hz1IM?_H1a;MPIuv1600Ng?eZ2Q80{i&uFcFFd|cM>svg zBMf7ZAhUf{M5y9DqT@!-9r@wUwaQKW4E=OBo@EwBDE$ zeW1^!;COkGUvigG<=$hu=|my9&*!%NC!rw8Vt0w&QIm0CckIn5a>HOBzF}V-uHkz_ zMfBD>sXE`fyg2)<_S*xjf5lB+T%>)M76_d ziLZ)Rnbi~P51qbp?kd})Ke}bx8vWMHf-Kt)cvs=?N}TYMmn55?-_kqC`)TVzG;PlT zqjqb83E@zQ&Ukr&75^p2tSW0GYdH%-rc%}rHR$c4GyIeMWn#+m@9n=H=A`G8ZMVnS z7n*alzQ?S!dw58}h?aLStV5P_t2BFp`Ca~d^@sS@t>P_WJJEfLjrQJ7y?nwp)mE}L z2E#XjBBw=Ps;4f`eAq`tM+NwlO4?iElDkH&+Kz|ry~6^|9|=AD^7nWt(*$ttheuU%i4ZLwK>*(kbRCZ_Z9 zjqD55VCso}1?=<27bXrz>T;Xbkw3dAD(B%hI7UgX4>vK`MZTwV-|@75q<+}_9mf6* z+Zrn3cE#CW`+J0kH=o2o>^=O$Cd=`MxD3&c86zPdF@ntr1p&2h?(OR ztXaTLY;sKU-koRA_=Ao1wl}?>>bISEZSiI!=Tccs@p?{dX@9Y2S59|I=|IVFms@wL zZ-VzGpX4F$H`F(BL+(RD6X@=LzV#}FKvj@J z5D+C@jQ(mKWlr>YoY#=w5E8dvK0wS}x?HAs>+!V(Tly$Tu-tUvwb(<;Qp|}(6;}P4O^KoCB z_0>GH!~Vx2Cf7bnya^^Gd`*Y}Qwc%Iu8-LAYDG#%{?qVkeNSay$?zWU)qdi~z^S9j z#r{ErS=^`@Z(oi2Cg@c|b^y#uz=n zkQ1ADp(EZYE-rqHsn?#DsT!M6D_WhjB(EwRyszB1n`!AN->xWG&wiL&HyO1ij)Ol0 z9!l_MUF+|#b4$$|J}~;)=bO*xiR;2UrHbeh?eFvRU02_dJ7U@9RPnGRskUg0PRNff z#!&|t1Z%>50^^?(47Y~X#_njf(rzzb{}kJlZ48%F!;!LT&1(E=zFrC)9FMj(zr(-2 zFS;7s65Mp6t7<^gW{Nmc{qb{gcZL3l{;5%i(IulopE;er88-Rec7yLLFa1a~e!qEq zGh;NbJ>c5YqHlG)uXyWusqN7P&q5{yN7qf-rVurVy$70Yvdv4b4*isQk^Zat^C_uF zm-TY0L;YDj?<=;y%z5it{h@{YoP54ErS*90x5>K6{Oi^gMQI}qp1q#^r+Zd+bCz>B zTXAM$t<~S7Cr(>7U!hO+eYbSu%S_KPzm(sjtg1lx`RZLls$kRNYZQ1?xJH& zpI)AH$~sCLwldZUyM-$#{E|FXavlG!=(4@Sv0X2&KRN7qc-+riFkki`@<>(bvL_d) z7h=R;qa%-8>t9e){p9s2yDfj2(y@L&luyEgM()xP+vpW2eIUnqN2{(@Qk%V5~F zEXnkC`_E@j-ClP3HS}5!&}utr{$W3MPIOP8UksMA-YEX~91#TZ`G@Qf_#&(?V)SM3 zbHvNNl~WViZ$r-aDu{q0!yDw(xBKS~7Ehe6k1@!u_%<2ZR`>9V%-&PGOES>`O=CZY zZ9Zck+-!N*(KJv)tLfb8w}pP&X58^3)X{LhzFbpuD$5U={1C6%JylLv1L8&we)Ad0 zt5R`gMBXm+@5D(f{~WV>PA`U^EvJ9=sI~~H4l5wFaC%n&9ca)b3vm7!aeZJ{gZotoV}KU^DX=#0BxkJt3zTz z6acxcTEKnF9NVA0J8~!IUeOIe0O_WFYnOBx?hK|S(lt)^Qy{=KF~jlOg&1zeXFWnX zD}(>*+6!{q`#-O{yqC_EMYdvAob=T^VZ80@Bma+aYedTN_6)sU25^X8CEB}3OhGEwU9W}%K8!%ceySLOWNw5+h6ON!~ zM%LV0A8d6|`JT3(kBR_kw>-c3)-S}v35mPE^~SFLzKPKG&In78-x~2q+AMr|Xh&nJ z!pgi%xh^Zcjb=iijI#kc1UHJ623@2!kAlUivsGDq-)1$Z`+9v_lwn;aPgLW3bI3*RT9sN&En7>|@RN z$CsKlZw{{47+yIwWL4SbrRh+ar#bM(c$^(ntodW&i`xHI{J+g`u-N&c{&R|>O{X*1 zaMS*0XG|o)qYrLVhc<;MRMLNI*0FQ_`~2IirujA)p7zXl$GY$?f@dErfV&lDduAdD z8HfNNcbYU^?sL0ve%yg<8DlC1nK6LeK7Dq`fYxPj5b+tGr`vlZ2-X5!^FtCumo*jg zg5Ws_G&j6J4u)BukP#F-8=5DWz!M>08$_R-+Hnoc`wZhXGb+&~!L2gG`A^&4uo)Bv z098@bBM;;gz~gXO9N;88M?>P`uF&Ucc-X52G!P4;vVf+f3?D8FX?VB_7SQmxzh&_% z@QJp7PXz-Xh=0;REcP!xWsK5aJ}YCC|B_Y4sQxVrs{9q3vI+q{w*R!Ntcq020vh3O zJ{$)BTWoA5iRuo)lQ}rmMtX43Yy&ZP6=QKn_IeQ3kCx&rMZa?+LG!6 ZA!*}I97s$yH<37yfI)5EtiRXbe*lgUkYWG; literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Close.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/Close.imageset/Contents.json new file mode 100644 index 0000000000..543bd65b23 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/Close.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "headercross_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Close.imageset/headercross_30.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/Close.imageset/headercross_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..215dd38459e87829928947ab2e255f3e88c3c1c7 GIT binary patch literal 5916 zcmb7I2{=@3`zI;QtF$3W9jTOU<`^?*k!6T%W0xh3aWIw{Wv99&6z zU?cE_o>SfJ2$Ad{I+;#mFswkVJQjoSf3k)B#g-04bznLIaxl>lkzz3E5Xlt<%3x*X zWI;ukjFACx3V1~jkVfu61so0lWk5U*Co8{nV+W!TXXmvS+(xX<1&IR}W5F#Fk;v!Q z$N#oIT1|_fMWrwSG#o712x0>E6sjE^V$fK0G6dK{4iqX1lm+Z4Waj)6n?`mexuFnc zqvH%Fx#ROGgKNRg8d{$Y=ZZpg_?>RVa&vQmTp=nGz@U_r06WMY zHmgf=(<8Y;09r*_Ra$L{Xnfoa0$^ugA#lzcGR+QRxRKx@k*E$3YCi^}1nk$)U{~}v z-n@pbJ=u{&pO+-4s$mEO1v$3-JO@rHJGw`9-QFT;2I5uwG;$xsFt)!m%RR zMTFT&LNAc!dX_D=@CCmG_V%#%R5HW>EMZG%*rN6Cf^d({8P}junX0NZFGN%Z!@mCQ zX5J7QQPU+c=@di}{NIZI!dfsj7ZGA`-X&w236+AB7eJcaaKTXuoxwy>1-H_i>pG-) z0ys}Fqu4PW832;}1uet*UT5caF=!)}EfZ-rb~dpK3&0)_AO@LEabwcxKP4M+ZHX}_ zy^!sne$g5(BnJk7mHih9*wX($**~qZaEsPrl3XZc6{-Wg&CTayfw%C>ju!q}*hY|D zXlbuhabZGqDv1dvfdF|>$dKV7)rCY*5FUWU!-NPBQi*xa{5B(t!^_W8^Z+D%@GA4K zg;!!>G=;dKOQ4SCIdNW^lk-S*)fwGrj{GyyDJKJ5($+_CkXEf-f7g1QLEt)d!g4F+ z73y2unC5nC*BSJ#;N~{AI?0QAsG#t-utYA`Hs(9s_M2w8&+ptQ_xF33*z+c7I(3#K zoXSPO&@*`MryaA=_a@kF6q#JUsv$_xy+nx8-^cA&;~6wY^W2nQ9?~0Ew|aUF)3{c6 zLSX%o%30!c<{@5qrS68udD<4O-$qs*q z87pZ}(=5fW{40OX+PvvP*Y)?r-?ybH*C_*9MdY7LnU$@KQ@K;R{1AL6g z5!)k5dsL%qbT>382$5D&m#u6NZ3kSG`eqY(gz7n5v$9-gYm#Q28y?#wjbB&>B>$y6 zCY?jLq_|D3k-ABlpclI0X@Ecr*Mk5~mjEue6~lLhzXmC9FmCx-XRG2V*hM*SkOfdJ1+13==+3X0l_ za3MP`P}Hnm*kT#C+C?JoM!r>|s|q-~SMV#}Cn_3qx2qgfdy^8} zd=qyhX>-;p;p%A2hKT^Kexeuu7QSmW@B6L1mI_kJa#-dj*=6+ z9QOQ_3?U|6KP92{+1q_@*NtzSl00*=^=>RF5#yq>eAm{<(+xG3Y@I;e*v44SrcJL! z1VT*iZ6=A_I?<`=vfblA(OMs#p0gioCrF3#o+xb5TkUX$agY0jf&wVR=egDs<;mG` zLAb{6Zsg}~FX0M4B^0{$O~3Yt_=w2Y6Eln%UX{RO%5K}2ulG7tcxt=q>eTaYAsCHV zQHk~E&&t#iZMVBi=7n}DSLpa^`l@Ze$kYnE@@fYs<*)0Z%y?#$oa&UO>Gp((BZH3l zrz#SB6MMC)^iQaz5_$IwU)ue5Y<_~_p;E$eoo~RQsN3P>#*smMqe(rk@k?W6gm}Gh zy-$5^w2iopgum&J=$R|aRPGT2GwvF6ULmH!f0CLci;FiLvo~eNrkQ7Y8IHuiC|R%9 zKy0u)ckR+mrp{1A+irgK_VoNrt9N*()n8;;p=UCLT008}7Mvf&Ezquqk7#w7D(Wa& zma2ZM$T#NBu+6MC)iT{}g|>$3XdEEds@}Ifryiyra$i#0uW@%{W%Pk) zo9qu+5!p^zXR@QRzGpMCda`Eo!NP%V*6sOP#qa`{#i?E0z8;A3?6 z$6N37r)@tSL+8_OCA+nFo^{V;ZnABQRcTUvZE`3y{n|_WI*+;wdJ}rK*RXp2d$Ea; ziTZmlpz#(>HdeR2o~n18erfn>J1e97LCIEDRM}98d+&{BNoDU#$9kQgCHusB?eI<< z^?F5lwP)02)VG;#>uSgHPOb2*9If=OjH)p6it#x((fZ}wi#&H1|q?dr>k%`Isy1N;vpcSx3157wmBysLg5 z>~mT$SX)d=46o69*7(9kLXpNmT6LOJ`c*UU+}=BMscxCQXm7Wu)*By8I!tfo>K*kv zaeij=;De36hjkt*rbb}2IxpXdioe_) zV;>zIvrE_Wa9^_0PSo0Vdu5@UvKIH{1`pAVY^Azngc_KSvg&8T`J-8QOVBdbkC8pp zZS9<#J9cF9rT4h^@Kny~y~0J*O?tMO<(%me#jt<|WKfWxxd9v+f z^XcB|_Xo{qi4!#+hD)ARs!yl~Pad1(ndBX2_4uTj=XqI=ysgTZj@N#>^V?3^WNw## z_S2&Add?S|4V;v&i2Q$ordCdFnXyVDDiH^cw3=TxD7`uQUG#bCkDB3N;V{RodlW{8 zGW*|FO7z`$?Nsxwjoe9&t4nG>)jmE`Ka-bZT3M7b(da(lK6I{s!!yoa*vLb(y!O0zo|Bfx%sgpG3Rpmnjacty5$MFce=j+ z^VGSo$G367^u23ckE>tE^xmmwQ|RX-WsFxc)5GTjAuhil$rYbgtMh4n8W}#{r(ZQY zrSdu`Za|t3y_g(XFKX2T@%$0K2(~H_HX|Beaw6q`|x(# zo9^cK4_qJgi2LrM-ZB4X`vK~1jBBVkxN$bq7n*q&bMV=0g(5$Qo3I%7p2)41ccO*e zDfH{XiO7BbYW=W0Zt~lt{O0_RKsPi_2;*X@+ccstWv8f1Pr}Lmm_R_-yY@ z*tdSn7Ww#52+!+WCU`JROiSPi^f)XkK2c&_t6H0amC|LEn7F9f1pRXdc$6DGIE=UX zhKQo#{hNI96;=9^x&6&NHUeELL;`hIfFUtc=GzEEv{Dq&)R*l_xZ<*Xzkr$gRo%9}ybIsOVn+#mNecHB4 zv;HEhRx4?r$i=L>P~(f{fEIX7Os{QCKxm0No}sN`S1U$NYIes!K0huTOTncX-V#7-si~=w7?2%+oK_9t zxn)u8*Wn#Gle3TL8X$m7Q$LMMYBU!b-3XZ)=jJI8U@tK*@za4A{B?%c_`i<5AbY(3 z^|;HK5mlKZEWcJ`+V5}a$Z8)&>|pql2bzB~Rj4sn$^r(eJCyq;SJKa1U>MF70dO(VS zB)2AIch|(JoOH9EhFfP1Z%ei*i7mTqDC@GtTD+nZP!oL^r{)^P$>ARGwIEGUmP?>i z|23d@Uj&MAcN`;OUq|&^!Dsg46gLGI$3{2evLuDZ1UxRR+iN?XFdY?IYp_?q==-s5 zxqNLi)|U3e7q3@MpYz?OH@0=8wl(7o;hyQB(E-Pkgtm4i^QT8y_P5ItrE0m#8xC~)8rcSmF< z{dS-!^$l*tUZd#>zwRok|4hrB9&~O3reJKWC6TYgb9Oe_$FJ|`iN?N)*SGJS=>E~| zKb26IJn28t(N}C^vugODXemqdzrFcO0G7PM5=H|43Sp^GsWc{xSo{hL5J@oZ!=7l- zx{w`(^uLe_>>_xvbhlt>2?Kzkt|e$d4c;%T;uf@o z^&m)$HpLDBSlD48JJp<*YBUz2KL0d_Z6HA~ezUcN5FxfqDUc@wgFw(Ca)a!O3qB$E z8w`vs5p-ZU4A>1(7cfiq0!u!_p3LJ!Y9tsZpgH`ywimn%3Il)&s5!g``37Jl5{m=u zS1;0#xY!)(5)BV~^&1VwM*pD6%E1-?O->g7r#(FGPhGq`yz~F2E06ql{7M6{*gxdt zFtTukewCBM;Qr8+!zlcz3(CU@{#6%;`7;h22LFf8xc?F_4lDc9x=cEW;sU|2BwT7O z0^BsaLD-HJMqU;KWfd`d{&;406WWkQgIfvd6m}<|jVRs_QZ_c+LZUO-g~Z9p$)R@c IRM*$|4_8McJ^%m! literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Done.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/Done.imageset/Contents.json new file mode 100644 index 0000000000..d541a6103b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/Done.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "headercheck_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Done.imageset/headercheck_30.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/Done.imageset/headercheck_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d78868146cd82b22e6a0b21c04e9f9b4a29137cd GIT binary patch literal 5711 zcmb7I2{=@3`?n-DrHy1sPP8e^9AgG0S%%2cSdz*%W{!-Q8M9;(A(URUDEk(YB@&g$ zQke=-qExn0w5TYAhydP|MfrDW#-KN-1~h$kLUiaB@&EOKvfOY+5q@LfdB@e zySSqOKwBF?6R0eP2k8)jPPU=4Js^P1=tbRhOo-%(0#(&P zbqpS{i%|vf7`!?TP{F9GVKAT;4ggiva9DLv6M0d`XsBTU3W!3Sosyn*8__x$Bo3U3 z1vf)PBA>F4|HD4oz=U8zqp|@s94y%yVgs&J8ifh5=o}^)0$iZ|R2m9Y11MB7d+HaT zM)o8zP>8VgAr>3*G@-fDQHU8USO9kJ&{WFcTJW=m-pPb>MWyZklWxsnFgzenh{gsm zC>5C^COQ!-Q%&G7v#dF@jAYdS_86%Ti{Hcs7y#)lej818 zq#+HC70E8b%uf<}nl#z7e75Nico(?3!rs%!5DVA`YeK^s9sXp5dvsE{5uL`?*Qaw4 zR#goD`L~xTMQB9KoWy2Q5kc^O3;xT}w5rL75QS4N+0b{xc#SEF z1%RXvwlcLXxQW-JsYLN+$tt?{P=t!0;GW8gW9HEUg~wFR9}VzGTNWumn!jY(ZHJ|n zflCbubL@5J8m?rp?I=r@TDHv<5wWp9x)Ak1Q}Z9$jrqdz?2qP6mv-mByp>bx@AomW z^?mY>)CqypG+_dUnKAEPnzPeR9|A=}Zg|f8njkH&Vrgo}D-l1QchCUcTdJ@$tSzoe z?8hRu&0X0c$z^-WCx}1FCAeb?RSOeWRJPf5bAL+p zz8lL~;$!6Yxa`qU(vRkui`QsMlSFAkqBX0V01us46N&Srs|7rBb3G?`$rA@^9#N9N zoe~047U&MBTqc~;k~gTON$CkbEqBKS0nUK$tPN_qOc!Al)S>g*)!IMdkw{ zp}LYU1d5Jo69t9>#?P-@DTq72NId|1UgTO(SO=O_+zKX>!n1_l4Nq{%T`8X#D@ZeJBZxnD;H<-K6Ti0HSFi^=4E!cepm1nUKNn> z)s!nELR@nWwL|otE@-K218hVsKfWv#pW1Jkk>ze9oFmp0bTcS2ZJnijgPrL#eMwI{7kKl@OD@^L#=>`-d$bmVT2M-E{0|!|hm7BF4jP&bn1+Luz>ET--tP z*xFdZI;nSZl3}~<$dKf&9C=~vp>S|((UQaST0?v84w1GOJkeZfA-4Y*>yF49O-)dB zvG)>hl(*pXQ?fk2+h@PFaAnID>!8qg-*=eyt?!fjeq@X_woot7S(l+OXBjv6da#1N zSZV|#3}Y0#dgHQ)P}RFc7X`0P1>rAr%glU@eGL@OuuV>0c)LcBx*$889nX$Z*B>>u zRY-{3)8$qeT$bRQ*k)3(^N2wzaiLQ0xefos7A9D2FCiQ<8v(XQT|G^%?d!tV?yAPs z4%U`OuCJD@K3tt2?X=!$qrdIX=&=hzdUuF{8MiH8Tp*^x|0In`d(Y&!-Mru4!r`;T~cu|YLX_^}M>h8Ncedj)&e??rp= z*kjUct7WFOuS9>Otk6az!zHWI*2GrHN-JG0tDhS9PUw`#m`JIly7C97ua2D5oYIX> zIH&8D9NodQG7b;-shD`??+t5F-t()%#LTH zXh$x;4@M-&Jc-{MUlC7@7sa8e7Vg<@B?s2JN4as_%-yW7J6soOS=+MWn$zQ`?p5C? zKGNk0IvMGyotcUa*^S*zaSgaeht!1h)~p}Kr?RS^hCSKT@ToDO@%b~SoLhRg947(= z?*vd!J2P&duj(jIJwqyfy@)5t^HSWUSk$mN-6#F{eF?HgC%e4p zFJ8$`q8_D&bgN)r)V(xwK3JVwzmnYRrmCKgmv=cWy(-+yXe;@y{ymoyh9?cf?rk#l ztKCpr9=$c%DerS`WS)EOvAn3<@jO;;YwkoLc>Q&Y&5Je%!k(_HZUbr04D&r7dW;ku z_x#dq*%RH;bLCUv50@{_=tBCH6vkukP_HbuluKi*UY-8CUE9OcGdo?Y4pyDA7_xB5 z#9H`o#wMOk+`0J_8o#&B$^I($sp0dGPOG;HoQ%@@#j7|`H@l0y+H%^HZ+6 z5(u1@D@K2{JZ(wzd7NKcP#YAzTiIXIL#}Lf@%mty!gXDg>QZ9;^!ZsiH}+j9)u4SurPm6vdgnpp5sjfY8%guK0!+y#3$s6cy#|KU-ZfV#evgNS1N(+ba{JF2= z&q1L}M;njUhqP6G+GaOF9O8ZMEp9J295OsU>^wYgcwsN6^>CV90oS4LLq*1qc+(GS zN7mAZ^PBzio)&$p7JMUEBS>wIEPNI;DmuJ!%s!c@Lwvob!7kgfX)fMdA-MF zPr9vA((Lcf>iAH;@m0<{cizWF@(XfYRdQ2s)3>qev4YFC-{9uYXCLD7lP(Uv$w)<;d2Tm!CL#Iga>RiWVr|AP-gET<|1` znsjzWC;H^Uyl$Ba39oM2aDVU1TEUon$G#F~c2)ZO%3U#+dY&gS>#gdK-iSa**e__)+%IB=i%q`t z^+vqfSuruH_bw>zwaQ{}@qoON_C~+lzT(jnHD`^o%fF34x72^S?|HvqwYXzw?l zq5Mh>clya&*Zo>?a_ZyX9Uhd%4Ufn_)k*wbJ@#TiY3#{BTyA}FJ>+Coam`Qc`zQks z9UR)S*yYZUYeP?-Tu+ZQe&KpgF&OFy-t!|OkC7N!p;+0 z=jqlS6tI!^4O@+h_pdu#sHNADBI3W>TdIWOc%bTrojXpTjw(PrtTv~Q1==z;jF)O* zj*fWwE_Dj^z?t2yUpBAv%=m&sLIuDs;}!F|{bip_R1Dnuv@hjVSwU=yO7bTx&NI(= zNeXcBYKTjParGI_U6bT3a%Xa@!fnpj0VZIkqDAAPfbe2NJj+y%a#xX@T=%E~oFlMX z=ypt6b8}oyb2GYHnaC4h@AxSaF|R=pxpe+9D1q<{Jazx%-yf&1rQqxgzYjp07#Qf2 zSP%t3ZmU-C+%m)V`|yt3$@y1wBM?BQsb9t=1G)#DX^l*clk*e^@J&o{{Bj`%f1lxv z{;z8<$RF>2zwQcVoGYiwJ6)roLBwv-UcR>Eu2yI)wYFOS6qo+bI5o&D!x$IP1KN}IOq^LpiPOh zv`fQzMSBjckA3iRjdM{_<3(l5MqtVFjvY%Zt~G&Y-nnmAk^Exex7>6&N8(Ijy`5~+ zTF1!Du$71E>tY!KX~Aub5nT?B9ir3xWZ{KEDKT$HW5DZOWs*5(qlCJ+$kNc5`3jZ$ zKJ0j8u$y@5^#c#jKx4U(EhO<^(=h4xhCM1F+Mzpb&u!h~eJ=u#Z0hfijsiq>lYX36 zGEz1-RnaiN@^SCx`Ap|HZRxdIQH9bOvTBcn&na3JD~TREFPFU}hGC(bw5-pS_*h8H zL8_1>#C)}VNL-|o@=h@!TWAx;+$wyL$3Rj0n}E&h_Jw|n3h#B+tCVUKO5!bx{^yY+ z*h}^~m%pLmVx~v6$ov|NwrXXV0J%EGtzT@nT*~>Wt#M`j7N=(EFM~Ag@cN0tIIh3U*5pp_hb796z2oC|G}})l zoch|%y4y|jO`=zjMh+fH$=un=oBwcpZfp3LwEwnQe!+Y+XF(tEchpCZMx(RgA^-PD znn;4768;c~Hisxw(*GLnu`~1j?Ax@a*|8Z$hi0c~19;cLSP2W@Z=LBGn@BS;5F=Fm=pxD7&WE8e)GEtZAGWUtu%uGp{=Pt5K=ZiLP27(`Gv%( NVYN_e*Bb6L`VURonRNgF literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Search.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/Search.imageset/Contents.json new file mode 100644 index 0000000000..05fa42c0b6 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/Search.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "headersearch_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/Search.imageset/headersearch_30.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/Search.imageset/headersearch_30.pdf new file mode 100644 index 0000000000000000000000000000000000000000..298c3d2044321be0ba3bc20523232a22f4e68bbb GIT binary patch literal 6286 zcmb7I2|UyP|99kQ<=Z8a)R086eb~k%u{m@=6e?K=1_tpl~S7 z&ItmAs;WX^nq)T@XS}y2jbKh@IFq0Zx;qJ~rUoHVi7X|E!f)C*I*sT~Akm@wwaE?? zybS0Be8Cn?_dAmCL=xSNgr^f6tpG(OML0|07iWsUIWr(p9T<*K1Sm9RX}K}zBs>KI z$RiaL&P zftAT1SA?voFU3c#i4Vi-YU)zS3@8koEy0Y$fZCI(L^{ch=1wP&pmrn&G8FsqD!@>L0Dl-K?5*3?jMkKkp;K3^4sSYHF zDjco`Rn^vJmvqh8l7*c;!4Xei($vIY;hI_+7`Fb>2nZ^Bb}LyB-05^MK{f>J+*k>a zsiYN@Txc#V)R*6Dio=3@u{er~sFms{tw>LSWnO9m>y=^Um0=dDSTJK&ecaexjD$#g^2ff^%h|%Ti{81X~s@HZ5Ci`2+kL*xQ5IQwbzD=qb<>4D@KdRuSycMd#Wy zDg%R|d9hUG;q2FcdsuP=W0@J^8FVts5ctoA|7B>|)#8j;4wq6gryZk`S>=VYn%s0b zQ8L|)!Ga2GrNz_@@Jj+P{QnJ)D@8{3sho4MODSk1;pQ*k)}n1LqA zjX)>6Flh8&z-FbkI`Z26l#TkvCf8DXtLtBa+EGlN8@;u#e82e}v1;wVM>TZEH#<)UKV}e{zH8TaeWV?_9B-a+dpJb8n zA3f_r%D3!u(EEN=%mfuLcEaw2nhYk6X}GOkSp?5V<>IRsZ-Y9ky;(@%7pdc*Xdnfk6or;6j43Y@>OYZoW-#$Sj)#Tz_D!Qp+75>9F`Cw${g+l>|L z%=*LN>;qbnRoi^ zzf1UW?+(26Ro`nT4{q<@5Iw%f>VU~XbEqt2`-QD(sI+mDTbWMg+}Q$c!DYc$)AyO| zX|Y6XH`GyVe(rc=5`qxEcI9=5yk>lc@r~q`-oZnITP6i(4xB&N@*n}90(Ul8w{Le$ zXg%|9J14*}p&@~@amNQSq3~l5cjCqVIoom6S;FIR@n&EC?y%2~rtmriFO+u~2{@d0 zd&u)nSs9Sup<_Da!PJ5c9RmOSEE>8U*&}W1s$*^qAQ9vvY298#RJ#)mx=ystw=O@$#r&u!H0QZxmsw+>YawHb&QQo4CkK7tisYH&DHlQ<9JKj* z+C441T-F9@bKk^W{aegV>u0CrbSe2K!@DHii^{TQ=-&>0V}6QnlNN3jQh^P~H`;jF zzvULNWSUD_5-Ohnmy>tCNIIERl|)YBL&C^LPFaqnXQZ5B9o-!b9nJ1r-{Jl!p zX^tJ*JxTNtsZ3V8m66teN3tcWb*L?|1=(tymYmU@IeRoRv-V~9ivumgt;wzJuWYib zHL6c91aUqLBuCl0Jh)NYSD6-#FM0bHQ;6v*c}%jnMJ2;0J-D->JWRpXIjO zx>xJPW4dqulXZ=JjvP89r`XZ>+Q8PMF1KkHVboC`k&oJA7bUVg!a(~l;SuJs-9@b{ zTH%im==(S9Z>Wqr9A}d^l6y7JDffI{Z0>xXTW)vmLLqSfZI^jRkG1BBfqRY<>94f% zDbJjL6knix=`{Hq*Y)|I;lf$FFSf8k+CQl-&7NVdnT#EFtqB^9m=DKvA~Npu+t+&3 zMjA~S+1*hz3Q$o@iAgb5iG-m}HriO-^LnY(9@=mEUc&uW`IC~}?y+SZmN+c({^z{`4Uuun)zlXG=*1$Qkfi*HOdxW08Ay4bg^ z*ZnVdcWI=NP#g2x)tQTCEqAC31K-TfaA%}to7^hsLnzCk=ihxyNabr0Q*v;@dWIXAi%=`{K0g=^uV3dVX%RwY=|KJ0>Uj(!^ z>3$g-z5K?wYGFp>LvZ3-xlO>Pi9Ir^vi`YaB{LW6W3;m>C+EW2>z>^aH@=`#ngI)F z`aVBlIjZ>dUhBuMrr{@)C*9J1`>55HKkPxhm1IVe9 zlRi`VHA+sjE7kY?yOCmu`R~?G%M+)6?0Kn{^1W`ZV?t)`#YAFmQ%Mua#-OUmU*P+U z3yS11b!d~_!zufg&v{~>KZ~F?o+%SPaz#>C=mqKdmDr>d*)1*DR%I)-YZ~#1u?xw@ z7Z3BRH+XQE@9_&4ha?3w`WB)!`cioUj(hGXC7wQ0`>&-FlB1E#f%8>ZH|7Rdxa^-V zN5jwkaP`|_6XuLGc+j+!zdw3@gEv|%Rzy5+WnF4^+RIOxwR4I(UwqM;EtqG>tBHpC0ZyqeGTFxNy7EU=T*Qu zj^kVp;?p}j6SF%zVRZ*_Ob&+LPoB&CEt0H5=bDQW0N#O@&Y$e_X+*hmgY>Oo3U<v`DDHIWYx1_nnCS&6 zwWh{lZ$9y4=NzAr;+&_i=f9dvMOIVxeE;^nY`#)AtG4MU*8ln|e6KEW*40s}tVm_L zcD7h;_RHJhe*M#@zD4z3vW$?Kot^T2VW*(kB`;uk>dOPA4*g--E)Tia6_Fi}^*(wG zbZ{ysq$>P_SI9|<-u}g-A3m0>7r6buasW9c$-XG_H#AQL@lk`3{2iS8370A%n@H zM)voUNsU_K9Vv#qMO7|_w_o2nUbyGj&Hc871Q|fTTzKO%qv*&%X$bc@=}f=zcUcvJ z&ZM(Mb60^+Srtlue+j!SanVRDM6*K%CgySRFwWFT^4w1=o^DO?oS4x9@_|^6m+cA} zJoEFh(EDZkj-DK@3*vWJfAWd*vl4E4@?YJdPn)zkeA|Wi&5{z05qxy!dv%QZN2*3m z&gebsG~>-fmAmsjT$Oo00b_ocJX0%-s)~C`+Ez>M)*ZP~J{2cP7Q1t#xTrEQR5*vk zON^LmbeiCi?Qc1CV$frhxVwPu@UR9vm5G4>@W>i_%b?! zno)fwfgc7nIKC)Ab_7RgU&!mPJfUhX;@!{T&1vfIvDp$T?Nk_6f!Xi+eCl0(LyI3Z zi}<9VwhkK}flBYQuii$PL?g zAKT~GF*__viB&*){2nT!ly^|Cq>-oS^!dUU!JZd8`Vg|sZR-&a4xK$q5!r3j&3(Ej zw==LL9AjB+AND2H>dn0;-(>XY?oNe8bOE`=RW1e#uGt{d<_2Xi zyvqy?4$q?Bpa7e@TwGjN*8Ej+jYWOwlZh-w3t+QC%aj%jOLJ$LFXg;QCb1^rw#L^g zCCBn*K_+>UK-P`4LY1+$q2-)dgeS;Et_lo5{t>#LL|tZy*#TB_2Ju=V=CF8>n4vlR z9@`5H0|9AXWym7I#`=V!kVr)&)Lvjk#+nyJ^v9{thdZ=GxFz9%)GYRB@!BXpLf=#m@fTNVG;7SSrpnz78`Qtacbz!D78rVuJ e{2|PY>_cLejm>Z2=?r!uk;(u9A|<6|to=VFM#ZxL literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/TitleExpand.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Navigation/TitleExpand.imageset/Contents.json new file mode 100644 index 0000000000..c5c261d24f --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Navigation/TitleExpand.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "down_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Navigation/TitleExpand.imageset/down_24.pdf b/submodules/TelegramUI/Images.xcassets/Navigation/TitleExpand.imageset/down_24.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b8b10dad74031916a190eba945fb301e45e51c76 GIT binary patch literal 4616 zcmZ`-c|26@7biqh(jt=7Ep3XKxwB77_Q=v$k}PSAk#@)EKq zQ{km7rL0A?NGZFd{AR?Y_}xFapYM6j_iXoh=A36cK~q}^gkmAveYg(<1mFPO))4{# z`}P4aO^T~Cm3T&zPBNu1sbql3V37e86$qJT$6EwJ{IQzApxd!XWCpNHn{v{LsK6!R zKCoY^yX@$0G%FB^MWA?sc_hd`NYtoIGJ{5BlDXE&rzs?|@j>-jB5ev!2EeqbT(KsZ zM7JZ)O7zLJlS~HyfyQ8G5kmm^%Ze+LK_)su*m3z06vEn8sVchLSdfyS;IYc`3kDGa zxfhgfob#b3tqBz%E?c#x!g{rl?`kbgVJp?8S{t32=60)A8+9xd5izwow*vAX4)>4j z?rfpW%#Q}mw~uDOx}Q4eQ;cTGDHBcAk0!aw-b;eXq=IByv z{r8Yv8a;nfWCFi+2>X|;-f{BKgpQmMCQ|O0?J*Sv^$3o^+8Ue;QIxhsv}Q{)Kvj7? z9VaGJE#Q=y=`_uWpFUOd)GmJH$`T-9x$3ae9nEX_%^J0|^{SeNmzO^Gk!lcn>?26^ z5prHSP$4_vr@9KV^?_rB$TC3WqN-GjK*70v1c5Q1A2&8`6hz-xiSj|-5Xtik?1B^u z()02jyKgR~Xe2vS|!VAHU3y+{~wL*_Es$_V({F(08W0(;#?iBwL1 zmlYdpwe%<_wab2DtEbQ=pOmk9>ZPy@&L?5YwMvA7uZpgZTHHnzSA)}NuM^m8; z@n*kbztE%|Mw=VWQR@s2A{(AM92kM1q^}3R@`q|hCLg{L+t~R*`NQfF$;mwz&NWs< z5#!)gec>IO!p_%luGu<*22r(9f_3ZP$w>tsd9Z;f_wU&j9qQK8`wLcih_zq*{BVqT zFy|R=qoMf83$71D-r#T`RKjhQ8^lfU?ebv!k;US;YA z-+a4Gkg`1eGBbu5j#8i0G20p&daTzW*S|E@Gp<9o{P0;NF`)(m|bB?LJIjQV17!blLH=0lCV9@ z9><=N9p-Cq>4w&932VRgZ~Ar0Im-DyC1gw8EBzCvt2662k_H^0sBFw;+Yp&em-V&x zlOC!+vc04gtQGiZkDgcUuG+GQ{Sh`%ZBvw5o%*{Y z2QMe5_SjdQuDW74W@wv=H1yt!j0=l9y!Q$WV^L>gb(j5I>*e_#IQHMP18MaP#Ece7x&$6L1_p9gSUnK(TwmkxHR-caUaa^0HaN6Xh zec6+u*s6jF8a^ky5KZZ)8EO&>pDY~i#y#syyC6mchMaJhJ)pm8_4ncKUdBt9RD(f{|*$H-a^Sl-AJP7k-nX;~S@};t48*?qiMS=|)Aj zhkk5%mH3l05Fi`uut@Z&Qgl^(vw!o*RP|KO9ka568)LOD z-7bBXy4H5GR~{HcDsrzq<46xXbZxo<^cM z^7lw%mBq`SU8h_RlkR~9pU&#rP%i1xM;jj+cvUMHnSFe)h>>2E{J!!?1`h6hCmn`Jv zw`b`WaV-hmFM|U?uMd|`PpZB1i|$sE040VuE9~3tl{r{Ad8sB$JH2dV>f+1lC#hQw z2OKO)hI!Xd{1`SLK>l~P>3v&$|6`}e?K?ep(C(Xmv;9PFtBtNHJs>%q=1HFV7FC6W0HgM2~;l{9Gk&qI#-jSYhhfaCByUVLjPK zzdX-Nd}7iWLq0vGEMfa#%)aq+mfYvhGME*oilq+(%j-%#BR>rekBQs8x>2JEXQgso zEiyWMI`;6T{bH)Mrv*$mdj@WS#CX?vE5glor`K5J=Bd?mb;o+b=mZ)IiL%s${RMV^toK9g>ltWvwJ8{ zim!WG4hjn#T~ZO5)Y=-I(b@{DRwQr)n1_Cf1c^1u^E%hrj&QDrMe*Cunf7rGM)P4y z0H&*d8T{T00AD0F}K;B-|u4o(3x#r zakC2{v);kD${HpR-anEn#03IE;WG~aU+}{1-e+Z)xfc)%1@UMkhyX}VkcAq2#Fa#Y zD8ZpvI2MFs0R$8eN1#AF-~{1u;;|Sc$OX|Lh{L0S-w+7JVR2X_9QdsYLQyyz0*(Pl zJU%3dLF16XFAf}cNLU1!wK-3b?-gRsdFo=$Q6UZxexSIyrA}OImJ&A?29=A=x`Kxy z@CZB#1)!lA91;a$fq4j5gu^2Nz8+~2ex6kjp?T5F^K!9h93b`(8pH^)$jQLllfPzRZp+T!@_0VV z+wy8O8lCA1SX$4{ou+4;xsxk_cyewuH6Ytji0X7UV7bWFMWE*L`}=K<$(Za)XE8`* zSAciO7|-%+&}mFEjcbv2ip+roB7=(oAbb{^H(bwPzXy6fiXGQkfENSr82dG#YtUId z?FA9Ia*_E|f8Gt4QOIs&27y7gCv&ay{N%~!T+*f4(*fS~z#O1Yq@4tIk!f?c0=yJ) zRpwltxhR;)od%Ifq|#6RX`9V81Azk|0`lv7ZL+#_KDhFWADmitxw$%nu%Z0Hn?oY~J7>Jx_gUKLL zsAL9&_p#|}a%;B>gkvyBD>xPbf+z$=VIeS6SC}!K&MhjxCNL9() @@ -1025,7 +1025,7 @@ extension ChatControllerImpl { return message.withUpdatedAttributes { attributes in var attributes = attributes attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60, repeatPeriod: nil)) return attributes } } @@ -1052,7 +1052,7 @@ extension ChatControllerImpl { return message.withUpdatedAttributes { attributes in var attributes = attributes attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60, repeatPeriod: nil)) return attributes } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift index c0ed919eb2..e7be83595c 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerMediaRecording.swift @@ -684,6 +684,7 @@ extension ChatControllerImpl { func sendMediaRecording( silentPosting: Bool? = nil, scheduleTime: Int32? = nil, + repeatPeriod: Int32? = nil, viewOnce: Bool = false, messageEffect: ChatSendMessageEffect? = nil, postpone: Bool = false @@ -710,8 +711,6 @@ extension ChatControllerImpl { return } - - self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in if let strongSelf = self { strongSelf.chatDisplayNode.collapseInput() @@ -754,7 +753,7 @@ extension ChatControllerImpl { if let silentPosting = silentPosting { transformedMessages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, postpone: postpone) } else if let scheduleTime = scheduleTime { - transformedMessages = self.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, postpone: postpone) + transformedMessages = self.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, postpone: postpone) } else { transformedMessages = self.transformEnqueueMessages(messages) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 1032806be7..56ad1b1f2f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2266,9 +2266,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: silentPosting, postpone: postpone) strongSelf.sendMessages(transformedMessages) } else if schedule { - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in + strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime, repeatPeriod in if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, postpone: postpone) + let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod, postpone: postpone) strongSelf.sendMessages(transformedMessages) } }) @@ -2354,9 +2354,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G messages = strongSelf.transformEnqueueMessages(messages, silentPosting: true) strongSelf.sendMessages(messages) } else if schedule { - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in + strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime, repeatPeriod in if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime) + let transformedMessages = strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod) strongSelf.sendMessages(transformedMessages) } }) @@ -3649,7 +3649,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard !self.presentAccountFrozenInfoIfNeeded(delay: true) else { return } - self.presentScheduleTimePicker(completion: { [weak self] time in + self.presentScheduleTimePicker(completion: { [weak self] time, repeatPeriod in if let strongSelf = self { if let _ = strongSelf.presentationInterfaceState.interfaceState.mediaDraftState { strongSelf.sendMediaRecording(scheduleTime: time, messageEffect: (params?.effect).flatMap { @@ -3657,7 +3657,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } else { let silentPosting = strongSelf.presentationInterfaceState.interfaceState.silentPosting - strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, scheduleTime: time, messageEffect: (params?.effect).flatMap { + strongSelf.chatDisplayNode.sendCurrentMessage(silentPosting: silentPosting, scheduleTime: time, repeatPeriod: repeatPeriod, messageEffect: (params?.effect).flatMap { return ChatSendMessageEffect(id: $0.id) }) { [weak self] in if let strongSelf = self { @@ -3697,7 +3697,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let message = message else { return } - strongSelf.presentScheduleTimePicker(selectedTime: message.timestamp, completion: { [weak self] time in + strongSelf.presentScheduleTimePicker(selectedTime: message.timestamp, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { var entities: TextEntitiesMessageAttribute? for attribute in message.attributes { @@ -3708,7 +3708,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let inlineStickers: [MediaId: TelegramMediaFile] = [:] - strongSelf.editMessageDisposable.set((strongSelf.context.engine.messages.requestEditMessage(messageId: messageId, text: message.text, media: .keep, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleTime: time) |> deliverOnMainQueue).startStrict(next: { result in + strongSelf.editMessageDisposable.set((strongSelf.context.engine.messages.requestEditMessage(messageId: messageId, text: message.text, media: .keep, entities: entities, inlineStickers: inlineStickers, webpagePreviewAttribute: nil, disableUrlPreview: false, scheduleInfoAttribute: OutgoingScheduleInfoMessageAttribute(scheduleTime: time, repeatPeriod: repeatPeriod)) |> deliverOnMainQueue).startStrict(next: { result in }, error: { error in })) } @@ -8079,7 +8079,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } - func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil, postpone: Bool = false) -> [EnqueueMessage] { + func transformEnqueueMessages(_ messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, postpone: Bool = false) -> [EnqueueMessage] { var defaultThreadId: Int64? var defaultReplyMessageSubject: EngineMessageReplySubject? switch self.chatLocation { @@ -8162,8 +8162,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if silentPosting { attributes.append(NotificationInfoMessageAttribute(flags: .muted)) } - if let scheduleTime = scheduleTime { - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime)) + if let scheduleTime { + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: repeatPeriod)) } } @@ -8229,7 +8229,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return message.withUpdatedAttributes { attributes in var attributes = attributes attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60, repeatPeriod: nil)) return attributes } } @@ -8265,9 +8265,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } } else { - self.presentScheduleTimePicker(style: media ? .media : .default, dismissByTapOutside: false, completion: { [weak self] time in + self.presentScheduleTimePicker(style: media ? .media : .default, dismissByTapOutside: false, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { - strongSelf.sendMessages(strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: time, postpone: postpone), commit: true) + strongSelf.sendMessages(strongSelf.transformEnqueueMessages(messages, silentPosting: false, scheduleTime: time, repeatPeriod: repeatPeriod, postpone: postpone), commit: true) } }) } @@ -8490,7 +8490,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if isScheduledMessages { - self.presentScheduleTimePicker(style: .default, dismissByTapOutside: false, completion: { time in + self.presentScheduleTimePicker(style: .default, dismissByTapOutside: false, completion: { time, repeatPeriod in sendMessage(time) }) } else { @@ -9742,7 +9742,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G ) } - func presentScheduleTimePicker(style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) { + func presentScheduleTimePicker(style: ChatScheduleTimeControllerStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32, Int32?) -> Void) { guard let peerId = self.chatLocation.peerId else { return } @@ -9760,15 +9760,24 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G sendWhenOnlineAvailable = false } - let mode: ChatScheduleTimeControllerMode + let mode: ChatScheduleTimeScreen.Mode if peerId == strongSelf.context.account.peerId { mode = .reminders } else { mode = .scheduledMessages(sendWhenOnlineAvailable: sendWhenOnlineAvailable) } - let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in - completion(time) - }) + + let controller = ChatScheduleTimeScreen( + context: strongSelf.context, + mode: mode, + completion: { result in + completion(result.time, result.repeatPeriod) + } + ) + +// let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: mode, style: style, currentTime: selectedTime, minimalTime: strongSelf.presentationInterfaceState.slowmodeState?.timeout, dismissByTapOutside: dismissByTapOutside, completion: { time in +// completion(time) +// }) strongSelf.chatDisplayNode.dismissInput() strongSelf.present(controller, in: .window(.root)) }) diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 5bc7c60639..6913d4cbb1 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -199,7 +199,7 @@ extension ChatControllerImpl { return message.withUpdatedAttributes { attributes in var attributes = attributes attributes.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute }) - attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60)) + attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: Int32(Date().timeIntervalSince1970) + 10 * 24 * 60 * 60, repeatPeriod: nil)) return attributes } } @@ -317,9 +317,9 @@ extension ChatControllerImpl { let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: true) commit(transformedMessages) case .schedule: - strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime in + strongSelf.presentScheduleTimePicker(completion: { [weak self] scheduleTime, repeatPeriod in if let strongSelf = self { - let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime) + let transformedMessages = strongSelf.transformEnqueueMessages(result, silentPosting: false, scheduleTime: scheduleTime, repeatPeriod: repeatPeriod) commit(transformedMessages) } }) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 584843c3c4..d4263bcfe6 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -300,7 +300,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { var requestUpdateChatInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatInterfaceState) -> ChatInterfaceState) -> Void = { _, _, _ in } var requestUpdateInterfaceState: (ContainedViewLayoutTransition, Bool, (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void = { _, _, _ in } - var sendMessages: ([EnqueueMessage], Bool?, Int32?, Bool, Bool) -> Void = { _, _, _, _, _ in } + var sendMessages: ([EnqueueMessage], Bool?, Int32?, Int32?, Bool, Bool) -> Void = { _, _, _, _, _, _ in } var displayAttachmentMenu: () -> Void = { } var paste: (ChatTextInputPanelPasteData) -> Void = { _ in } var updateTypingActivity: (Bool) -> Void = { _ in } @@ -3948,6 +3948,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { return nil } + func getAttachmentButton() -> UIView? { + if let textInputPanelNode = self.textInputPanelNode, self.inputPanelNode === textInputPanelNode { + return textInputPanelNode.getAttachmentButton() + } + return nil + } + func frameForMenuButton() -> CGRect? { if let textInputPanelNode = self.textInputPanelNode, self.inputPanelNode === textInputPanelNode { return textInputPanelNode.frameForMenuButton().flatMap { @@ -4387,7 +4394,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, postpone: Bool = false, messageEffect: ChatSendMessageEffect? = nil, completion: @escaping () -> Void = {}) { + func sendCurrentMessage(silentPosting: Bool? = nil, scheduleTime: Int32? = nil, repeatPeriod: Int32? = nil, postpone: Bool = false, messageEffect: ChatSendMessageEffect? = nil, completion: @escaping () -> Void = {}) { guard let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else { return @@ -4721,7 +4728,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { }, usedCorrelationId) completion() - self.sendMessages(messages, silentPosting, scheduleTime, messages.count > 1, postpone) + self.sendMessages(messages, silentPosting, scheduleTime, repeatPeriod, messages.count > 1, postpone) } var targetThreadId: Int64? diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 37eec54c2c..551db3f3fb 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -36,6 +36,7 @@ import ShareController import ComposeTodoScreen import ComposePollUI import Photos +import AttachmentFileController extension ChatControllerImpl { enum AttachMenuSubject { @@ -203,7 +204,7 @@ extension ChatControllerImpl { allButtons.insert(button, at: 1) } - if let user = peer as? TelegramUser, user.botInfo == nil { + if !"".isEmpty, let user = peer as? TelegramUser, user.botInfo == nil { if let index = buttons.firstIndex(where: { $0 == .location }) { buttons.insert(.quickReply, at: index + 1) } else { @@ -306,12 +307,13 @@ extension ChatControllerImpl { strongSelf.canReadHistory.set(false) - let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, chatLocation: strongSelf.chatLocation, isScheduledMessages: isScheduledMessages, buttons: buttons, initialButton: initialButton, makeEntityInputView: { [weak self] in + let attachmentController = AttachmentController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, style: .glass, chatLocation: strongSelf.chatLocation, isScheduledMessages: isScheduledMessages, buttons: buttons, initialButton: initialButton, makeEntityInputView: { [weak self] in guard let strongSelf = self else { return nil } return EntityInputView(context: strongSelf.context, isDark: false, areCustomEmojiEnabled: strongSelf.presentationInterfaceState.customEmojiAvailable) }) + attachmentController.attachmentButton = strongSelf.chatDisplayNode.getAttachmentButton() attachmentController.shouldMinimizeOnSwipe = { [weak attachmentController] button in if case .app = button { attachmentController?.convertToStandalone() @@ -416,7 +418,7 @@ extension ChatControllerImpl { hasLiveLocation = false } let sharePeer = (strongSelf.presentationInterfaceState.renderedPeer?.peer).flatMap(EnginePeer.init) - let controller = LocationPickerController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: sharePeer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { location, _, _, _, _ in + let controller = LocationPickerController(context: strongSelf.context, style: .glass, updatedPresentationData: strongSelf.updatedPresentationData, mode: .share(peer: sharePeer, selfPeer: selfPeer, hasLiveLocation: hasLiveLocation), completion: { location, _, _, _, _ in guard let strongSelf = self else { return } @@ -444,7 +446,7 @@ extension ChatControllerImpl { let _ = currentLocationController.swap(controller) }) case .contact: - let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: .always, requirePhoneNumbers: true)) + let contactsController = ContactSelectionControllerImpl(ContactSelectionControllerParams(context: strongSelf.context, style: .glass, updatedPresentationData: strongSelf.updatedPresentationData, title: { $0.Contacts_Title }, displayDeviceContacts: true, multipleSelection: .always, requirePhoneNumbers: true)) contactsController.presentScheduleTimePicker = { [weak self] completion in if let strongSelf = self { strongSelf.presentScheduleTimePicker(completion: completion) @@ -671,7 +673,6 @@ extension ChatControllerImpl { self.hintPlayNextOutgoingGift() self.attachmentController?.dismiss(animated: true) }) - completion(controller, controller.mediaPickerContext) strongSelf.controllerNavigationDisposable.set(nil) @@ -969,7 +970,7 @@ extension ChatControllerImpl { } }, presentSchedulePicker: { [weak self] _, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { done(time) if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { @@ -1026,7 +1027,7 @@ extension ChatControllerImpl { })], actionLayout: .vertical), in: .window(.root)) }, presentSchedulePicker: { [weak self] _, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { done(time) if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { @@ -1249,6 +1250,7 @@ extension ChatControllerImpl { let controller = MediaPickerScreenImpl( context: self.context, updatedPresentationData: self.updatedPresentationData, + style: .glass, peer: (self.presentationInterfaceState.renderedPeer?.peer).flatMap(EnginePeer.init), threadTitle: self.contentData?.state.threadInfo?.title, chatLocation: self.chatLocation, @@ -1298,7 +1300,7 @@ extension ChatControllerImpl { } controller.presentSchedulePicker = { [weak self] media, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time in + strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { done(time) if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { @@ -1464,7 +1466,7 @@ extension ChatControllerImpl { strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, presentSchedulePicker: { [weak self] media, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time in + strongSelf.presentScheduleTimePicker(style: media ? .media : .default, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { done(time) if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { @@ -1894,7 +1896,7 @@ extension ChatControllerImpl { } }, presentSchedulePicker: { [weak self] _, done in if let strongSelf = self { - strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time in + strongSelf.presentScheduleTimePicker(style: .media, completion: { [weak self] time, repeatPeriod in if let strongSelf = self { done(time) if strongSelf.presentationInterfaceState.subject != .scheduledMessages && time != scheduleWhenOnlineTimestamp { diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index a0946cb004..5af605104d 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -15,9 +15,13 @@ import SearchBarNode import ChatSendAudioMessageContextPreview import ChatSendMessageActionUI import ContextUI +import ComponentFlow +import BundleIconComponent +import GlassBarButtonComponent class ContactSelectionControllerImpl: ViewController, ContactSelectionController, PresentableController, AttachmentContainable { private let context: AccountContext + private let style: ContactSelectionControllerParams.Style private let mode: ContactSelectionControllerMode private let autoDismiss: Bool @@ -44,6 +48,9 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController private let requirePhoneNumbers: Bool private let allowChannelsInSearch: Bool + private var closeButtonNode: BarComponentHostNode? + private var searchButtonNode: BarComponentHostNode? + private let openProfile: ((EnginePeer) -> Void)? private let sendMessage: ((EnginePeer) -> Void)? @@ -63,7 +70,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController private let isPeerEnabled: (ContactListPeer) -> Bool var dismissed: (() -> Void)? - var presentScheduleTimePicker: (@escaping (Int32) -> Void) -> Void = { _ in } + var presentScheduleTimePicker: (@escaping (Int32, Int32?) -> Void) -> Void = { _ in } private let createActionDisposable = MetaDisposable() private let confirmationDisposable = MetaDisposable() @@ -71,6 +78,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController private var presentationData: PresentationData private var presentationDataDisposable: Disposable? + private var isSearching = false private var searchContentNode: NavigationBarContentNode? var displayNavigationActivity: Bool = false { @@ -101,6 +109,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController init(_ params: ContactSelectionControllerParams) { self.context = params.context + self.style = params.style self.mode = params.mode self.autoDismiss = params.autoDismiss self.titleProducer = params.title @@ -118,18 +127,19 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.presentationData = params.updatedPresentationData?.initial ?? params.context.sharedContext.currentPresentationData.with { $0 } - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) + var glass = false + if case .glass = params.style { + glass = true + self.presentationData = self.presentationData.withUpdated(theme: self.presentationData.theme.withModalBlocksBackground()) + } + + super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) self.blocksBackgroundWhenInOverlay = true self.acceptsFocusWhenInOverlay = true self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - - self.title = self.titleProducer(self.presentationData.strings) - - self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) - + self.scrollToTop = { [weak self] in if let strongSelf = self { if let searchContentNode = strongSelf.searchContentNode as? NavigationBarSearchContentNode { @@ -141,14 +151,17 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.presentationDataDisposable = ((params.updatedPresentationData?.signal ?? params.context.sharedContext.presentationData) |> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in - if let strongSelf = self { - let previousTheme = strongSelf.presentationData.theme - let previousStrings = strongSelf.presentationData.strings + if let self { + let previousTheme = self.presentationData.theme + let previousStrings = self.presentationData.strings - strongSelf.presentationData = presentationData + self.presentationData = presentationData + if case .glass = params.style { + self.presentationData = self.presentationData.withUpdated(theme: self.presentationData.theme.withModalBlocksBackground()) + } if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { - strongSelf.updateThemeAndStrings() + self.updateThemeAndStrings() } } }) @@ -160,12 +173,26 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.navigationBar?.setContentNode(self.searchContentNode, animated: false) } + self.title = self.titleProducer(self.presentationData.strings) + + if glass { + + } else { + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + } + if self.multipleSelection == .always { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.beginSearch)) + if glass { + } else { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.beginSearch)) + } } else if self.multipleSelection == .possible { self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.beginSelection)) } + self.updateNavigationButtons() + self.getCurrentSendMessageContextMediaPreview = { [weak self] in guard let self else { return nil @@ -205,14 +232,89 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController self.contactsNode.beginSelection() } + private func updateNavigationButtons() { + guard case .glass = self.style else { + return + } + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let closeComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( + id: "close", + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.cancelPressed() + } + )) + ) + + let searchComponent: AnyComponentWithIdentity? + if !self.isSearching && self.multipleSelection == .always { + searchComponent = AnyComponentWithIdentity( + id: "search", + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "search", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Search", + tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.beginSearch() + } + )) + ) + } else { + searchComponent = nil + } + + let closeButtonNode: BarComponentHostNode + if let current = self.closeButtonNode { + closeButtonNode = current + closeButtonNode.component = closeComponent + } else { + closeButtonNode = BarComponentHostNode(component: closeComponent, size: barButtonSize) + self.closeButtonNode = closeButtonNode + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: closeButtonNode) + } + + let searchButtonNode: BarComponentHostNode + if let current = self.searchButtonNode { + searchButtonNode = current + searchButtonNode.component = searchComponent + } else { + searchButtonNode = BarComponentHostNode(component: searchComponent, size: barButtonSize) + self.searchButtonNode = searchButtonNode + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: searchButtonNode) + } + } + private func updateThemeAndStrings() { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style - self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) + var glass = false + if case .glass = self.style { + glass = true + } + self.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: self.presentationData.theme, hideBackground: glass, hideSeparator: glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) (self.searchContentNode as? NavigationBarSearchContentNode)?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search) self.title = self.titleProducer(self.presentationData.strings) self.tabBarItem.title = self.presentationData.strings.Contacts_Title self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.contactsNode.updatePresentationData(self.presentationData) + + self.updateNavigationButtons() } @objc func cancelPressed() { @@ -222,7 +324,19 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController } override func loadDisplayNode() { - self.displayNode = ContactSelectionControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, options: self.options, displayDeviceContacts: self.displayDeviceContacts, displayCallIcons: self.displayCallIcons, multipleSelection: self.multipleSelection == .always, requirePhoneNumbers: self.requirePhoneNumbers, allowChannelsInSearch: self.allowChannelsInSearch, isPeerEnabled: self.isPeerEnabled) + self.displayNode = ContactSelectionControllerNode( + context: self.context, + listStyle: self.style == .glass ? .blocks : .plain, + mode: self.mode, + presentationData: self.presentationData, + options: self.options, + displayDeviceContacts: self.displayDeviceContacts, + displayCallIcons: self.displayCallIcons, + multipleSelection: self.multipleSelection == .always, + requirePhoneNumbers: self.requirePhoneNumbers, + allowChannelsInSearch: self.allowChannelsInSearch, + isPeerEnabled: self.isPeerEnabled + ) self._ready.set(self.contactsNode.contactListNode.ready) self.contactsNode.navigationBar = self.navigationBar @@ -334,39 +448,56 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController private func activateSearch() { if self.displayNavigationBar { + self.isSearching = true if let searchContentNode = self.searchContentNode as? NavigationBarSearchContentNode { self.contactsNode.activateSearch(placeholderNode: searchContentNode.placeholderNode) self.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring)) } else if self.multipleSelection == .always { - let contentNode = ContactsSearchNavigationContentNode(presentationData: self.presentationData, dismissSearch: { [weak self] in - if let strongSelf = self, let navigationBar = strongSelf.navigationBar, let searchContentNode = strongSelf.searchContentNode as? ContactsSearchNavigationContentNode { - searchContentNode.deactivate() - strongSelf.searchContentNode = nil - navigationBar.setContentNode(nil, animated: true) - strongSelf.contactsNode.deactivateOverlaySearch() - } - }, updateSearchQuery: { [weak self] query in - if let strongSelf = self { - strongSelf.contactsNode.searchContainerNode?.searchTextUpdated(text: query) - } - }) - self.searchContentNode = contentNode - self.navigationBar?.setContentNode(contentNode, animated: true) + if case .glass = self.style { + self.updateNavigationButtons() + self.requestAttachmentMenuExpansion() + self.updateTabBarVisibility(false, .animated(duration: 0.4, curve: .spring)) + } else { + let contentNode = ContactsSearchNavigationContentNode(presentationData: self.presentationData, dismissSearch: { [weak self] in + if let strongSelf = self, let navigationBar = strongSelf.navigationBar, let searchContentNode = strongSelf.searchContentNode as? ContactsSearchNavigationContentNode { + searchContentNode.deactivate() + strongSelf.searchContentNode = nil + navigationBar.setContentNode(nil, animated: true) + strongSelf.contactsNode.deactivateOverlaySearch() + } + }, updateSearchQuery: { [weak self] query in + if let strongSelf = self { + strongSelf.contactsNode.searchContainerNode?.searchTextUpdated(text: query) + } + }) + self.searchContentNode = contentNode + self.navigationBar?.setContentNode(contentNode, animated: true) + contentNode.activate() + } self.contactsNode.activateOverlaySearch() - contentNode.activate() } } } private func deactivateSearch() { - if !self.displayNavigationBar { + self.isSearching = false + if case .glass = self.style { + self.updateNavigationButtons() self.contactsNode.prepareDeactivateSearch() - self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) - if let searchContentNode = self.searchContentNode as? NavigationBarSearchContentNode { - self.contactsNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode) + self.contactsNode.deactivateOverlaySearch() + + self.updateTabBarVisibility(true, .animated(duration: 0.4, curve: .spring)) + } else { + if !self.displayNavigationBar { + self.contactsNode.prepareDeactivateSearch() + + self.setDisplayNavigationBar(true, transition: .animated(duration: 0.5, curve: .spring)) + if let searchContentNode = self.searchContentNode as? NavigationBarSearchContentNode { + self.contactsNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode) + } + } else if let searchContentNode = self.searchContentNode as? ContactsSearchNavigationContentNode { + searchContentNode.cancel() } - } else if let searchContentNode = self.searchContentNode as? ContactsSearchNavigationContentNode { - searchContentNode.cancel() } } @@ -515,7 +646,7 @@ final class ContactsPickerContext: AttachmentMediaPickerContext { } func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { - self.controller?.presentScheduleTimePicker ({ time in + self.controller?.presentScheduleTimePicker ({ time, repeatPeriod in self.controller?.contactsNode.requestMultipleAction?(false, time, parameters) }) } diff --git a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift index 78d954af74..9b9837ea57 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionControllerNode.swift @@ -10,6 +10,9 @@ import SearchBarNode import ContactListUI import SearchUI import SolidRoundedButtonNode +import ItemListUI +import EdgeEffect +import ComponentFlow final class ContactSelectionControllerNode: ASDisplayNode { var displayProgress: Bool = false { @@ -30,6 +33,7 @@ final class ContactSelectionControllerNode: ASDisplayNode { private let dimNode: ASDisplayNode private let context: AccountContext + private let listStyle: ItemListStyle private var searchDisplayController: SearchDisplayController? private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)? @@ -59,8 +63,24 @@ final class ContactSelectionControllerNode: ASDisplayNode { var searchContainerNode: ContactsSearchContainerNode? - init(context: AccountContext, mode: ContactSelectionControllerMode, presentationData: PresentationData, options: Signal<[ContactListAdditionalOption], NoError>, displayDeviceContacts: Bool, displayCallIcons: Bool, multipleSelection: Bool, requirePhoneNumbers: Bool, allowChannelsInSearch: Bool, isPeerEnabled: @escaping (ContactListPeer) -> Bool) { + private let topEdgeEffectView: EdgeEffectView + private let bottomEdgeEffectView: EdgeEffectView + + init( + context: AccountContext, + listStyle: ItemListStyle, + mode: ContactSelectionControllerMode, + presentationData: PresentationData, + options: Signal<[ContactListAdditionalOption], NoError>, + displayDeviceContacts: Bool, + displayCallIcons: Bool, + multipleSelection: Bool, + requirePhoneNumbers: Bool, + allowChannelsInSearch: Bool, + isPeerEnabled: @escaping (ContactListPeer) -> Bool + ) { self.context = context + self.listStyle = listStyle self.presentationData = presentationData self.displayDeviceContacts = displayDeviceContacts self.displayCallIcons = displayCallIcons @@ -127,14 +147,31 @@ final class ContactSelectionControllerNode: ASDisplayNode { } var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? - self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: presentation, filters: filters, onlyWriteable: false, isGroupInvitation: false, isPeerEnabled: { peer in - return isPeerEnabled(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) - }, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in - contextActionImpl?(peer, node, gesture, nil) - } : nil, multipleSelection: multipleSelection) + self.contactListNode = ContactListNode( + context: context, + updatedPresentationData: (presentationData, self.presentationDataPromise.get()), + listStyle: listStyle, + presentation: presentation, + filters: filters, + onlyWriteable: false, + isGroupInvitation: false, + isPeerEnabled: { peer in + return isPeerEnabled(.peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil)) + }, + displayCallIcons: displayCallIcons, + contextAction: multipleSelection ? { peer, node, gesture, _, _ in + contextActionImpl?(peer, node, gesture, nil) + } : nil, + multipleSelection: multipleSelection) self.dimNode = ASDisplayNode() + self.topEdgeEffectView = EdgeEffectView() + self.topEdgeEffectView.isUserInteractionEnabled = false + + self.bottomEdgeEffectView = EdgeEffectView() + self.bottomEdgeEffectView.isUserInteractionEnabled = false + var shareImpl: (() -> Void)? self.countPanelNode = ContactSelectionCountPanelNode(theme: self.presentationData.theme, strings: self.presentationData.strings, action: { shareImpl?() @@ -146,11 +183,11 @@ final class ContactSelectionControllerNode: ASDisplayNode { return UITracingLayerView() }) - self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.backgroundColor = listStyle == .blocks ? self.presentationData.theme.list.blocksBackgroundColor : self.presentationData.theme.chatList.backgroundColor self.addSubnode(self.contactListNode) - self.dimNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.5) + self.dimNode.backgroundColor = .clear // self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.5) self.dimNode.alpha = 0.0 self.dimNode.isUserInteractionEnabled = false self.addSubnode(self.dimNode) @@ -183,6 +220,11 @@ final class ContactSelectionControllerNode: ASDisplayNode { } } } + + if case .blocks = listStyle { + self.view.addSubview(self.topEdgeEffectView) + self.view.addSubview(self.bottomEdgeEffectView) + } } func beginSelection() { @@ -193,14 +235,18 @@ final class ContactSelectionControllerNode: ASDisplayNode { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.backgroundColor = self.listStyle == .blocks ? self.presentationData.theme.list.blocksBackgroundColor : self.presentationData.theme.chatList.backgroundColor self.searchDisplayController?.updatePresentationData(presentationData) self.dimNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.5) + + if let (layout, navigationHeight, actualNavigationHeight) = self.containerLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, actualNavigationBarHeight: actualNavigationHeight, transition: .immediate) + } } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, actualNavigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.containerLayout = (layout, navigationBarHeight, actualNavigationBarHeight) - + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) var insets = layout.insets(options: [.input]) @@ -213,9 +259,23 @@ final class ContactSelectionControllerNode: ASDisplayNode { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } - self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition) + let safeInsets = layout.safeInsets + var size = layout.size + if case .blocks = self.listStyle { + insets.top -= 25.0 + + let inset: CGFloat + if layout.size.width >= 375.0 { + inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) + } else { + inset = 0.0 + } + size.width -= inset * 2.0 + } - self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size) + self.contactListNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: insets, safeInsets: safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), headerInsets: headerInsets, storiesInset: 0.0, transition: transition) + + self.contactListNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: 0.0), size: size) // let countPanelHeight = self.countPanelNode.updateLayout(width: layout.size.width, sideInset: layout.safeInsets.left, bottomInset: layout.intrinsicInsets.bottom, transition: transition) // if (self.selectionState?.selectedPeerIndices.isEmpty ?? true) { @@ -233,6 +293,16 @@ final class ContactSelectionControllerNode: ASDisplayNode { searchContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size) searchContainerNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, additionalInsets: layout.additionalInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition) } + + let topEdgeEffectHeight: CGFloat = 80.0 + let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topEdgeEffectHeight)) + transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) + self.topEdgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition)) + + let bottomEdgeEffectHeight: CGFloat = 88.0 + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomEdgeEffectHeight - layout.additionalInsets.bottom), size: CGSize(width: layout.size.width, height: bottomEdgeEffectHeight)) + transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + self.bottomEdgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition)) } func scrollToTop() { @@ -258,51 +328,63 @@ final class ContactSelectionControllerNode: ASDisplayNode { categories.insert(.channels) } - let searchContainerNode = ContactsSearchContainerNode(context: self.context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), onlyWriteable: false, categories: categories, filters: self.filters, displayCallIcons: self.displayCallIcons, isPeerEnabled: self.isPeerEnabled, addContact: nil, openPeer: { [weak self] peer, action in - if let strongSelf = self { - var updated = false - strongSelf.contactListNode.updateSelectionState { state -> ContactListNodeGroupSelectionState? in - if let state = state { - updated = true - var foundPeers = state.foundPeers - var selectedPeerMap = state.selectedPeerMap - selectedPeerMap[peer.id] = peer - var exists = false - for foundPeer in foundPeers { - if peer.id == foundPeer.id { - exists = true - break + let searchContainerNode = ContactsSearchContainerNode( + context: self.context, + glass: self.listStyle == .blocks, + updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), + onlyWriteable: false, + categories: categories, + filters: self.filters, + displayCallIcons: self.displayCallIcons, + isPeerEnabled: self.isPeerEnabled, + addContact: nil, + openPeer: { [weak self] peer, action in + if let strongSelf = self { + var updated = false + strongSelf.contactListNode.updateSelectionState { state -> ContactListNodeGroupSelectionState? in + if let state = state { + updated = true + var foundPeers = state.foundPeers + var selectedPeerMap = state.selectedPeerMap + selectedPeerMap[peer.id] = peer + var exists = false + for foundPeer in foundPeers { + if peer.id == foundPeer.id { + exists = true + break + } } + if !exists { + foundPeers.insert(peer, at: 0) + } + return state.withToggledPeerId(peer.id).withFoundPeers(foundPeers).withSelectedPeerMap(selectedPeerMap) + } else { + return nil } - if !exists { - foundPeers.insert(peer, at: 0) - } - return state.withToggledPeerId(peer.id).withFoundPeers(foundPeers).withSelectedPeerMap(selectedPeerMap) + } + if updated { + strongSelf.requestDeactivateSearch?() } else { - return nil + let mappedAction: ContactListAction + switch action { + case .generic: + mappedAction = .generic + case .voiceCall: + mappedAction = .voiceCall + case .videoCall: + mappedAction = .videoCall + } + strongSelf.requestOpenPeerFromSearch?(peer, mappedAction) } } - if updated { - strongSelf.requestDeactivateSearch?() - } else { - let mappedAction: ContactListAction - switch action { - case .generic: - mappedAction = .generic - case .voiceCall: - mappedAction = .voiceCall - case .videoCall: - mappedAction = .videoCall - } - strongSelf.requestOpenPeerFromSearch?(peer, mappedAction) + }, + openDisabledPeer: { [weak self] peer, reason in + guard let self else { + return } - } - }, openDisabledPeer: { [weak self] peer, reason in - guard let self else { - return - } - self.requestOpenDisabledPeerFromSearch?(peer, reason) - }, contextAction: nil) + self.requestOpenDisabledPeerFromSearch?(peer, reason) + }, + contextAction: nil) searchContainerNode.cancel = { [weak self] in self?.cancelSearch?() } diff --git a/submodules/TelegramUI/Sources/PeersNearbyManager.swift b/submodules/TelegramUI/Sources/PeersNearbyManager.swift deleted file mode 100644 index 1cc01c4f94..0000000000 --- a/submodules/TelegramUI/Sources/PeersNearbyManager.swift +++ /dev/null @@ -1,86 +0,0 @@ -import Foundation -import SwiftSignalKit -import TelegramCore -import TelegramApi -import DeviceLocationManager -import CoreLocation -import AccountContext -import DeviceAccess - -private let locationUpdateTimePeriod: Double = 1.0 * 60.0 * 60.0 -private let locationDistanceUpdateThreshold: Double = 1000 - -final class PeersNearbyManagerImpl: PeersNearbyManager { - private let account: Account - private let engine: TelegramEngine - private let locationManager: DeviceLocationManager - private let inForeground: Signal - - private var preferencesDisposable: Disposable? - private var locationDisposable = MetaDisposable() - private var updateDisposable = MetaDisposable() - private var accessDisposable: Disposable? - - private var previousLocation: CLLocation? - - init(account: Account, engine: TelegramEngine, locationManager: DeviceLocationManager, inForeground: Signal) { - self.account = account - self.engine = engine - self.locationManager = locationManager - self.inForeground = inForeground - - self.preferencesDisposable = (account.postbox.preferencesView(keys: [PreferencesKeys.peersNearby]) - |> map { view -> Int32? in - let state = view.values[PreferencesKeys.peersNearby]?.get(PeersNearbyState.self) ?? .default - return state.visibilityExpires - } - |> deliverOnMainQueue - |> distinctUntilChanged).startStrict(next: { [weak self] visibility in - if let strongSelf = self { - strongSelf.visibilityUpdated(visible: visibility != nil) - } - }) - } - - deinit { - self.preferencesDisposable?.dispose() - self.locationDisposable.dispose() - self.updateDisposable.dispose() - self.accessDisposable?.dispose() - } - - private func visibilityUpdated(visible: Bool) { - if visible { - let poll = self.inForeground - |> take(until: { value in - return SignalTakeAction(passthrough: value, complete: value) - }) - |> mapToSignal { _ in - return currentLocationManagerCoordinate(manager: self.locationManager, timeout: 5.0) - } - - let signal = (poll |> then(.complete() |> suspendAwareDelay(locationUpdateTimePeriod, queue: Queue.concurrentDefaultQueue()))) |> restart - self.locationDisposable.set(signal.startStrict(next: { [weak self] coordinate in - if let strongSelf = self, let coordinate = coordinate { - let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude) - var update = true - if let previousLocation = strongSelf.previousLocation, location.distance(from: previousLocation) < locationDistanceUpdateThreshold { - update = false - } - if update { - strongSelf.updateLocation(location) - strongSelf.previousLocation = location - } - } - })) - } else { - self.previousLocation = nil - self.locationDisposable.set(nil) - self.updateDisposable.set(nil) - } - } - - private func updateLocation(_ location: CLLocation) { - self.updateDisposable.set(self.engine.peersNearby.updatePeersNearbyVisibility(update: .location(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude), background: true).startStrict()) - } -} diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index f4ced25eca..dcebfd5c1d 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -87,6 +87,8 @@ import PostSuggestionsSettingsScreen import ForumSettingsScreen import ForumCreateTopicScreen import GlassBackgroundComponent +import AttachmentFileController +import NewContactScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -3971,6 +3973,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makeChannelMembersSearchController(params: ChannelMembersSearchControllerParams) -> ChannelMembersSearchController { return ChannelMembersSearchControllerImpl(params: params) } + + public func makeNewContactScreen(context: AccountContext, firstName: String?, lastName: String?, phoneNumber: String?) -> ViewController { + return NewContactScreen(context: context, initialData: NewContactScreen.initialData(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber), completion: { _ in }) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? { diff --git a/submodules/TranslateUI/Sources/LocalizationListItem.swift b/submodules/TranslateUI/Sources/LocalizationListItem.swift index 6723f2bdc5..5ea365bcd1 100644 --- a/submodules/TranslateUI/Sources/LocalizationListItem.swift +++ b/submodules/TranslateUI/Sources/LocalizationListItem.swift @@ -26,6 +26,7 @@ public struct LocalizationListItemEditing: Equatable { public class LocalizationListItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let id: String let title: String let subtitle: String @@ -40,8 +41,9 @@ public class LocalizationListItem: ListViewItem, ItemListItem { let setItemWithRevealedOptions: ((String?, String?) -> Void)? let removeItem: ((String) -> Void)? - public init(presentationData: ItemListPresentationData, id: String, title: String, subtitle: String, checked: Bool, activity: Bool, loading: Bool, editing: LocalizationListItemEditing, enabled: Bool = true, sectionId: ItemListSectionId, alwaysPlain: Bool, action: @escaping () -> Void, setItemWithRevealedOptions: ((String?, String?) -> Void)?, removeItem: ((String) -> Void)?) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, id: String, title: String, subtitle: String, checked: Bool, activity: Bool, loading: Bool, editing: LocalizationListItemEditing, enabled: Bool = true, sectionId: ItemListSectionId, alwaysPlain: Bool, action: @escaping () -> Void, setItemWithRevealedOptions: ((String?, String?) -> Void)?, removeItem: ((String) -> Void)?) { self.presentationData = presentationData + self.systemStyle = systemStyle self.id = id self.title = title self.subtitle = subtitle @@ -222,7 +224,16 @@ class LocalizationListItemNode: ItemListRevealOptionsItemNode { let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 50.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let insets = itemListNeighborsGroupedInsets(neighbors, params) - let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 1.0 + subtitleLayout.size.height + 8.0 * 2.0) + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 12.0 + case .legacy: + verticalInset = 8.0 + } + + let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 1.0 + subtitleLayout.size.height + verticalInset * 2.0) let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -239,6 +250,7 @@ class LocalizationListItemNode: ItemListRevealOptionsItemNode { leftInset += 16.0 let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 var updateCheckImage: UIImage? var updatedTheme: PresentationTheme? @@ -323,15 +335,15 @@ class LocalizationListItemNode: ItemListRevealOptionsItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: strongSelf.backgroundNode.frame.size) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + leftInset, y: 8.0), size: titleLayout.size)) + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + leftInset, y: verticalInset), size: titleLayout.size)) transition.updateFrame(node: strongSelf.subtitleNode, frame: CGRect(origin: CGPoint(x: editingOffset + revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY + 1.0), size: subtitleLayout.size)) strongSelf.titleNode.alpha = item.enabled ? 1.0 : 0.5 diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index 3c807f17c0..a812278b61 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -54,6 +54,9 @@ swift_library( "//submodules/DeviceAccess", "//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage", "//submodules/AvatarNode", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/LottieComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 2d92f8bbe6..bb20434165 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -39,6 +39,9 @@ import LegacyMediaPickerUI import GenerateStickerPlaceholderImage import PassKit import Photos +import GlassBarButtonComponent +import BundleIconComponent +import LottieComponent private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] @@ -182,7 +185,7 @@ public final class WebAppController: ViewController, AttachmentContainable { private var queryId: Int64? fileprivate let canMinimize = true - private var hasBackButton = false + fileprivate var hasBackButton = false private var placeholderDisposable = MetaDisposable() private var keepAliveDisposable: Disposable? @@ -808,7 +811,7 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let self else { return } - self.controller?.morePressed(node: node, gesture: gesture) + self.controller?.morePressed(view: node.view, gesture: gesture) } ) ), @@ -1218,7 +1221,7 @@ public final class WebAppController: ViewController, AttachmentContainable { case "web_app_setup_back_button": if let json = json, let isVisible = json["is_visible"] as? Bool { self.hasBackButton = isVisible - self.controller?.cancelButtonNode.setState(isVisible ? .back : .cancel, animated: true) + self.controller?.updateNavigationButtons() if controller.isFullscreen { self.requestLayout(transition: .immediate) } @@ -3260,6 +3263,9 @@ public final class WebAppController: ViewController, AttachmentContainable { private var titleView: WebAppTitleView? fileprivate let cancelButtonNode: WebAppCancelButtonNode fileprivate let moreButtonNode: MoreButtonNode + private var cancelBarButtonNode: BarComponentHostNode? + private var moreBarButtonNode: BarComponentHostNode? + private let moreButtonPlayOnce = ActionSlot() private let context: AccountContext public let source: WebAppParameters.Source @@ -3331,20 +3337,24 @@ public final class WebAppController: ViewController, AttachmentContainable { self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.automaticallyControlPresentationContextLayout = false - self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode) - self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) - self.navigationItem.leftBarButtonItem?.target = self - - if !self.isVerifyAgeBot { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) - self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed) - self.navigationItem.rightBarButtonItem?.target = self + if case .attachMenu = self.source { + + } else { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: self.cancelButtonNode) + self.navigationItem.leftBarButtonItem?.action = #selector(self.cancelPressed) + self.navigationItem.leftBarButtonItem?.target = self + + if !self.isVerifyAgeBot { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreButtonNode) + self.navigationItem.rightBarButtonItem?.action = #selector(self.moreButtonPressed) + self.navigationItem.rightBarButtonItem?.target = self + } } self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) if !self.isVerifyAgeBot { - let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme) + let titleView = WebAppTitleView(context: self.context, theme: self.presentationData.theme, isAttachMenu: self.source == .attachMenu) titleView.title = WebAppTitle(title: params.botName, counter: self.presentationData.strings.WebApp_Miniapp, isVerified: params.botVerified) self.navigationItem.titleView = titleView self.titleView = titleView @@ -3352,7 +3362,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.moreButtonNode.action = { [weak self] _, gesture in if let strongSelf = self { - strongSelf.morePressed(node: strongSelf.moreButtonNode.contextSourceNode, gesture: gesture) + strongSelf.morePressed(view: strongSelf.moreButtonNode.contextSourceNode.view, gesture: gesture) } } @@ -3392,6 +3402,8 @@ public final class WebAppController: ViewController, AttachmentContainable { } }) } + + self.updateNavigationButtons() } required public init(coder aDecoder: NSCoder) { @@ -3403,6 +3415,76 @@ public final class WebAppController: ViewController, AttachmentContainable { self.presentationDataDisposable?.dispose() } + private func updateNavigationButtons() { + if case .attachMenu = self.source { + let barButtonSize = CGSize(width: 40.0, height: 40.0) + let closeComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( + id: "close", + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: self.controllerNode.hasBackButton ? "back" : "close", component: AnyComponent( + BundleIconComponent( + name: self.controllerNode.hasBackButton ? "Navigation/Back" : "Navigation/Close", + tintColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { [weak self] _ in + self?.cancelPressed() + } + )) + ) + + let moreComponent: AnyComponentWithIdentity = AnyComponentWithIdentity( + id: "more", + component: AnyComponent(GlassBarButtonComponent( + size: barButtonSize, + backgroundColor: self.presentationData.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: self.presentationData.theme.overallDarkAppearance, + state: .generic, + component: AnyComponentWithIdentity(id: "more", component: AnyComponent( + LottieComponent( + content: LottieComponent.AppBundleContent( + name: "anim_morewide" + ), + color: self.presentationData.theme.rootController.navigationBar.glassBarButtonForegroundColor, + size: CGSize(width: 34.0, height: 34.0), + playOnce: self.moreButtonPlayOnce + ) + )), + action: { [weak self] view in + self?.morePressed(view: view, gesture: nil) + self?.moreButtonPlayOnce.invoke(Void()) + } + )) + ) + + let cancelButtonNode: BarComponentHostNode + if let current = self.cancelBarButtonNode { + cancelButtonNode = current + cancelButtonNode.component = closeComponent + } else { + cancelButtonNode = BarComponentHostNode(component: closeComponent, size: barButtonSize) + self.cancelBarButtonNode = cancelButtonNode + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customDisplayNode: cancelButtonNode) + } + + let morehButtonNode: BarComponentHostNode + if let current = self.moreBarButtonNode { + morehButtonNode = current + morehButtonNode.component = moreComponent + } else { + morehButtonNode = BarComponentHostNode(component: moreComponent, size: barButtonSize) + self.moreBarButtonNode = morehButtonNode + self.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: morehButtonNode) + } + } + + self.cancelButtonNode.setState(self.controllerNode.hasBackButton ? .back : .cancel, animated: true) + } + private var isVerifyAgeBot: Bool { if let ageBotUsername = self.context.currentAppConfiguration.with({ $0 }).data?["verify_age_bot_username"] as? String { return self.botAddress == ageBotUsername @@ -3469,10 +3551,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.moreButtonNode.buttonPressed() } - @objc fileprivate func morePressed(node: ASDisplayNode, gesture: ContextGesture?) { - guard let node = node as? ContextReferenceContentNode else { - return - } + @objc fileprivate func morePressed(view: UIView, gesture: ContextGesture?) { let context = self.context var presentationData = self.presentationData if !presentationData.theme.overallDarkAppearance, let headerColor = self.controllerNode.headerColor { @@ -3667,7 +3746,7 @@ public final class WebAppController: ViewController, AttachmentContainable { return ContextController.Items(content: .list(items)) } - let contextController = ContextController(presentationData: presentationData, source: .reference(WebAppContextReferenceContentSource(controller: self, sourceNode: node)), items: items, gesture: gesture) + let contextController = ContextController(presentationData: presentationData, source: .reference(WebAppContextReferenceContentSource(controller: self, sourceView: view)), items: items, gesture: gesture) self.presentInGlobalOverlay(contextController) } @@ -3698,15 +3777,17 @@ public final class WebAppController: ViewController, AttachmentContainable { self.validLayout = layout super.containerLayoutUpdated(layout, transition: transition) + let navigationBarHeight = self.navigationLayout(layout: layout).navigationFrame.maxY + var presentationLayout = layout if self.isFullscreen { presentationLayout.intrinsicInsets.top = (presentationLayout.statusBarHeight ?? 0.0) + 36.0 } else { - presentationLayout.intrinsicInsets.top = 56.0 + presentationLayout.intrinsicInsets.top = navigationBarHeight } self.presentationContext.containerLayoutUpdated(presentationLayout, transition: transition) - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) } override public var presentationController: UIPresentationController? { @@ -3856,15 +3937,15 @@ final class WebAppPickerContext: AttachmentMediaPickerContext { private final class WebAppContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController - private let sourceNode: ContextReferenceContentNode + private let sourceView: UIView - init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + init(controller: ViewController, sourceView: UIView) { self.controller = controller - self.sourceNode = sourceNode + self.sourceView = sourceView } func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds) + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) } } diff --git a/submodules/WebUI/Sources/WebAppTitleView.swift b/submodules/WebUI/Sources/WebAppTitleView.swift index 627c1ba82f..8e680194ce 100644 --- a/submodules/WebUI/Sources/WebAppTitleView.swift +++ b/submodules/WebUI/Sources/WebAppTitleView.swift @@ -39,6 +39,7 @@ public final class WebAppTitleView: UIView { self.update() } } + private let isAttachMenu: Bool private var primaryTextColor: UIColor? private var secondaryTextColor: UIColor? @@ -75,9 +76,10 @@ public final class WebAppTitleView: UIView { self.setNeedsLayout() } - public init(context: AccountContext, theme: PresentationTheme) { + public init(context: AccountContext, theme: PresentationTheme, isAttachMenu: Bool) { self.context = context self.theme = theme + self.isAttachMenu = isAttachMenu self.titleNode = ImmediateTextNode() self.titleNode.displaysAsynchronously = false @@ -116,6 +118,7 @@ public final class WebAppTitleView: UIView { var totalWidth = titleSize.width + let topOffset = self.isAttachMenu ? 3.0 : 0.0 if self.title.isVerified { let statusContent: EmojiStatusComponent.Content = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor, sizeType: .large) @@ -147,13 +150,13 @@ public final class WebAppTitleView: UIView { totalWidth += titleIconSize.width + 2.0 - titleCredibilityIconTransition.setFrame(view: titleCredibilityIconView, frame: CGRect(origin: CGPoint(x:floorToScreenPixels((size.width - totalWidth) / 2.0) + titleSize.width + 2.0, y: floorToScreenPixels(floorToScreenPixels((size.height - combinedHeight) / 2.0 + titleSize.height / 2.0) - titleIconSize.height / 2.0)), size: titleIconSize)) + titleCredibilityIconTransition.setFrame(view: titleCredibilityIconView, frame: CGRect(origin: CGPoint(x:floorToScreenPixels((size.width - totalWidth) / 2.0) + titleSize.width + 2.0, y: topOffset + floorToScreenPixels(floorToScreenPixels((size.height - combinedHeight) / 2.0 + titleSize.height / 2.0) - titleIconSize.height / 2.0)), size: titleIconSize)) } - let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: floorToScreenPixels((size.height - combinedHeight) / 2.0)), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - totalWidth) / 2.0), y: topOffset + floorToScreenPixels((size.height - combinedHeight) / 2.0)), size: titleSize) self.titleNode.frame = titleFrame - let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: floorToScreenPixels((size.height - combinedHeight) / 2.0) + titleSize.height + spacing), size: subtitleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - subtitleSize.width) / 2.0), y: topOffset + floorToScreenPixels((size.height - combinedHeight) / 2.0) + titleSize.height + spacing), size: subtitleSize) self.subtitleNode.frame = subtitleFrame } } From 473b00c2e757ed9aafb5cf03c4d632ab2ec24f7a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 16 Oct 2025 08:09:30 +0400 Subject: [PATCH 2/4] Various improvements --- .../Sources/AttachmentController.swift | 19 +++++---- .../Sources/AttachmentPanel.swift | 42 +++++++++++-------- .../Sources/ContactsSearchContainerNode.swift | 1 + ...quenceCountrySelectionControllerNode.swift | 1 + .../Navigation/NavigationContainer.swift | 6 ++- .../Sources/ItemListAddressItem.swift | 26 ++++++++---- .../LocationPickerControllerNode.swift | 1 + .../Sources/AttachmentFileSearchItem.swift | 1 + .../ListItems/PeerInfoScreenAddressItem.swift | 8 ++-- .../Sources/SearchInputPanelComponent.swift | 11 ++++- 10 files changed, 77 insertions(+), 39 deletions(-) diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index c1301ffdc9..dc69edc45b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -595,7 +595,7 @@ public class AttachmentController: ViewController, MinimizableController { strongSelf.controller?.minimizedBounds = bounds if !strongSelf.isMinimizing { - if controller.style != .glass { + if controller.style != .glass || strongSelf.didMaximizeOnce { strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition) } strongSelf.containerLayoutUpdated(layout, transition: transition) @@ -623,6 +623,7 @@ public class AttachmentController: ViewController, MinimizableController { self.minimize(damping: damping, initialVelocity: initialVelocity) return false } else { + controller.willDismiss() self.animateOut(damping: damping, initialVelocity: initialVelocity, completion: { self.controller?.dismiss(animated: false) }) @@ -822,12 +823,14 @@ public class AttachmentController: ViewController, MinimizableController { } } + private var didMaximizeOnce = false fileprivate func minimize(damping: CGFloat? = nil, initialVelocity: CGFloat? = nil) { guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else { return } - navigationController.minimizeViewController(controller, damping: damping, velocity: initialVelocity, beforeMaximize: { navigationController, completion in - controller.mainController.beforeMaximize(navigationController: navigationController, completion: completion) + navigationController.minimizeViewController(controller, damping: damping, velocity: initialVelocity, beforeMaximize: { [weak self, weak controller] navigationController, completion in + self?.didMaximizeOnce = true + controller?.mainController.beforeMaximize(navigationController: navigationController, completion: completion) }, setupContainer: { [weak self] current in let minimizedContainer: MinimizedContainerImpl? if let current = current as? MinimizedContainerImpl { @@ -1228,7 +1231,7 @@ public class AttachmentController: ViewController, MinimizableController { self?.isAnimating = false }) - if controller.style != .glass { + if controller.style != .glass || self.didMaximizeOnce { self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) } @@ -1293,7 +1296,8 @@ public class AttachmentController: ViewController, MinimizableController { self.wrapperNode.view.mask = nil self.shadowNode.alpha = 0.0 } else { - let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0 + let inputHeight = layout.inputHeight ?? 0.0 + let availableHeight = layout.size.height - inputHeight let size = CGSize(width: 390.0, height: min(620.0, availableHeight)) @@ -1301,7 +1305,7 @@ public class AttachmentController: ViewController, MinimizableController { let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0)) let position: CGPoint - let positionY = layout.size.height - size.height - insets.bottom - 54.0 + let positionY = layout.size.height - size.height - insets.bottom - (inputHeight > 0.0 ? 0.0 : 54.0) if let sourceRect = controller.getSourceRect?() { position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0) - 2.0), y: min(positionY, sourceRect.minY - size.height)) } else { @@ -1674,10 +1678,11 @@ public class AttachmentController: ViewController, MinimizableController { public func makeContentSnapshotView() -> UIView? { let snapshotView = self.view.snapshotView(afterScreenUpdates: false) + let navigationBarHeight = self.validLayout.flatMap { self.navigationLayout(layout: $0) }?.defaultContentHeight ?? 56.0 if let contentSnapshotView = self.mainController.makeContentSnapshotView() { if !self.mainController.isFullscreen { if let layout = self.validLayout { - contentSnapshotView.frame = contentSnapshotView.frame.offsetBy(dx: 0.0, dy: (layout.statusBarHeight ?? 0.0) + 10.0 + 56.0) + contentSnapshotView.frame = contentSnapshotView.frame.offsetBy(dx: 0.0, dy: (layout.statusBarHeight ?? 0.0) + 10.0 + navigationBarHeight) } } snapshotView?.addSubview(contentSnapshotView) diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index 30383b408b..db6eaa1eed 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -27,10 +27,10 @@ import GlassBackgroundComponent private let legacyButtonSize = CGSize(width: 88.0, height: 49.0) private let glassButtonSize = CGSize(width: 72.0, height: 62.0) -private let smallGlassButtonSize = CGSize(width: 70.0, height: 62.0) +private let smallGlassButtonSize = CGSize(width: 72.0, height: 62.0) private let smallButtonWidth: CGFloat = 69.0 private let iconSize = CGSize(width: 30.0, height: 30.0) -private let sideInset: CGFloat = 3.0 +private let glassPanelSideInset: CGFloat = 20.0 private final class IconComponent: Component { public let account: Account @@ -321,6 +321,8 @@ private final class AttachButtonComponent: CombinedComponent { transition: .immediate ) + let size = CGSize(width: max(context.availableSize.width, title.size.width + 24.0), height: context.availableSize.height) + let button = button.update( component: Rectangle( color: .clear, @@ -332,7 +334,6 @@ private final class AttachButtonComponent: CombinedComponent { ) let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((context.availableSize.width - title.size.width) / 2.0), y: iconFrame.midY + spacing), size: title.size) - context.add(title .position(CGPoint(x: titleFrame.midX, y: titleFrame.midY)) ) @@ -349,7 +350,7 @@ private final class AttachButtonComponent: CombinedComponent { })) ) - return context.availableSize + return size } } } @@ -1471,37 +1472,42 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { return } - let width: CGFloat + let width = layout.size.width + var panelSideInset: CGFloat switch self.panelStyle { case .glass: - width = layout.size.width - 44.0 + panelSideInset = glassPanelSideInset + 3.0 case .legacy: - width = layout.size.width + panelSideInset = 3.0 } let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0) - var distanceBetweenNodes = (width - sideInset * 2.0) / CGFloat(self.buttons.count) + var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1))) let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1) var buttonWidth = self.buttonSize.width var leftNodeOriginX: CGFloat + var maxButtonsToFit = 6 switch self.panelStyle { case .glass: - leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0 + leftNodeOriginX = layout.safeInsets.left + 3.0 + buttonWidth / 2.0 + if layout.size.width < 400.0 { + maxButtonsToFit = 5 + } case .legacy: leftNodeOriginX = (width - internalWidth) / 2.0 } - if self.buttons.count > 5 && layout.size.width < layout.size.height { + if self.buttons.count > maxButtonsToFit && layout.size.width < layout.size.height { switch self.panelStyle { case .glass: buttonWidth = smallGlassButtonSize.width - distanceBetweenNodes = 60.0 + distanceBetweenNodes = 62.0 case .legacy: buttonWidth = smallButtonWidth distanceBetweenNodes = 60.0 } - leftNodeOriginX = layout.safeInsets.left + sideInset + buttonWidth / 2.0 + leftNodeOriginX = layout.safeInsets.left + 3.0 + buttonWidth / 2.0 } var validIds = Set() @@ -1559,7 +1565,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { } } } - let _ = buttonView.update( + let actualButtonSize = buttonView.update( transition: buttonTransition, component: AnyComponent(AttachButtonComponent( context: self.context, @@ -1590,7 +1596,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { ) if i == self.selectedIndex { - selectionFrame = buttonFrame + selectionFrame = CGRect(origin: CGPoint(x: buttonFrame.midX - actualButtonSize.width * 0.5, y: buttonFrame.minY), size: actualButtonSize) } buttonTransition.setFrame(view: buttonView, frame: buttonFrame) var accessibilityTitle = "" @@ -1635,7 +1641,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.selectionNode.cornerRadius = selectionFrame.height * 0.5 transition.setFrame(view: self.selectionNode.view, frame: selectionFrame) - mostRightX += layout.safeInsets.right + sideInset + mostRightX += layout.safeInsets.right + 3.0 let contentSize = CGSize(width: mostRightX, height: self.buttonSize.height) if contentSize != self.scrollNode.view.contentSize { @@ -1877,7 +1883,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { self.textInputPanelNode?.ensureUnfocused() } - let panelSideInset: CGFloat = isSelecting ? 8.0 : 22.0 + let textPanelSideInset: CGFloat = 8.0 + let defaultPanelSideInset: CGFloat = glassPanelSideInset + let panelSideInset: CGFloat = isSelecting ? textPanelSideInset : defaultPanelSideInset var textPanelHeight: CGFloat = 0.0 var textPanelWidth: CGFloat = 0.0 if let textInputPanelNode = self.textInputPanelNode { @@ -2058,7 +2066,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { containerTransition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: UIScreenPixel))) if case .glass = self.panelStyle { - transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - 22.0 : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height))) + transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height))) } self.updateViews(transition: .immediate) diff --git a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift index d03d0e0089..a935febec2 100644 --- a/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsSearchContainerNode.swift @@ -707,6 +707,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo SearchInputPanelComponent( theme: self.presentationData.theme, strings: self.presentationData.strings, + metrics: layout.metrics, placeholder: nil, resetText: nil, updated: { [weak self] query in diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift index b9f45e1ca8..bedee5acaf 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionControllerNode.swift @@ -320,6 +320,7 @@ final class AuthorizationSequenceCountrySelectionControllerNode: ASDisplayNode, SearchInputPanelComponent( theme: self.theme, strings: self.strings, + metrics: layout.metrics, updated: { [weak self] query in guard let self else { return diff --git a/submodules/Display/Source/Navigation/NavigationContainer.swift b/submodules/Display/Source/Navigation/NavigationContainer.swift index 2aa94dcd55..4354213cec 100644 --- a/submodules/Display/Source/Navigation/NavigationContainer.swift +++ b/submodules/Display/Source/Navigation/NavigationContainer.swift @@ -250,7 +250,8 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega bottomController.viewWillAppear(true) let bottomNode = bottomController.displayNode - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, isFlat: self.isFlat, container: self, topNode: topNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: layout.deviceMetrics.screenCornerRadius, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in + let screenCornerRadius = self.minimizedContainer == nil ? layout.deviceMetrics.screenCornerRadius : 0.0 + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, isInteractive: true, isFlat: self.isFlat, container: self, topNode: topNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: screenCornerRadius, didUpdateProgress: { [weak self, weak bottomController] progress, transition, topFrame, bottomFrame in if let strongSelf = self { if let top = strongSelf.state.top { strongSelf.syncKeyboard(leftEdge: top.value.displayNode.frame.minX, transition: transition) @@ -490,7 +491,8 @@ public final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelega } toValue.value.setIgnoreAppearanceMethodInvocations(false) - let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, isFlat: self.isFlat, container: self, topNode: topController.displayNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: layout.deviceMetrics.screenCornerRadius, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in + let screenCornerRadius = self.minimizedContainer == nil ? layout.deviceMetrics.screenCornerRadius : 0.0 + let topTransition = TopTransition(type: transitionType, previous: fromValue, coordinator: NavigationTransitionCoordinator(transition: mappedTransitionType, isInteractive: false, isFlat: self.isFlat, container: self, topNode: topController.displayNode, topNavigationBar: topController.transitionNavigationBar, bottomNode: bottomController.displayNode, bottomNavigationBar: bottomController.transitionNavigationBar, screenCornerRadius: screenCornerRadius, didUpdateProgress: { [weak self] _, transition, topFrame, bottomFrame in guard let strongSelf = self else { return } diff --git a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift index bf8300a214..595695b61e 100644 --- a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift +++ b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift @@ -12,6 +12,7 @@ import AppBundle public final class ItemListAddressItem: ListViewItem, ItemListItem { let theme: PresentationTheme + let systemStyle: ItemListSystemStyle let label: String let text: String let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? @@ -25,8 +26,9 @@ public final class ItemListAddressItem: ListViewItem, ItemListItem { public let tag: Any? - public init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true, action: (() -> Void)?, longTapAction: ((ASDisplayNode, String) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { + public init(theme: PresentationTheme, systemStyle: ItemListSystemStyle = .legacy, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true, action: (() -> Void)?, longTapAction: ((ASDisplayNode, String) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { self.theme = theme + self.systemStyle = systemStyle self.label = label self.text = text self.imageSignal = imageSignal @@ -167,6 +169,7 @@ public class ItemListAddressItemNode: ListViewItemNode { let leftInset: CGFloat = 16.0 + params.leftInset let rightInset: CGFloat = 8.0 + params.rightInset let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -202,11 +205,16 @@ public class ItemListAddressItemNode: ListViewItemNode { let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftOffset - leftInset - rightInset - 98.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let padding: CGFloat = !item.label.isEmpty ? 39.0 : 20.0 + var verticalInset: CGFloat = 0.0 + if case .glass = item.systemStyle { + verticalInset = 4.0 + } + let imageSide = min(90.0, max(66.0, textLayout.size.height + padding - 18.0)) let imageSize = CGSize(width: imageSide, height: imageSide) - let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: item.systemStyle == .glass ? 18.0 : 4.0), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) - let contentSize = CGSize(width: params.width, height: max(textLayout.size.height + padding, imageSize.height + 14.0)) + let contentSize = CGSize(width: params.width, height: max(textLayout.size.height + padding, imageSize.height + 14.0) + verticalInset * 2.0) let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) return (nodeLayout, { [weak self] animation in @@ -264,11 +272,11 @@ public class ItemListAddressItemNode: ListViewItemNode { selectionNode?.removeFromSupernode() }) } + + strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: verticalInset + 11.0), size: labelLayout.size) + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: verticalInset + (item.label.isEmpty ? 11.0 : 31.0)), size: textLayout.size) - strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 11.0), size: labelLayout.size) - strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: item.label.isEmpty ? 11.0 : 31.0), size: textLayout.size) - - let imageFrame = CGRect(origin: CGPoint(x: params.width - imageSize.width - rightInset, y: 7.0), size: imageSize) + let imageFrame = CGRect(origin: CGPoint(x: params.width - imageSize.width - rightInset - 3.0, y: verticalInset + 7.0), size: imageSize) strongSelf.imageNode.frame = imageFrame if let icon = strongSelf.iconNode.image { @@ -335,12 +343,12 @@ public class ItemListAddressItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners || !item.displayDecorations } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)) diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index 0813263b1c..b62188f445 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -1441,6 +1441,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM SearchInputPanelComponent( theme: self.presentationData.theme, strings: self.presentationData.strings, + metrics: layout.metrics, placeholder: self.presentationData.strings.Map_Search, resetText: nil, updated: { [weak self] query in diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index 88c1a22a89..f14c567522 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -237,6 +237,7 @@ private final class AttachmentFileSearchItemNode: ItemListControllerSearchNode { SearchInputPanelComponent( theme: self.theme, strings: self.strings, + metrics: layout.metrics, placeholder: self.strings.Attachment_FilesSearchPlaceholder, resetText: nil, updated: { [weak self] query in diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift index 3461f6182b..02374ccfa0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift @@ -184,7 +184,7 @@ private final class PeerInfoScreenAddressItemNode: PeerInfoScreenItemNode { self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - let addressItem = ItemListAddressItem(theme: presentationData.theme, label: item.label, text: item.text, imageSignal: item.imageSignal, sectionId: 0, style: .blocks, displayDecorations: false, action: nil, longTapAction: nil, linkItemAction: item.linkItemAction) + let addressItem = ItemListAddressItem(theme: presentationData.theme, systemStyle: .glass, label: item.label, text: item.text, imageSignal: item.imageSignal, sectionId: 0, style: .blocks, displayDecorations: false, action: nil, longTapAction: nil, linkItemAction: item.linkItemAction) let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0) @@ -216,14 +216,16 @@ private final class PeerInfoScreenAddressItemNode: PeerInfoScreenItemNode { let height = itemNode.contentSize.height - transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel))) + let separatorRightInset: CGFloat = 16.0 + + transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset - separatorRightInset, height: UIScreenPixel))) transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0) let hasCorners = hasCorners && (topItem == nil || bottomItem == nil) let hasTopCorners = hasCorners && topItem == nil let hasBottomCorners = hasCorners && bottomItem == nil - self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + self.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: true) : nil transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: safeInsets.left, y: 0.0), size: CGSize(width: width - safeInsets.left - safeInsets.right, height: height))) self.bottomSeparatorNode.isHidden = hasBottomCorners diff --git a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift index 7a93999db6..824d55d56c 100644 --- a/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/SearchInputPanelComponent/Sources/SearchInputPanelComponent.swift @@ -28,6 +28,7 @@ public final class SearchInputPanelComponent: Component { public let theme: PresentationTheme public let strings: PresentationStrings + public let metrics: LayoutMetrics public let placeholder: String? public let resetText: ResetText? public let updated: ((String) -> Void) @@ -36,6 +37,7 @@ public final class SearchInputPanelComponent: Component { public init( theme: PresentationTheme, strings: PresentationStrings, + metrics: LayoutMetrics, placeholder: String? = nil, resetText: ResetText? = nil, updated: @escaping ((String) -> Void), @@ -43,6 +45,7 @@ public final class SearchInputPanelComponent: Component { ) { self.theme = theme self.strings = strings + self.metrics = metrics self.placeholder = placeholder self.resetText = resetText self.updated = updated @@ -56,6 +59,9 @@ public final class SearchInputPanelComponent: Component { if lhs.strings !== rhs.strings { return false } + if lhs.metrics != rhs.metrics { + return false + } if lhs.placeholder != rhs.placeholder { return false } @@ -180,7 +186,10 @@ public final class SearchInputPanelComponent: Component { let backgroundColor = component.theme.list.plainBackgroundColor.withMultipliedAlpha(0.75) - let edgeInsets = UIEdgeInsets(top: 10.0, left: 11.0, bottom: 10.0, right: 11.0) + var edgeInsets = UIEdgeInsets(top: 10.0, left: 11.0, bottom: 10.0, right: 11.0) + if case .regular = component.metrics.widthClass { + edgeInsets.bottom += 18.0 + } let fieldHeight: CGFloat = 48.0 let buttonSpacing: CGFloat = 10.0 From d58757ca3c23008a46ad82ffeb6c934fcf524028 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 16 Oct 2025 19:19:50 +0400 Subject: [PATCH 3/4] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../SelectivePrivacySettingsController.swift | 26 +++++++++++-- .../Sources/Settings/PrivacySettings.swift | 28 ++++++++++++++ .../Peers/UpdateCachedPeerData.swift | 16 +------- .../UpdatedAccountPrivacySettings.swift | 37 +++---------------- .../Sources/GiftViewScreen.swift | 8 +++- 6 files changed, 65 insertions(+), 51 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 221538f5c1..0bab75d3ac 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14065,6 +14065,7 @@ Sorry for the inconvenience."; "Privacy.Gifts.AcceptedTypes.Unlimited" = "Unlimited"; "Privacy.Gifts.AcceptedTypes.Limited" = "Limited-Edition"; "Privacy.Gifts.AcceptedTypes.Unique" = "Unique"; +"Privacy.Gifts.AcceptedTypes.Channel" = "From Channels"; "Privacy.Gifts.AcceptedTypes.Premium" = "Premium Subscriptions"; "Privacy.Gifts.AcceptedTypes.Info" = "Choose the types of gifts that you allow others to send you."; diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index c024fa6cfa..450dfbbed4 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -174,6 +174,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case disallowedGiftsUnlimited(PresentationTheme, String, Bool, Bool) case disallowedGiftsLimited(PresentationTheme, String, Bool, Bool) case disallowedGiftsUnique(PresentationTheme, String, Bool, Bool) + case disallowedGiftsChannel(PresentationTheme, String, Bool, Bool) case disallowedGiftsPremium(PresentationTheme, String, Bool, Bool) case disallowedGiftsInfo(PresentationTheme, String) case showGiftButton(PresentationTheme, String, Bool, Bool, Bool) @@ -203,7 +204,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return SelectivePrivacySettingsSection.hideReadTime.rawValue case .subscribeToPremium, .subscribeToPremiumInfo: return SelectivePrivacySettingsSection.premium.rawValue - case .disallowedGiftsHeader, .disallowedGiftsUnlimited, .disallowedGiftsLimited, .disallowedGiftsUnique, .disallowedGiftsPremium, .disallowedGiftsInfo: + case .disallowedGiftsHeader, .disallowedGiftsUnlimited, .disallowedGiftsLimited, .disallowedGiftsUnique, .disallowedGiftsChannel, .disallowedGiftsPremium, .disallowedGiftsInfo: return SelectivePrivacySettingsSection.disallowedGifts.rawValue case .showGiftButton, .showGiftButtonInfo: return SelectivePrivacySettingsSection.giftButton.rawValue @@ -290,10 +291,12 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return 37 case .disallowedGiftsUnique: return 38 - case .disallowedGiftsPremium: + case .disallowedGiftsChannel: return 39 - case .disallowedGiftsInfo: + case .disallowedGiftsPremium: return 40 + case .disallowedGiftsInfo: + return 41 } } @@ -521,6 +524,12 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { return false } + case let .disallowedGiftsChannel(lhsTheme, lhsText, lhsEnabled, lhsValue): + if case let .disallowedGiftsChannel(rhsTheme, rhsText, rhsEnabled, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled, lhsValue == rhsValue { + return true + } else { + return false + } case let .disallowedGiftsPremium(lhsTheme, lhsText, lhsEnabled, lhsValue): if case let .disallowedGiftsPremium(rhsTheme, rhsText, rhsEnabled, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEnabled == rhsEnabled, lhsValue == rhsValue { return true @@ -705,6 +714,16 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { }, activatedWhileDisabled: { arguments.displayLockedGiftsInfo() }) + case let .disallowedGiftsChannel(_, text, isLocked, value): + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in + if !isLocked { + arguments.updateDisallowedGifts?(.channel, !updatedValue) + } else { + arguments.displayLockedGiftsInfo() + } + }, activatedWhileDisabled: { + arguments.displayLockedGiftsInfo() + }) case let .disallowedGiftsPremium(_, text, isLocked, value): return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, enableInteractiveChanges: !isLocked, enabled: true, displayLocked: isLocked, sectionId: self.section, style: .blocks, updated: { updatedValue in if !isLocked { @@ -1152,6 +1171,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.disallowedGiftsUnlimited(presentationData.theme, presentationData.strings.Privacy_Gifts_AcceptedTypes_Unlimited, !isPremium, !(state.disallowedGifts?.contains(.unlimited) ?? false))) entries.append(.disallowedGiftsLimited(presentationData.theme, presentationData.strings.Privacy_Gifts_AcceptedTypes_Limited, !isPremium, !(state.disallowedGifts?.contains(.limited) ?? false))) entries.append(.disallowedGiftsUnique(presentationData.theme, presentationData.strings.Privacy_Gifts_AcceptedTypes_Unique, !isPremium, !(state.disallowedGifts?.contains(.unique) ?? false))) + entries.append(.disallowedGiftsChannel(presentationData.theme, presentationData.strings.Privacy_Gifts_AcceptedTypes_Channel, !isPremium, !(state.disallowedGifts?.contains(.channel) ?? false))) entries.append(.disallowedGiftsPremium(presentationData.theme, presentationData.strings.Privacy_Gifts_AcceptedTypes_Premium, !isPremium, !(state.disallowedGifts?.contains(.premium) ?? false))) entries.append(.disallowedGiftsInfo(presentationData.theme, presentationData.strings.Privacy_Gifts_AcceptedTypes_Info)) } diff --git a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift index 11ad57ff69..0acae94121 100644 --- a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift @@ -307,6 +307,7 @@ public struct TelegramDisallowedGifts: OptionSet, Codable { public static let limited = TelegramDisallowedGifts(rawValue: 1 << 1) public static let unique = TelegramDisallowedGifts(rawValue: 1 << 2) public static let premium = TelegramDisallowedGifts(rawValue: 1 << 3) + public static let channel = TelegramDisallowedGifts(rawValue: 1 << 4) public static let All: TelegramDisallowedGifts = [ .unlimited, @@ -327,6 +328,33 @@ public struct TelegramDisallowedGifts: OptionSet, Codable { } } +extension TelegramDisallowedGifts { + init(apiDisallowedGifts: Api.DisallowedGiftsSettings?) { + var disallowedGifts: TelegramDisallowedGifts = [] + switch apiDisallowedGifts { + case let .disallowedGiftsSettings(giftFlags): + if (giftFlags & (1 << 0)) != 0 { + disallowedGifts.insert(.unlimited) + } + if (giftFlags & (1 << 1)) != 0 { + disallowedGifts.insert(.limited) + } + if (giftFlags & (1 << 2)) != 0 { + disallowedGifts.insert(.unique) + } + if (giftFlags & (1 << 3)) != 0 { + disallowedGifts.insert(.premium) + } + if (giftFlags & (1 << 4)) != 0 { + disallowedGifts.insert(.channel) + } + default: + break + } + self = disallowedGifts + } +} + public struct GlobalPrivacySettings: Equatable, Codable { public enum NonContactChatsPrivacy: Equatable, Codable { case everybody diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 1953276db4..7786b7ae10 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -403,21 +403,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee let sendPaidMessageStars = sendPaidMessageStars.flatMap { StarsAmount(value: $0, nanos: 0) } - var disallowedGifts: TelegramDisallowedGifts = [] - if case let .disallowedGiftsSettings(giftFlags) = disallowedStarGifts { - if (giftFlags & (1 << 0)) != 0 { - disallowedGifts.insert(.unlimited) - } - if (giftFlags & (1 << 1)) != 0 { - disallowedGifts.insert(.limited) - } - if (giftFlags & (1 << 2)) != 0 { - disallowedGifts.insert(.unique) - } - if (giftFlags & (1 << 3)) != 0 { - disallowedGifts.insert(.premium) - } - } + let disallowedGifts = TelegramDisallowedGifts(apiDisallowedGifts: disallowedStarGifts) let botGroupAdminRights = groupAdminRights.flatMap { TelegramChatAdminRights(apiAdminRights: $0) } let botChannelAdminRights = channelAdminRights.flatMap { TelegramChatAdminRights(apiAdminRights: $0) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift index b10afb4e58..54f62ead1c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift @@ -33,21 +33,7 @@ func _internal_updateGlobalPrivacySettings(account: Account) -> Signal Signal Date: Fri, 17 Oct 2025 17:01:25 +0400 Subject: [PATCH 4/4] Various improvements --- .../Sources/AttachmentController.swift | 41 +++-- .../Sources/AttachmentPanel.swift | 9 +- ...ationSequenceCodeEntryControllerNode.swift | 2 +- ...tionSequenceEmailEntryControllerNode.swift | 2 +- ...nSequencePasswordEntryControllerNode.swift | 2 +- ...tionSequencePhoneEntryControllerNode.swift | 4 +- ...rizationSequenceSignUpControllerNode.swift | 2 +- ...uthorizationSequenceSplashController.swift | 2 +- .../Sources/ChatImportActivityScreen.swift | 2 +- .../Sources/ChatListEmptyNode.swift | 2 +- submodules/Display/Source/ImageNode.swift | 18 +- .../Sources/AdditionalLinkItem.swift | 16 +- .../Sources/InviteLinkListController.swift | 12 +- .../Sources/ItemListInviteLinkItem.swift | 16 +- .../ItemListPermanentInviteLinkItem.swift | 24 ++- .../Items/ItemListExpandableSwitchItem.swift | 33 ++-- .../JoinLinkPreviewPeerContentNode.swift | 2 +- .../Sources/ListMessageFileItemNode.swift | 2 +- .../LocationDistancePickerScreen.swift | 2 +- .../Sources/LocationPlaceholderNode.swift | 2 +- .../Sources/MediaPickerPlaceholderNode.swift | 2 +- .../Sources/MediaPickerScreen.swift | 2 +- .../TwoFactorAuthDataInputScreen.swift | 2 +- .../Sources/TwoFactorAuthSplashScreen.swift | 2 +- .../CreateExternalMediaStreamScreen/BUILD | 31 ++-- .../CreateExternalMediaStreamScreen.swift | 156 ++++++++++++------ .../ChannelPermissionsController.swift | 22 +-- .../Sources/ChannelVisibilityController.swift | 22 +-- .../PeerInfoUI/Sources/ChatSlowmodeItem.swift | 9 +- .../Sources/ChatUnrestrictBoostersItem.swift | 9 +- .../Sources/ItemListReactionItem.swift | 19 ++- .../PeerAllowedReactionListController.swift | 6 +- .../Sources/PremiumIntroScreen.swift | 12 +- .../Sources/PremiumLimitsListScreen.swift | 2 +- .../Sources/PremiumOptionComponent.swift | 2 +- .../QrCodeUI/Sources/QrCodeScreen.swift | 2 +- .../Sources/DeleteAccountFooterItem.swift | 2 +- .../IncomingMessagePrivacyScreen.swift | 10 +- .../RecentSessionScreen.swift | 2 +- .../Sources/UsernameSetupController.swift | 4 +- submodules/SolidRoundedButtonNode/BUILD | 1 + .../Sources/SolidRoundedButtonNode.swift | 37 ++++- .../Sources/StickerPreviewPeekContent.swift | 2 +- .../VoiceChatCameraPreviewController.swift | 2 +- .../VoiceChatRecordingSetupController.swift | 2 +- .../Sources/PermissionContentNode.swift | 2 +- .../Sources/AttachmentFileEmptyItem.swift | 2 +- .../Sources/ChatQrCodeScreen.swift | 4 +- .../Sources/ChatTextInputPanelNode.swift | 2 +- .../ChatScheduleTimeControllerNode.swift | 4 +- .../Sources/ChatThemeScreen.swift | 2 +- .../Sources/ChatTimerScreen.swift | 2 +- .../Sources/NewContactScreen.swift | 25 ++- .../Sources/GiftOptionsScreen.swift | 42 +---- .../Sources/MessagePriceItem.swift | 9 +- .../Sources/OldChannelsController.swift | 2 +- .../Sources/PeerSelectionControllerNode.swift | 2 +- .../Sources/StarsPurchaseScreen.swift | 4 + .../Sources/DataButtonComponent.swift | 4 +- .../Sources/DataCategoriesComponent.swift | 2 +- .../Sources/DataCategoryItemCompoment.swift | 4 +- .../Sources/DataUsageScreen.swift | 2 +- .../Sources/StorageCategoriesComponent.swift | 6 +- .../StorageCategoryItemCompoment.swift | 2 +- .../Sources/StorageKeepSizeComponent.swift | 8 +- .../StoragePeerTypeItemComponent.swift | 4 +- .../Sources/StorageUsageScreen.swift | 2 +- .../Stories/StoryStealthModeSheetScreen/BUILD | 1 + .../Sources/StoryStealthModeSheetScreen.swift | 85 +++++----- 69 files changed, 476 insertions(+), 303 deletions(-) diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index dc69edc45b..f179033672 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -1048,7 +1048,10 @@ public class AttachmentController: ViewController, MinimizableController { ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) if case .glass = controller.style, let attachmentButton = controller.attachmentButton { - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .customSpring(damping: 115.0, initialVelocity: 0.0)) + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: 110.0, initialVelocity: 1.1)) + let cornersTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) + let scaleTransition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .customSpring(damping: 110.0, initialVelocity: 0.0)) + let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut) let targetFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view) @@ -1080,11 +1083,13 @@ public class AttachmentController: ViewController, MinimizableController { let initialContainerBounds = self.container.bounds let initialContainerFrame = self.container.frame + let initialBottomClipRadius = self.container.bottomClipNode.cornerRadius let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view) self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size) self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels ((targetFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size) self.container.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.container.bottomClipNode.cornerRadius = 0.0 containerView.addSubnode(self.container) let buttonIcon = GlassBackgroundView.ContentImageView() @@ -1099,16 +1104,17 @@ public class AttachmentController: ViewController, MinimizableController { localGlassView.contentView.addSubview(buttonIcon) ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 0.0, toRadius: 10.0) - transition.animateBounds(layer: containerView.layer, from: CGRect(origin: CGPoint(x: 0.0, y: (targetFrame.height - targetFrame.width) * 0.5), size: CGSize(width: targetFrame.width, height: targetFrame.width))) - ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).animateView { + scaleTransition.animateBounds(layer: containerView.layer, from: CGRect(origin: CGPoint(x: 0.0, y: (targetFrame.height - targetFrame.width) * 0.5), size: CGSize(width: targetFrame.width, height: targetFrame.width))) + cornersTransition.animateView { if #available(iOS 26.0, *) { containerView.cornerConfiguration = .corners(topLeftRadius: 38.0, topRightRadius: 38.0, bottomLeftRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0), bottomRightRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0)) } else { containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0 } } - transition.animateTransformScale(view: containerView, from: sourceButtonScale) - transition.animatePosition(layer: containerView.layer, from: sourceButtonFrame.center, to: containerView.center, completion: { _ in + scaleTransition.animateTransformScale(view: containerView, from: sourceButtonScale) + positionTransition.animatePosition(layer: containerView.layer, from: sourceButtonFrame.center, to: containerView.center, completion: { _ in + self.container.bottomClipNode.cornerRadius = initialBottomClipRadius self.container.bounds = initialContainerBounds self.container.frame = initialContainerFrame self.wrapperNode.addSubnode(self.container) @@ -1158,8 +1164,11 @@ public class AttachmentController: ViewController, MinimizableController { alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) if case .glass = controller.style, let attachmentButton = controller.attachmentButton { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: damping ?? 124.0, initialVelocity: initialVelocity ?? 0.0)) - let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut) + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: 110.0, initialVelocity: initialVelocity ?? 0.0)) + let cornersTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity ?? 0.0)) + let scaleTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity ?? 0.0)) + + let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.22, curve: .easeInOut) let initialFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view) @@ -1192,7 +1201,7 @@ public class AttachmentController: ViewController, MinimizableController { self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size) self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((initialFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size) self.container.isUserInteractionEnabled = false - self.container.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.container.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false) containerView.addSubnode(self.container) let buttonIcon = GlassBackgroundView.ContentImageView() @@ -1206,23 +1215,27 @@ public class AttachmentController: ViewController, MinimizableController { } localGlassView.contentView.addSubview(buttonIcon) ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 10.0, toRadius: 0.0) + ComponentTransition(buttonTransition).animateBlur(layer: sourceGlassView.contentView.layer, fromRadius: 10.0, toRadius: 0.0) - transition.updateBounds(layer: containerView.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: (initialFrame.height - initialFrame.width) * 0.5), size: CGSize(width: initialFrame.width, height: initialFrame.width))) - transition.animateView { + scaleTransition.updateBounds(layer: containerView.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: (initialFrame.height - initialFrame.width) * 0.5), size: CGSize(width: initialFrame.width, height: initialFrame.width))) + cornersTransition.animateView { if #available(iOS 26.0, *) { containerView.cornerConfiguration = .uniformCorners(radius: .fixed(containerView.bounds.width * 0.5)) } else { containerView.layer.cornerRadius = containerView.bounds.width * 0.5 } } - transition.updateTransformScale(layer: containerView.layer, scale: targetButtonScale) - transition.updatePosition(layer: containerView.layer, position: targetButtonFrame.center, completion: { [weak self] _ in - sourceGlassView.isHidden = false + scaleTransition.updateTransformScale(layer: containerView.layer, scale: targetButtonScale) + positionTransition.updatePosition(layer: containerView.layer, position: targetButtonFrame.center, completion: { [weak self] _ in let _ = self?.container.dismiss(transition: .immediate, completion: completion) self?.isAnimating = false }) - sourceGlassView.isHidden = true + localGlassView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, delay: 0.2, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false) + //sourceGlassView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: 0.27, timingFunction: CAMediaTimingFunctionName.linear.rawValue) + scaleTransition.animateTransformScale(view: sourceGlassView, from: 1.0 / targetButtonScale) + + positionTransition.animatePosition(layer: sourceGlassView.layer, from: self.view.convert(initialFrame.center, to: sourceGlassView.superview), to: sourceGlassView.center) } } else { let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index db6eaa1eed..c40171acd7 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -134,6 +134,7 @@ private final class AttachButtonComponent: CombinedComponent { let context: AccountContext let style: Style let type: AttachmentButtonType + let isFirstOrLast: Bool let isSelected: Bool let strings: PresentationStrings let theme: PresentationTheme @@ -144,6 +145,7 @@ private final class AttachButtonComponent: CombinedComponent { context: AccountContext, style: Style, type: AttachmentButtonType, + isFirstOrLast: Bool, isSelected: Bool, strings: PresentationStrings, theme: PresentationTheme, @@ -153,6 +155,7 @@ private final class AttachButtonComponent: CombinedComponent { self.context = context self.style = style self.type = type + self.isFirstOrLast = isFirstOrLast self.isSelected = isSelected self.strings = strings self.theme = theme @@ -170,6 +173,9 @@ private final class AttachButtonComponent: CombinedComponent { if lhs.type != rhs.type { return false } + if lhs.isFirstOrLast != rhs.isFirstOrLast { + return false + } if lhs.isSelected != rhs.isSelected { return false } @@ -321,7 +327,7 @@ private final class AttachButtonComponent: CombinedComponent { transition: .immediate ) - let size = CGSize(width: max(context.availableSize.width, title.size.width + 24.0), height: context.availableSize.height) + let size = CGSize(width: max(component.isFirstOrLast ? context.availableSize.width : 64.0, title.size.width + 24.0), height: context.availableSize.height) let button = button.update( component: Rectangle( @@ -1571,6 +1577,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { context: self.context, style: self.panelStyle == .glass ? .glass : .legacy, type: type, + isFirstOrLast: i == 0 || i == self.buttons.count - 1, isSelected: i == self.selectedIndex, strings: self.presentationData.strings, theme: self.presentationData.theme, diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift index 4139082470..7ac6de341d 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift @@ -248,7 +248,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.signInWithAppleButton?.isHidden = true (self.signInWithAppleButton as? ASAuthorizationAppleIDButton)?.cornerRadius = 11 } - self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0) self.proceedNode.progressType = .embedded self.proceedNode.isHidden = true self.proceedNode.iconSpacing = 4.0 diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift index a9aa7a5347..9080447d22 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift @@ -113,7 +113,7 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText (self.signInWithAppleButton as? ASAuthorizationAppleIDButton)?.cornerRadius = 11 } - self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0) self.proceedNode.progressType = .embedded self.codeSeparatorNode = ASDisplayNode() diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift index f67dfecd9c..2a7c258b36 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift @@ -107,7 +107,7 @@ final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UIT self.codeField.textField.tintColor = self.theme.list.itemAccentColor self.codeField.textField.accessibilityHint = self.strings.Login_VoiceOver_Password - self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0) self.proceedNode.progressType = .embedded self.proceedNode.isEnabled = false diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift index 784136d90c..fafaac93ba 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift @@ -420,7 +420,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.phoneAndCountryNode = PhoneAndCountryNode(strings: strings, theme: theme) - self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0) self.proceedNode.progressType = .embedded self.proceedNode.isEnabled = false @@ -816,7 +816,7 @@ final class PhoneConfirmationController: ViewController { self.cancelButton.accessibilityTraits = [.button] self.cancelButton.accessibilityLabel = strings.Login_Edit - self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0) self.proceedNode.progressType = .embedded let font = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers]) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift index 4ba5cee279..dcc3c8fc81 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift @@ -152,7 +152,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel self.addPhotoButton.addSubnode(self.currentPhotoNode) self.addPhotoButton.allowsGroupOpacity = true - self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0) self.proceedNode.progressType = .embedded super.init() diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift index 3863cae372..044c5c70b8 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift @@ -73,7 +73,7 @@ public final class AuthorizationSequenceSplashController: ViewController { self.controller = RMIntroViewController(backgroundColor: theme.list.plainBackgroundColor, primaryColor: theme.list.itemPrimaryTextColor, buttonColor: theme.intro.startButtonColor, accentColor: theme.list.itemAccentColor, regularDotColor: theme.intro.dotColor, highlightedDotColor: theme.list.itemAccentColor, suggestedLocalizationSignal: localizationSignal) - self.startButton = SolidRoundedButtonNode(title: "Start Messaging", theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 13.0, gloss: true) + self.startButton = SolidRoundedButtonNode(title: "Start Messaging", theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 13.0, isShimmering: true) super.init(navigationBarPresentationData: nil) diff --git a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift index 7919d85b1e..846dbe2ea0 100644 --- a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift +++ b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift @@ -395,7 +395,7 @@ public final class ChatImportActivityScreen: ViewController { self.statusButton = HighlightableButtonNode() - self.doneButton = SolidRoundedButtonNode(title: self.presentationData.strings.ChatImportActivity_OpenApp, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(title: self.presentationData.strings.ChatImportActivity_OpenApp, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, isShimmering: false) super.init() diff --git a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift index c8c78b3c3f..1b821a5edb 100644 --- a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift +++ b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift @@ -85,7 +85,7 @@ final class ChatListEmptyNode: ASDisplayNode { gloss = false } - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), cornerRadius: 11.0, gloss: gloss) + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), cornerRadius: 11.0, isShimmering: gloss) self.secondaryButtonNode = HighlightableButtonNode() diff --git a/submodules/Display/Source/ImageNode.swift b/submodules/Display/Source/ImageNode.swift index f609ca9f0a..c0c1f44452 100644 --- a/submodules/Display/Source/ImageNode.swift +++ b/submodules/Display/Source/ImageNode.swift @@ -66,10 +66,16 @@ public func isRoundEqualCorners(_ corners: ImageCorners) -> Bool { } public struct ImageCorners: Equatable { + public enum Curve { + case circular + case continuous + } + public let topLeft: ImageCorner public let topRight: ImageCorner public let bottomLeft: ImageCorner public let bottomRight: ImageCorner + public let curve: Curve public var isEmpty: Bool { if self.topLeft != .Corner(0.0) { @@ -87,22 +93,24 @@ public struct ImageCorners: Equatable { return true } - public init(radius: CGFloat) { + public init(radius: CGFloat, curve: Curve = .circular) { self.topLeft = .Corner(radius) self.topRight = .Corner(radius) self.bottomLeft = .Corner(radius) self.bottomRight = .Corner(radius) + self.curve = curve } - public init(topLeft: ImageCorner, topRight: ImageCorner, bottomLeft: ImageCorner, bottomRight: ImageCorner) { + public init(topLeft: ImageCorner, topRight: ImageCorner, bottomLeft: ImageCorner, bottomRight: ImageCorner, curve: Curve = .circular) { self.topLeft = topLeft self.topRight = topRight self.bottomLeft = bottomLeft self.bottomRight = bottomRight + self.curve = curve } public init() { - self.init(topLeft: .Corner(0.0), topRight: .Corner(0.0), bottomLeft: .Corner(0.0), bottomRight: .Corner(0.0)) + self.init(topLeft: .Corner(0.0), topRight: .Corner(0.0), bottomLeft: .Corner(0.0), bottomRight: .Corner(0.0), curve: .circular) } public var extendedEdges: UIEdgeInsets { @@ -113,12 +121,12 @@ public struct ImageCorners: Equatable { } public func withRemovedTails() -> ImageCorners { - return ImageCorners(topLeft: self.topLeft.withoutTail, topRight: self.topRight.withoutTail, bottomLeft: self.bottomLeft.withoutTail, bottomRight: self.bottomRight.withoutTail) + return ImageCorners(topLeft: self.topLeft.withoutTail, topRight: self.topRight.withoutTail, bottomLeft: self.bottomLeft.withoutTail, bottomRight: self.bottomRight.withoutTail, curve: self.curve) } } public func ==(lhs: ImageCorners, rhs: ImageCorners) -> Bool { - return lhs.topLeft == rhs.topLeft && lhs.topRight == rhs.topRight && lhs.bottomLeft == rhs.bottomLeft && lhs.bottomRight == rhs.bottomRight + return lhs.topLeft == rhs.topLeft && lhs.topRight == rhs.topRight && lhs.bottomLeft == rhs.bottomLeft && lhs.bottomRight == rhs.bottomRight && lhs.curve == rhs.curve } public class ImageNode: ASDisplayNode { diff --git a/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift index f4f4ea73f7..c66e876ff5 100644 --- a/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift @@ -10,6 +10,7 @@ import TelegramCore public class AdditionalLinkItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let username: TelegramPeerUsername? public let sectionId: ItemListSectionId let style: ItemListStyle @@ -18,6 +19,7 @@ public class AdditionalLinkItem: ListViewItem, ItemListItem { public init( presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .legacy, username: TelegramPeerUsername?, sectionId: ItemListSectionId, style: ItemListStyle, @@ -25,6 +27,7 @@ public class AdditionalLinkItem: ListViewItem, ItemListItem { tag: ItemListItemTag? = nil ) { self.presentationData = presentationData + self.systemStyle = systemStyle self.username = username self.sectionId = sectionId self.style = style @@ -243,7 +246,13 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode { let leftInset: CGFloat = 65.0 + params.leftInset let rightInset: CGFloat = 16.0 + params.rightInset - let verticalInset: CGFloat = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = subtitleAttributedString.string.isEmpty ? 18.0 : 12.0 + case .legacy: + verticalInset = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 + } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - reorderInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - reorderInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -271,6 +280,7 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode { let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -377,12 +387,12 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } let iconSize: CGSize = CGSize(width: 40.0, height: 40.0) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index cf7cdf5aeb..30f28682f3 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -215,7 +215,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry { case let .mainLinkHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .mainLink(_, invite, peers, importersCount, isPublic): - return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, separateButtons: true, displayImporters: !isPublic, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { + return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, invite: invite, count: importersCount, peers: peers, displayButton: true, separateButtons: true, displayImporters: !isPublic, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { if let invite = invite { arguments.copyLink(invite) } @@ -236,11 +236,11 @@ private enum InviteLinksListEntry: ItemListNodeEntry { case let .linksHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .linksCreate(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, editing: false, action: { arguments.createLink() }) case let .link(_, _, invite, canEdit, _): - return ItemListInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in + return ItemListInviteLinkItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in arguments.openLink(invite) } contextAction: { invite, node, gesture in arguments.linkContextAction(invite, canEdit, node, gesture) @@ -250,11 +250,11 @@ private enum InviteLinksListEntry: ItemListNodeEntry { case let .revokedLinksHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .revokedLinksDeleteAll(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, color: .destructive, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, color: .destructive, editing: false, action: { arguments.deleteAllRevokedLinks() }) case let .revokedLink(_, _, invite): - return ItemListInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in + return ItemListInviteLinkItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, invite: invite, share: false, sectionId: self.section, style: .blocks) { invite in arguments.openLink(invite) } contextAction: { invite, node, gesture in arguments.linkContextAction(invite, false, node, gesture) @@ -262,7 +262,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry { case let .adminsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .admin(_, _, creator): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(creator.peer.peer!), height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: creator.count > 1 ? .disclosure("\(creator.count)") : .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(creator.peer.peer!), height: .peerList, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .none, label: creator.count > 1 ? .disclosure("\(creator.count)") : .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: { arguments.openAdmin(creator) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil) } diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift index c1c9a6e3cb..d484603045 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift @@ -59,6 +59,7 @@ private enum ItemBackgroundColor: Equatable { public class ItemListInviteLinkItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let invite: ExportedInvitation? let share: Bool public let sectionId: ItemListSectionId @@ -70,6 +71,7 @@ public class ItemListInviteLinkItem: ListViewItem, ItemListItem { public init( context: AccountContext, presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .legacy, invite: ExportedInvitation?, share: Bool, sectionId: ItemListSectionId, @@ -80,6 +82,7 @@ public class ItemListInviteLinkItem: ListViewItem, ItemListItem { ) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.invite = invite self.share = share self.sectionId = sectionId @@ -479,7 +482,13 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { let leftInset: CGFloat = 65.0 + params.leftInset let rightInset: CGFloat = 16.0 + params.rightInset - let verticalInset: CGFloat = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = subtitleAttributedString.string.isEmpty ? 18.0 : 12.0 + case .legacy: + verticalInset = subtitleAttributedString.string.isEmpty ? 14.0 : 8.0 + } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -508,6 +517,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { let contentSize = CGSize(width: params.width, height: max(minHeight, rawHeight)) let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) @@ -624,12 +634,12 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } let iconSize: CGSize = CGSize(width: 40.0, height: 40.0) diff --git a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift index de6d03c3a7..7ff09939d8 100644 --- a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift @@ -33,6 +33,7 @@ private func actionButtonImage(color: UIColor) -> UIImage? { public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let invite: ExportedInvitation? let count: Int32 let peers: [EnginePeer] @@ -53,6 +54,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem { public init( context: AccountContext, presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .legacy, invite: ExportedInvitation?, count: Int32, peers: [EnginePeer], @@ -72,6 +74,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem { ) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.invite = invite self.count = count self.peers = peers @@ -323,6 +326,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -389,7 +394,14 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem let avatarsContent = avatarsContext.update(peers: item.peers, animated: false) - let verticalInset: CGFloat = 16.0 + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 16.0 + case .legacy: + verticalInset = 16.0 + } + let fieldHeight: CGFloat = 52.0 let fieldSpacing: CGFloat = 16.0 let buttonHeight: CGFloat = 50.0 @@ -439,7 +451,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem strongSelf.topStripeNode.backgroundColor = itemSeparatorColor strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor strongSelf.backgroundNode.backgroundColor = itemBackgroundColor - strongSelf.fieldNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor) + strongSelf.fieldNode.image = generateStretchableFilledCircleImage(diameter: item.systemStyle == .glass ? 52.0 : 18.0, color: item.presentationData.theme.list.itemInputField.backgroundColor) strongSelf.addressButtonIconNode.image = actionButtonImage(color: item.presentationData.theme.list.itemInputField.controlColor) } @@ -496,12 +508,12 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } let fieldFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: CGSize(width: params.width - leftInset - rightInset, height: fieldHeight)) @@ -533,7 +545,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem } else { buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) } - copyButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: 50.0, cornerRadius: 11.0) + copyButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: 52.0, cornerRadius: item.systemStyle == .glass ? 26.0 : 11.0) copyButtonNode.title = item.presentationData.strings.InviteLink_CopyShort copyButtonNode.pressed = { [weak self] in self?.item?.copyAction?() @@ -552,7 +564,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem } else { buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme) } - shareButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: 50.0, cornerRadius: 11.0) + shareButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: 52.0, cornerRadius: item.systemStyle == .glass ? 26.0 : 11.0) if let invite = item.invite, invitationAvailability(invite).isZero { shareButtonNode.title = item.presentationData.strings.InviteLink_ReactivateLink } else { diff --git a/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift index 3cf649e9b2..a7f52e9eb4 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListExpandableSwitchItem.swift @@ -34,6 +34,7 @@ public class ItemListExpandableSwitchItem: ListViewItem, ItemListItem { } let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let icon: UIImage? let title: String let value: Bool @@ -56,8 +57,9 @@ public class ItemListExpandableSwitchItem: ListViewItem, ItemListItem { public let selectable: Bool = true - public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, value: Bool, isExpanded: Bool, subItems: [SubItem], type: ItemListExpandableSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, selectAction: @escaping () -> Void, subAction: @escaping (SubItem) -> Void, tag: ItemListItemTag? = nil) { + public init(presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle = .legacy, icon: UIImage? = nil, title: String, value: Bool, isExpanded: Bool, subItems: [SubItem], type: ItemListExpandableSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, selectAction: @escaping () -> Void, subAction: @escaping (SubItem) -> Void, tag: ItemListItemTag? = nil) { self.presentationData = presentationData + self.systemStyle = systemStyle self.icon = icon self.title = title self.value = value @@ -156,6 +158,7 @@ private final class SubItemNode: HighlightTrackingButtonNode { self.action = action let leftInset: CGFloat = 60.0 + let separatorRightInset: CGFloat = 16.0 if themeUpdated { self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor @@ -179,7 +182,7 @@ private final class SubItemNode: HighlightTrackingButtonNode { checkNode.setSelected(item.isSelected, animated: transition.isAnimated) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset, y: size.height - UIScreenPixel), size: CGSize(width: size.width - leftInset, height: UIScreenPixel))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset, y: size.height - UIScreenPixel), size: CGSize(width: size.width - leftInset - separatorRightInset, height: UIScreenPixel))) self.textNode.attributedText = NSAttributedString(string: item.title, font: Font.regular(17.0), textColor: presentationData.theme.list.itemPrimaryTextColor) let titleSize = self.textNode.updateLayout(CGSize(width: size.width - leftInset, height: 100.0)) @@ -305,6 +308,8 @@ public class ItemListExpandableSwitchItemNode: ListViewItemNode, ItemListItemNod var contentSize: CGSize var insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -324,12 +329,12 @@ public class ItemListExpandableSwitchItemNode: ListViewItemNode, ItemListItemNod case .plain: itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor - contentSize = CGSize(width: params.width, height: 44.0) + contentSize = CGSize(width: params.width, height: item.systemStyle == .glass ? 52.0 : 44.0) insets = itemListNeighborsPlainInsets(neighbors) case .blocks: itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor - contentSize = CGSize(width: params.width, height: 44.0) + contentSize = CGSize(width: params.width, height: item.systemStyle == .glass ? 52.0 : 44.0) insets = itemListNeighborsGroupedInsets(neighbors, params) } @@ -347,13 +352,21 @@ public class ItemListExpandableSwitchItemNode: ListViewItemNode, ItemListItemNod let titleValue = "\(item.subItems.filter(\.isSelected).count)/\(item.subItems.count)" let (titleValueLayout, titleValueApply) = makeTitleValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleValue, font: Font.bold(14.0), textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 64.0 - titleLayout.size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } - contentSize.height = max(contentSize.height, titleLayout.size.height + 22.0) + contentSize.height = max(contentSize.height, titleLayout.size.height + verticalInset * 2.0) let mainContentHeight = contentSize.height var effectiveSubItemsHeight: CGFloat = 0.0 if item.isExpanded { - effectiveSubItemsHeight = CGFloat(item.subItems.count) * 44.0 + effectiveSubItemsHeight = CGFloat(item.subItems.count) * (item.systemStyle == .glass ? 52.0 : 44.0) } contentSize.height += effectiveSubItemsHeight @@ -494,14 +507,14 @@ public class ItemListExpandableSwitchItemNode: ListViewItemNode, ItemListItemNod strongSelf.bottomTopStripeNode.isHidden = false } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) animation.animator.updateFrame(layer: strongSelf.backgroundNode.layer, frame: backgroundFrame, completion: nil) animation.animator.updateFrame(layer: strongSelf.maskNode.layer, frame: backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0), completion: nil) animation.animator.updateFrame(layer: strongSelf.topStripeNode.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)), completion: nil) animation.animator.updateFrame(layer: strongSelf.bottomTopStripeNode.layer, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: mainContentHeight - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)), completion: nil) - animation.animator.updateFrame(layer: strongSelf.bottomStripeNode.layer, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)), completion: nil) + animation.animator.updateFrame(layer: strongSelf.bottomStripeNode.layer, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)), completion: nil) } strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((mainContentHeight - titleLayout.size.height) / 2.0)), size: titleLayout.size) @@ -564,12 +577,12 @@ public class ItemListExpandableSwitchItemNode: ListViewItemNode, ItemListItemNod lockedIconNode.removeFromSupernode() } - strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel)) + strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: (item.systemStyle == .glass ? 52.0 : 44.0) + UIScreenPixel + UIScreenPixel)) animation.animator.updateFrame(layer: strongSelf.subItemContainer.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: mainContentHeight), size: CGSize(width: params.width, height: effectiveSubItemsHeight)), completion: nil) var validIds: [AnyHashable] = [] - let subItemSize = CGSize(width: params.width - params.leftInset - params.rightInset, height: 44.0) + let subItemSize = CGSize(width: params.width - params.leftInset - params.rightInset, height: item.systemStyle == .glass ? 52.0 : 44.0) var nextSubItemPosition = CGPoint(x: params.leftInset, y: 0.0) for subItem in item.subItems { validIds.append(subItem.id) diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift index 6acb4bc232..7599eed5e5 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift @@ -134,7 +134,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer self.peersScrollNode = ASScrollNode() self.peersScrollNode.view.showsHorizontalScrollIndicator = false - self.actionButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.actionButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 11.0) let itemTheme = SelectablePeerNodeTheme(textColor: theme.actionSheet.primaryTextColor, secretTextColor: .green, selectedTextColor: theme.actionSheet.controlAccentColor, checkBackgroundColor: theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: theme.actionSheet.controlAccentColor, checkColor: theme.actionSheet.opaqueItemBackgroundColor, avatarPlaceholderColor: theme.list.mediaPlaceholderColor) diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 021f2da0e2..ade628dba4 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -1022,7 +1022,7 @@ public final class ListMessageFileItemNode: ListMessageNode { switch iconImage { case let .imageRepresentation(_, representation): let iconSize = CGSize(width: 40.0, height: 40.0) - let imageCorners = ImageCorners(radius: iconCornerRadius) + let imageCorners = ImageCorners(radius: iconCornerRadius, curve: item.systemStyle == .glass ? .continuous : .circular) let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor) iconImageApply = iconImageLayout(arguments) case .albumArt: diff --git a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift index 29dac3d2cb..3547383093 100644 --- a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift +++ b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift @@ -258,7 +258,7 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, ASScrollViewD self.cancelButton = HighlightableButtonNode() self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0) self.doneButton.title = self.presentationData.strings.Conversation_Timer_Send self.unitLabelNode = ImmediateTextNode() diff --git a/submodules/LocationUI/Sources/LocationPlaceholderNode.swift b/submodules/LocationUI/Sources/LocationPlaceholderNode.swift index 48a6e908c1..0caf1c7e6a 100644 --- a/submodules/LocationUI/Sources/LocationPlaceholderNode.swift +++ b/submodules/LocationUI/Sources/LocationPlaceholderNode.swift @@ -52,7 +52,7 @@ final class LocationPlaceholderNode: ASDisplayNode { self.textNode.textAlignment = .center self.textNode.maximumNumberOfLines = 0 - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true) + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, isShimmering: true) self.cameraTextNode = ImmediateTextNode() self.cameraTextNode.isUserInteractionEnabled = false diff --git a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift index 24e03f5f77..21a8179ec0 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerPlaceholderNode.swift @@ -59,7 +59,7 @@ final class MediaPickerPlaceholderNode: ASDisplayNode { self.textNode.textAlignment = .center self.textNode.maximumNumberOfLines = 0 - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true) + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, isShimmering: true) self.cameraButtonNode = HighlightTrackingButtonNode() self.cameraButtonNode.alpha = 0.0 diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index c6d47c3e77..557f1ef87c 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2510,7 +2510,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att let title: String let isEnabled: Bool if self.controllerNode.currentDisplayMode == .selected { - title = self.presentationData.strings.Attachment_SelectedMedia(count) + title = "" // self.presentationData.strings.Attachment_SelectedMedia(count) isEnabled = false } else { title = self.selectedCollectionValue?.localizedTitle ?? self.presentationData.strings.MediaPicker_Recents diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift index 9ec039326d..eaa8449741 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift @@ -1562,7 +1562,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, ASS self.inputNodes = inputNodes - self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 11.0, gloss: false) + self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 11.0, isShimmering: false) super.init() diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift index acb4735354..66e73dbaff 100644 --- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift +++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthSplashScreen.swift @@ -245,7 +245,7 @@ private final class TwoFactorAuthSplashScreenNode: ViewControllerTracingNode { return iconNode } - self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 11.0, gloss: false) + self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 11.0, isShimmering: false) self.buttonNode.isHidden = buttonText.isEmpty super.init() diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD index b34e2bac80..768e2b7bd2 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/BUILD @@ -10,21 +10,22 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/Postbox:Postbox", - "//submodules/TelegramCore:TelegramCore", - "//submodules/TelegramPresentationData:TelegramPresentationData", - "//submodules/AccountContext:AccountContext", - "//submodules/PresentationDataUtils:PresentationDataUtils", - "//submodules/ComponentFlow:ComponentFlow", - "//submodules/Components/ViewControllerComponent:ViewControllerComponent", - "//submodules/Components/MultilineTextComponent:MultilineTextComponent", - "//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent", - "//submodules/Components/BundleIconComponent:BundleIconComponent", - "//submodules/Components/AnimatedStickerComponent:AnimatedStickerComponent", - "//submodules/Components/ActivityIndicatorComponent:ActivityIndicatorComponent", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/PresentationDataUtils", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/AnimatedStickerComponent", + "//submodules/Components/ActivityIndicatorComponent", + "//submodules/TelegramUI/Components/ButtonComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift index f60934640f..577a18f4b2 100644 --- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift +++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift @@ -9,10 +9,11 @@ import AccountContext import ComponentFlow import ViewControllerComponent import MultilineTextComponent -import SolidRoundedButtonComponent +import ButtonComponent import BundleIconComponent import AnimatedStickerComponent import ActivityIndicatorComponent +import GlassBarButtonComponent private final class CreateExternalMediaStreamScreenComponent: CombinedComponent { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -165,10 +166,13 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent static var body: Body { let background = Child(Rectangle.self) + let closeButton = Child(GlassBarButtonComponent.self) + let title = Child(Text.self) + let animation = Child(AnimatedStickerComponent.self) let text = Child(MultilineTextComponent.self) let bottomText = Child(MultilineTextComponent.self) - let button = Child(SolidRoundedButtonComponent.self) + let button = Child(ButtonComponent.self) let activityIndicator = Child(ActivityIndicatorComponent.self) @@ -187,12 +191,16 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent return { context in let topInset: CGFloat = 16.0 let sideInset: CGFloat = 16.0 + let buttonSideInset: CGFloat = 36.0 let credentialsSideInset: CGFloat = 16.0 - let credentialsTopInset: CGFloat = 9.0 + let credentialsTopInset: CGFloat = 12.0 let credentialsTitleSpacing: CGFloat = 5.0 let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let state = context.state + + let theme = environment.theme.withModalBlocksBackground() + let mode = context.component.mode let controller = environment.controller @@ -200,14 +208,65 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent if environment.safeInsets.bottom.isZero { bottomInset = 16.0 } else { - bottomInset = 42.0 + bottomInset = 34.0 } let background = background.update( - component: Rectangle(color: environment.theme.list.blocksBackgroundColor), + component: Rectangle(color: theme.list.blocksBackgroundColor), availableSize: context.availableSize, transition: context.transition ) + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + if case .create = context.component.mode { + let closeButton = closeButton.update( + component: GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: theme.overallDarkAppearance, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { _ in + guard let controller = controller() else { + return + } + controller.dismiss() + } + ), + availableSize: CGSize(width: 40.0, height: 40.0), + transition: context.transition + ) + context.add(closeButton + .position(CGPoint(x: 16.0 + closeButton.size.width * 0.5, y: 16.0 + closeButton.size.height * 0.5)) + ) + } + + let titleString: String + switch context.component.mode { + case .create: + titleString = environment.strings.CreateExternalStream_Title + case .view: + titleString = environment.strings.CreateExternalStream_StreamKeyTitle + } + let title = title.update( + component: Text( + text: titleString, + font: Font.semibold(17.0), + color: theme.list.itemPrimaryTextColor + ), + availableSize: context.availableSize, + transition: context.transition + ) + context.add(title + .position(CGPoint(x: context.availableSize.width * 0.5, y: 26.0 + title.size.height * 0.5)) + ) let animation = animation.update( component: AnimatedStickerComponent( @@ -224,10 +283,10 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let text = text.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_Text, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor, paragraphAlignment: .center)), + text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_Text, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)), horizontalAlignment: .center, maximumNumberOfLines: 0, - lineSpacing: 0.1 + lineSpacing: 0.2 ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), transition: context.transition @@ -236,25 +295,32 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let bottomText = Condition(mode == .create) { bottomText.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_StartStreamingInfo, font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor, paragraphAlignment: .center)), + text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_StartStreamingInfo, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor, paragraphAlignment: .center)), horizontalAlignment: .center, maximumNumberOfLines: 0, - lineSpacing: 0.1 + lineSpacing: 0.2 ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height), transition: context.transition ) } + let buttonAttributedString = NSMutableAttributedString(string: mode == .create ? environment.strings.CreateExternalStream_StartStreaming : environment.strings.Common_Close, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let button = button.update( - component: SolidRoundedButtonComponent( - title: mode == .create ? environment.strings.CreateExternalStream_StartStreaming : environment.strings.Common_Close, - theme: SolidRoundedButtonComponent.Theme(theme: environment.theme), - font: .bold, - fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, - gloss: true, + component: ButtonComponent( + background: ButtonComponent.Background( + style: .glass, + color: mode == .create ? UIColor(rgb: 0xfa325a) : theme.list.itemCheckColors.fillColor, + foreground: mode == .create ? .white : theme.list.itemCheckColors.foregroundColor, + pressedColor: mode == .create ? UIColor(rgb: 0xfa325a) : theme.list.itemCheckColors.fillColor, + isShimmering: mode == .create + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) + ), + isEnabled: true, + displaysProgress: false, action: { [weak state] in guard let state = state, let controller = controller() else { return @@ -270,24 +336,20 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent } } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - buttonSideInset * 2.0, height: 52.0), transition: context.transition ) - let credentialsItemHeight: CGFloat = 60.0 + let credentialsItemHeight: CGFloat = 64.0 let credentialsAreaSize = CGSize(width: context.availableSize.width - sideInset * 2.0, height: credentialsItemHeight * 2.0) - context.add(background - .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) - ) - let animationFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - animation.size.width) / 2.0), y: environment.navigationHeight + topInset), size: animation.size) context.add(animation .position(CGPoint(x: animationFrame.midX, y: animationFrame.midY)) ) - let textFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - text.size.width) / 2.0), y: animationFrame.maxY + 16.0), size: text.size) + let textFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - text.size.width) / 2.0), y: animationFrame.maxY + 18.0), size: text.size) context.add(text .position(CGPoint(x: textFrame.midX, y: textFrame.midY)) @@ -298,7 +360,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent if let credentials = context.state.credentials { let credentialsURLTitle = credentialsURLTitle.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_ServerUrl, font: Font.regular(14.0), textColor: environment.theme.list.itemPrimaryTextColor, paragraphAlignment: .left)), + text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_ServerUrl, font: Font.regular(15.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .left)), horizontalAlignment: .left, maximumNumberOfLines: 1 ), @@ -308,7 +370,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let credentialsKeyTitle = credentialsKeyTitle.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_StreamKey, font: Font.regular(14.0), textColor: environment.theme.list.itemPrimaryTextColor, paragraphAlignment: .left)), + text: .plain(NSAttributedString(string: environment.strings.CreateExternalStream_StreamKey, font: Font.regular(15.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .left)), horizontalAlignment: .left, maximumNumberOfLines: 1 ), @@ -318,7 +380,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let credentialsURLText = credentialsURLText.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: credentials.url, font: Font.regular(16.0), textColor: environment.theme.list.itemAccentColor, paragraphAlignment: .left)), + text: .plain(NSAttributedString(string: credentials.url, font: Font.regular(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .left)), horizontalAlignment: .left, truncationType: .middle, maximumNumberOfLines: 1 @@ -329,30 +391,30 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let credentialsKeyText = credentialsKeyText.update( component: MultilineTextComponent( - text: .plain(NSAttributedString(string: credentials.streamKey, font: Font.regular(16.0), textColor: environment.theme.list.itemAccentColor, paragraphAlignment: .left)), + text: .plain(NSAttributedString(string: credentials.streamKey, font: Font.regular(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .left)), horizontalAlignment: .left, truncationType: .middle, maximumNumberOfLines: 1 ), - availableSize: CGSize(width: credentialsAreaSize.width - credentialsSideInset * 2.0 - 22.0, height: credentialsAreaSize.height), + availableSize: CGSize(width: credentialsAreaSize.width - credentialsSideInset * 2.0 - 48.0, height: credentialsAreaSize.height), transition: context.transition ) let credentialsBackground = credentialsBackground.update( - component: RoundedRectangle(color: environment.theme.list.itemBlocksBackgroundColor, cornerRadius: 10.0), + component: RoundedRectangle(color: theme.list.itemBlocksBackgroundColor, cornerRadius: 26.0), availableSize: credentialsAreaSize, transition: context.transition ) let credentialsStripe = credentialsStripe.update( - component: Rectangle(color: environment.theme.list.itemPlainSeparatorColor), - availableSize: CGSize(width: credentialsAreaSize.width - credentialsSideInset, height: UIScreenPixel), + component: Rectangle(color: theme.list.itemPlainSeparatorColor), + availableSize: CGSize(width: credentialsAreaSize.width - credentialsSideInset * 2.0, height: UIScreenPixel), transition: context.transition ) let credentialsCopyURLButton = credentialsCopyURLButton.update( component: Button( - content: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: environment.theme.list.itemAccentColor)), + content: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: theme.list.itemAccentColor)), action: { [weak state] in guard let state = state else { return @@ -366,7 +428,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent let credentialsCopyKeyButton = credentialsCopyKeyButton.update( component: Button( - content: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: environment.theme.list.itemAccentColor)), + content: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: theme.list.itemAccentColor)), action: { [weak state] in guard let state = state else { return @@ -393,7 +455,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent .position(CGPoint(x: credentialsFrame.minX + credentialsSideInset + credentialsURLText.size.width / 2.0, y: credentialsFrame.minY + credentialsTopInset + credentialsTitleSpacing + credentialsURLTitle.size.height + credentialsURLText.size.height / 2.0)) ) context.add(credentialsCopyURLButton - .position(CGPoint(x: credentialsFrame.maxX - 12.0 - credentialsCopyURLButton.size.width / 2.0, y: credentialsFrame.minY + credentialsItemHeight / 2.0)) + .position(CGPoint(x: credentialsFrame.maxX - 4.0 - credentialsCopyURLButton.size.width / 2.0, y: credentialsFrame.minY + credentialsItemHeight / 2.0)) ) context.add(credentialsKeyTitle @@ -403,11 +465,11 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent .position(CGPoint(x: credentialsFrame.minX + credentialsSideInset + credentialsKeyText.size.width / 2.0, y: credentialsFrame.minY + credentialsItemHeight + credentialsTopInset + credentialsTitleSpacing + credentialsKeyTitle.size.height + credentialsKeyText.size.height / 2.0)) ) context.add(credentialsCopyKeyButton - .position(CGPoint(x: credentialsFrame.maxX - 12.0 - credentialsCopyKeyButton.size.width / 2.0, y: credentialsFrame.minY + credentialsItemHeight + credentialsItemHeight / 2.0)) + .position(CGPoint(x: credentialsFrame.maxX - 4.0 - credentialsCopyKeyButton.size.width / 2.0, y: credentialsFrame.minY + credentialsItemHeight + credentialsItemHeight / 2.0)) ) } else if !context.state.isDelayingLoadingIndication { let activityIndicator = activityIndicator.update( - component: ActivityIndicatorComponent(color: environment.theme.list.controlSecondaryColor), + component: ActivityIndicatorComponent(color: theme.list.controlSecondaryColor), availableSize: CGSize(width: 100.0, height: 100.0), transition: context.transition ) @@ -416,11 +478,11 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent ) } - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: context.availableSize.height - bottomInset - button.size.height), size: button.size) + let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: context.availableSize.height - bottomInset - button.size.height), size: button.size) if let bottomText = bottomText { context.add(bottomText - .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.minY - 14.0 - bottomText.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.minY - 12.0 - bottomText.size.height / 2.0)) ) } @@ -448,19 +510,13 @@ public final class CreateExternalMediaStreamScreen: ViewControllerComponentConta self.peerId = peerId self.mode = mode - super.init(context: context, component: CreateExternalMediaStreamScreenComponent(context: context, peerId: peerId, mode: mode, credentialsPromise: credentialsPromise), navigationBarAppearance: .transparent, theme: .dark) + super.init(context: context, component: CreateExternalMediaStreamScreenComponent(context: context, peerId: peerId, mode: mode, credentialsPromise: credentialsPromise), navigationBarAppearance: .none, theme: .dark) + + self._hasGlassStyle = true self.navigationPresentation = .modal - - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - switch mode { - case .create: - self.title = presentationData.strings.CreateExternalStream_Title - case .view: - self.title = presentationData.strings.CreateExternalStream_StreamKeyTitle - } - - self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + + self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView()) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) } diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index 4e1bbc8d26..df917543dc 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -360,7 +360,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .permission(_, _, title, value, rights, enabled, subPermissions, isExpanded): if !subPermissions.isEmpty { - return ItemListExpandableSwitchItem(presentationData: presentationData, title: title, value: value, isExpanded: isExpanded, subItems: subPermissions.map { item in + return ItemListExpandableSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, isExpanded: isExpanded, subItems: subPermissions.map { item in return ItemListExpandableSwitchItem.SubItem( id: AnyHashable(item.flags.rawValue), title: item.title, @@ -390,7 +390,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { } }) } else { - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, type: .icon, enableInteractiveChanges: enabled != nil, enabled: enabled ?? true, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, type: .icon, enableInteractiveChanges: enabled != nil, enabled: enabled ?? true, sectionId: self.section, style: .blocks, updated: { value in if let _ = enabled { arguments.updatePermission(rights, value) } else { @@ -403,7 +403,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { case let .slowmodeHeader(_, value): return ItemListSectionHeaderItem(presentationData: presentationData, text: value, sectionId: self.section) case let .slowmode(theme, strings, value): - return ChatSlowmodeItem(theme: theme, strings: strings, value: value, enabled: true, sectionId: self.section, updated: { value in + return ChatSlowmodeItem(theme: theme, strings: strings, systemStyle: .glass, value: value, enabled: true, sectionId: self.section, updated: { value in arguments.updateSlowmode(value) }) case let .slowmodeInfo(_, value): @@ -411,7 +411,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { case let .conversionHeader(_, value): return ItemListSectionHeaderItem(presentationData: presentationData, text: value, sectionId: self.section) case let .conversion(_, text): - return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks) { + return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks) { arguments.presentConversionToBroadcastGroup() } case let .conversionInfo(_, value): @@ -419,7 +419,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { arguments.openChannelExample() } case let .chargeForMessages(_, title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateStarsAmount(value ? StarsAmount(value: 400, nanos: 0) : nil, true) }) case let .chargeForMessagesInfo(_, value): @@ -427,29 +427,29 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { case let .messagePriceHeader(_, value): return ItemListSectionHeaderItem(presentationData: presentationData, text: value, sectionId: self.section) case let .messagePrice(_, value, maxValue, price): - return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, apply in + return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, systemStyle: .glass, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, apply in arguments.updateStarsAmount(StarsAmount(value: value, nanos: 0), apply) }, openSetCustom: nil) case let .messagePriceInfo(_, value): return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section) case let .unrestrictBoostersSwitch(_, title, value): - return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateUnrestrictBoosters(value ? 1 : 0) }) case let .unrestrictBoosters(theme, strings, value): - return ChatUnrestrictBoostersItem(theme: theme, strings: strings, value: value, enabled: true, sectionId: self.section, updated: { value in + return ChatUnrestrictBoostersItem(theme: theme, strings: strings, systemStyle: .glass, value: value, enabled: true, sectionId: self.section, updated: { value in arguments.updateUnrestrictBoosters(value) }) case let .unrestrictBoostersInfo(_, value): return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section) case let .kicked(_, text, value): - return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openKicked() }) case let .exceptionsHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .add(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: false, action: { arguments.addPeer() }) case let .peerItem(_, strings, dateTimeFormat, nameDisplayOrder, _, participant, editing, enabled, canOpen, defaultBannedRights): @@ -477,7 +477,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { default: break } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: canOpen ? { + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: canOpen ? { arguments.openPeer(participant.participant) } : { arguments.openPeerInfo(EnginePeer(participant.peer)) diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 3f27534b60..e309e56162 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -623,11 +623,11 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case let .typeHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .typePublic(_, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateCurrentType(.publicChannel) }) case let .typePrivate(_, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateCurrentType(.privateChannel) }) case let .typeInfo(_, text): @@ -641,7 +641,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case let .privateLinkHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .privateLink(_, invite, peers, importersCount, displayImporters): - return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { + return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, invite: invite, count: importersCount, peers: peers, displayButton: true, displayImporters: displayImporters, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: { if let invite = invite { arguments.copyLink(invite) } @@ -658,7 +658,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { }, openCallAction: { }) case let .editablePublicLink(theme, _, placeholder, currentText): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: currentText, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), clearType: .always, tag: ChannelVisibilityEntryTag.publicLink, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: currentText, placeholder: placeholder, type: .regular(capitalization: false, autocorrection: false), clearType: .always, tag: ChannelVisibilityEntryTag.publicLink, sectionId: self.section, textUpdated: { updatedText in arguments.updatePublicLinkText(currentText, updatedText) }, updatedFocus: { focus in if focus { @@ -669,7 +669,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case let .privateLinkInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) case let .privateLinkManage(theme, text): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(theme), title: text, sectionId: self.section, editing: false, action: { + return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.linkIcon(theme), title: text, sectionId: self.section, editing: false, action: { arguments.manageInviteLinks() }) case let .privateLinkManageInfo(_, text): @@ -707,7 +707,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { if let addressName = peer.addressName { label = "t.me/" + addressName } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(label, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(label, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.revokePeerId(peerId) @@ -715,7 +715,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case let .additionalLinkHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .additionalLink(_, link, _): - return AdditionalLinkItem(presentationData: presentationData, username: link, sectionId: self.section, style: .blocks, tapAction: { + return AdditionalLinkItem(presentationData: presentationData, systemStyle: .glass, username: link, sectionId: self.section, style: .blocks, tapAction: { if !link.flags.contains(.isEditable) { if link.flags.contains(.isActive) { arguments.deactivateLink(link.username) @@ -729,16 +729,16 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case let .joinToSendHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .joinToSendEveryone(_, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateJoinToSend(.everyone) arguments.toggleApproveMembers(false) }) case let .joinToSendMembers(_, text, selected): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: text, style: .left, checked: selected, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateJoinToSend(.members) }) case let .approveMembers(_, text, selected): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: selected, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: selected, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleApproveMembers(value) }) case let .approveMembersInfo(_, text): @@ -746,7 +746,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case let .forwardingHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .forwardingDisabled(_, text, selected): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: selected, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: selected, sectionId: self.section, style: .blocks, updated: { value in arguments.toggleForwarding(!value) }) case let .forwardingInfo(_, text): diff --git a/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift b/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift index 739c992377..781c3eec41 100644 --- a/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift +++ b/submodules/PeerInfoUI/Sources/ChatSlowmodeItem.swift @@ -13,13 +13,15 @@ import PresentationDataUtils class ChatSlowmodeItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let value: Int32 let sectionId: ItemListSectionId let updated: (Int32) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, value: Int32, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle, value: Int32, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.value = value self.sectionId = sectionId self.updated = updated @@ -181,6 +183,7 @@ class ChatSlowmodeItemNode: ListViewItemNode { let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 var textLayoutAndApply: [(TextNodeLayout, () -> TextNode)] = [] @@ -248,12 +251,12 @@ class ChatSlowmodeItemNode: ListViewItemNode { bottomStripeOffset = 0.0 } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) for (_, apply) in textLayoutAndApply { let _ = apply() diff --git a/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift index a42aef2ccf..baa545c79c 100644 --- a/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift +++ b/submodules/PeerInfoUI/Sources/ChatUnrestrictBoostersItem.swift @@ -13,13 +13,15 @@ import PresentationDataUtils class ChatUnrestrictBoostersItem: ListViewItem, ItemListItem { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let value: Int32 let sectionId: ItemListSectionId let updated: (Int32) -> Void - init(theme: PresentationTheme, strings: PresentationStrings, value: Int32, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, value: Int32, enabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.value = value self.sectionId = sectionId self.updated = updated @@ -190,6 +192,7 @@ class ChatUnrestrictBoostersItemNode: ListViewItemNode { let contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 var textLayoutAndApply: [(TextNodeLayout, () -> TextNode)] = [] @@ -257,12 +260,12 @@ class ChatUnrestrictBoostersItemNode: ListViewItemNode { bottomStripeOffset = 0.0 } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) for (_, apply) in textLayoutAndApply { let _ = apply() diff --git a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift index 294577c2b2..e1e2d04c61 100644 --- a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift +++ b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift @@ -13,6 +13,7 @@ import AccountContext public class ItemListReactionItem: ListViewItem, ItemListItem { let context: AccountContext let presentationData: ItemListPresentationData + let systemStyle: ItemListSystemStyle let availableReactions: AvailableReactions? let reaction: MessageReaction.Reaction let title: String @@ -26,6 +27,7 @@ public class ItemListReactionItem: ListViewItem, ItemListItem { public init( context: AccountContext, presentationData: ItemListPresentationData, + systemStyle: ItemListSystemStyle = .glass, availableReactions: AvailableReactions?, reaction: MessageReaction.Reaction, title: String, @@ -38,6 +40,7 @@ public class ItemListReactionItem: ListViewItem, ItemListItem { ) { self.context = context self.presentationData = presentationData + self.systemStyle = systemStyle self.availableReactions = availableReactions self.reaction = reaction self.title = title @@ -200,6 +203,8 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { var contentSize: CGSize var insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 + let itemBackgroundColor: UIColor let itemSeparatorColor: UIColor @@ -228,7 +233,15 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 80.0 - sideImageInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - contentSize.height = max(contentSize.height, titleLayout.size.height + 22.0) + + let verticalInset: CGFloat + switch item.systemStyle { + case .glass: + verticalInset = 15.0 + case .legacy: + verticalInset = 11.0 + } + contentSize.height = max(contentSize.height, titleLayout.size.height + verticalInset * 2.0) if !item.enabled { if currentDisabledOverlayNode == nil { @@ -350,12 +363,12 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) } if strongSelf.imageNode == nil, let availableReactions = item.availableReactions { diff --git a/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift b/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift index cc328ee989..a07c119fb2 100644 --- a/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift +++ b/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift @@ -173,7 +173,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry { let arguments = arguments as! PeerAllowedReactionListControllerArguments switch self { case let .allowSwitch(text, value): - return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in + return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in if value { arguments.setMode(.some, false) } else { @@ -185,6 +185,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry { case let .allowAll(text, isEnabled): return ItemListCheckboxItem( presentationData: presentationData, + systemStyle: .glass, icon: nil, iconSize: nil, iconPlacement: .default, @@ -204,6 +205,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry { case let .allowSome(text, isEnabled): return ItemListCheckboxItem( presentationData: presentationData, + systemStyle: .glass, icon: nil, iconSize: nil, iconPlacement: .default, @@ -223,6 +225,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry { case let .allowNone(text, isEnabled): return ItemListCheckboxItem( presentationData: presentationData, + systemStyle: .glass, icon: nil, iconSize: nil, iconPlacement: .default, @@ -247,6 +250,7 @@ private enum PeerAllowedReactionListControllerEntry: ItemListNodeEntry { return ItemListReactionItem( context: arguments.context, presentationData: presentationData, + systemStyle: .glass, availableReactions: availableReactions, reaction: reaction, title: text, diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 7564d28dfb..c8f2b6b49e 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -10,6 +10,7 @@ import PresentationDataUtils import ViewControllerComponent import AccountContext import SolidRoundedButtonComponent +import ButtonComponent import MultilineTextComponent import MultilineTextWithEntitiesComponent import BundleIconComponent @@ -2098,7 +2099,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { context.add(optionsSection .position(CGPoint(x: availableWidth / 2.0, y: size.height + optionsSection.size.height / 2.0)) .clipsToBounds(true) - .cornerRadius(10.0) + .cornerRadius(26.0) ) size.height += optionsSection.size.height @@ -3836,8 +3837,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } } + let buttonSideInset: CGFloat = 36.0 + let controller = environment.controller - let sideInset: CGFloat = 16.0 let button = button.update( component: SolidRoundedButtonComponent( title: buttonTitle, @@ -3852,8 +3854,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { ], foregroundColor: .white ), - height: 50.0, - cornerRadius: 11.0, + height: 52.0, + cornerRadius: 26.0, gloss: true, isLoading: state.inProgress, action: { @@ -3865,7 +3867,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent { } } ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 50.0), + availableSize: CGSize(width: context.availableSize.width - buttonSideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 52.0), transition: context.transition) let bottomPanel = bottomPanel.update( diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 7c6e798d43..028532c260 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -1535,7 +1535,7 @@ private class FooterNode: ASDisplayNode { self.backgroundNode = NavigationBackgroundNode(color: theme.rootController.tabBar.backgroundColor) self.separatorNode = ASDisplayNode() - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: gloss) + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, isShimmering: gloss) self.buttonNode.title = title self.coverNode = ASDisplayNode() diff --git a/submodules/PremiumUI/Sources/PremiumOptionComponent.swift b/submodules/PremiumUI/Sources/PremiumOptionComponent.swift index 2bc42b0c5c..733ae40cae 100644 --- a/submodules/PremiumUI/Sources/PremiumOptionComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumOptionComponent.swift @@ -93,7 +93,7 @@ final class PremiumOptionComponent: CombinedComponent { return { context in let component = context.component - var insets = UIEdgeInsets(top: 11.0, left: 46.0, bottom: 13.0, right: 16.0) + var insets = UIEdgeInsets(top: 15.0, left: 46.0, bottom: 17.0, right: 16.0) let label = label.update( component: MultilineTextComponent( diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift index c5630feda9..94a293a081 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift @@ -269,7 +269,7 @@ public final class QrCodeScreen: ViewController { self.cancelButton = HighlightableButtonNode() self.cancelButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: accentColor, for: .normal) - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, isShimmering: false) self.textNode = ImmediateTextNode() self.textNode.maximumNumberOfLines = 3 diff --git a/submodules/SettingsUI/Sources/DeleteAccountFooterItem.swift b/submodules/SettingsUI/Sources/DeleteAccountFooterItem.swift index 3d2cd1966d..33a1be550a 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountFooterItem.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountFooterItem.swift @@ -68,7 +68,7 @@ final class DeleteAccountFooterItemNode: ItemListControllerFooterItemNode { self.clipNode = ASDisplayNode() self.clipNode.clipsToBounds = true - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true) + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, isShimmering: true) self.secondaryButtonNode = HighlightableButtonNode() diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 2b7957f22d..c99013d06d 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -118,11 +118,11 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { case .header: return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_SectionTitle, sectionId: self.section) case let .optionEverybody(value): - return ItemListCheckboxItem(presentationData: presentationData, title: presentationData.strings.Privacy_Messages_ValueEveryone, style: .left, checked: value == .everybody, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.Privacy_Messages_ValueEveryone, style: .left, checked: value == .everybody, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateValue(.everybody) }) case let .optionPremium(value, isEnabled): - return ItemListCheckboxItem(presentationData: presentationData, icon: isEnabled ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ValueContactsAndPremium, style: .left, checked: isEnabled && value == .requirePremium, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: isEnabled ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ValueContactsAndPremium, style: .left, checked: isEnabled && value == .requirePremium, zeroSeparatorInsets: false, sectionId: self.section, action: { if isEnabled { arguments.updateValue(.requirePremium) } else { @@ -134,7 +134,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { if case .paidMessages = value { isChecked = true } - return ItemListCheckboxItem(presentationData: presentationData, icon: isEnabled || isChecked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ChargeForMessages, style: .left, checked: isChecked, zeroSeparatorInsets: false, sectionId: self.section, action: { + return ItemListCheckboxItem(presentationData: presentationData, systemStyle: .glass, icon: isEnabled || isChecked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ChargeForMessages, style: .left, checked: isChecked, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateValue(.paidMessages(StarsAmount(value: 400, nanos: 0))) }) case let .footer(value): @@ -152,7 +152,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { case .priceHeader: return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_MessagePrice, sectionId: self.section) case let .price(value, maxValue, price, isEnabled): - return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: isEnabled, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, _ in + return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, systemStyle: .glass, isEnabled: isEnabled, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value, _ in arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0))) }, openSetCustom: { arguments.openSetCustomStarsAmount() @@ -164,7 +164,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry { case .exceptionsHeader: return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_RemoveFeeHeader, sectionId: self.section) case let .exceptions(count): - return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.Privacy_Messages_RemoveFee, label: count > 0 ? "\(count)" : "", sectionId: self.section, style: .blocks, action: { + return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.Privacy_Messages_RemoveFee, label: count > 0 ? "\(count)" : "", sectionId: self.section, style: .blocks, action: { arguments.openExceptions() }) case .exceptionsInfo: diff --git a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift index e22d5e2f17..ef326ba14f 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift @@ -273,7 +273,7 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, ASScrollViewDe self.cancelButton.accessibilityLabel = presentationData.strings.Common_Close self.cancelButton.accessibilityTraits = [.button] - self.terminateButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: self.presentationData.theme.list.itemDestructiveColor), font: .regular, height: 44.0, cornerRadius: 11.0, gloss: false) + self.terminateButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: self.presentationData.theme.list.itemDestructiveColor), font: .regular, height: 44.0, cornerRadius: 11.0) var hasSecretChats = false var hasIncomingCalls = false diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index d7fb506d42..9af8ac6442 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -198,7 +198,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry { case let .publicLinkHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .editablePublicLink(theme, _, prefix, currentText, text, enabled): - return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: enabled ? prefix : "", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, spacing: 10.0, clearType: enabled ? .always : .none, enabled: enabled, tag: UsernameEntryTag.username, sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: enabled ? prefix : "", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, spacing: 10.0, clearType: enabled ? .always : .none, enabled: enabled, tag: UsernameEntryTag.username, sectionId: self.section, textUpdated: { updatedText in arguments.updatePublicLinkText(currentText, updatedText) }, action: { }) @@ -233,7 +233,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry { case let .additionalLinkHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) case let .additionalLink(_, link, _, canToggleIsActive): - return AdditionalLinkItem(presentationData: presentationData, username: link, sectionId: self.section, style: .blocks, tapAction: { + return AdditionalLinkItem(presentationData: presentationData, systemStyle: .glass, username: link, sectionId: self.section, style: .blocks, tapAction: { if canToggleIsActive { if link.isActive { arguments.deactivateLink(link.username) diff --git a/submodules/SolidRoundedButtonNode/BUILD b/submodules/SolidRoundedButtonNode/BUILD index 4166c5fe10..4d1c6b9def 100644 --- a/submodules/SolidRoundedButtonNode/BUILD +++ b/submodules/SolidRoundedButtonNode/BUILD @@ -16,6 +16,7 @@ swift_library( "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/Components/HierarchyTrackingLayer:HierarchyTrackingLayer", "//submodules/ManagedAnimationNode:ManagedAnimationNode", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 3c10b7dca4..9ec46b40b4 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import HierarchyTrackingLayer import ShimmerEffect import ManagedAnimationNode +import GlassBackgroundComponent private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: CGFloat = 22.0, lineWidth: CGFloat = 2.0) -> UIImage? { return generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in @@ -164,8 +165,9 @@ private final class BadgeNode: ASDisplayNode { public final class SolidRoundedButtonNode: ASDisplayNode { private var theme: SolidRoundedButtonTheme + private var glass: Bool private var fontSize: CGFloat - private let gloss: Bool + private let isShimmering: Bool public let buttonBackgroundNode: ASImageNode private var buttonBackgroundAnimationView: UIImageView? @@ -174,7 +176,9 @@ public final class SolidRoundedButtonNode: ASDisplayNode { private var borderView: UIView? private var borderMaskView: UIView? private var borderShimmerView: ShimmerEffectForegroundView? - + + private var chromeView: UIImageView? + private let buttonNode: HighlightTrackingButtonNode public let titleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode @@ -267,7 +271,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { _ = self.updateLayout(width: width, transition: .immediate) } - if self.gloss { + if self.isShimmering { self.animationTimer?.invalidate() Queue.mainQueue().after(1.25) { @@ -338,14 +342,15 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } } - public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) { + public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, glass: Bool = false, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, isShimmering: Bool = false) { self.theme = theme + self.glass = glass self.font = font self.fontSize = fontSize self.buttonHeight = height self.buttonCornerRadius = cornerRadius self.title = title - self.gloss = gloss + self.isShimmering = isShimmering self.buttonBackgroundNode = ASImageNode() self.buttonBackgroundNode.displaysAsynchronously = false @@ -443,7 +448,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } private func setupGloss() { - if self.gloss { + if self.isShimmering { if self.shimmerView == nil { let shimmerView = ShimmerEffectForegroundView() self.shimmerView = shimmerView @@ -792,6 +797,26 @@ public final class SolidRoundedButtonNode: ASDisplayNode { self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } + let chromeView: UIImageView + var chromeTransition = transition + if let current = self.chromeView { + chromeView = current + } else { + chromeTransition = .immediate + chromeView = UIImageView() + self.chromeView = chromeView + if let shimmeringView = self.shimmerView { + self.view.insertSubview(chromeView, aboveSubview: shimmeringView) + } else { + self.view.insertSubview(chromeView, aboveSubview: self.buttonBackgroundNode.view) + } + + chromeView.layer.compositingFilter = "overlayBlendMode" + chromeView.alpha = 0.8 + chromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 26.0 * 2.0, height: 26.0 * 2.0), isDark: self.theme.backgroundColor.lightness < 0.4, fillColor: .clear) + } + chromeTransition.updateFrame(view: chromeView, frame: CGRect(origin: .zero, size: buttonSize)) + return buttonSize.height } diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index c7624af0e2..749f133e9b 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -366,7 +366,7 @@ final class PremiumStickerPackAccessoryNode: SparseNode, PeekControllerAccessory UIColor(rgb: 0x6b93ff), UIColor(rgb: 0x8878ff), UIColor(rgb: 0xe46ace) - ], foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true) + ], foregroundColor: .white), height: 50.0, cornerRadius: 11.0, isShimmering: true) self.proceedButton.iconPosition = .right self.proceedButton.iconSpacing = 4.0 self.proceedButton.animation = "premium_unlock" diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift index 76b7e7ab54..e6de2d4862 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift @@ -180,7 +180,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, A self.titleNode = ASTextNode() self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: UIColor(rgb: 0xffffff)) - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0xffffff), foregroundColor: UIColor(rgb: 0x4f5352)), font: .bold, height: 48.0, cornerRadius: 24.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0xffffff), foregroundColor: UIColor(rgb: 0x4f5352)), font: .bold, height: 48.0, cornerRadius: 24.0) self.doneButton.title = self.presentationData.strings.VoiceChat_VideoPreviewContinue if #available(iOS 12.0, *) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift index d66762b760..8d61502466 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift @@ -196,7 +196,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, self.doneButton = VoiceChatActionButton() - self.cancelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false) + self.cancelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0) self.cancelButton.title = self.presentationData.strings.Common_Cancel self.modeContainerNode = ASDisplayNode() diff --git a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift index f8f301bda2..04a7a16029 100644 --- a/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift +++ b/submodules/TelegramPermissionsUI/Sources/PermissionContentNode.swift @@ -107,7 +107,7 @@ public final class PermissionContentNode: ASDisplayNode { self.textNode.displaysAsynchronously = false self.textNode.isAccessibilityElement = true - self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 9.0, gloss: true) + self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 9.0, isShimmering: true) self.footerNode = ImmediateTextNode() self.footerNode.textAlignment = .center diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift index f76d478415..c01a8efea5 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileEmptyItem.swift @@ -84,7 +84,7 @@ final class AttachmentFileEmptyStateItemNode: ItemListControllerEmptyStateItemNo self.textNode.lineSpacing = 0.1 self.textNode.textAlignment = .center - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0, gloss: true) + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 52.0, cornerRadius: 26.0, isShimmering: true) super.init() diff --git a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift index 1871012fc6..5443b39d29 100644 --- a/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatQrCodeScreen/Sources/ChatQrCodeScreen.swift @@ -890,7 +890,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) self.animationNode.isUserInteractionEnabled = false - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0) switch controller.subject { case .peer: self.doneButton.title = self.presentationData.strings.InviteLink_QRCode_Share @@ -898,7 +898,7 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, ASScrollViewDeleg self.doneButton.title = self.presentationData.strings.Share_ShareMessage } - self.scanButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .clear, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor), font: .regular, height: 42.0, cornerRadius: 0.0, gloss: false) + self.scanButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .clear, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor), font: .regular, height: 42.0, cornerRadius: 0.0) self.scanButton.title = presentationData.strings.PeerInfo_QRCode_Scan self.scanButton.icon = UIImage(bundleImageName: "Settings/ScanQr") diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index ebc2697c6b..2999a3ab84 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -557,7 +557,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.menuButtonIconNode.customColor = presentationInterfaceState.theme.chat.inputPanel.actionControlForegroundColor self.menuButtonTextNode = ImmediateTextNode() - self.startButton = SolidRoundedButtonNode(title: presentationInterfaceState.strings.Bot_Start, theme: SolidRoundedButtonTheme(theme: presentationInterfaceState.theme), height: 50.0, cornerRadius: 11.0, gloss: true) + self.startButton = SolidRoundedButtonNode(title: presentationInterfaceState.strings.Bot_Start, theme: SolidRoundedButtonTheme(theme: presentationInterfaceState.theme), height: 50.0, cornerRadius: 11.0, isShimmering: true) self.startButton.progressType = .embedded self.startButton.isHidden = true diff --git a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift index 659def128e..08357a3959 100644 --- a/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift +++ b/submodules/TelegramUI/Components/ChatScheduleTimeController/Sources/ChatScheduleTimeControllerNode.swift @@ -175,9 +175,9 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.cancelButton.accessibilityLabel = self.presentationData.strings.Common_Cancel self.cancelButton.accessibilityTraits = [.button] - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, isShimmering: false) - self.onlineButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false) + self.onlineButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, isShimmering: false) switch mode { case let .suggestPost(needsTime, _, _): if needsTime { diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift index 6ab4a46adc..589c5c5582 100644 --- a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift @@ -925,7 +925,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0) self.animationNode.isUserInteractionEnabled = false - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 11.0) self.otherButton = HighlightableButtonNode() diff --git a/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift b/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift index 24b85cc119..f784eb74b2 100644 --- a/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift +++ b/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift @@ -361,7 +361,7 @@ class ChatTimerScreenNode: ViewControllerTracingNode, ASScrollViewDelegate, UIPi self.cancelButton = HighlightableButtonNode() self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) - self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) + self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, isShimmering: false) self.doneButton.title = self.presentationData.strings.Conversation_Timer_Send self.disableButton = HighlightableButtonNode() diff --git a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift index 41c4807997..1b02c24cba 100644 --- a/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift +++ b/submodules/TelegramUI/Components/Contacts/NewContactScreen/Sources/NewContactScreen.swift @@ -391,16 +391,19 @@ final class NewContactScreenComponent: Component { let cleanNumber = number.replacingOccurrences(of: "+", with: "") var scheduleResolve = false + var resolveDelay: Double = 2.5 if !mask.isEmpty && abs(cleanNumber.count - mask.count) < 3 { scheduleResolve = true + if abs(cleanNumber.count - mask.count) == 0 { + resolveDelay = 0.1 + } } else if mask.isEmpty && cleanNumber.count > 4 { scheduleResolve = true } - let _ = component if scheduleResolve { self.resolvedPeerDisposable.set( - ((Signal.complete() |> delay(2.5, queue: Queue.mainQueue())) + ((Signal.complete() |> delay(resolveDelay, queue: Queue.mainQueue())) |> then( component.context.engine.peers.resolvePeerByPhone(phone: number) |> beforeStarted({ [weak self] in @@ -414,16 +417,24 @@ final class NewContactScreenComponent: Component { }) ) |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self else { + guard let self, let component = self.component else { return } if let peer { - self.resolvedPeer = .peer(peer: peer, isContact: false) + self.resolvedPeerDisposable.set((component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.IsContact(id: peer.id)) |> deliverOnMainQueue).start(next: { [weak self] isContact in + guard let self else { + return + } + self.resolvedPeer = .peer(peer: peer, isContact: isContact) + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + })) } else { self.resolvedPeer = .notFound - } - if !self.isUpdating { - self.state?.updated(transition: .easeInOut(duration: 0.2)) + if !self.isUpdating { + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } } }) ) diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index d08746e994..ebbd50f265 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -661,15 +661,11 @@ final class GiftOptionsScreenComponent: Component { topPanelHeight += 39.0 } -// if let tabSelectorView = self.tabSelector.view { -// let tabSelectorSize = tabSelectorView.bounds.size -// transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableWidth - tabSelectorSize.width) / 2.0), y: max(56.0, self.tabSelectorOrigin - contentOffset)), size: tabSelectorSize)) -// } let edgeEffectHeight: CGFloat = 88.0 let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableWidth, height: edgeEffectHeight)) transition.setFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame) - self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition) + self.topEdgeEffectView.update(content: .clear, blur: true, alpha: 1.0, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: transition) if self.topEdgeEffectView.superview == nil { if let headerView = self.header.view { self.insertSubview(self.topEdgeEffectView, aboveSubview: headerView) @@ -682,41 +678,7 @@ final class GiftOptionsScreenComponent: Component { let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: self.bounds.height - edgeEffectHeight - environment.additionalInsets.bottom), size: CGSize(width: availableWidth, height: edgeEffectHeight)) transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) self.bottomEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: transition) - -// var panelTransition = transition -// if self.topPanel.view?.superview != nil && !self.switchingFilter { -// panelTransition = .spring(duration: 0.3) -// } -// let topPanelSize = self.topPanel.update( -// transition: panelTransition, -// component: AnyComponent(BlurredBackgroundComponent( -// color: environment.theme.rootController.navigationBar.blurredBackgroundColor -// )), -// environment: {}, -// containerSize: CGSize(width: availableWidth, height: topPanelHeight) -// ) -// -// let topSeparatorSize = self.topSeparator.update( -// transition: panelTransition, -// component: AnyComponent(Rectangle( -// color: environment.theme.rootController.navigationBar.separatorColor -// )), -// environment: {}, -// containerSize: CGSize(width: availableWidth, height: UIScreenPixel) -// ) -// let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableWidth, height: topPanelSize.height)) -// let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height)) -// if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view { -// if topPanelView.superview == nil { -// if let headerView = self.header.view { -// self.insertSubview(topSeparatorView, aboveSubview: headerView) -// self.insertSubview(topPanelView, aboveSubview: headerView) -// } -// } -// panelTransition.setFrame(view: topPanelView, frame: topPanelFrame) -// panelTransition.setFrame(view: topSeparatorView, frame: topSeparatorFrame) -// } - + let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height) if interactive, bottomContentOffset < 320.0, case .transfer = self.starsFilter { self.state?.starGiftsContext.loadMore() diff --git a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift index 72c25742c8..6db29e5792 100644 --- a/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/MessagePriceItem/Sources/MessagePriceItem.swift @@ -20,6 +20,7 @@ private let smallTextFont = Font.with(size: 13.0, traits: .monospacedNumbers) public final class MessagePriceItem: Equatable, ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { let theme: PresentationTheme let strings: PresentationStrings + let systemStyle: ItemListSystemStyle let isEnabled: Bool let minValue: Int64 let maxValue: Int64 @@ -30,9 +31,10 @@ public final class MessagePriceItem: Equatable, ListViewItem, ItemListItem, List let openSetCustom: (() -> Void)? let openPremiumInfo: (() -> Void)? - public init(theme: PresentationTheme, strings: PresentationStrings, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64, Bool) -> Void, openSetCustom: (() -> Void)? = nil, openPremiumInfo: (() -> Void)? = nil) { + public init(theme: PresentationTheme, strings: PresentationStrings, systemStyle: ItemListSystemStyle = .legacy, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64, Bool) -> Void, openSetCustom: (() -> Void)? = nil, openPremiumInfo: (() -> Void)? = nil) { self.theme = theme self.strings = strings + self.systemStyle = systemStyle self.isEnabled = isEnabled self.minValue = minValue self.maxValue = maxValue @@ -301,6 +303,7 @@ private class MessagePriceItemNode: ListViewItemNode { var contentSize: CGSize let insets: UIEdgeInsets let separatorHeight = UIScreenPixel + let separatorRightInset: CGFloat = item.systemStyle == .glass ? 16.0 : 0.0 contentSize = CGSize(width: params.width, height: 88.0) if !item.isEnabled { @@ -358,12 +361,12 @@ private class MessagePriceItemNode: ListViewItemNode { strongSelf.bottomStripeNode.isHidden = hasCorners } - strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil + strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight))) strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0) strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) - strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)) + strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight)) strongSelf.leftTextNode.attributedText = NSAttributedString(string: "\(item.minValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) diff --git a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift index ac6b3e26b2..743d9178cb 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift @@ -172,7 +172,7 @@ private enum OldChannelsEntry: ItemListNodeEntry { case let .peersHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .peer(_, peer, selected): - return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in + return ContactsPeerItem(presentationData: presentationData, style: .blocks, systemStyle: .glass, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in arguments.togglePeer(peer.peer.id, true) }, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil) } diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 84cc9ef8f1..f34f345f4d 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -163,7 +163,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { self.emptyAnimationNode.isHidden = true self.emptyAnimationSize = CGSize(width: 120.0, height: 120.0) - self.emptyButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), cornerRadius: 11.0, gloss: true) + self.emptyButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), cornerRadius: 11.0, isShimmering: true) self.emptyButtonNode.isHidden = true self.emptyButtonNode.pressed = { createNewGroup?() diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index adfe977e20..92e404b74f 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -359,10 +359,12 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { id: product.id, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, background: backgroundComponent, title: titleComponent, contentInsets: UIEdgeInsets(top: 12.0, left: -6.0, bottom: 12.0, right: 0.0), @@ -414,10 +416,12 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { id: items.count, component: AnyComponent(ListSectionComponent( theme: environment.theme, + style: .glass, header: nil, footer: nil, items: [AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, + style: .glass, title: titleCombinedComponent, titleAlignment: .center, contentInsets: UIEdgeInsets(top: 7.0, left: 0.0, bottom: 7.0, right: 0.0), diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataButtonComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataButtonComponent.swift index b6ad5a2756..ff169817fe 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataButtonComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataButtonComponent.swift @@ -51,7 +51,7 @@ final class DataButtonComponent: Component { super.init(frame: frame) self.clipsToBounds = true - self.layer.cornerRadius = 10.0 + self.layer.cornerRadius = 26.0 self.highligthedChanged = { [weak self] isHighlighted in guard let self, let component = self.component, let highlightBackgroundFrame = self.highlightBackgroundFrame else { @@ -111,7 +111,7 @@ final class DataButtonComponent: Component { containerSize: CGSize(width: availableSize.width, height: 100.0) ) - let height: CGFloat = 44.0 + let height: CGFloat = 52.0 let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((height - titleSize.height) / 2.0)), size: titleSize) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift index cacfe2e0bf..d4845397b7 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift @@ -82,7 +82,7 @@ final class DataCategoriesComponent: Component { super.init(frame: frame) self.clipsToBounds = true - self.layer.cornerRadius = 10.0 + self.layer.cornerRadius = 26.0 self.addSubview(self.containerView) } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift index fe49ed2ee1..6e253dd9a9 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift @@ -175,7 +175,7 @@ private final class SubItemComponent: Component { containerSize: CGSize(width: availableWidth, height: 100.0) ) - let height: CGFloat = 44.0 + let height: CGFloat = 52.0 let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) let titleValueFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floor((height - titleValueSize.height) / 2.0)), size: titleValueSize) @@ -469,7 +469,7 @@ final class DataCategoryItemComponent: Component { containerSize: CGSize(width: availableWidth, height: 100.0) ) - var height: CGFloat = 44.0 + var height: CGFloat = 52.0 let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) let titleValueFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floor((height - titleValueSize.height) / 2.0)), size: titleValueSize) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index e81a49e8d7..4ceb81cb3c 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -370,7 +370,7 @@ final class DataUsageScreenComponent: Component { self.autoDownloadSettingsContainerView = UIView() self.autoDownloadSettingsContainerView.clipsToBounds = true - self.autoDownloadSettingsContainerView.layer.cornerRadius = 10.0 + self.autoDownloadSettingsContainerView.layer.cornerRadius = 26.0 super.init(frame: frame) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift index 2643e14d8d..89b8638f50 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift @@ -94,7 +94,7 @@ final class StorageCategoriesComponent: Component { super.init(frame: frame) self.clipsToBounds = true - self.layer.cornerRadius = 10.0 + self.layer.cornerRadius = 26.0 } required init?(coder: NSCoder) { @@ -227,8 +227,8 @@ final class StorageCategoriesComponent: Component { ), font: .bold, fontSize: 17.0, - height: 50.0, - cornerRadius: 10.0, + height: 52.0, + cornerRadius: 26.0, gloss: false, isEnabled: totalSelectedSize != 0, animationName: nil, diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift index d22a9711d8..cf407b2f85 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift @@ -242,7 +242,7 @@ final class StorageCategoryItemComponent: Component { containerSize: CGSize(width: availableWidth, height: 100.0) ) - var height: CGFloat = 44.0 + var height: CGFloat = 52.0 let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) let titleValueFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floor((height - titleValueSize.height) / 2.0)), size: titleValueSize) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift index f59150b4d1..115020cc8a 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift @@ -89,7 +89,7 @@ final class StorageKeepSizeComponent: Component { super.init(frame: frame) self.clipsToBounds = true - self.layer.cornerRadius = 10.0 + self.layer.cornerRadius = 26.0 } required init?(coder: NSCoder) { @@ -106,7 +106,7 @@ final class StorageKeepSizeComponent: Component { self.backgroundColor = component.theme.list.itemBlocksBackgroundColor } - let height: CGFloat = 88.0 + let height: CGFloat = 96.0 var titleSizes: [CGSize] = [] for i in 0 ..< self.titles.count { @@ -133,7 +133,7 @@ final class StorageKeepSizeComponent: Component { } else if i > 0 { position -= titleSize.width / 2.0 } - transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: position, y: 15.0), size: titleSize)) + transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: position, y: 19.0), size: titleSize)) } } @@ -167,7 +167,7 @@ final class StorageKeepSizeComponent: Component { sliderView.knobImage = PresentationResourcesItemList.knobImage(component.theme) } - transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 15.0, y: 37.0), size: CGSize(width: availableSize.width - 15.0 * 2.0, height: 44.0))) + transition.setFrame(view: sliderView, frame: CGRect(origin: CGPoint(x: 15.0, y: 41.0), size: CGSize(width: availableSize.width - 15.0 * 2.0, height: 44.0))) sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX) self.updateSliderView() diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift index 07c7c18676..7b71243887 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift @@ -202,9 +202,9 @@ final class StoragePeerTypeItemComponent: Component { } } - var height: CGFloat = 44.0 + var height: CGFloat = 52.0 if subtitleSize != nil { - height = 60.0 + height = 64.0 } let titleFrame: CGRect diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 0d53a0d87c..b6629a6d46 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -816,7 +816,7 @@ final class StorageUsageScreenComponent: Component { self.keepDurationSectionContainerView = UIView() self.keepDurationSectionContainerView.clipsToBounds = true - self.keepDurationSectionContainerView.layer.cornerRadius = 10.0 + self.keepDurationSectionContainerView.layer.cornerRadius = 26.0 self.headerProgressBackgroundLayer = SimpleLayer() self.headerProgressForegroundLayer = SimpleLayer() diff --git a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD index 0fc67aed4c..4777b4f3da 100644 --- a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/BUILD @@ -23,6 +23,7 @@ swift_library( "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/ToastComponent", "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", "//submodules/Markdown", "//submodules/TelegramStringFormatting", ], diff --git a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift index cde05abfc0..ff1195be56 100644 --- a/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen/Sources/StoryStealthModeSheetScreen.swift @@ -9,8 +9,10 @@ import ButtonComponent import ToastComponent import LottieComponent import MultilineTextComponent +import BundleIconComponent import Markdown import TelegramStringFormatting +import GlassBarButtonComponent private final class StoryStealthModeSheetContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -176,40 +178,7 @@ private final class StoryStealthModeSheetContentComponent: Component { } } } - - if case .upgrade = component.mode { - let cancelButton: ComponentView - if let current = self.cancelButton { - cancelButton = current - } else { - cancelButton = ComponentView() - self.cancelButton = cancelButton - } - let cancelButtonSize = cancelButton.update( - transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), - action: { [weak self] in - guard let self, let component = self.component else { - return - } - component.dismiss() - } - ).minSize(CGSize(width: 8.0, height: 44.0))), - environment: {}, - containerSize: CGSize(width: 200.0, height: 100.0) - ) - if let cancelButtonView = cancelButton.view { - if cancelButtonView.superview == nil { - self.addSubview(cancelButtonView) - } - transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 6.0), size: cancelButtonSize)) - } - } else if let cancelButton = self.cancelButton { - self.cancelButton = nil - cancelButton.view?.removeFromSuperview() - } - + var contentHeight: CGFloat = 0.0 contentHeight += 32.0 @@ -240,6 +209,45 @@ private final class StoryStealthModeSheetContentComponent: Component { contentHeight += contentSize.height contentHeight += 41.0 + if case .upgrade = component.mode { + let cancelButton: ComponentView + if let current = self.cancelButton { + cancelButton = current + } else { + cancelButton = ComponentView() + self.cancelButton = cancelButton + } + let cancelButtonSize = cancelButton.update( + transition: transition, + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 40.0, height: 40.0), + backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor, + isDark: environment.theme.overallDarkAppearance, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.rootController.navigationBar.glassBarButtonForegroundColor + ) + )), + action: { _ in + component.dismiss() + } + )), + environment: {}, + containerSize: CGSize(width: 40.0, height: 40.0) + ) + if let cancelButtonView = cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelButtonSize)) + } + } else if let cancelButton = self.cancelButton { + self.cancelButton = nil + cancelButton.view?.removeFromSuperview() + } + let buttonText: String let content: AnyComponentWithIdentity switch component.mode { @@ -266,10 +274,12 @@ private final class StoryStealthModeSheetContentComponent: Component { )) } + let buttonSideInset: CGFloat = 30.0 let buttonSize = self.button.update( transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) @@ -302,9 +312,9 @@ private final class StoryStealthModeSheetContentComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0) ) - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: contentHeight), size: buttonSize) if let buttonView = self.button.view { if buttonView.superview == nil { self.addSubview(buttonView) @@ -316,7 +326,7 @@ private final class StoryStealthModeSheetContentComponent: Component { if environment.safeInsets.bottom.isZero { contentHeight += 16.0 } else { - contentHeight += environment.safeInsets.bottom + 14.0 + contentHeight += environment.safeInsets.bottom + 10.0 } return CGSize(width: availableSize.width, height: contentHeight) @@ -438,6 +448,7 @@ private final class StoryStealthModeSheetScreenComponent: Component { }) } )), + style: .glass, backgroundColor: .color(environment.theme.overallDarkAppearance ? environment.theme.list.itemBlocksBackgroundColor : environment.theme.list.blocksBackgroundColor), animateOut: self.sheetAnimateOut )),