diff --git a/Telegram/BUILD b/Telegram/BUILD
index 10de06ab84..59cc632763 100644
--- a/Telegram/BUILD
+++ b/Telegram/BUILD
@@ -1587,6 +1587,8 @@ plist_fragment(
here-location
yandexmaps
yandexnavi
+ yandextaxi
+ yangoride
comgooglemaps
youtube
twitter
@@ -1615,6 +1617,7 @@ plist_fragment(
dolphin
instagram-stories
yangomaps
+ vivaldi
LSRequiresIPhoneOS
diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings
index 117c2807bb..a626678a02 100644
--- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings
+++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings
@@ -2661,6 +2661,8 @@ Unused sets are archived when you add more.";
"Channel.EditAdmin.PermissionInviteViaLink" = "Invite Users via Link";
"Channel.EditAdmin.PermissionPinMessages" = "Pin Messages";
"Channel.EditAdmin.PermissionAddAdmins" = "Add New Admins";
+"Channel.EditAdmin.PermissionProcessJoinRequests" = "Process Join Requests";
+"Channel.EditAdmin.PermissionProcessJoinRequestsInfo" = "Allow this bot to approve new members using its own interfaces, such as captchas.";
"Channel.EditAdmin.PermissinAddAdminOn" = "This Admin will be able to add new admins with the same (or more limited) permissions.";
"Channel.EditAdmin.PermissinAddAdminOff" = "This Admin will not be able to add new admins.";
@@ -2874,6 +2876,13 @@ Unused sets are archived when you add more.";
"Call.ReportSend" = "Send";
"Channel.EditAdmin.CannotEdit" = "You cannot edit the rights of this admin.";
+"Channel.EditAdmin.GuardBotEnableMembersText" = "New members will now need approval from **%@** before they can send messages in this group";
+"Channel.EditAdmin.GuardBotEnableSubscribersText" = "New subscribers will now need approval from **%@** before they can join this channel";
+"Channel.EditAdmin.GuardBotEnable" = "Enable";
+"Channel.EditAdmin.GuardBotReplaceTitle" = "Replace Bot";
+"Channel.EditAdmin.GuardBotReplaceText" = "**%@** is currently processing join requests. Switch to **%@** instead?";
+"Channel.EditAdmin.GuardBotReplaceKeep" = "Keep %@";
+"Channel.EditAdmin.GuardBotReplaceUse" = "Use %@";
"Call.RateCall" = "Rate This Call";
"Call.ShareStats" = "Share Statistics";
@@ -7020,6 +7029,9 @@ Sorry for the inconvenience.";
"InviteLink.Create.RequestApprovalOffInfoChannel" = "New users will be able to join the channel without being approved by the admins.";
"InviteLink.Create.RequestApprovalOnInfoGroup" = "New users will be able to join the group only after having been approved by the admins.";
"InviteLink.Create.RequestApprovalOnInfoChannel" = "New users will be able to join the channel only after having been approved by the admins.";
+"InviteLink.Create.RequestApprovalInfo" = "Request admin approval for people joining through this link.";
+"InviteLink.Create.RequestApprovalPublicGroupUnavailable" = "This option is unavailable because anyone can join and send messages through the public group link.";
+"InviteLink.ApprovalRequired" = "approval required";
"InviteLink.Create.LinkNameTitle" = "Link Name";
"InviteLink.Create.LinkName" = "Link Name (Optional)";
@@ -7606,6 +7618,15 @@ Sorry for the inconvenience.";
"Group.Setup.ApproveNewMembers" = "Approve New Members";
"Group.Setup.ApproveNewMembersInfo" = "Turn this on if you want users to be able to send messages only after they are approved by an admin.";
+"Group.Setup.ApproveNewMembersManagedBy" = "Managed by %@";
+"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksTitle" = "Apply to existing invite links?";
+"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksText" = "Also %@ **%@** for **%@ existing invite links** in this %@?";
+"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksEnable" = "enable";
+"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksDisable" = "disable";
+"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksPeerGroup" = "group";
+"Group.Setup.ApproveNewMembersApplyToExistingInviteLinksPeerChannel" = "channel";
+"Channel.Setup.ApproveNewSubscribers" = "Approve New Subscribers";
+"Channel.Setup.ApproveNewSubscribersInfo" = "Turn this on if you want users to be able to join only after they are approved by an admin.";
"Gallery.GifSaved" = "GIF Saved";
@@ -16306,3 +16327,18 @@ Error: %8$@";
"VideoChat.StatusPeerJoined" = "%@ joined";
"VideoChat.StatusPeerLeft" = "%@ left";
+
+"ChatList.BotConnectionReview.PanelTitle" = "A bot just got access to your messages!";
+"ChatList.BotConnectionReview.PanelText" = "%1$@ connected to your account from %2$@ (%3$@). Was this you?";
+
+"RecentSession.ConnectedBot.Subtitle" = "Chat Automation Bot";
+"RecentSession.ConnectedBot.ConnectedFrom" = "CONNECTED FROM";
+"RecentSession.ConnectedBot.Date" = "Date";
+"RecentSession.ConnectedBot.Device" = "Device";
+"RecentSessions.ConnectedBot.ConnectedTime" = "connected %@";
+"RecentSessions.ConnectedBot.TerminateCheckbox" = "Also terminate %@";
+
+"Chat.GuestChatMessageTooltip" = "This bot can't read the chat – only the messages where it was mentioned.";
+
+"MediaPicker.SetNewGroupPhoto" = "Set new group photo";
+"MediaPicker.SetNewChannelPhoto" = "Set new channel photo";
diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift
index ab53edec28..bd5b3a8537 100644
--- a/submodules/AccountContext/Sources/AccountContext.swift
+++ b/submodules/AccountContext/Sources/AccountContext.swift
@@ -185,6 +185,22 @@ public enum WallpaperUrlParameter {
case gradient([UInt32], Int32?)
}
+public enum PeerType: Equatable {
+ case user(isBot: Bool)
+ case group
+ case channel
+
+ public static func getType(for peer: EnginePeer) -> PeerType {
+ if case let .user(user) = peer {
+ return .user(isBot: user.botInfo != nil)
+ } else if case let .channel(channel) = peer, case .broadcast = channel.info {
+ return .channel
+ } else {
+ return .group
+ }
+ }
+}
+
public struct ResolvedBotChoosePeerTypes: OptionSet {
public var rawValue: UInt32
@@ -391,6 +407,16 @@ public enum ResolveUrlResult {
case result(ResolvedUrl)
}
+public struct OpenUserGeneratedUrlConcealedAlertOption {
+ public let title: String
+ public let action: () -> Void
+
+ public init(title: String, action: @escaping () -> Void) {
+ self.title = title
+ self.action = action
+ }
+}
+
public enum NavigateToChatKeepStack {
case `default`
case always
@@ -735,7 +761,6 @@ public enum PeerInfoControllerMode {
case generic
case calls(messages: [EngineMessage])
- case nearbyPeer(distance: Int32)
case group(sourceMessageId: EngineMessage.Id)
case reaction(EngineMessage.Id)
case forumTopic(thread: ChatReplyThreadMessage)
@@ -1370,7 +1395,6 @@ public protocol SharedAccountContext: AnyObject {
func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: EnginePeer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController?
func makeChannelAdminController(context: AccountContext, peerId: EnginePeer.Id, adminId: EnginePeer.Id, initialParticipant: ChannelParticipant) -> ViewController?
func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
- func makePeersNearbyController(context: AccountContext) -> ViewController
func makeComposeController(context: AccountContext) -> ViewController
func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode, params: ChatControllerParams?) -> ChatController
@@ -1441,6 +1465,7 @@ public protocol SharedAccountContext: AnyObject {
func resolveUrl(context: AccountContext, peerId: EnginePeer.Id?, url: String, skipUrlAuth: Bool) -> Signal
func resolveUrlWithProgress(context: AccountContext, peerId: EnginePeer.Id?, url: String, skipUrlAuth: Bool) -> Signal
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, forceExternal: Bool, forceUpdate: Bool, openPeer: @escaping (EnginePeer, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((EnginePeer.Id, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?, progress: Promise?, completion: (() -> Void)?)
+ func openUserGeneratedUrl(context: AccountContext, peerId: EnginePeer.Id?, url: String, webpage: TelegramMediaWebpage?, concealed: Bool, forceConcealed: Bool, skipUrlAuth: Bool, skipConcealedAlert: Bool, forceDark: Bool, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise?, alertDisplayUpdated: ((ViewController?) -> Void)?, concealedAlertOption: OpenUserGeneratedUrlConcealedAlertOption?) -> Disposable
func openAddContact(context: AccountContext, peer: EnginePeer?, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
func openAddPersonContact(context: AccountContext, peerId: EnginePeer.Id, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
@@ -1468,14 +1493,14 @@ public protocol SharedAccountContext: AnyObject {
func makeGiftStoreController(context: AccountContext, peerId: EnginePeer.Id, gift: StarGift.Gift) -> ViewController
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
- func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: ((Bool) -> Void)?) -> ViewController
+ func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: (([StickerPackScreenActionResult]) -> Void)?) -> ViewController
func makeCameraScreen(context: AccountContext, mode: CameraScreenMode, cameraHolder: Any?, transitionIn: CameraScreenTransitionIn?, transitionOut: @escaping (Bool) -> CameraScreenTransitionOut?, completion: @escaping (Any, @escaping () -> Void) -> Void, transitionedOut: (() -> Void)?) -> ViewController
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: (url: String, name: String?)?, remainingCount: Int32, completion: @escaping ([MediaEditorScreenResult], MediaEditorTransitionOutExternalState, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController
func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerEditorScreen(context: AccountContext, source: Any?, mode: StickerEditorMode, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, [String], @escaping () -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
- func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?)
+ func makeAvatarMediaPickerScreen(context: AccountContext, peerType: PeerType, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> (ViewController?, Any?)
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
func makeStickerPickerScreen(context: AccountContext, inputData: Promise, completion: @escaping (FileMediaReference) -> Void) -> ViewController
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController
@@ -1521,6 +1546,7 @@ public protocol SharedAccountContext: AnyObject {
func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController
func makeIncomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?, verifyAgeCompletion: ((Int) -> Void)?)
+ func openJoinChatWebView(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal)?, webView: JoinChatWebView)
func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, revenuePerUser: Double, mode: JoinAffiliateProgramScreenMode) -> ViewController
@@ -1530,7 +1556,7 @@ public protocol SharedAccountContext: AnyObject {
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
func makeSendInviteLinkScreen(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme?) -> ViewController
func makeCocoonInfoScreen(context: AccountContext) -> ViewController
- func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?) -> Void) -> ViewController
+ func makeLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, text: String, link: String?, apply: @escaping (String?, TelegramMediaWebpage?) -> Void) -> ViewController
@available(iOS 13.0, *)
func makePostSuggestionsSettingsScreen(context: AccountContext, peerId: EnginePeer.Id) async -> ViewController
@@ -1637,6 +1663,23 @@ public protocol ChatSendMessageActionSheetController: ViewController {
typealias SendParameters = ChatSendMessageActionSheetControllerSendParameters
}
+public enum StickerPackScreenActionKind {
+ case add
+ case remove(positionInList: Int)
+}
+
+public struct StickerPackScreenActionResult {
+ public let info: StickerPackCollectionInfo
+ public let items: [StickerPackItem]
+ public let action: StickerPackScreenActionKind
+
+ public init(info: StickerPackCollectionInfo, items: [StickerPackItem], action: StickerPackScreenActionKind) {
+ self.info = info
+ self.items = items
+ self.action = action
+ }
+}
+
public protocol AccountContext: AnyObject {
var sharedContext: SharedAccountContext { get }
var account: Account { get }
diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift
index 39418cacac..09231d3c95 100644
--- a/submodules/AccountContext/Sources/ChatController.swift
+++ b/submodules/AccountContext/Sources/ChatController.swift
@@ -264,6 +264,23 @@ public extension ChatMessageItemAssociatedData {
return false
}
}
+
+ func isPollVotingRestricted(poll: TelegramMediaPoll, accountTestingEnvironment: Bool, currentTimestamp: Int32) -> Bool {
+ if !poll.countries.isEmpty, let accountCountry = self.accountCountry, !poll.countries.contains(accountCountry) {
+ return true
+ }
+
+ if poll.restrictToSubscribers {
+ let period: Int32 = accountTestingEnvironment ? 5 * 60 : 24 * 60 * 60
+ if !self.isParticipant {
+ return true
+ } else if let invitedOn = self.invitedOn, invitedOn + period > currentTimestamp {
+ return true
+ }
+ }
+
+ return false
+ }
}
public enum ChatControllerInteractionLongTapAction {
diff --git a/submodules/AdUI/Sources/AdInfoScreen.swift b/submodules/AdUI/Sources/AdInfoScreen.swift
index 7cecf8224b..df5a2684fd 100644
--- a/submodules/AdUI/Sources/AdInfoScreen.swift
+++ b/submodules/AdUI/Sources/AdInfoScreen.swift
@@ -78,7 +78,7 @@ public final class AdInfoScreen: ViewController {
self.scrollNode = ASScrollNode()
self.scrollNode.view.showsVerticalScrollIndicator = true
self.scrollNode.view.showsHorizontalScrollIndicator = false
- self.scrollNode.view.scrollsToTop = true
+ self.scrollNode.view.scrollsToTop = false
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.canCancelContentTouches = true
if #available(iOS 11.0, *) {
diff --git a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm
index ba05b9d895..e0d85c8863 100644
--- a/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm
+++ b/submodules/AsyncDisplayKit/Source/ASEditableTextNode.mm
@@ -341,6 +341,7 @@
textView.opaque = NO;
}
textView.textContainerInset = self.textContainerInset;
+ textView.scrollsToTop = false;
// Configure textView with UITextInputTraits
{
@@ -366,6 +367,7 @@
_placeholderTextKitComponents.textView = [[ASTextKitComponentsTextView alloc] initWithFrame:CGRectZero textContainer:_placeholderTextKitComponents.textContainer];
_placeholderTextKitComponents.textView.userInteractionEnabled = NO;
_placeholderTextKitComponents.textView.accessibilityElementsHidden = YES;
+ _placeholderTextKitComponents.textView.scrollsToTop = false;
configureTextView(_placeholderTextKitComponents.textView);
// Create and configure our text view.
diff --git a/submodules/AttachmentUI/BUILD b/submodules/AttachmentUI/BUILD
index 2181367a5f..239607ee2e 100644
--- a/submodules/AttachmentUI/BUILD
+++ b/submodules/AttachmentUI/BUILD
@@ -26,7 +26,6 @@ swift_library(
"//submodules/DirectionalPanGesture:DirectionalPanGesture",
"//submodules/AttachmentTextInputPanelNode:AttachmentTextInputPanelNode",
"//submodules/ChatSendMessageActionUI:ChatSendMessageActionUI",
- "//submodules/ChatTextLinkEditUI:ChatTextLinkEditUI",
"//submodules/ContextUI:ContextUI",
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
"//submodules/PhotoResources:PhotoResources",
diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift
index ac85742469..6946cf7fa1 100644
--- a/submodules/AttachmentUI/Sources/AttachmentController.swift
+++ b/submodules/AttachmentUI/Sources/AttachmentController.swift
@@ -33,6 +33,7 @@ public enum AttachmentButtonType: Equatable {
case sticker
case emoji
case audio
+ case link
case standalone
public var key: String {
@@ -61,6 +62,8 @@ public enum AttachmentButtonType: Equatable {
return "emoji"
case .audio:
return "audio"
+ case .link:
+ return "link"
case .standalone:
return "standalone"
}
@@ -140,6 +143,12 @@ public enum AttachmentButtonType: Equatable {
} else {
return false
}
+ case .link:
+ if case .link = rhs {
+ return true
+ } else {
+ return false
+ }
case .standalone:
if case .standalone = rhs {
return true
diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift
index 7c4e1b3f04..f60228bde9 100644
--- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift
+++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift
@@ -12,7 +12,6 @@ import AccountContext
import AttachmentTextInputPanelNode
import ChatPresentationInterfaceState
import ChatSendMessageActionUI
-import ChatTextLinkEditUI
import PhotoResources
import AnimatedStickerComponent
import SemanticStatusNode
@@ -244,6 +243,10 @@ private final class AttachButtonComponent: CombinedComponent {
case .audio:
name = strings.Attachment_Audio
imageName = "Chat/Attach Menu/Audio"
+ case .link:
+ //TODO:localize
+ name = "Link"
+ imageName = "Chat/Attach Menu/Link"
case let .app(bot):
botPeer = bot.peer
name = bot.shortName
@@ -1248,7 +1251,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
- let controller = chatTextLinkEditController(context: strongSelf.context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link in
+ let controller = strongSelf.context.sharedContext.makeLinkEditController(context: strongSelf.context, updatedPresentationData: (presentationData, .never()), text: text?.string ?? "", link: link, apply: { [weak self] link, _ in
if let strongSelf = self, let inputMode = inputMode, let selectionRange = selectionRange {
if let link = link {
strongSelf.updateChatPresentationInterfaceState(animated: true, { state in
@@ -1420,7 +1423,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
})
})
}, openScheduledMessages: {
- }, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, unarchivePeer: {
}, scrollToTop: {
@@ -1561,12 +1563,22 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
if let data = view.cachedData as? CachedUserData {
return data.sendPaidMessageStars
} else if let channel = peerViewMainPeer(view) as? TelegramChannel {
- if channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
- return nil
- } else if let cachedData = view.cachedData as? CachedChannelData, let sendPaidMessageStarsValue = cachedData.sendPaidMessageStars, sendPaidMessageStarsValue == .zero {
- return nil
+ if channel.isMonoForum {
+ if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = view.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
+ return nil
+ } else if let cachedData = view.cachedData as? CachedChannelData, let value = cachedData.sendPaidMessageStars, value == .zero {
+ return nil
+ } else {
+ return channel.sendPaidMessageStars
+ }
} else {
- return channel.sendPaidMessageStars
+ if channel.flags.contains(.isCreator) || channel.adminRights != nil {
+ return nil
+ } else if let cachedData = view.cachedData as? CachedChannelData, let value = cachedData.sendPaidMessageStars {
+ return value == .zero ? nil : value
+ } else {
+ return channel.sendPaidMessageStars
+ }
}
} else {
return nil
@@ -1598,6 +1610,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
self.containerNode.layer.cornerCurve = .continuous
}
+ self.scrollNode.view.scrollsToTop = false
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.showsVerticalScrollIndicator = false
@@ -1990,6 +2003,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
var width = layout.size.width
if buttons.count == 3 {
width = smallPanelWidth
+ } else if buttons.count == 4 {
+ width = 300
}
var panelSideInset: CGFloat
@@ -2001,7 +2016,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
}
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, buttons.count - 1)))
- if buttons.count == 3 {
+ if buttons.count == 3 || buttons.count == 4 {
distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - 32.0) / CGFloat(max(1, buttons.count - 1)))
}
let internalWidth = distanceBetweenNodes * CGFloat(max(0, buttons.count - 1))
@@ -2017,7 +2032,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
case .legacy:
leftNodeOriginX = (width - internalWidth) / 2.0
}
- if buttons.count == 3 {
+ if buttons.count == 3 || buttons.count == 4 {
leftNodeOriginX = floor((layout.size.width - width) / 2.0) + 16.0
}
@@ -2171,6 +2186,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
accessibilityTitle = "Emoji"
case .audio:
accessibilityTitle = self.presentationData.strings.Attachment_Audio
+ case .link:
+ //TODO:localize
+ accessibilityTitle = "Link"
case let .app(bot):
accessibilityTitle = bot.shortName
case .standalone:
@@ -2544,6 +2562,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
let buttonsPanelWidth: CGFloat
if buttons.count == 3 {
buttonsPanelWidth = smallPanelWidth
+ } else if buttons.count == 4 {
+ buttonsPanelWidth = 300
} else {
buttonsPanelWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0
}
diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD
index 00851a3e15..4757af3b84 100644
--- a/submodules/AuthorizationUI/BUILD
+++ b/submodules/AuthorizationUI/BUILD
@@ -47,6 +47,7 @@ swift_library(
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/Utils/DeviceModel",
"//submodules/PresentationDataUtils",
+ "//submodules/TelegramUI/Components/GlassBarButtonComponent",
],
visibility = [
"//visibility:public",
diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift
index ddb76da69a..eee43282af 100644
--- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift
+++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift
@@ -386,7 +386,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
- let buttonHeight: CGFloat = 50.0
+ let buttonHeight: CGFloat = 52.0
let bottomPanelPadding: CGFloat = 12.0
let titleSpacing: CGFloat = -24.0
let listSpacing: CGFloat = 12.0
@@ -416,7 +416,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
listView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - listSize.width) / 2.0), y: originY), size: listSize)
}
- let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
+ let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 10.0 : bottomPanelPadding
let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset
let priceString: String
@@ -463,7 +463,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
}
)),
environment: {},
- containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight)
+ containerSize: CGSize(width: availableSize.width - 30.0 * 2.0, height: buttonHeight)
)
if let buttonView = self.button.view {
if buttonView.superview == nil {
diff --git a/submodules/BotPaymentsUI/BUILD b/submodules/BotPaymentsUI/BUILD
index 45cfed992b..0b35e43a4f 100644
--- a/submodules/BotPaymentsUI/BUILD
+++ b/submodules/BotPaymentsUI/BUILD
@@ -11,10 +11,21 @@ swift_library(
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
+ "//submodules/ComponentFlow:ComponentFlow",
"//submodules/TelegramCore:TelegramCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/LocalAuth:LocalAuth",
"//submodules/AccountContext:AccountContext",
+ "//submodules/Components/ViewControllerComponent:ViewControllerComponent",
+ "//submodules/Components/ResizableSheetComponent",
+ "//submodules/Components/MultilineTextComponent",
+ "//submodules/Components/BundleIconComponent",
+ "//submodules/TelegramUI/Components/ButtonComponent",
+ "//submodules/TelegramUI/Components/ListSectionComponent",
+ "//submodules/TelegramUI/Components/ListActionItemComponent",
+ "//submodules/TelegramUI/Components/GlassBarButtonComponent",
+ "//submodules/TelegramUI/Components/AlertComponent",
+ "//submodules/TelegramUI/Components/AlertComponent/AlertInputFieldComponent",
"//submodules/ItemListUI:ItemListUI",
"//submodules/PasswordSetupUI:PasswordSetupUI",
"//submodules/PhotoResources:PhotoResources",
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift
index e970e67688..c63254c00e 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutActionButton.swift
@@ -117,6 +117,7 @@ final class BotCheckoutActionButton: HighlightTrackingButtonNode {
} else {
applePayButton = PKPaymentButton(paymentButtonType: .buy, paymentButtonStyle: .black)
}
+ applePayButton.cornerRadius = BotCheckoutActionButton.height * 0.5
applePayButton.addTarget(self, action: #selector(self.applePayButtonPressed), for: .touchUpInside)
self.view.addSubview(applePayButton)
self.applePayButton = applePayButton
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift
index 035fa49f99..dab1b83bd0 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutController.swift
@@ -141,9 +141,10 @@ public final class BotCheckoutController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
- super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
+ super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
+ self._hasGlassStyle = true
var title = self.presentationData.strings.Checkout_Title
if invoice.flags.contains(.isTest) {
@@ -151,7 +152,7 @@ public final class BotCheckoutController: ViewController {
}
self.title = title
- self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed))
}
required public init(coder aDecoder: NSCoder) {
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift
index e81bf2e2a4..ee0a215277 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift
@@ -1077,13 +1077,13 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
if methods.isEmpty {
openNewCard(nil, nil)
} else {
- strongSelf.present(BotCheckoutPaymentMethodSheetController(context: strongSelf.context, currentMethod: strongSelf.currentPaymentMethod, methods: methods, applyValue: { method in
+ strongSelf.controller?.push(BotCheckoutPaymentMethodScreen(context: strongSelf.context, currentMethod: strongSelf.currentPaymentMethod, methods: methods, applyValue: { method in
applyPaymentMethod(method)
}, newCard: {
openNewCard(nil, nil)
}, otherMethod: { url, title in
openNewCard(url, title)
- }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+ }))
}
}
}
@@ -1091,14 +1091,14 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
openShippingMethodImpl = { [weak self] in
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let shippingOptions = strongSelf.currentValidatedFormInfo?.shippingOptions, !shippingOptions.isEmpty {
strongSelf.controller?.view.endEditing(true)
- strongSelf.present(BotCheckoutPaymentShippingOptionSheetController(context: strongSelf.context, currency: paymentFormValue.invoice.currency, options: shippingOptions, currentId: strongSelf.currentShippingOptionId, applyValue: { id in
+ strongSelf.controller?.push(BotCheckoutShippingOptionScreen(context: strongSelf.context, currency: paymentFormValue.invoice.currency, options: shippingOptions, currentId: strongSelf.currentShippingOptionId, applyValue: { id in
if let strongSelf = self, let paymentFormValue = strongSelf.paymentFormValue, let currentFormInfo = strongSelf.currentFormInfo {
strongSelf.currentShippingOptionId = id
strongSelf.paymentFormAndInfo.set(.single((paymentFormValue, currentFormInfo, strongSelf.currentValidatedFormInfo, strongSelf.currentShippingOptionId, strongSelf.currentPaymentMethod, strongSelf.currentTipAmount)))
strongSelf.updateActionButton()
}
- }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+ }))
}
}
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift
index 84ff228769..0172056d75 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutHeaderItem.swift
@@ -206,7 +206,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
}
let contentSize = CGSize(width: params.width, height: contentHeight)
- let insets = itemListNeighborsPlainInsets(neighbors)
+ let insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift
index bee72bda48..9a178d626c 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoController.swift
@@ -60,14 +60,16 @@ final class BotCheckoutInfoController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
- super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
+ super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
+
+ self._hasGlassStyle = true
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
- self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
+ self.doneItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed))
self.title = self.presentationData.strings.CheckoutInfo_Title
- self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed))
self.navigationItem.rightBarButtonItem = self.doneItem
self.doneItem?.isEnabled = false
}
@@ -84,13 +86,13 @@ final class BotCheckoutInfoController: ViewController {
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}, openCountrySelection: { [weak self] in
if let strongSelf = self {
- let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false)
+ let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false, glass: true)
controller.completeWithCountryCode = { _, id in
if let strongSelf = self {
strongSelf.controllerNode.updateCountry(id)
}
}
- strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+ strongSelf.push(controller)
}
}, updateStatus: { [weak self] status in
if let strongSelf = self {
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift
index 353b1adfbf..cbc2ae8aff 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutInfoControllerNode.swift
@@ -455,7 +455,7 @@ final class BotCheckoutInfoControllerNode: ViewControllerTracingNode, ASScrollVi
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
var sideInset: CGFloat = 0.0
- if layout.size.width >= 375.0 {
+ if layout.size.width >= 320.0 {
sideInset = inset
}
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift
index 74af758711..b1bc797121 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryController.swift
@@ -55,14 +55,16 @@ final class BotCheckoutNativeCardEntryController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
- super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
+ super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData, style: .glass))
+
+ self._hasGlassStyle = true
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
- self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
+ self.doneItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed))
self.title = self.presentationData.strings.Checkout_NewCard_Title
- self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed))
self.navigationItem.rightBarButtonItem = self.doneItem
self.doneItem?.isEnabled = false
}
@@ -78,13 +80,13 @@ final class BotCheckoutNativeCardEntryController: ViewController {
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}, openCountrySelection: { [weak self] in
if let strongSelf = self {
- let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false)
+ let controller = AuthorizationSequenceCountrySelectionController(strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, displayCodes: false, glass: true)
controller.completeWithCountryCode = { _, id in
if let strongSelf = self {
strongSelf.controllerNode.updateCountry(id)
}
}
- strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
+ strongSelf.push(controller)
}
}, updateStatus: { [weak self] status in
if let strongSelf = self {
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift
index 7a0c7afc0c..32025f0f50 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift
@@ -468,7 +468,7 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode,
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
var sideInset: CGFloat = 0.0
- if layout.size.width >= 375.0 {
+ if layout.size.width >= 320.0 {
sideInset = inset
}
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift
index 7e08b985b8..644e8ce4a6 100644
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPasswordEntryController.swift
@@ -1,374 +1,96 @@
import Foundation
import UIKit
-import AsyncDisplayKit
import Display
import TelegramCore
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
+import ComponentFlow
+import AlertComponent
+import AlertInputFieldComponent
-private final class BotCheckoutPassworInputFieldNode: ASDisplayNode, UITextFieldDelegate {
- private var theme: PresentationTheme
- private let backgroundNode: ASImageNode
- private let textInputNode: TextFieldNode
- private let placeholderNode: ASTextNode
-
- var updateHeight: (() -> Void)?
- var complete: (() -> Void)?
- var textChanged: ((String) -> Void)?
-
- private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
- private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
-
- var text: String {
- get {
- return self.textInputNode.textField.text ?? ""
- }
- set {
- self.textInputNode.textField.text = newValue
- self.placeholderNode.isHidden = !newValue.isEmpty
- }
- }
-
- var placeholder: String = "" {
- didSet {
- self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
- }
- }
-
- init(theme: PresentationTheme, placeholder: String) {
- self.theme = theme
-
- self.backgroundNode = ASImageNode()
- self.backgroundNode.isLayerBacked = true
- self.backgroundNode.displaysAsynchronously = false
- self.backgroundNode.displayWithoutProcessing = true
- self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
-
- self.textInputNode = TextFieldNode()
- self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
- self.textInputNode.textField.clipsToBounds = true
- self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
- self.textInputNode.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
- self.textInputNode.textField.returnKeyType = .done
- self.textInputNode.textField.isSecureTextEntry = true
- self.textInputNode.textField.tintColor = theme.actionSheet.controlAccentColor
- self.textInputNode.textField.textColor = theme.actionSheet.inputTextColor
-
- self.placeholderNode = ASTextNode()
- self.placeholderNode.isUserInteractionEnabled = false
- self.placeholderNode.displaysAsynchronously = false
- self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
-
- super.init()
-
- self.textInputNode.textField.delegate = self
- self.textInputNode.textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
-
- self.addSubnode(self.backgroundNode)
- self.addSubnode(self.textInputNode)
- self.addSubnode(self.placeholderNode)
- }
-
- func updateTheme(_ theme: PresentationTheme) {
- self.theme = theme
-
- self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
- self.textInputNode.textField.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
- self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
- self.textInputNode.textField.tintColor = self.theme.actionSheet.controlAccentColor
- self.textInputNode.textField.typingAttributes = [NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: theme.actionSheet.inputTextColor]
- self.textInputNode.textField.textColor = self.theme.actionSheet.inputTextColor
- }
-
- func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
- let backgroundInsets = self.backgroundInsets
- let inputInsets = self.inputInsets
-
- let textFieldHeight = self.calculateTextFieldMetrics(width: width)
- let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
-
- let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
- transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
-
- let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
- transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
-
- transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height)))
-
- return panelHeight
- }
-
- func activateInput() {
- self.textInputNode.becomeFirstResponder()
- }
-
- func deactivateInput() {
- self.textInputNode.resignFirstResponder()
- }
-
- func shake() {
- self.layer.addShakeAnimation()
+func botCheckoutPasswordEntryController(context: AccountContext, strings: PresentationStrings, passwordTip: String?, cartTitle: String, period: Int32, requiresBiometrics: Bool, completion: @escaping (TemporaryTwoStepPasswordToken) -> Void) -> ViewController {
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+
+ let inputState = AlertInputFieldComponent.ExternalState()
+ let doneInProgressPromise = ValuePromise(false)
+ let doneIsEnabled: Signal = combineLatest(inputState.valueSignal, doneInProgressPromise.get())
+ |> map { value, isInProgress in
+ return !value.isEmpty && !isInProgress
}
- @objc func textDidChange() {
- self.updateTextNodeText(animated: true)
- self.textChanged?(self.textInputNode.textField.text ?? "")
- self.placeholderNode.isHidden = !(self.textInputNode.textField.text ?? "").isEmpty
- }
-
- func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
- if text == "\n" {
- self.complete?()
- return false
- }
- return true
- }
-
- private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
- let backgroundInsets = self.backgroundInsets
- let inputInsets = self.inputInsets
-
- let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height))
-
- return min(61.0, max(33.0, unboundTextFieldHeight))
- }
-
- private func updateTextNodeText(animated: Bool) {
- let backgroundInsets = self.backgroundInsets
-
- let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
-
- let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
- if !self.bounds.size.height.isEqual(to: panelHeight) {
- self.updateHeight?()
- }
- }
-
- @objc func clearPressed() {
- self.textInputNode.textField.text = nil
- self.deactivateInput()
- }
-}
+ var content: [AnyComponentWithIdentity] = []
+ content.append(AnyComponentWithIdentity(
+ id: "title",
+ component: AnyComponent(
+ AlertTitleComponent(title: strings.Checkout_PasswordEntry_Title)
+ )
+ ))
+ content.append(AnyComponentWithIdentity(
+ id: "text",
+ component: AnyComponent(
+ AlertTextComponent(content: .plain(strings.Checkout_PasswordEntry_Text(cartTitle).string))
+ )
+ ))
-private final class BotCheckoutPasswordAlertContentNode: AlertContentNode {
- private let context: AccountContext
- private let period: Int32
- private let requiresBiometrics: Bool
- private let completion: (TemporaryTwoStepPasswordToken) -> Void
-
- private let titleNode: ASTextNode
- private let textNode: ASTextNode
-
- private let actionNodesSeparator: ASDisplayNode
- private let actionNodes: [TextAlertContentActionNode]
- private let actionVerticalSeparators: [ASDisplayNode]
-
- private let cancelActionNode: TextAlertContentActionNode
- private let doneActionNode: TextAlertContentActionNode
-
- let inputFieldNode: BotCheckoutPassworInputFieldNode
-
- private var validLayout: CGSize?
- private var isVerifying = false
- private let disposable = MetaDisposable()
-
- private let hapticFeedback = HapticFeedback()
-
- init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, passwordTip: String?, cardTitle: String, period: Int32, requiresBiometrics: Bool, cancel: @escaping () -> Void, completion: @escaping (TemporaryTwoStepPasswordToken) -> Void) {
- self.context = context
- self.period = period
- self.requiresBiometrics = requiresBiometrics
- self.completion = completion
-
- let alertTheme = AlertControllerTheme(presentationTheme: theme, fontSize: .regular)
-
- let titleNode = ASTextNode()
- titleNode.attributedText = NSAttributedString(string: strings.Checkout_PasswordEntry_Title, font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
- titleNode.displaysAsynchronously = false
- titleNode.isUserInteractionEnabled = false
- titleNode.maximumNumberOfLines = 1
- titleNode.truncationMode = .byTruncatingTail
- self.titleNode = titleNode
-
- self.textNode = ASTextNode()
- self.textNode.attributedText = NSAttributedString(string: strings.Checkout_PasswordEntry_Text(cardTitle).string, font: Font.regular(13.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
- self.textNode.displaysAsynchronously = false
- self.textNode.isUserInteractionEnabled = false
-
- self.inputFieldNode = BotCheckoutPassworInputFieldNode(theme: theme, placeholder: passwordTip ?? "")
-
- self.actionNodesSeparator = ASDisplayNode()
- self.actionNodesSeparator.isLayerBacked = true
- self.actionNodesSeparator.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor
-
- self.cancelActionNode = TextAlertContentActionNode(theme: alertTheme, action: TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {
- cancel()
- }))
-
- var doneImpl: (() -> Void)?
- self.doneActionNode = TextAlertContentActionNode(theme: alertTheme, action: TextAlertAction(type: .defaultAction, title: strings.Checkout_PasswordEntry_Pay, action: {
- doneImpl?()
- }))
-
- self.actionNodes = [self.cancelActionNode, self.doneActionNode]
-
- var actionVerticalSeparators: [ASDisplayNode] = []
- if self.actionNodes.count > 1 {
- for _ in 0 ..< self.actionNodes.count - 1 {
- let separatorNode = ASDisplayNode()
- separatorNode.isLayerBacked = true
- separatorNode.backgroundColor = theme.actionSheet.opaqueItemSeparatorColor
- actionVerticalSeparators.append(separatorNode)
- }
- }
- self.actionVerticalSeparators = actionVerticalSeparators
-
- super.init()
-
- self.addSubnode(self.titleNode)
- self.addSubnode(self.textNode)
-
- self.addSubnode(self.actionNodesSeparator)
-
- for actionNode in self.actionNodes {
- self.addSubnode(actionNode)
- }
-
- for separatorNode in self.actionVerticalSeparators {
- self.addSubnode(separatorNode)
- }
-
- self.addSubnode(self.inputFieldNode)
-
- self.inputFieldNode.textChanged = { [weak self] _ in
- if let strongSelf = self {
- strongSelf.updateState()
- }
- }
-
- self.updateState()
-
- doneImpl = { [weak self] in
- self?.verify()
- }
+ var applyImpl: (() -> Void)?
+ content.append(AnyComponentWithIdentity(
+ id: "input",
+ component: AnyComponent(
+ AlertInputFieldComponent(
+ context: context,
+ placeholder: passwordTip ?? "",
+ isSecureTextEntry: true,
+ autocapitalizationType: .none,
+ autocorrectionType: .no,
+ isInitiallyFocused: true,
+ externalState: inputState,
+ returnKeyAction: {
+ applyImpl?()
+ }
+ )
+ )
+ ))
+
+ var isVerifying = false
+ let disposable = MetaDisposable()
+ var dismissImpl: (() -> Void)?
+ let alertController = AlertScreen(
+ configuration: AlertScreen.Configuration(allowInputInset: true),
+ content: content,
+ actions: [
+ .init(title: strings.Common_Cancel),
+ .init(title: strings.Checkout_PasswordEntry_Pay, type: .default, action: {
+ applyImpl?()
+ }, autoDismiss: false, isEnabled: doneIsEnabled, progress: doneInProgressPromise.get())
+ ],
+ updatedPresentationData: (initial: presentationData, signal: context.sharedContext.presentationData)
+ )
+ alertController.dismissed = { _ in
+ disposable.dispose()
}
-
- override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let previousLayout = self.validLayout
- self.validLayout = size
-
- let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
-
- let titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
- let textSize = self.textNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
-
- let actionsHeight: CGFloat = 44.0
-
- var minActionsWidth: CGFloat = 0.0
- let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
- let actionTitleInsets: CGFloat = 8.0
- for actionNode in self.actionNodes {
- let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionsHeight))
- minActionsWidth += actionTitleSize.width + actionTitleInsets
- }
-
- let contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth)
-
- let spacing: CGFloat = 6.0
- let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize)
- transition.updateFrame(node: titleNode, frame: titleFrame)
-
- let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: titleFrame.maxY + spacing), size: textSize)
- transition.updateFrame(node: self.textNode, frame: textFrame)
-
- let resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom + 46.0)
-
- let inputFieldWidth = resultSize.width
- let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
- transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: resultSize.height - 36.0 - actionsHeight - insets.bottom, width: resultSize.width, height: inputFieldHeight))
-
- self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))
-
- var actionOffset: CGFloat = 0.0
- let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
- var separatorIndex = -1
- var nodeIndex = 0
- for actionNode in self.actionNodes {
- if separatorIndex >= 0 {
- let separatorNode = self.actionVerticalSeparators[separatorIndex]
- transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
- }
- separatorIndex += 1
-
- let currentActionWidth: CGFloat
- if nodeIndex == self.actionNodes.count - 1 {
- currentActionWidth = resultSize.width - actionOffset
- } else {
- currentActionWidth = actionWidth
- }
-
- let actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionsHeight))
-
- actionOffset += currentActionWidth
- transition.updateFrame(node: actionNode, frame: actionNodeFrame)
-
- nodeIndex += 1
- }
-
- if previousLayout == nil {
- self.inputFieldNode.activateInput()
- }
-
- return resultSize
- }
-
- @objc func textFieldChanged(_ textField: UITextField) {
- self.updateState()
- }
-
- private func updateState() {
- var enabled = true
- if self.isVerifying || self.inputFieldNode.text.isEmpty {
- enabled = false
- }
- self.doneActionNode.actionEnabled = enabled
- }
-
- private func verify() {
- let text = self.inputFieldNode.text
- guard !text.isEmpty else {
+
+ applyImpl = {
+ let password = inputState.value
+ guard !isVerifying, !password.isEmpty else {
return
}
-
- self.isVerifying = true
- self.disposable.set((self.context.engine.auth.requestTemporaryTwoStepPasswordToken(password: text, period: self.period, requiresBiometrics: self.requiresBiometrics) |> deliverOnMainQueue).start(next: { [weak self] token in
- if let strongSelf = self {
- strongSelf.completion(token)
- }
- }, error: { [weak self] _ in
- if let strongSelf = self {
- strongSelf.inputFieldNode.shake()
- strongSelf.hapticFeedback.error()
- strongSelf.isVerifying = false
- strongSelf.updateState()
- }
- }))
- self.updateState()
- }
-}
-func botCheckoutPasswordEntryController(context: AccountContext, strings: PresentationStrings, passwordTip: String?, cartTitle: String, period: Int32, requiresBiometrics: Bool, completion: @escaping (TemporaryTwoStepPasswordToken) -> Void) -> AlertController {
- var dismissImpl: (() -> Void)?
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: BotCheckoutPasswordAlertContentNode(context: context, theme: presentationData.theme, strings: strings, passwordTip: passwordTip, cardTitle: cartTitle, period: period, requiresBiometrics: requiresBiometrics, cancel: {
- dismissImpl?()
- }, completion: { token in
- completion(token)
- dismissImpl?()
- }))
- dismissImpl = { [weak controller] in
- controller?.dismissAnimated()
+ isVerifying = true
+ doneInProgressPromise.set(true)
+
+ disposable.set((context.engine.auth.requestTemporaryTwoStepPasswordToken(password: password, period: period, requiresBiometrics: requiresBiometrics)
+ |> deliverOnMainQueue).start(next: { token in
+ completion(token)
+ dismissImpl?()
+ }, error: { _ in
+ inputState.animateError()
+ isVerifying = false
+ doneInProgressPromise.set(false)
+ }))
}
- return controller
+ dismissImpl = { [weak alertController] in
+ alertController?.dismiss(completion: nil)
+ }
+ return alertController
}
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift
new file mode 100644
index 0000000000..1e85b5f263
--- /dev/null
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift
@@ -0,0 +1,536 @@
+import Foundation
+import UIKit
+import Display
+import ComponentFlow
+import TelegramCore
+import AccountContext
+import ViewControllerComponent
+import ResizableSheetComponent
+import TelegramPresentationData
+import PresentationDataUtils
+import MultilineTextComponent
+import ButtonComponent
+import ListSectionComponent
+import ListActionItemComponent
+import GlassBarButtonComponent
+import BundleIconComponent
+
+struct BotCheckoutPaymentWebToken: Equatable {
+ let title: String
+ let data: String
+ var saveOnServer: Bool
+}
+
+enum BotCheckoutPaymentMethod: Equatable {
+ case savedCredentials(BotPaymentSavedCredentials)
+ case webToken(BotCheckoutPaymentWebToken)
+ case applePay
+ case other(BotPaymentMethod)
+
+ var title: String {
+ switch self {
+ case let .savedCredentials(credentials):
+ switch credentials {
+ case let .card(_, title):
+ return title
+ }
+ case let .webToken(token):
+ return token.title
+ case .applePay:
+ return "Apple Pay"
+ case let .other(method):
+ return method.title
+ }
+ }
+}
+
+private func splitSavedCardTitle(_ title: String) -> (String, String?) {
+ guard let separatorIndex = title.lastIndex(of: "*") else {
+ return (title, nil)
+ }
+
+ let name = String(title[.. Void
+ let addCard: () -> Void
+
+ init(
+ methods: [BotCheckoutPaymentMethod],
+ selectedMethod: BotCheckoutPaymentMethod?,
+ selectMethod: @escaping (BotCheckoutPaymentMethod) -> Void,
+ addCard: @escaping () -> Void
+ ) {
+ self.methods = methods
+ self.selectedMethod = selectedMethod
+ self.selectMethod = selectMethod
+ self.addCard = addCard
+ }
+
+ static func ==(lhs: BotCheckoutPaymentMethodContentComponent, rhs: BotCheckoutPaymentMethodContentComponent) -> Bool {
+ if lhs.methods != rhs.methods {
+ return false
+ }
+ if lhs.selectedMethod != rhs.selectedMethod {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView {
+ private let section = ComponentView()
+
+ private var component: BotCheckoutPaymentMethodContentComponent?
+ private weak var state: EmptyComponentState?
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func update(component: BotCheckoutPaymentMethodContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ self.component = component
+ self.state = state
+
+ let environment = environment[ViewControllerComponentContainer.Environment.self].value
+ let theme = environment.theme.withModalBlocksBackground()
+ let itemFontSize: CGFloat = 17.0
+ let sideInset: CGFloat = 16.0
+
+ var contentHeight: CGFloat = 76.0 + 9.0
+
+ var items: [AnyComponentWithIdentity] = []
+ for i in 0 ..< component.methods.count {
+ let method = component.methods[i]
+ let isSelected = method == component.selectedMethod
+
+ var title = method.title
+ var icon: ListActionItemComponent.Icon?
+ var accessory: ListActionItemComponent.Accessory?
+
+ switch method {
+ case let .savedCredentials(credentials):
+ switch credentials {
+ case let .card(_, cardTitle):
+ let (cardName, cardSuffix) = splitSavedCardTitle(cardTitle)
+ title = cardName
+ if let cardSuffix {
+ accessory = .custom(ListActionItemComponent.CustomAccessory(
+ component: AnyComponentWithIdentity(
+ id: AnyHashable("card-suffix-\(i)-\(cardSuffix)"),
+ component: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: cardSuffix,
+ font: Font.regular(itemFontSize),
+ textColor: theme.list.itemSecondaryTextColor
+ )),
+ maximumNumberOfLines: 1
+ ))
+ ),
+ insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 16.0)
+ ))
+ }
+ }
+ case .applePay:
+ title = "Apple Pay"
+ icon = ListActionItemComponent.Icon(
+ component: AnyComponentWithIdentity(
+ id: AnyHashable("apple-pay"),
+ component: AnyComponent(BundleIconComponent(
+ name: "Bot Payments/ApplePayLogo",
+ tintColor: nil
+ ))
+ )
+ )
+ case let .webToken(token):
+ let (cardName, cardSuffix) = splitSavedCardTitle(token.title)
+ title = cardName
+ if let cardSuffix {
+ accessory = .custom(ListActionItemComponent.CustomAccessory(
+ component: AnyComponentWithIdentity(
+ id: AnyHashable("card-suffix-\(i)-\(cardSuffix)"),
+ component: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: cardSuffix,
+ font: Font.regular(itemFontSize),
+ textColor: theme.list.itemSecondaryTextColor
+ )),
+ maximumNumberOfLines: 1
+ ))
+ ),
+ insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 16.0)
+ ))
+ }
+ case let .other(method):
+ title = method.title
+ }
+
+ items.append(AnyComponentWithIdentity(id: AnyHashable("method-\(i)"), component: AnyComponent(ListActionItemComponent(
+ theme: theme,
+ style: .glass,
+ title: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: title,
+ font: Font.regular(itemFontSize),
+ textColor: theme.list.itemPrimaryTextColor
+ )),
+ maximumNumberOfLines: 1
+ )),
+ leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
+ isSelected: isSelected,
+ toggle: {
+ component.selectMethod(method)
+ }
+ )),
+ icon: icon,
+ accessory: accessory,
+ action: { _ in
+ component.selectMethod(method)
+ }
+ ))))
+ }
+
+ items.append(AnyComponentWithIdentity(id: AnyHashable("add-card"), component: AnyComponent(ListActionItemComponent(
+ theme: theme,
+ style: .glass,
+ title: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: environment.strings.Checkout_PaymentMethod_New,
+ font: Font.regular(itemFontSize),
+ textColor: theme.list.itemAccentColor
+ )),
+ maximumNumberOfLines: 1
+ )),
+ contentInsets: UIEdgeInsets(top: 12.0, left: 45.0, bottom: 12.0, right: 0.0),
+ leftIcon: nil,
+ icon: nil,
+ accessory: nil,
+ action: { _ in
+ component.addCard()
+ }
+ ))))
+
+ self.section.parentState = state
+ let sectionSize = self.section.update(
+ transition: transition,
+ component: AnyComponent(ListSectionComponent(
+ theme: theme,
+ style: .glass,
+ header: nil,
+ footer: nil,
+ items: items
+ )),
+ environment: {},
+ containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
+ )
+ let sectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sectionSize)
+ if let sectionView = self.section.view {
+ if sectionView.superview == nil {
+ self.addSubview(sectionView)
+ }
+ transition.setFrame(view: sectionView, frame: sectionFrame)
+ }
+ contentHeight += sectionSize.height
+ contentHeight += 112.0
+
+ 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 BotCheckoutPaymentMethodScreenComponent: Component {
+ typealias EnvironmentType = ViewControllerComponentContainer.Environment
+
+ let context: AccountContext
+ let currentMethod: BotCheckoutPaymentMethod?
+ let methods: [BotCheckoutPaymentMethod]
+ let applyValue: (BotCheckoutPaymentMethod) -> Void
+ let newCard: () -> Void
+ let otherMethod: (String, String) -> Void
+
+ init(
+ context: AccountContext,
+ currentMethod: BotCheckoutPaymentMethod?,
+ methods: [BotCheckoutPaymentMethod],
+ applyValue: @escaping (BotCheckoutPaymentMethod) -> Void,
+ newCard: @escaping () -> Void,
+ otherMethod: @escaping (String, String) -> Void
+ ) {
+ self.context = context
+ self.currentMethod = currentMethod
+ self.methods = methods
+ self.applyValue = applyValue
+ self.newCard = newCard
+ self.otherMethod = otherMethod
+ }
+
+ static func ==(lhs: BotCheckoutPaymentMethodScreenComponent, rhs: BotCheckoutPaymentMethodScreenComponent) -> Bool {
+ if lhs.context !== rhs.context {
+ return false
+ }
+ if lhs.currentMethod != rhs.currentMethod {
+ return false
+ }
+ if lhs.methods != rhs.methods {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView {
+ private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>()
+ private let animateOut = ActionSlot>()
+
+ private var component: BotCheckoutPaymentMethodScreenComponent?
+ private weak var state: EmptyComponentState?
+ private var selectedMethod: BotCheckoutPaymentMethod?
+ private var isDismissing = false
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func update(component: BotCheckoutPaymentMethodScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ if self.component == nil {
+ if let currentMethod = component.currentMethod, component.methods.contains(currentMethod) {
+ self.selectedMethod = currentMethod
+ } else {
+ self.selectedMethod = nil
+ }
+ }
+
+ self.component = component
+ self.state = state
+
+ let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value
+ let controller = environmentValue.controller
+ let theme = environmentValue.theme.withModalBlocksBackground()
+
+ let dismiss: (Bool, (() -> Void)?) -> Void = { [weak self] animated, completion in
+ guard let self, !self.isDismissing else {
+ return
+ }
+ self.isDismissing = true
+
+ let performDismiss: () -> Void = {
+ if let controller = controller() as? BotCheckoutPaymentMethodScreen {
+ controller.completePendingDismiss()
+ controller.dismiss(animated: false)
+ }
+ completion?()
+ }
+
+ if animated {
+ self.animateOut.invoke(Action { _ in
+ performDismiss()
+ })
+ } else {
+ performDismiss()
+ }
+ }
+
+ let sheetSize = self.sheet.update(
+ transition: transition,
+ component: AnyComponent(ResizableSheetComponent(
+ content: AnyComponent(BotCheckoutPaymentMethodContentComponent(
+ methods: component.methods,
+ selectedMethod: self.selectedMethod,
+ selectMethod: { [weak self] method in
+ guard let self else {
+ return
+ }
+ self.selectedMethod = method
+ self.state?.updated(transition: .spring(duration: 0.35))
+ },
+ addCard: { [weak self] in
+ guard let self, let component = self.component else {
+ return
+ }
+ dismiss(true, {
+ component.newCard()
+ })
+ }
+ )),
+ titleItem: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: environmentValue.strings.Checkout_PaymentMethod,
+ font: Font.semibold(17.0),
+ textColor: theme.list.itemPrimaryTextColor
+ )),
+ maximumNumberOfLines: 1
+ )),
+ leftItem: AnyComponent(
+ GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: theme.overallDarkAppearance,
+ state: .glass,
+ component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
+ BundleIconComponent(
+ name: "Navigation/Close",
+ tintColor: theme.chat.inputPanel.panelControlColor
+ )
+ )),
+ action: { _ in
+ dismiss(true, nil)
+ }
+ )
+ ),
+ //TODO:localize
+ bottomItem: AnyComponent(ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: theme.list.itemCheckColors.fillColor,
+ foreground: theme.list.itemCheckColors.foregroundColor,
+ pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
+ ),
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("proceed"),
+ component: AnyComponent(ButtonTextContentComponent(
+ text: "Proceed",
+ badge: 0,
+ textColor: theme.list.itemCheckColors.foregroundColor,
+ badgeBackground: theme.list.itemCheckColors.foregroundColor,
+ badgeForeground: theme.list.itemCheckColors.fillColor
+ ))
+ ),
+ isEnabled: self.selectedMethod != nil,
+ displaysProgress: false,
+ action: { [weak self] in
+ guard let self, let component = self.component, let selectedMethod = self.selectedMethod else {
+ return
+ }
+ dismiss(true, {
+ switch selectedMethod {
+ case let .other(method):
+ component.otherMethod(method.url, method.title)
+ default:
+ component.applyValue(selectedMethod)
+ }
+ })
+ }
+ )),
+ backgroundColor: .color(theme.list.modalBlocksBackgroundColor),
+ animateOut: self.animateOut
+ )),
+ environment: {
+ environmentValue
+ ResizableSheetComponentEnvironment(
+ theme: theme,
+ statusBarHeight: environmentValue.statusBarHeight,
+ safeInsets: environmentValue.safeInsets,
+ inputHeight: 0.0,
+ metrics: environmentValue.metrics,
+ deviceMetrics: environmentValue.deviceMetrics,
+ isDisplaying: environmentValue.isVisible,
+ isCentered: environmentValue.metrics.widthClass == .regular,
+ screenSize: availableSize,
+ regularMetricsSize: nil,
+ dismiss: { animated in
+ dismiss(animated, nil)
+ }
+ )
+ },
+ forceUpdate: true,
+ containerSize: availableSize
+ )
+ self.sheet.parentState = state
+ if let sheetView = self.sheet.view {
+ if sheetView.superview == nil {
+ self.addSubview(sheetView)
+ }
+ transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize))
+ }
+
+ 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)
+ }
+}
+
+final class BotCheckoutPaymentMethodScreen: ViewControllerComponentContainer {
+ private var isDismissed = false
+ private var dismissCompletion: (() -> Void)?
+
+ init(context: AccountContext, currentMethod: BotCheckoutPaymentMethod?, methods: [BotCheckoutPaymentMethod], applyValue: @escaping (BotCheckoutPaymentMethod) -> Void, newCard: @escaping () -> Void, otherMethod: @escaping (String, String) -> Void) {
+ super.init(
+ context: context,
+ component: BotCheckoutPaymentMethodScreenComponent(
+ context: context,
+ currentMethod: currentMethod,
+ methods: methods,
+ applyValue: applyValue,
+ newCard: newCard,
+ otherMethod: otherMethod
+ ),
+ navigationBarAppearance: .none
+ )
+
+ self.statusBar.statusBarStyle = .Ignore
+ self.navigationPresentation = .flatModal
+ self.blocksBackgroundWhenInOverlay = true
+ }
+
+ required init(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ fileprivate func completePendingDismiss() {
+ let dismissCompletion = self.dismissCompletion
+ self.dismissCompletion = nil
+ dismissCompletion?()
+ }
+
+ func dismissAnimated() {
+ if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View {
+ view.dismissAnimated()
+ }
+ }
+
+ override func dismiss(completion: (() -> Void)? = nil) {
+ if !self.isDismissed {
+ self.isDismissed = true
+ self.dismissCompletion = completion
+
+ if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View {
+ view.dismissAnimated()
+ } else {
+ self.completePendingDismiss()
+ self.dismiss(animated: false)
+ }
+ }
+ }
+}
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodSheet.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodSheet.swift
deleted file mode 100644
index a728d78155..0000000000
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodSheet.swift
+++ /dev/null
@@ -1,262 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import SwiftSignalKit
-import TelegramCore
-import AccountContext
-import AppBundle
-
-struct BotCheckoutPaymentWebToken: Equatable {
- let title: String
- let data: String
- var saveOnServer: Bool
-}
-
-enum BotCheckoutPaymentMethod: Equatable {
- case savedCredentials(BotPaymentSavedCredentials)
- case webToken(BotCheckoutPaymentWebToken)
- case applePay
- case other(BotPaymentMethod)
-
- var title: String {
- switch self {
- case let .savedCredentials(credentials):
- switch credentials {
- case let .card(_, title):
- return title
- }
- case let .webToken(token):
- return token.title
- case .applePay:
- return "Apple Pay"
- case let .other(method):
- return method.title
- }
- }
-}
-
-final class BotCheckoutPaymentMethodSheetController: ActionSheetController {
- private var presentationDisposable: Disposable?
-
- init(context: AccountContext, currentMethod: BotCheckoutPaymentMethod?, methods: [BotCheckoutPaymentMethod], applyValue: @escaping (BotCheckoutPaymentMethod) -> Void, newCard: @escaping () -> Void, otherMethod: @escaping (String, String) -> Void) {
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- let strings = presentationData.strings
-
- super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
-
- self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in
- if let strongSelf = self {
- strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
- }
- }).strict()
-
- var items: [ActionSheetItem] = []
-
- items.append(ActionSheetTextItem(title: strings.Checkout_PaymentMethod))
-
- for method in methods {
- let title: String
- let icon: UIImage?
- switch method {
- case let .savedCredentials(credentials):
- switch credentials {
- case let .card(_, cardTitle):
- title = cardTitle
- icon = nil
- }
- case let .webToken(token):
- title = token.title
- icon = nil
- case .applePay:
- title = "Apple Pay"
- icon = UIImage(bundleImageName: "Bot Payments/ApplePayLogo")?.precomposed()
- case let .other(method):
- title = method.title
- icon = nil
- }
- let value: Bool?
- if let currentMethod = currentMethod {
- value = method == currentMethod
- } else {
- value = nil
- }
- items.append(BotCheckoutPaymentMethodItem(title: title, icon: icon, value: value, action: { [weak self] _ in
- if case let .other(method) = method {
- otherMethod(method.url, method.title)
- } else {
- applyValue(method)
- }
- self?.dismissAnimated()
- }))
- }
-
- items.append(ActionSheetButtonItem(title: strings.Checkout_PaymentMethod_New, action: { [weak self] in
- self?.dismissAnimated()
- newCard()
- }))
-
- self.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
- self?.dismissAnimated()
- }),
- ])
- ])
- }
-
- required init(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- deinit {
- self.presentationDisposable?.dispose()
- }
-}
-
-public class BotCheckoutPaymentMethodItem: ActionSheetItem {
- public let title: String
- public let icon: UIImage?
- public let value: Bool?
- public let action: (Bool) -> Void
-
- public init(title: String, icon: UIImage?, value: Bool?, action: @escaping (Bool) -> Void) {
- self.title = title
- self.icon = icon
- self.value = value
- self.action = action
- }
-
- public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- let node = BotCheckoutPaymentMethodItemNode(theme: theme)
- node.setItem(self)
- return node
- }
-
- public func updateNode(_ node: ActionSheetItemNode) {
- guard let node = node as? BotCheckoutPaymentMethodItemNode else {
- assertionFailure()
- return
- }
-
- node.setItem(self)
- node.requestLayoutUpdate()
- }
-}
-
-public class BotCheckoutPaymentMethodItemNode: ActionSheetItemNode {
- private let defaultFont: UIFont
-
- private let theme: ActionSheetControllerTheme
-
- private var item: BotCheckoutPaymentMethodItem?
-
- private let button: HighlightTrackingButton
- private let titleNode: ASTextNode
- private let iconNode: ASImageNode
- private let checkNode: ASImageNode
-
- public override init(theme: ActionSheetControllerTheme) {
- self.theme = theme
- self.defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
-
- self.button = HighlightTrackingButton()
-
- self.titleNode = ASTextNode()
- self.titleNode.maximumNumberOfLines = 1
- self.titleNode.isUserInteractionEnabled = false
- self.titleNode.displaysAsynchronously = false
-
- self.iconNode = ASImageNode()
- self.iconNode.displaysAsynchronously = false
- self.iconNode.displayWithoutProcessing = true
-
- self.checkNode = ASImageNode()
- self.checkNode.isUserInteractionEnabled = false
- self.checkNode.displayWithoutProcessing = true
- self.checkNode.displaysAsynchronously = false
- self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in
- context.clear(CGRect(origin: CGPoint(), size: size))
- context.setStrokeColor(theme.controlAccentColor.cgColor)
- context.setLineWidth(2.0)
- context.move(to: CGPoint(x: 12.0, y: 1.0))
- context.addLine(to: CGPoint(x: 4.16482734, y: 9.0))
- context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
- context.strokePath()
- })
-
- super.init(theme: theme)
-
- self.view.addSubview(self.button)
- self.addSubnode(self.titleNode)
- self.addSubnode(self.iconNode)
- self.addSubnode(self.checkNode)
-
- self.button.highligthedChanged = { [weak self] highlighted in
- if let strongSelf = self {
- if highlighted {
- strongSelf.backgroundNode.backgroundColor = theme.itemHighlightedBackgroundColor
- } else {
- UIView.animate(withDuration: 0.3, animations: {
- strongSelf.backgroundNode.backgroundColor = theme.itemBackgroundColor
- })
- }
- }
- }
-
- self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
- }
-
- func setItem(_ item: BotCheckoutPaymentMethodItem) {
- self.item = item
-
- self.titleNode.attributedText = NSAttributedString(string: item.title, font: self.defaultFont, textColor: self.theme.primaryTextColor)
- self.iconNode.image = item.icon
- if let value = item.value {
- self.checkNode.isHidden = !value
- } else {
- self.checkNode.isHidden = true
- }
- }
-
- public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let size = CGSize(width: constrainedSize.width, height: 57.0)
-
- self.button.frame = CGRect(origin: CGPoint(), size: size)
-
- var checkInset: CGFloat = 15.0
- if let _ = self.item?.value {
- checkInset = 44.0
- }
-
- let iconSize: CGSize
- if let image = self.iconNode.image {
- iconSize = image.size
- } else {
- iconSize = CGSize()
- }
- let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - iconSize.width - 15.0 - 8.0, height: size.height))
- self.titleNode.frame = CGRect(origin: CGPoint(x: checkInset, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
- self.iconNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - iconSize.width, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize)
-
- if let image = self.checkNode.image {
- self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
- }
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-
- @objc func buttonPressed() {
- if let item = self.item {
- let updatedValue: Bool
- if let value = item.value {
- updatedValue = !value
- } else {
- updatedValue = true
- }
- item.action(updatedValue)
- }
- }
-}
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentShippingOptionSheetController.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentShippingOptionSheetController.swift
deleted file mode 100644
index 10b86ab94f..0000000000
--- a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentShippingOptionSheetController.swift
+++ /dev/null
@@ -1,224 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import SwiftSignalKit
-import TelegramCore
-import AccountContext
-import TelegramStringFormatting
-
-final class BotCheckoutPaymentShippingOptionSheetController: ActionSheetController {
- private var presentationDisposable: Disposable?
-
- init(context: AccountContext, currency: String, options: [BotPaymentShippingOption], currentId: String?, applyValue: @escaping (String) -> Void) {
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- let strings = presentationData.strings
-
- super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
-
- self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in
- if let strongSelf = self {
- strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
- }
- }).strict()
-
- var items: [ActionSheetItem] = []
-
- items.append(ActionSheetTextItem(title: strings.Checkout_ShippingMethod))
-
- let dismissAction: () -> Void = { [weak self] in
- self?.dismissAnimated()
- }
-
- let toggleCheck: (String, Int) -> Void = { [weak self] id, itemIndex in
- for i in 0 ..< options.count {
- self?.updateItem(groupIndex: 0, itemIndex: i + 1, { item in
- if let item = item as? BotCheckoutPaymentShippingOptionItem, let value = item.value {
- return BotCheckoutPaymentShippingOptionItem(title: item.title, label: item.label, value: i == itemIndex ? !value : false, action: item.action)
- }
- return item
- })
- }
- applyValue(id)
- dismissAction()
- }
-
- var itemIndex = 0
- for option in options {
- let index = itemIndex
- var totalPrice: Int64 = 0
- for price in option.prices {
- totalPrice += price.amount
- }
- let value: Bool?
- if let currentId = currentId {
- value = option.id == currentId
- } else {
- value = nil
- }
- items.append(BotCheckoutPaymentShippingOptionItem(title: option.title, label: formatCurrencyAmount(totalPrice, currency: currency), value: value, action: { value in
- toggleCheck(option.id, index)
- }))
- itemIndex += 1
- }
-
- self.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
- self?.dismissAnimated()
- }),
- ])
- ])
- }
-
- required init(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- deinit {
- self.presentationDisposable?.dispose()
- }
-}
-
-public class BotCheckoutPaymentShippingOptionItem: ActionSheetItem {
- public let title: String
- public let label: String
- public let value: Bool?
- public let action: (Bool) -> Void
-
- public init(title: String, label: String, value: Bool?, action: @escaping (Bool) -> Void) {
- self.title = title
- self.label = label
- self.value = value
- self.action = action
- }
-
- public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- let node = BotCheckoutPaymentShippingOptionItemNode(theme: theme)
- node.setItem(self)
- return node
- }
-
- public func updateNode(_ node: ActionSheetItemNode) {
- guard let node = node as? BotCheckoutPaymentShippingOptionItemNode else {
- assertionFailure()
- return
- }
-
- node.setItem(self)
- node.requestLayoutUpdate()
- }
-}
-
-public class BotCheckoutPaymentShippingOptionItemNode: ActionSheetItemNode {
- private let defaultFont: UIFont
-
- private let theme: ActionSheetControllerTheme
-
- private var item: BotCheckoutPaymentShippingOptionItem?
-
- private let button: HighlightTrackingButton
- private let titleNode: ASTextNode
- private let labelNode: ASTextNode
- private let checkNode: ASImageNode
-
- public override init(theme: ActionSheetControllerTheme) {
- self.theme = theme
- self.defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
-
- self.button = HighlightTrackingButton()
-
- self.titleNode = ASTextNode()
- self.titleNode.maximumNumberOfLines = 1
- self.titleNode.isUserInteractionEnabled = false
- self.titleNode.displaysAsynchronously = false
-
- self.labelNode = ASTextNode()
- self.labelNode.maximumNumberOfLines = 1
- self.labelNode.isUserInteractionEnabled = false
- self.labelNode.displaysAsynchronously = false
-
- self.checkNode = ASImageNode()
- self.checkNode.isUserInteractionEnabled = false
- self.checkNode.displayWithoutProcessing = true
- self.checkNode.displaysAsynchronously = false
- self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in
- context.clear(CGRect(origin: CGPoint(), size: size))
- context.setStrokeColor(theme.controlAccentColor.cgColor)
- context.setLineWidth(2.0)
- context.move(to: CGPoint(x: 12.0, y: 1.0))
- context.addLine(to: CGPoint(x: 4.16482734, y: 9.0))
- context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
- context.strokePath()
- })
-
- super.init(theme: theme)
-
- self.view.addSubview(self.button)
- self.addSubnode(self.titleNode)
- self.addSubnode(self.labelNode)
- self.addSubnode(self.checkNode)
-
- self.button.highligthedChanged = { [weak self] highlighted in
- if let strongSelf = self {
- if highlighted {
- strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor
- } else {
- UIView.animate(withDuration: 0.3, animations: {
- strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor
- })
- }
- }
- }
-
- self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
- }
-
- func setItem(_ item: BotCheckoutPaymentShippingOptionItem) {
- self.item = item
-
- self.titleNode.attributedText = NSAttributedString(string: item.title, font: self.defaultFont, textColor: self.theme.primaryTextColor)
- self.labelNode.attributedText = NSAttributedString(string: item.label, font: self.defaultFont, textColor: self.theme.primaryTextColor)
- if let value = item.value {
- self.checkNode.isHidden = !value
- } else {
- self.checkNode.isHidden = true
- }
- }
-
- public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let size = CGSize(width: constrainedSize.width, height: 57.0)
-
- self.button.frame = CGRect(origin: CGPoint(), size: size)
-
- var checkInset: CGFloat = 15.0
- if let _ = self.item?.value {
- checkInset = 44.0
- }
-
- let labelSize = self.labelNode.measure(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height))
- let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height))
- self.titleNode.frame = CGRect(origin: CGPoint(x: checkInset, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
- self.labelNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
-
- if let image = self.checkNode.image {
- self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
- }
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-
- @objc func buttonPressed() {
- if let item = self.item {
- let updatedValue: Bool
- if let value = item.value {
- updatedValue = !value
- } else {
- updatedValue = true
- }
- item.action(updatedValue)
- }
- }
-}
diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift
new file mode 100644
index 0000000000..aa848c41fc
--- /dev/null
+++ b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift
@@ -0,0 +1,418 @@
+import Foundation
+import UIKit
+import Display
+import ComponentFlow
+import SwiftSignalKit
+import TelegramCore
+import AccountContext
+import TelegramStringFormatting
+import ViewControllerComponent
+import ResizableSheetComponent
+import TelegramPresentationData
+import PresentationDataUtils
+import MultilineTextComponent
+import ButtonComponent
+import ListSectionComponent
+import ListActionItemComponent
+import GlassBarButtonComponent
+import BundleIconComponent
+
+private final class BotCheckoutShippingOptionContentComponent: Component {
+ typealias EnvironmentType = ViewControllerComponentContainer.Environment
+
+ let currency: String
+ let options: [BotPaymentShippingOption]
+ let selectedId: String?
+ let selectOption: (String) -> Void
+
+ init(
+ currency: String,
+ options: [BotPaymentShippingOption],
+ selectedId: String?,
+ selectOption: @escaping (String) -> Void
+ ) {
+ self.currency = currency
+ self.options = options
+ self.selectedId = selectedId
+ self.selectOption = selectOption
+ }
+
+ static func ==(lhs: BotCheckoutShippingOptionContentComponent, rhs: BotCheckoutShippingOptionContentComponent) -> Bool {
+ if lhs.currency != rhs.currency {
+ return false
+ }
+ if lhs.options != rhs.options {
+ return false
+ }
+ if lhs.selectedId != rhs.selectedId {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView {
+ private let section = ComponentView()
+
+ private var component: BotCheckoutShippingOptionContentComponent?
+ private weak var state: EmptyComponentState?
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func update(component: BotCheckoutShippingOptionContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ self.component = component
+ self.state = state
+
+ let environment = environment[ViewControllerComponentContainer.Environment.self].value
+ let theme = environment.theme.withModalBlocksBackground()
+ let itemFontSize: CGFloat = 17.0
+ let sideInset: CGFloat = 16.0
+
+ var contentHeight: CGFloat = 76.0 + 9.0
+
+ var items: [AnyComponentWithIdentity] = []
+ for option in component.options {
+ let totalPrice = option.prices.reduce(Int64(0)) { current, price in
+ return current + price.amount
+ }
+ let priceText = formatCurrencyAmount(totalPrice, currency: component.currency)
+ let isSelected = option.id == component.selectedId
+
+ items.append(AnyComponentWithIdentity(id: option.id, component: AnyComponent(ListActionItemComponent(
+ theme: theme,
+ style: .glass,
+ title: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: option.title,
+ font: Font.regular(itemFontSize),
+ textColor: theme.list.itemPrimaryTextColor
+ )),
+ maximumNumberOfLines: 1
+ )),
+ leftIcon: .check(ListActionItemComponent.LeftIcon.Check(
+ isSelected: isSelected,
+ toggle: {
+ component.selectOption(option.id)
+ }
+ )),
+ icon: nil,
+ accessory: .custom(ListActionItemComponent.CustomAccessory(
+ component: AnyComponentWithIdentity(
+ id: AnyHashable("price-\(option.id)"),
+ component: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: priceText,
+ font: Font.regular(itemFontSize),
+ textColor: theme.list.itemSecondaryTextColor
+ )),
+ maximumNumberOfLines: 1
+ ))
+ ),
+ insets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 16.0)
+ )),
+ action: { _ in
+ component.selectOption(option.id)
+ }
+ ))))
+ }
+
+ self.section.parentState = state
+ let sectionSize = self.section.update(
+ transition: transition,
+ component: AnyComponent(ListSectionComponent(
+ theme: theme,
+ style: .glass,
+ header: nil,
+ footer: nil,
+ items: items
+ )),
+ environment: {},
+ containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
+ )
+ let sectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sectionSize)
+ if let sectionView = self.section.view {
+ if sectionView.superview == nil {
+ self.addSubview(sectionView)
+ }
+ transition.setFrame(view: sectionView, frame: sectionFrame)
+ }
+ contentHeight += sectionSize.height
+ contentHeight += 112.0
+
+ 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 BotCheckoutShippingOptionScreenComponent: Component {
+ typealias EnvironmentType = ViewControllerComponentContainer.Environment
+
+ let context: AccountContext
+ let currency: String
+ let options: [BotPaymentShippingOption]
+ let currentId: String?
+ let applyValue: (String) -> Void
+
+ init(
+ context: AccountContext,
+ currency: String,
+ options: [BotPaymentShippingOption],
+ currentId: String?,
+ applyValue: @escaping (String) -> Void
+ ) {
+ self.context = context
+ self.currency = currency
+ self.options = options
+ self.currentId = currentId
+ self.applyValue = applyValue
+ }
+
+ static func ==(lhs: BotCheckoutShippingOptionScreenComponent, rhs: BotCheckoutShippingOptionScreenComponent) -> Bool {
+ if lhs.context !== rhs.context {
+ return false
+ }
+ if lhs.currency != rhs.currency {
+ return false
+ }
+ if lhs.options != rhs.options {
+ return false
+ }
+ if lhs.currentId != rhs.currentId {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView {
+ private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>()
+ private let animateOut = ActionSlot>()
+
+ private var component: BotCheckoutShippingOptionScreenComponent?
+ private weak var state: EmptyComponentState?
+ private var selectedId: String?
+ private var isDismissing = false
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func update(component: BotCheckoutShippingOptionScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ if self.component == nil {
+ if let currentId = component.currentId, component.options.contains(where: { $0.id == currentId }) {
+ self.selectedId = currentId
+ } else {
+ self.selectedId = nil
+ }
+ }
+
+ self.component = component
+ self.state = state
+
+ let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value
+ let controller = environmentValue.controller
+ let theme = environmentValue.theme.withModalBlocksBackground()
+
+ let dismiss: (Bool) -> Void = { [weak self] animated in
+ guard let self, !self.isDismissing else {
+ return
+ }
+ self.isDismissing = true
+
+ let performDismiss: () -> Void = {
+ if let controller = controller() as? BotCheckoutShippingOptionScreen {
+ controller.completePendingDismiss()
+ controller.dismiss(animated: false)
+ }
+ }
+
+ if animated {
+ self.animateOut.invoke(Action { _ in
+ performDismiss()
+ })
+ } else {
+ performDismiss()
+ }
+ }
+
+ let sheetSize = self.sheet.update(
+ transition: transition,
+ component: AnyComponent(ResizableSheetComponent(
+ content: AnyComponent(BotCheckoutShippingOptionContentComponent(
+ currency: component.currency,
+ options: component.options,
+ selectedId: self.selectedId,
+ selectOption: { [weak self] id in
+ guard let self else {
+ return
+ }
+ self.selectedId = id
+ self.state?.updated(transition: .spring(duration: 0.35))
+ }
+ )),
+ titleItem: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: environmentValue.strings.Checkout_ShippingMethod,
+ font: Font.semibold(17.0),
+ textColor: theme.list.itemPrimaryTextColor
+ )),
+ maximumNumberOfLines: 1
+ )),
+ leftItem: AnyComponent(
+ GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: theme.overallDarkAppearance,
+ state: .glass,
+ component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
+ BundleIconComponent(
+ name: "Navigation/Close",
+ tintColor: theme.chat.inputPanel.panelControlColor
+ )
+ )),
+ action: { _ in
+ dismiss(true)
+ }
+ )
+ ),
+ //TODO:localize
+ bottomItem: AnyComponent(ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: theme.list.itemCheckColors.fillColor,
+ foreground: theme.list.itemCheckColors.foregroundColor,
+ pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
+ ),
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("proceed"),
+ component: AnyComponent(ButtonTextContentComponent(
+ text: "Proceed",
+ badge: 0,
+ textColor: theme.list.itemCheckColors.foregroundColor,
+ badgeBackground: theme.list.itemCheckColors.foregroundColor,
+ badgeForeground: theme.list.itemCheckColors.fillColor
+ ))
+ ),
+ isEnabled: self.selectedId != nil,
+ displaysProgress: false,
+ action: { [weak self] in
+ guard let self, let component = self.component, let selectedId = self.selectedId else {
+ return
+ }
+ component.applyValue(selectedId)
+ dismiss(true)
+ }
+ )),
+ backgroundColor: .color(theme.list.modalBlocksBackgroundColor),
+ animateOut: self.animateOut
+ )),
+ environment: {
+ environmentValue
+ ResizableSheetComponentEnvironment(
+ theme: theme,
+ statusBarHeight: environmentValue.statusBarHeight,
+ safeInsets: environmentValue.safeInsets,
+ inputHeight: 0.0,
+ metrics: environmentValue.metrics,
+ deviceMetrics: environmentValue.deviceMetrics,
+ isDisplaying: environmentValue.isVisible,
+ isCentered: environmentValue.metrics.widthClass == .regular,
+ screenSize: availableSize,
+ regularMetricsSize: nil,
+ dismiss: { animated in
+ dismiss(animated)
+ }
+ )
+ },
+ forceUpdate: true,
+ containerSize: availableSize
+ )
+ self.sheet.parentState = state
+ if let sheetView = self.sheet.view {
+ if sheetView.superview == nil {
+ self.addSubview(sheetView)
+ }
+ transition.setFrame(view: sheetView, frame: CGRect(origin: .zero, size: sheetSize))
+ }
+
+ 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)
+ }
+}
+
+final class BotCheckoutShippingOptionScreen: ViewControllerComponentContainer {
+ private var isDismissed = false
+ private var dismissCompletion: (() -> Void)?
+
+ init(context: AccountContext, currency: String, options: [BotPaymentShippingOption], currentId: String?, applyValue: @escaping (String) -> Void) {
+ super.init(
+ context: context,
+ component: BotCheckoutShippingOptionScreenComponent(
+ context: context,
+ currency: currency,
+ options: options,
+ currentId: currentId,
+ applyValue: applyValue
+ ),
+ navigationBarAppearance: .none
+ )
+
+ self.statusBar.statusBarStyle = .Ignore
+ self.navigationPresentation = .flatModal
+ self.blocksBackgroundWhenInOverlay = true
+ }
+
+ required init(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ fileprivate func completePendingDismiss() {
+ let dismissCompletion = self.dismissCompletion
+ self.dismissCompletion = nil
+ dismissCompletion?()
+ }
+
+ func dismissAnimated() {
+ if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View {
+ view.dismissAnimated()
+ }
+ }
+
+ override func dismiss(completion: (() -> Void)? = nil) {
+ if !self.isDismissed {
+ self.isDismissed = true
+ self.dismissCompletion = completion
+
+ if let view = self.node.hostView.findTaggedView(tag: ResizableSheetComponent.View.Tag()) as? ResizableSheetComponent.View {
+ view.dismissAnimated()
+ } else {
+ self.completePendingDismiss()
+ self.dismiss(animated: false)
+ }
+ }
+ }
+}
diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift
index 4967f82d46..6ca5cb0f41 100644
--- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift
+++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift
@@ -65,7 +65,7 @@ public final class BrowserBookmarksScreen: ViewController {
}, tapMessage: nil, clickThroughMessage: { _, _ in
}, toggleMessagesSelection: { _, _ in
}, sendCurrentMessage: { _, _ in
- }, sendMessage: { _ in
+ }, sendMessage: { _, _ in
}, sendSticker: { _, _, _, _, _, _, _, _, _ in
return false
}, sendEmoji: { _, _, _ in
@@ -83,8 +83,8 @@ public final class BrowserBookmarksScreen: ViewController {
controller.dismiss()
}
}, openExternalInstantPage: { _ in
- }, shareCurrentLocation: {
- }, shareAccountContact: {
+ }, shareCurrentLocation: { _ in
+ }, shareAccountContact: { _ in
}, sendBotCommand: { _, _ in
}, openInstantPage: { message, _ in
if let openMessageImpl = openMessageImpl {
@@ -134,7 +134,7 @@ public final class BrowserBookmarksScreen: ViewController {
}, displaySwipeToReplyHint: {
}, dismissReplyMarkupMessage: { _ in
}, openMessagePollResults: { _, _ in
- }, openPollCreation: { _ in
+ }, openPollCreation: { _, _ in
}, openPollMedia: { _, _ in
}, displayPollSolution: { _, _ in
}, displayPsa: { _, _ in
@@ -402,7 +402,7 @@ public final class BrowserBookmarksScreen: ViewController {
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_Close, style: .plain, target: self, action: #selector(self.cancelPressed))
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed))
self.title = self.presentationData.strings.WebBrowser_Bookmarks_Title
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Common_Search, activate: { [weak self] in
diff --git a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift
index ed1ef99ea2..af5e88e55b 100644
--- a/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift
+++ b/submodules/BrowserUI/Sources/BrowserInstantPageContent.swift
@@ -166,6 +166,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.delegate = self
+ self.scrollNode.view.scrollsToTop = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
@@ -1532,12 +1533,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
- let actionSheet = OpenInActionSheetController(context: self.context, item: .url(url: baseUrl), openUrl: { [weak self] url in
+ let actionSheet = OpenInOptionsScreen(context: self.context, item: .url(url: baseUrl), openUrl: { [weak self] url in
if let self {
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
})
- self.present(actionSheet, nil)
+ self.push(actionSheet)
}
private func openMedia(_ media: InstantPageMedia) {
diff --git a/submodules/BrowserUI/Sources/BrowserReadability.swift b/submodules/BrowserUI/Sources/BrowserReadability.swift
index 5a388762d6..8875493226 100644
--- a/submodules/BrowserUI/Sources/BrowserReadability.swift
+++ b/submodules/BrowserUI/Sources/BrowserReadability.swift
@@ -34,7 +34,40 @@ public class Readability: NSObject, WKNavigationDelegate {
if let (html, subresources) = extractHtmlString(from: archiveData) {
self.subresources = subresources
- self.webView.loadHTMLString(html, baseURL: url.baseURL)
+ self.sanitizeHtmlString(html) { [weak self] html in
+ guard let self else {
+ return
+ }
+ self.webView.loadHTMLString(html, baseURL: url.baseURL)
+ }
+ }
+ }
+
+ private func sanitizeHtmlString(_ html: String, completion: @escaping (String) -> Void) {
+ guard let readerModeJS = loadFile(name: "ReaderMode", type: "js") else {
+ completion(htmlByRemovingScriptTags(html))
+ return
+ }
+
+ let domPurifyJS = extractDOMPurifyScript(from: readerModeJS) ?? readerModeJS
+ self.webView.evaluateJavaScript(domPurifyJS) { [weak self] _, error in
+ guard let self else {
+ return
+ }
+
+ guard error == nil, let htmlLiteral = javascriptStringLiteral(html) else {
+ completion(htmlByRemovingScriptTags(html))
+ return
+ }
+
+ let sanitizeJS = """
+ (function(html) {
+ return DOMPurify.sanitize(html, {WHOLE_DOCUMENT: true, ADD_TAGS: ["iframe"]});
+ })(\(htmlLiteral));
+ """
+ self.webView.evaluateJavaScript(sanitizeJS) { result, _ in
+ completion((result as? String) ?? htmlByRemovingScriptTags(html))
+ }
}
}
@@ -49,6 +82,7 @@ public class Readability: NSObject, WKNavigationDelegate {
return
}
guard let page = parseJson(result, url: self.url.absoluteString) else {
+ completion(nil, error)
return
}
completion(page, nil)
@@ -95,6 +129,32 @@ func loadFile(name: String, type: String) -> String? {
return userScript
}
+private func extractDOMPurifyScript(from readerModeJS: String) -> String? {
+ guard let range = readerModeJS.range(of: "\n\n(function () {") else {
+ return nil
+ }
+ return String(readerModeJS[.. String? {
+ guard let data = try? JSONSerialization.data(withJSONObject: [input], options: []),
+ var arrayString = String(data: data, encoding: .utf8),
+ arrayString.count >= 2 else {
+ return nil
+ }
+ arrayString.removeFirst()
+ arrayString.removeLast()
+ return arrayString
+}
+
+private func htmlByRemovingScriptTags(_ input: String) -> String {
+ guard let regex = try? NSRegularExpression(pattern: "", options: [.caseInsensitive]) else {
+ return input
+ }
+ let range = NSRange(input.startIndex ..< input.endIndex, in: input)
+ return regex.stringByReplacingMatches(in: input, options: [], range: range, withTemplate: "")
+}
+
private func extractHtmlString(from webArchiveData: Data) -> (String, [Any]?)? {
if let webArchiveDict = try? PropertyListSerialization.propertyList(from: webArchiveData, format: nil) as? [String: Any],
let mainResource = webArchiveDict["WebMainResource"] as? [String: Any],
@@ -724,30 +784,38 @@ private func parseVideo(_ input: [String: Any], _ media: inout [EngineMedia.Id:
)
}
+private func firstElement(withTag tag: String, in input: [Any], skippingSubtreesWithTag skippedTag: String? = nil) -> [String: Any]? {
+ for item in input {
+ guard let item = item as? [String: Any] else {
+ continue
+ }
+ let itemTag = item["tag"] as? String
+ if itemTag == tag {
+ return item
+ }
+ if itemTag == skippedTag {
+ continue
+ }
+ if let content = item["content"] as? [Any], let result = firstElement(withTag: tag, in: content, skippingSubtreesWithTag: skippedTag) {
+ return result
+ }
+ }
+ return nil
+}
+
private func parseFigure(_ input: [String: Any], _ media: inout [EngineMedia.Id: EngineRawMedia]) -> InstantPageBlock? {
guard let content = input["content"] as? [Any] else {
return nil
}
var block: InstantPageBlock?
var caption: RichText?
- for item in content {
- if let item = item as? [String: Any], let tag = item["tag"] as? String {
- if tag == "p", let content = item["content"] as? [Any] {
- for item in content {
- if let item = item as? [String: Any], let tag = item["tag"] as? String {
- if tag == "iframe" {
- block = parseVideo(item, &media)
- }
- }
- }
- } else if tag == "iframe" {
- block = parseVideo(item, &media)
- } else if tag == "img" {
- block = parseImage(item, &media)
- } else if tag == "figcaption" {
- caption = trim(parseRichText(item, &media))
- }
- }
+ if let iframe = firstElement(withTag: "iframe", in: content, skippingSubtreesWithTag: "figcaption") {
+ block = parseVideo(iframe, &media)
+ } else if let image = firstElement(withTag: "img", in: content, skippingSubtreesWithTag: "figcaption") {
+ block = parseImage(image, &media)
+ }
+ if let figcaption = firstElement(withTag: "figcaption", in: content) {
+ caption = trim(parseRichText(figcaption, &media))
}
guard var block else {
return nil
diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift
index 46503f1a7e..ce7f1a6747 100644
--- a/submodules/BrowserUI/Sources/BrowserScreen.swift
+++ b/submodules/BrowserUI/Sources/BrowserScreen.swift
@@ -1228,7 +1228,7 @@ public class BrowserScreen: ViewController, MinimizableController {
)
var defaultWebBrowser: String? = settings.defaultWebBrowser
- if defaultWebBrowser == nil || defaultWebBrowser == "inAppSafari" {
+ if defaultWebBrowser == nil || defaultWebBrowser == "inApp" || defaultWebBrowser == "inAppSafari" {
defaultWebBrowser = "safari"
}
@@ -1265,6 +1265,7 @@ public class BrowserScreen: ViewController, MinimizableController {
} else {
items.append(.custom(fontItem, false))
+ items.append(.separator)
if case .webPage = contentState.contentType {
let isAvailable = contentState.hasInstantView
@@ -1280,6 +1281,17 @@ public class BrowserScreen: ViewController, MinimizableController {
}
}
+ if toolbarMode != .markdown && [.webPage, .instantPage].contains(contentState.contentType) {
+ if !layout.metrics.isTablet && canOpenIn {
+ items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in
+ if let self {
+ self.context.sharedContext.applicationBindings.openUrl(openInUrl)
+ }
+ action(.default)
+ })))
+ }
+ }
+
if !items.isEmpty {
items.append(.separator)
}
@@ -1290,12 +1302,6 @@ public class BrowserScreen: ViewController, MinimizableController {
action(.default)
})))
}
- if [.webPage, .document].contains(contentState.contentType) {
- items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
- performAction.invoke(.updateSearchActive(true))
- action(.default)
- })))
- }
if canShare && !layout.metrics.isTablet {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_Share, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
@@ -1304,7 +1310,16 @@ public class BrowserScreen: ViewController, MinimizableController {
})))
}
+ if [.webPage, .document].contains(contentState.contentType) {
+ items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Instant View/Settings/Search"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
+ performAction.invoke(.updateSearchActive(true))
+ action(.default)
+ })))
+ }
+
if toolbarMode != .markdown && [.webPage, .instantPage].contains(contentState.contentType) {
+ items.append(.separator)
+
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.WebBrowser_AddBookmark, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { (controller, action) in
performAction.invoke(.addBookmark)
action(.default)
@@ -1316,15 +1331,6 @@ public class BrowserScreen: ViewController, MinimizableController {
action(.default)
})))
}
-
- if !layout.metrics.isTablet && canOpenIn {
- items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.InstantPage_OpenInBrowser(openInTitle).string, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Browser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] (controller, action) in
- if let self {
- self.context.sharedContext.applicationBindings.openUrl(openInUrl)
- }
- action(.default)
- })))
- }
}
return ContextController.Items(content: .list(items))
}
diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
index eb51c84e4b..28bf96d2a9 100644
--- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
+++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
@@ -1868,7 +1868,7 @@ public final class CalendarMessageScreen: ViewController {
self._hasGlassStyle = true
self.navigationPresentation = .modal
- self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(dismissPressed)), animated: false)
+ self.navigationItem.setLeftBarButton(UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(dismissPressed)), animated: false)
self.navigationItem.setTitle(self.presentationData.strings.MessageCalendar_Title, animated: false)
if self.enableMessageRangeDeletion {
@@ -1894,7 +1894,7 @@ public final class CalendarMessageScreen: ViewController {
self.node.toggleSelectionMode()
if self.node.selectionState != nil {
- self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true)
+ self.navigationItem.setRightBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true)
} else {
self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.toggleSelectPressed)), animated: true)
}
@@ -1904,7 +1904,7 @@ public final class CalendarMessageScreen: ViewController {
self.node.selectDay(timestamp: timestamp)
if self.node.selectionState != nil {
- self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true)
+ self.navigationItem.setRightBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.toggleSelectPressed)), animated: true)
}
}
diff --git a/submodules/CallListUI/Sources/CallListCallItem.swift b/submodules/CallListUI/Sources/CallListCallItem.swift
index 507eeb5dd1..9f9b853ea7 100644
--- a/submodules/CallListUI/Sources/CallListCallItem.swift
+++ b/submodules/CallListUI/Sources/CallListCallItem.swift
@@ -785,7 +785,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
strongSelf.view.accessibilityCustomActions = [UIAccessibilityCustomAction(name: item.presentationData.strings.Common_Delete, target: strongSelf, selector: #selector(strongSelf.performLocalAccessibilityCustomAction(_:)))]
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]))
strongSelf.setRevealOptionsOpened(item.revealed, animated: animated)
}
})
diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift
index 69e8defa35..97ec0f7cd9 100644
--- a/submodules/CallListUI/Sources/CallListController.swift
+++ b/submodules/CallListUI/Sources/CallListController.swift
@@ -213,7 +213,7 @@ public final class CallListController: TelegramBaseController {
if let isEmpty = self.isEmpty, isEmpty {
} else {
if self.editingMode {
- self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed))
} else {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
}
@@ -222,7 +222,7 @@ public final class CallListController: TelegramBaseController {
//self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCallIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.callPressed))
case .navigation:
if self.editingMode {
- self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
+ self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed))
} else {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
}
@@ -679,7 +679,7 @@ public final class CallListController: TelegramBaseController {
switch self.mode {
case .tab:
- self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)), animated: true)
+ self.navigationItem.setLeftBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)), animated: true)
self.navigationItem.setRightBarButton(UIBarButtonItem(customDisplayNode: buttonNode), animated: true)
self.navigationItem.rightBarButtonItem?.setCustomAction({
@@ -691,7 +691,7 @@ public final class CallListController: TelegramBaseController {
pressedImpl?()
})
- self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)), animated: true)
+ self.navigationItem.setRightBarButton(UIBarButtonItem(title: "___done", style: .done, target: self, action: #selector(self.donePressed)), animated: true)
}
self.controllerNode.updateState { state in
diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift
index b4b90095fa..ca0a001822 100644
--- a/submodules/CallListUI/Sources/CallListControllerNode.swift
+++ b/submodules/CallListUI/Sources/CallListControllerNode.swift
@@ -940,7 +940,7 @@ final class CallListControllerNode: ASDisplayNode {
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
let inset: CGFloat
- if layout.size.width >= 375.0 {
+ if layout.size.width >= 320.0 {
inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
} else {
inset = 0.0
diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD
index d3b4bd3af6..ed6f05ff25 100644
--- a/submodules/ChatListUI/BUILD
+++ b/submodules/ChatListUI/BUILD
@@ -125,9 +125,11 @@ swift_library(
"//submodules/TelegramUI/Components/ChatList/ChatListSearchFiltersContainerNode",
"//submodules/TelegramUI/Components/ChatList/ChatListHeaderNoticeComponent",
"//submodules/TelegramUI/Components/AlertComponent",
+ "//submodules/TelegramUI/Components/AlertComponent/AlertHeaderComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent",
"//submodules/TelegramUI/Components/AvatarComponent",
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
+ "//submodules/TelegramUI/Components/GlassControls",
],
visibility = [
"//visibility:public",
diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift
index 24b23d24e6..0d0908f79e 100644
--- a/submodules/ChatListUI/Sources/ChatContextMenus.swift
+++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift
@@ -503,14 +503,26 @@ func chatContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, promoI
joinChannelDisposable.set(nil)
}
+ var didJoin = false
joinChannelDisposable.set((createSignal
- |> deliverOnMainQueue).start(next: { _ in
+ |> deliverOnMainQueue).start(next: { result in
+ switch result {
+ case .joined:
+ didJoin = true
+ case let .webView(webView):
+ if let chatListController = chatListController {
+ context.sharedContext.openJoinChatWebView(context: context, parentController: chatListController, updatedPresentationData: nil, webView: webView)
+ }
+ }
}, error: { _ in
if let chatListController = chatListController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
chatListController.present(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}
}, completed: {
+ if !didJoin {
+ return
+ }
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).startStandalone(next: { peer in
guard let peer = peer else {
diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift
index 6841b665d0..79b806e43d 100644
--- a/submodules/ChatListUI/Sources/ChatListController.swift
+++ b/submodules/ChatListUI/Sources/ChatListController.swift
@@ -59,6 +59,9 @@ import ChatListFilterTabContainerNode
import HeaderPanelContainerComponent
import HorizontalTabsComponent
import GlobalControlPanelsContext
+import AlertComponent
+import AlertHeaderComponent
+import AvatarComponent
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
@@ -5199,15 +5202,26 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|> delay(0.8, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
- let signal: Signal = self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peerId, hash: nil)
+ let signal: Signal = self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peerId, hash: nil)
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
+ var didJoin = false
self.joinForumDisposable.set((signal
- |> deliverOnMainQueue).startStrict(error: { [weak self] error in
+ |> deliverOnMainQueue).startStrict(next: { [weak self] result in
+ guard let self else {
+ return
+ }
+ switch result {
+ case .joined:
+ didJoin = true
+ case let .webView(webView):
+ self.context.sharedContext.openJoinChatWebView(context: self.context, parentController: self, updatedPresentationData: nil, webView: webView)
+ }
+ }, error: { [weak self] error in
guard let strongSelf = self else {
return
}
@@ -5241,36 +5255,39 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
- }, completed: { [weak self] in
- guard let self else {
- return
- }
- Queue.mainQueue().after(0.5) {
- let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
- |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
- guard let self, let peer = peer?._asPeer() else {
- return
- }
- var canEditRank = false
- if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.editRank) {
- canEditRank = true
- } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) {
- canEditRank = true
- }
- if canEditRank {
- let context = self.context
- let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.Chat_JoinedGroup_Text, cancel: self.presentationData.strings.Chat_JoinedGroup_AddTag, destructive: false), elevatedLayout: true, action: { [weak self] action in
- if let self, case .undo = action {
- let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peerId, participantId: context.account.peerId, rank: nil, role: .member)
- self.push(tagController)
- }
- return true
- })
- self.present(controller, in: .current)
- }
- })
- }
})
+ }, completed: { [weak self] in
+ guard let self else {
+ return
+ }
+ if !didJoin {
+ return
+ }
+ Queue.mainQueue().after(0.5) {
+ let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
+ |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
+ guard let self, let peer = peer?._asPeer() else {
+ return
+ }
+ var canEditRank = false
+ if let channel = peer as? TelegramChannel, case .group = channel.info, channel.hasPermission(.editRank) {
+ canEditRank = true
+ } else if let group = peer as? TelegramGroup, !group.hasBannedPermission(.banEditRank) {
+ canEditRank = true
+ }
+ if canEditRank {
+ let context = self.context
+ let controller = UndoOverlayController(presentationData: self.presentationData, content: .actionSucceeded(title: nil, text: self.presentationData.strings.Chat_JoinedGroup_Text, cancel: self.presentationData.strings.Chat_JoinedGroup_AddTag, destructive: false), elevatedLayout: true, action: { [weak self] action in
+ if let self, case .undo = action {
+ let tagController = context.sharedContext.makeChatCustomRankSetupScreen(context: context, peerId: peerId, participantId: context.account.peerId, rank: nil, role: .member)
+ self.push(tagController)
+ }
+ return true
+ })
+ self.present(controller, in: .current)
+ }
+ })
+ }
}))
case .savedMessagesChats:
break
@@ -5375,21 +5392,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if case .broadcast = channel.info {
canClear = false
deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel
- if channel.addressName == nil && channel.flags.contains(.isCreator) {
- canRemoveGlobally = true
- }
} else {
deleteTitle = strongSelf.presentationData.strings.Group_DeleteGroup
- if channel.addressName == nil && channel.flags.contains(.isCreator) {
- canRemoveGlobally = true
- }
+ }
+ if strongSelf.canDeletePeerGloballyAsCreator(mainPeer) {
+ canRemoveGlobally = true
}
if let addressName = channel.addressName, !addressName.isEmpty {
canClear = false
}
}
- } else if case let .legacyGroup(group) = chatPeer {
- if case .creator = group.role {
+ } else if case .legacyGroup = chatPeer {
+ if strongSelf.canDeletePeerGloballyAsCreator(mainPeer) {
canRemoveGlobally = true
}
} else if case let .user(user) = chatPeer, user.botInfo != nil {
@@ -5842,30 +5856,32 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
canRemoveGlobally = true
}
+ if deleteGloballyIfPossible && self.canDeletePeerGloballyAsCreator(mainPeer) {
+ self.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: true, completion: {
+ removed()
+ })
+ completion(true)
+ return
+ }
+
if canRemoveGlobally {
- let actionSheet = ActionSheetController(presentationData: self.presentationData)
- var items: [ActionSheetItem] = []
-
- items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .delete, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder))
-
+ var actions: [AlertScreen.Action] = []
if joined || mainPeer.isDeleted {
- items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in
- actionSheet?.dismissAnimated()
+ actions.append(.init(title: self.presentationData.strings.Common_Delete, type: .defaultDestructive, action: { [weak self] in
self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
removed()
})
completion(true)
}))
} else {
- items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak self, weak actionSheet] in
- actionSheet?.dismissAnimated()
+ actions.append(.init(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, type: .destructive, action: { [weak self] in
self?.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
removed()
})
completion(true)
}))
- items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).string, color: .destructive, action: { [weak self, weak actionSheet] in
- actionSheet?.dismissAnimated()
+
+ actions.append(.init(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).string, type: .destructive, action: { [weak self] in
guard let strongSelf = self else {
return
}
@@ -5882,16 +5898,107 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
], parseMarkdown: true), in: .window(.root))
}))
}
- actionSheet.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- completion(false)
- })
- ])
- ])
- self.present(actionSheet, in: .window(.root))
+ actions.append(.init(title: self.presentationData.strings.Common_Cancel))
+
+ //TODO:localize
+ let title: String = "Delete Chat"
+ var text: String
+ if mainPeer.id == self.context.account.peerId {
+ text = self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmation
+ } else if case let .legacyGroup(chatPeer) = mainPeer {
+ text = self.presentationData.strings.ChatList_LeaveGroupConfirmation("**\(chatPeer.title)**").string
+ } else if case let .channel(chatPeer) = mainPeer {
+ text = self.presentationData.strings.ChatList_LeaveGroupConfirmation("**\(chatPeer.title)**").string
+ } else if case .secretChat = chatPeer {
+ text = self.presentationData.strings.ChatList_DeleteSecretChatConfirmation("**\(chatPeer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))**").string
+ } else {
+ text = self.presentationData.strings.ChatList_DeleteChatConfirmation("**\(chatPeer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))**").string
+ }
+
+ let alertScreen = AlertScreen(
+ context: self.context,
+ configuration: AlertScreen.Configuration(actionAlignment: .vertical),
+ content: [
+ AnyComponentWithIdentity(
+ id: "header",
+ component: AnyComponent(
+ AlertHeaderComponent(
+ component: AnyComponentWithIdentity(id: "user", component: AnyComponent(
+ AvatarComponent(
+ context: self.context,
+ theme: self.presentationData.theme,
+ peer: mainPeer
+ )
+ ))
+ )
+ )
+ ),
+ AnyComponentWithIdentity(
+ id: "title",
+ component: AnyComponent(
+ AlertTitleComponent(title: title)
+ )
+ ),
+ AnyComponentWithIdentity(
+ id: "text",
+ component: AnyComponent(
+ AlertTextComponent(content: .plain(text))
+ )
+ )
+ ],
+ actions: actions
+ )
+
+
+// let actionSheet = ActionSheetController(presentationData: self.presentationData)
+// var items: [ActionSheetItem] = []
+//
+// items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .delete, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder))
+//
+// if joined || mainPeer.isDeleted {
+// items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in
+// actionSheet?.dismissAnimated()
+// self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
+// removed()
+// })
+// completion(true)
+// }))
+// } else {
+// items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak self, weak actionSheet] in
+// actionSheet?.dismissAnimated()
+// self?.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
+// removed()
+// })
+// completion(true)
+// }))
+// items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).string, color: .destructive, action: { [weak self, weak actionSheet] in
+// actionSheet?.dismissAnimated()
+// guard let strongSelf = self else {
+// return
+// }
+// strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationText, actions: [
+// TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
+// completion(false)
+// }),
+// TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: {
+// self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: {
+// removed()
+// })
+// completion(true)
+// })
+// ], parseMarkdown: true), in: .window(.root))
+// }))
+// }
+// actionSheet.setItemGroups([
+// ActionSheetItemGroup(items: items),
+// ActionSheetItemGroup(items: [
+// ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
+// actionSheet?.dismissAnimated()
+// completion(false)
+// })
+// ])
+// ])
+ self.present(alertScreen, in: .window(.root))
} else if peer.peerId == self.context.account.peerId {
self.present(textAlertController(context: self.context, title: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
@@ -5945,6 +6052,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
+ private func canDeletePeerGloballyAsCreator(_ peer: EnginePeer) -> Bool {
+ if case let .channel(channel) = peer {
+ return !channel.isMonoForum && channel.flags.contains(.isCreator) && channel.addressName == nil
+ } else if case let .legacyGroup(group) = peer, case .creator = group.role {
+ return true
+ } else {
+ return false
+ }
+ }
+
func archiveChats(peerIds: [PeerId]) {
guard !peerIds.isEmpty else {
return
@@ -6126,7 +6243,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if case .chatList(.root) = self.chatListDisplayNode.mainContainerNode.location {
super.setToolbar(toolbar, transition: transition)
} else {
- self.chatListDisplayNode.toolbar = toolbar
+ self.chatListDisplayNode.toolbarData = toolbar
self.requestLayout(transition: transition)
}
}
@@ -7046,6 +7163,7 @@ private final class ChatListLocationContext {
if case .chatList(.root) = self.location {
self.rightButton = nil
self.storyButton = nil
+ self.proxyButton = nil
}
let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle
@@ -7061,6 +7179,7 @@ private final class ChatListLocationContext {
if case .chatList(.root) = self.location {
self.rightButton = nil
self.storyButton = nil
+ self.proxyButton = nil
}
self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
content: .text(title: presentationData.strings.Common_Done, isBold: true),
diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift
index 826aa80f86..61cbefe701 100644
--- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift
+++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift
@@ -28,6 +28,7 @@ import MediaPlaybackHeaderPanelComponent
import LiveLocationHeaderPanelComponent
import ChatListHeaderNoticeComponent
import ChatListFilterTabContainerNode
+import GlassControls
public enum ChatListContainerNodeFilter: Equatable {
case all
@@ -1134,8 +1135,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
let navigationBarView = ComponentView()
weak var controller: ChatListControllerImpl?
- var toolbar: Toolbar?
- private var toolbarNode: ToolbarNode?
+ private var toolbar: ComponentView?
+ var toolbarData: Toolbar?
var toolbarActionSelected: ((ToolbarActionOption) -> Void)?
private var isSearchDisplayControllerActive: ChatListNavigationBar.ActiveSearch?
@@ -1395,10 +1396,6 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.mainContainerNode.updatePresentationData(presentationData)
self.inlineStackContainerNode?.updatePresentationData(presentationData)
self.searchDisplayController?.updatePresentationData(presentationData)
-
- if let toolbarNode = self.toolbarNode {
- toolbarNode.updateTheme(ToolbarTheme(rootControllerTheme: self.presentationData.theme))
- }
}
private func updateNavigationBar(layout: ContainerViewLayout, deferScrollApplication: Bool, transition: ComponentTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) {
@@ -1435,6 +1432,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.effectiveContainerNode.currentItemNode.interaction?.openPremiumGift(peers, birthdays)
case .reviewLogin:
break
+ case .reviewBotConnection:
+ break
case let .starsSubscriptionLowBalance(amount, _):
self.effectiveContainerNode.currentItemNode.interaction?.openStarsTopup(amount.value)
case .setupPhoto:
@@ -1458,6 +1457,8 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
switch notice {
case let .reviewLogin(newSessionReview, _):
self.effectiveContainerNode.currentItemNode.interaction?.performActiveSessionAction(newSessionReview, isPositive)
+ case let .reviewBotConnection(newBotConnectionReview, _, _):
+ self.effectiveContainerNode.currentItemNode.interaction?.performBotConnectionReviewAction(newBotConnectionReview, isPositive)
default:
break
}
@@ -1838,53 +1839,101 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
- if let toolbar = self.toolbar {
- var tabBarHeight: CGFloat
- var options: ContainerViewLayoutInsetOptions = []
- if layout.metrics.widthClass == .regular {
- options.insert(.input)
+ if let toolbarData = self.toolbarData {
+ var panelsBottomInset: CGFloat = layout.insets(options: []).bottom
+ if layout.metrics.widthClass == .regular, let inputHeight = layout.inputHeight, inputHeight != 0.0 {
+ panelsBottomInset = inputHeight + 8.0
}
-
- var heightInset: CGFloat = 0.0
- if case .forum = self.location {
- heightInset = 4.0
- }
-
- let bottomInset: CGFloat = layout.insets(options: options).bottom
- if !layout.safeInsets.left.isZero {
- tabBarHeight = 34.0 + bottomInset
- insets.bottom += 34.0
+ if panelsBottomInset == 0.0 {
+ panelsBottomInset = 8.0
} else {
- tabBarHeight = 49.0 - heightInset + bottomInset
- insets.bottom += 49.0 - heightInset
+ panelsBottomInset = max(panelsBottomInset, 8.0)
}
- let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))
+ let sideInset: CGFloat = 20.0
+ let toolbarHeight = 44.0
+ let toolbarFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - panelsBottomInset - toolbarHeight), size: CGSize(width: layout.size.width - sideInset * 2.0, height: toolbarHeight))
- if let toolbarNode = self.toolbarNode {
- transition.updateFrame(node: toolbarNode, frame: toolbarFrame)
- toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
+ let toolbar: ComponentView
+ var toolbarTransition = ComponentTransition(transition)
+ if let current = self.toolbar {
+ toolbar = current
} else {
- let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: self.presentationData.theme), displaySeparator: true, left: { [weak self] in
- self?.toolbarActionSelected?(.left)
- }, right: { [weak self] in
- self?.toolbarActionSelected?(.right)
- }, middle: { [weak self] in
- self?.toolbarActionSelected?(.middle)
- })
- toolbarNode.frame = toolbarFrame
- toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate)
- self.addSubnode(toolbarNode)
- self.toolbarNode = toolbarNode
- if transition.isAnimated {
- toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ toolbar = ComponentView()
+ self.toolbar = toolbar
+ toolbarTransition = .immediate
+ }
+
+ let _ = toolbar.update(
+ transition: toolbarTransition,
+ component: AnyComponent(GlassControlPanelComponent(
+ theme: self.presentationData.theme,
+ leftItem: toolbarData.leftAction.flatMap { value in
+ return GlassControlPanelComponent.Item(
+ items: [GlassControlGroupComponent.Item(
+ id: "left_" + value.title,
+ content: .text(value.title),
+ action: value.isEnabled ? { [weak self] in
+ guard let self else {
+ return
+ }
+ self.toolbarActionSelected?(.left)
+ } : nil
+ )],
+ background: .panel
+ )
+ },
+ centralItem: toolbarData.middleAction.flatMap { value in
+ return GlassControlPanelComponent.Item(
+ items: [GlassControlGroupComponent.Item(
+ id: "right_" + value.title,
+ content: .text(value.title),
+ action: value.isEnabled ? { [weak self] in
+ guard let self else {
+ return
+ }
+ self.toolbarActionSelected?(.middle)
+ } : nil
+ )],
+ background: .panel
+ )
+ },
+ rightItem: toolbarData.rightAction.flatMap { value in
+ return GlassControlPanelComponent.Item(
+ items: [GlassControlGroupComponent.Item(
+ id: "right_" + value.title,
+ content: .text(value.title),
+ action: value.isEnabled ? { [weak self] in
+ guard let self else {
+ return
+ }
+ self.toolbarActionSelected?(.right)
+ } : nil
+ )],
+ background: .panel
+ )
+ },
+ centerAlignmentIfPossible: true
+ )),
+ environment: {},
+ containerSize: toolbarFrame.size
+ )
+
+ if let toolbarView = toolbar.view {
+ if toolbarView.superview == nil {
+ self.view.addSubview(toolbarView)
+ toolbarView.alpha = 0.0
}
+ toolbarTransition.setFrame(view: toolbarView, frame: toolbarFrame)
+ ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 1.0)
+ }
+ } else if let toolbar = self.toolbar {
+ self.toolbar = nil
+ if let toolbarView = toolbar.view {
+ ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 0.0, completion: { [weak toolbarView] _ in
+ toolbarView?.removeFromSuperview()
+ })
}
- } else if let toolbarNode = self.toolbarNode {
- self.toolbarNode = nil
- transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in
- toolbarNode?.removeFromSupernode()
- })
}
var childrenLayout = layout
diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift
index 6b75d9a15d..98fd3fb9f6 100644
--- a/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift
+++ b/submodules/ChatListUI/Sources/ChatListFilterPresetCategoryItem.swift
@@ -21,7 +21,7 @@ enum ChatListFilterCategoryIcon {
case archived
}
-final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
+final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem {
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String
@@ -31,6 +31,10 @@ final class ChatListFilterPresetCategoryItem: ListViewItem, ItemListItem {
let sectionId: ItemListSectionId
let updatedRevealedOptions: (Bool) -> Void
let remove: () -> Void
+
+ var hasActiveRevealOptions: Bool {
+ return self.isRevealed
+ }
init(
presentationData: ItemListPresentationData,
@@ -110,6 +114,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
private var item: ChatListFilterPresetCategoryItem?
private var layoutParams: ListViewItemLayoutParams?
+ private var isHighlighted = false
private var editableControlNode: ItemListEditableControlNode?
@@ -177,7 +182,7 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
var titleAttributedString: NSAttributedString?
let peerRevealOptions: [ItemListRevealOption]
- peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]
+ peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]
let rightInset: CGFloat = params.rightInset
@@ -326,26 +331,29 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
+ let topStripeIsHidden: Bool
switch neighbors.top {
case .sameSection(false):
- strongSelf.topStripeNode.isHidden = true
+ topStripeIsHidden = true
default:
hasTopCorners = true
- strongSelf.topStripeNode.isHidden = hasCorners
+ topStripeIsHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
+ let bottomStripeIsHidden: Bool
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset + editingOffset
bottomStripeOffset = -separatorHeight
- strongSelf.bottomStripeNode.isHidden = false
+ bottomStripeIsHidden = false
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
- strongSelf.bottomStripeNode.isHidden = hasCorners
+ bottomStripeIsHidden = hasCorners
}
+ strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions)
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
@@ -362,10 +370,9 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
strongSelf.avatarNode.image = updatedAvatarImage
}
- strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
+ strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition)
strongSelf.backgroundNode.isHidden = false
- strongSelf.highlightedBackgroundNode.isHidden = true
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
@@ -379,39 +386,8 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
- if highlighted {
- self.highlightedBackgroundNode.alpha = 1.0
- if self.highlightedBackgroundNode.supernode == nil {
- var anchorNode: ASDisplayNode?
- if self.bottomStripeNode.supernode != nil {
- anchorNode = self.bottomStripeNode
- } else if self.topStripeNode.supernode != nil {
- anchorNode = self.topStripeNode
- } else if self.backgroundNode.supernode != nil {
- anchorNode = self.backgroundNode
- }
- if let anchorNode = anchorNode {
- self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
- } else {
- self.addSubnode(self.highlightedBackgroundNode)
- }
- }
- } else {
- if self.highlightedBackgroundNode.supernode != nil {
- if animated {
- self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
- if let strongSelf = self {
- if completed {
- strongSelf.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- })
- self.highlightedBackgroundNode.alpha = 0.0
- } else {
- self.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- }
+ self.isHighlighted = highlighted
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
@@ -446,6 +422,12 @@ class ChatListFilterPresetCategoryItemNode: ItemListRevealOptionsItemNode, ItemL
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: revealOffset + editingOffset + params.leftInset + 15.0, y: self.avatarNode.frame.minY), size: self.avatarNode.bounds.size))
}
+
+ override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
+ }
override func revealOptionsInteractivelyOpened() {
if let item = self.item {
diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift
index 3d58d6a74c..54e099ffe6 100644
--- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift
+++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift
@@ -2071,11 +2071,16 @@ public func chatListFilterPresetController(context: AccountContext, currentPrese
)
|> deliverOnMainQueue
|> map { presentationData, stateWithPeers, peerView, premiumLimits, sharedLinks, currentPreset -> (ItemListControllerState, (ItemListNodeState, Any)) in
+ var presentationData = presentationData
+
+ let updatedTheme = presentationData.theme.withModalBlocksBackground()
+ presentationData = presentationData.withUpdated(theme: updatedTheme)
+
let (state, includePeers, excludePeers) = stateWithPeers
let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false
- let leftNavigationButton = ItemListNavigationButton(content: .text("___close"), style: .regular, enabled: true, action: {
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
if let attemptNavigationImpl {
attemptNavigationImpl({ value in
if value {
@@ -2086,7 +2091,7 @@ public func chatListFilterPresetController(context: AccountContext, currentPrese
dismissImpl?()
}
})
- let rightNavigationButton = ItemListNavigationButton(content: .text("___done"), style: .bold, enabled: state.isComplete, action: {
+ let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: state.isComplete, action: {
applyImpl?(false, {
dismissImpl?()
})
diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift
index 0d90835b6f..f4817f217f 100644
--- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift
+++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift
@@ -613,6 +613,11 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
)
)
|> map { presentationData, state, filtersWithCountsValue, preferences, updatedFilterOrderValue, suggestedFilters, peer, allLimits, displayTags -> (ItemListControllerState, (ItemListNodeState, Any)) in
+ var presentationData = presentationData
+
+ let updatedTheme = presentationData.theme.withModalBlocksBackground()
+ presentationData = presentationData.withUpdated(theme: updatedTheme)
+
let isPremium = peer?.isPremium ?? false
let limits = allLimits.0
let premiumLimits = allLimits.1
@@ -622,13 +627,13 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
case .default:
leftNavigationButton = nil
case .modal:
- leftNavigationButton = ItemListNavigationButton(content: .text("___close"), style: .regular, enabled: true, action: {
+ leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
}
let rightNavigationButton: ItemListNavigationButton?
if state.isEditing {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
let _ = (updatedFilterOrder.get()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak updatedFilterOrder] updatedFilterOrderValue in
diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift
index 84e5395075..37cba290b4 100644
--- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift
+++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift
@@ -16,7 +16,7 @@ struct ChatListFilterPresetListItemEditing: Equatable {
let revealed: Bool
}
-final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
+final class ChatListFilterPresetListItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem {
let context: AccountContext
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
@@ -33,6 +33,10 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
let action: () -> Void
let setItemWithRevealedOptions: (Int32?, Int32?) -> Void
let remove: () -> Void
+
+ var hasActiveRevealOptions: Bool {
+ return self.editing.revealed
+ }
init(
context: AccountContext,
@@ -145,6 +149,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
private var item: ChatListFilterPresetListItem?
private var layoutParams: ListViewItemLayoutParams?
+ private var isHighlighted = false
override var canBeSelected: Bool {
if self.editableControlNode != nil {
@@ -242,7 +247,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
let peerRevealOptions: [ItemListRevealOption]
if item.editing.editable && item.canBeDeleted {
- peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]
+ peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]
} else {
peerRevealOptions = []
}
@@ -396,26 +401,29 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
+ let topStripeIsHidden: Bool
switch neighbors.top {
case .sameSection(false):
- strongSelf.topStripeNode.isHidden = true
+ topStripeIsHidden = true
default:
hasTopCorners = true
- strongSelf.topStripeNode.isHidden = hasCorners
+ topStripeIsHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
+ let bottomStripeIsHidden: Bool
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset + editingOffset
bottomStripeOffset = -separatorHeight
- strongSelf.bottomStripeNode.isHidden = false
+ bottomStripeIsHidden = false
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
- strongSelf.bottomStripeNode.isHidden = hasCorners
+ bottomStripeIsHidden = hasCorners
}
+ strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions)
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
@@ -425,9 +433,9 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
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 - params.rightInset - separatorRightInset, height: separatorHeight)))
- transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset), size: titleLayout.size))
+ transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + 1.0), size: titleLayout.size))
- let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: verticalInset), size: labelLayout.size)
+ let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: verticalInset + 1.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
@@ -496,7 +504,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
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))
- strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
+ strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition)
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
@@ -517,39 +525,8 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
- if highlighted {
- self.highlightedBackgroundNode.alpha = 1.0
- if self.highlightedBackgroundNode.supernode == nil {
- var anchorNode: ASDisplayNode?
- if self.bottomStripeNode.supernode != nil {
- anchorNode = self.bottomStripeNode
- } else if self.topStripeNode.supernode != nil {
- anchorNode = self.topStripeNode
- } else if self.backgroundNode.supernode != nil {
- anchorNode = self.backgroundNode
- }
- if let anchorNode = anchorNode {
- self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
- } else {
- self.addSubnode(self.highlightedBackgroundNode)
- }
- }
- } else {
- if self.highlightedBackgroundNode.supernode != nil {
- if animated {
- self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
- if let strongSelf = self {
- if completed {
- strongSelf.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- })
- self.highlightedBackgroundNode.alpha = 0.0
- } else {
- self.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- }
+ self.isHighlighted = highlighted
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
@@ -605,6 +582,12 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(view: tagIconView, frame: tagIconFrame)
}
}
+
+ override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
+ }
override func revealOptionsInteractivelyOpened() {
if let item = self.item {
diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift
index 4cce378d6a..a2aa4bccb7 100644
--- a/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift
+++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListSuggestedItem.swift
@@ -215,14 +215,8 @@ 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
- switch item.systemStyle {
- case .glass:
- verticalInset = 15.0
- case .legacy:
- verticalInset = 11.0
- }
- let titleSpacing: CGFloat = 3.0
+ let verticalInset: CGFloat = 11.0
+ let titleSpacing: CGFloat = 2.0
let height: CGFloat
height = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + labelLayout.size.height
diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift
index 9a5b05d768..65b39eb784 100644
--- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift
+++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift
@@ -224,7 +224,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
context.engine.accountData.addAppLogEvent(type: "search_global_open_message", data: .dictionary(["msg_id": .number(Double(messageId.id))]))
}
}, openUrl: { [weak self] url in
- let _ = openUserGeneratedUrl(context: context, peerId: nil, url: url, concealed: false, present: { c in
+ let _ = context.sharedContext.openUserGeneratedUrl(context: context, peerId: nil, url: url, webpage: nil, concealed: false, forceConcealed: false, skipUrlAuth: false, skipConcealedAlert: false, forceDark: false, present: { c in
present(c, nil)
}, openResolved: { [weak self] resolved in
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { peerId, navigation in
@@ -240,7 +240,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}, dismissInput: {
self?.dismissInput()
}, contentContext: nil, progress: nil, completion: nil)
- })
+ }, progress: nil, alertDisplayUpdated: nil, concealedAlertOption: nil)
}, clearRecentSearch: { [weak self] in
guard let strongSelf = self else {
return
diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift
index 0b47c1eacf..498ebd2303 100644
--- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift
+++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift
@@ -3517,6 +3517,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in
+ }, performBotConnectionReviewAction: { _, _ in
}, openChatFolderUpdates: {
}, hideChatFolderUpdates: {
}, openStories: { [weak self] subject, sourceNode in
@@ -5814,6 +5815,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in
+ }, performBotConnectionReviewAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in
}, openStarsTopup: { _ in
@@ -6540,10 +6542,10 @@ private final class EmptyResultsButton: 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.9),
- cornerRadius: 10.0
+ pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
),
content: buttonContent,
isEnabled: isEnabled,
@@ -6555,7 +6557,7 @@ private final class EmptyResultsButton: Component {
}
)),
environment: {},
- containerSize: CGSize(width: availableSize.width, height: 50.0)
+ containerSize: CGSize(width: availableSize.width, height: 52.0)
)
if let buttonView = self.button.view {
if buttonView.superview == nil {
diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift
index 68b09cf72a..1189898132 100644
--- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift
+++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift
@@ -156,7 +156,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
- }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in
+ }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, performBotConnectionReviewAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in
}, editPeer: { _ in
}, openWebApp: { _ in
}, openPhotoSetup: {
diff --git a/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift
index 411cd295b9..4abb01fb48 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListArchiveInfoItem.swift
@@ -185,6 +185,7 @@ class ChatListArchiveInfoItemNode: ListViewItemNode, ASScrollViewDelegate {
self.view.disablesInteractiveTransitionGestureRecognizer = true
+ self.scrollNode.view.scrollsToTop = false
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.isPagingEnabled = true
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift
index 7cb442d48e..62ec39b3c2 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift
@@ -529,10 +529,10 @@ public class ChatListItem: ListViewItem {
public 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 = ChatListItemNode()
- let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
+ let (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
node.insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader)
- let (nodeLayout, apply) = node.asyncLayout()(self, params, first, last, firstWithHeader, nextIsPinned)
+ let (nodeLayout, apply) = node.asyncLayout()(self, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
@@ -556,13 +556,13 @@ public class ChatListItem: ListViewItem {
nodeValue.setupItem(item: self, synchronousLoads: false)
let layout = nodeValue.asyncLayout()
async {
- let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
+ let (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) = ChatListItem.mergeType(item: self, previousItem: previousItem, nextItem: nextItem)
var animated = true
if case .None = animation {
animated = false
}
- let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, nextIsPinned)
+ let (nodeLayout, apply) = layout(self, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply(false, animated)
@@ -600,7 +600,7 @@ public class ChatListItem: ListViewItem {
}
}
- static func mergeType(item: ChatListItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool) {
+ static func mergeType(item: ChatListItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, nextHasActiveRevealControls: Bool) {
var first = false
var last = false
var firstWithHeader = false
@@ -617,14 +617,16 @@ public class ChatListItem: ListViewItem {
firstWithHeader = item.header != nil
}
var nextIsPinned = false
+ var nextHasActiveRevealControls = false
if let nextItem = nextItem as? ChatListItem {
if case let .chatList(nextIndex) = nextItem.index, nextIndex.pinningIndex != nil {
nextIsPinned = true
}
+ nextHasActiveRevealControls = nextItem.hasActiveRevealControls
} else {
last = true
}
- return (first, last, firstWithHeader, nextIsPinned)
+ return (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls)
}
}
@@ -638,9 +640,9 @@ private let ungroupIcon = ItemListRevealOptionIcon.animation(animation: "anim_un
private let readIcon = ItemListRevealOptionIcon.animation(animation: "anim_read", scale: 1.0, offset: 0.0, replaceColors: nil, flip: false)
private let unreadIcon = ItemListRevealOptionIcon.animation(animation: "anim_unread", scale: 1.0, offset: 0.0, replaceColors: [0x2194fa], flip: false)
private let archiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_archive", scale: 1.0, offset: 2.0, replaceColors: [0xa9a9ad], flip: false)
-private let unarchiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_unarchive", scale: 0.642, offset: -9.0, replaceColors: [0xa9a9ad], flip: false)
-private let hideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: 2.0, replaceColors: [0xbdbdc2], flip: false)
-private let unhideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: -20.0, replaceColors: [0xbdbdc2], flip: true)
+private let unarchiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_unarchive", scale: 0.52, offset: -6.0, replaceColors: [0xa9a9ad], flip: false)
+private let hideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.1, offset: 2.0, replaceColors: [0xbdbdc2], flip: false)
+private let unhideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: -15.0, replaceColors: [0xbdbdc2], flip: true)
private let startIcon = ItemListRevealOptionIcon.animation(animation: "anim_play", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false)
private let closeIcon = ItemListRevealOptionIcon.animation(animation: "anim_pause", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false)
@@ -686,28 +688,28 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem
if !isEditing {
if case .savedMessagesChats = location {
if isPinned {
- options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
} else if case .chatList(.archive) = location {
if isPinned {
- options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
} else {
if let isMuted = isMuted {
if isMuted {
- options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
}
if canDelete {
- options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, textColor: theme.list.itemDisclosureActions.destructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, iconColor: theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
if case .savedMessagesChats = location {
} else {
@@ -729,10 +731,10 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem
}
if canArchive {
if canArchivePeer(id: peerId, accountPeerId: accountPeerId) {
- options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.inactive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
} else if canUnarchive {
- options.append(ItemListRevealOption(key: RevealOptionKey.unarchive.rawValue, title: strings.ChatList_UnarchiveAction, icon: unarchiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unarchive.rawValue, title: strings.ChatList_UnarchiveAction, icon: unarchiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.inactive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
@@ -743,9 +745,9 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr
var options: [ItemListRevealOption] = []
if !isEditing {
if hiddenByDefault {
- options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_UnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_UnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_HideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_HideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
return options
@@ -756,9 +758,9 @@ private func forumGeneralRevealOptions(strings: PresentationStrings, theme: Pres
if !isEditing {
if let isMuted = isMuted {
if isMuted {
- options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
@@ -767,16 +769,16 @@ private func forumGeneralRevealOptions(strings: PresentationStrings, theme: Pres
if !isClosed {
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
if canHide {
if !isEditing {
if hiddenByDefault {
- options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_ThreadUnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unhide.rawValue, title: strings.ChatList_ThreadUnhideAction, icon: unhideIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_ThreadHideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.hide.rawValue, title: strings.ChatList_ThreadHideAction, icon: hideIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
@@ -788,21 +790,21 @@ private func forumThreadRevealOptions(strings: PresentationStrings, theme: Prese
if !isEditing {
if let isMuted = isMuted {
if isMuted {
- options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.mute.rawValue, title: strings.ChatList_Mute, icon: muteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
if canDelete {
- options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, textColor: theme.list.itemDisclosureActions.destructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, iconColor: theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
if canOpenClose {
if !isEditing {
if !isClosed {
- options.append(ItemListRevealOption(key: RevealOptionKey.close.rawValue, title: strings.ChatList_CloseAction, icon: closeIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.close.rawValue, title: strings.ChatList_CloseAction, icon: closeIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.inactive.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
- options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.open.rawValue, title: strings.ChatList_StartAction, icon: startIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
@@ -815,7 +817,7 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation
if case .root = groupId {
var options: [ItemListRevealOption] = []
if isUnread {
- options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Read, icon: readIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.neutral1.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Read, icon: readIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, iconColor: theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
var canMarkUnread = true
if case let .channel(channel) = peer, channel.isForumOrMonoForum {
@@ -823,15 +825,15 @@ private func leftRevealOptions(strings: PresentationStrings, theme: Presentation
}
if canMarkUnread {
- options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, textColor: theme.list.itemDisclosureActions.accent.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: strings.DialogList_Unread, icon: unreadIcon, color: theme.list.itemDisclosureActions.accent.fillColor, iconColor: theme.list.itemDisclosureActions.accent.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
if !isEditing {
if isPinned {
- options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
} else {
if filterData == nil || peer.id.namespace != Namespaces.Peer.SecretChat {
- options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
+ options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, iconColor: theme.list.itemDisclosureActions.constructive.foregroundColor, textColor: theme.chatList.dateTextColor))
}
}
}
@@ -1387,9 +1389,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
private var cachedChatListQuoteSearchResult: CachedChatListSearchResult?
private var cachedCustomTextEntities: CachedCustomTextEntities?
- var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, ListViewItemLayoutParams, countersSize: CGFloat)?
+ var layoutParams: (ChatListItem, first: Bool, last: Bool, firstWithHeader: Bool, nextIsPinned: Bool, nextHasActiveRevealControls: Bool, ListViewItemLayoutParams, countersSize: CGFloat)?
private var isHighlighted: Bool = false
+ private var nextHasActiveRevealControls: Bool = false
private var skipFadeout: Bool = false
private var customAnimationInProgress: Bool = false
@@ -1686,7 +1689,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
- let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.5, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4)
+ let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.6, layoutParams.1, layoutParams.2, layoutParams.3, layoutParams.4, layoutParams.5)
let _ = apply(false, false)
}
})
@@ -2036,8 +2039,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let layout = self.asyncLayout()
- let (first, last, firstWithHeader, nextIsPinned) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem)
- let (nodeLayout, apply) = layout(item as! ChatListItem, params, first, last, firstWithHeader, nextIsPinned)
+ let (first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls) = ChatListItem.mergeType(item: item as! ChatListItem, previousItem: previousItem, nextItem: nextItem)
+ let (nodeLayout, apply) = layout(item as! ChatListItem, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls)
apply(false, false)
self.contentSize = nodeLayout.contentSize
self.insets = nodeLayout.insets
@@ -2056,7 +2059,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
var reallyHighlighted: Bool {
- var reallyHighlighted = self.isHighlighted
+ var reallyHighlighted = self.isHighlighted || self.isRevealOptionsActive
if let item = self.item {
if let itemChatLocation = item.content.chatLocation {
if itemChatLocation == item.interaction.highlightedChatLocation?.location {
@@ -2074,6 +2077,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
func updateIsHighlighted(transition: ContainedViewLayoutTransition) {
let highlightProgress: CGFloat = self.item?.interaction.highlightedChatLocation?.progress ?? 1.0
+ transition.updateCornerRadius(node: self.highlightedBackgroundNode, cornerRadius: self.isRevealOptionsActive ? 26.0 : 0.0)
+ self.updateSeparatorAlpha(transition: transition)
if self.reallyHighlighted {
if self.highlightedBackgroundNode.supernode == nil {
@@ -2136,6 +2141,15 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
}
+
+ private func updateSeparatorAlpha(transition: ContainedViewLayoutTransition, inlineNavigationProgress: CGFloat? = nil) {
+ let revealSeparatorAlpha: CGFloat = (self.isRevealOptionsActive || self.isNextRevealOptionsActive || self.nextHasActiveRevealControls) ? 0.0 : 1.0
+ if let inlineNavigationProgress = inlineNavigationProgress ?? self.item?.interaction.inlineNavigationLocation?.progress {
+ transition.updateAlpha(node: self.separatorNode, alpha: (1.0 - inlineNavigationProgress) * revealSeparatorAlpha)
+ } else {
+ transition.updateAlpha(node: self.separatorNode, alpha: revealSeparatorAlpha)
+ }
+ }
override public func tapped() {
guard let item = self.item, item.editing else {
@@ -2153,7 +2167,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
- func asyncLayout() -> (_ item: ChatListItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
+ func asyncLayout() -> (_ item: ChatListItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool, _ nextHasActiveRevealControls: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let dateLayout = TextNode.asyncLayout(self.dateNode)
let textLayout = TextNodeWithEntities.asyncLayout(self.textNode)
let makeTrailingTextBadgeLayout = TextNode.asyncLayout(self.trailingTextBadgeNode)
@@ -2175,8 +2189,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let currentChatListQuoteSearchResult = self.cachedChatListQuoteSearchResult
let currentCustomTextEntities = self.cachedCustomTextEntities
- return { item, params, first, last, firstWithHeader, nextIsPinned in
- let titleFont = Font.medium(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
+ return { item, params, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls in
+ let titleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0))
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
let italicTextFont = Font.italic(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
@@ -2450,7 +2464,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if !useChatListLayout {
avatarLeftInset = 50.0
} else {
- avatarLeftInset = 18.0 + avatarDiameter
+ avatarLeftInset = 24.0 + avatarDiameter
}
}
@@ -2589,7 +2603,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
isUser = true
}
var isGuestChatAuthor = false
- if case let .user(user) = messages.last?.author, let botInfo = user.botInfo, botInfo.flags.contains(.isGuestChat) {
+ if let message = messages.last, case let .user(user) = message.author, user.id != message.id.peerId, let botInfo = user.botInfo, botInfo.flags.contains(.isGuestChat) {
isGuestChatAuthor = true
}
@@ -3503,7 +3517,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let layoutOffset: CGFloat = 0.0
- let rawContentWidth = params.width - leftInset - params.rightInset - 10.0 - editingOffset
+ let rawContentWidth = params.width - leftInset - params.rightInset - 18.0 - editingOffset
let (dateLayout, dateApply) = dateLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@@ -3512,7 +3526,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let (mentionBadgeLayout, mentionBadgeApply) = mentionBadgeLayout(CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), badgeDiameter, badgeFont, currentMentionBadgeImage, mentionBadgeContent)
var actionButtonTitleNodeLayoutAndApply: (TextNodeLayout, () -> TextNode)?
- if case .none = badgeContent, case .none = mentionBadgeContent, case let .chat(itemPeer) = contentPeer, case let .user(user) = itemPeer.chatMainPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
+ if !item.editing, case .none = badgeContent, case .none = mentionBadgeContent, case let .chat(itemPeer) = contentPeer, case let .user(user) = itemPeer.chatMainPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
actionButtonTitleNodeLayoutAndApply = makeActionButtonTitleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.ChatList_InlineButtonOpenApp, font: Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)), textColor: theme.unreadBadgeActiveTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
}
@@ -3640,7 +3654,17 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
textMaxWidth -= 18.0
}
- let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: (authorAttributedString == nil && itemTags.isEmpty && forumThread == nil && topForumTopicItems.isEmpty) ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: textMaxWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
+ let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(
+ attributedString: textAttributedString,
+ backgroundColor: nil,
+ maximumNumberOfLines: (authorAttributedString == nil && itemTags.isEmpty && forumThread == nil && topForumTopicItems.isEmpty) ? 2 : 1,
+ truncationType: .end,
+ constrainedSize: CGSize(width: textMaxWidth, height: .greatestFiniteMagnitude),
+ alignment: .natural,
+ lineSpacing: 0.2,
+ cutout: textCutout,
+ insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)
+ ))
let maxTitleLines: Int
switch item.index {
@@ -3770,15 +3794,15 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
}
} else if case .psa = promoInfo {
peerRevealOptions = [
- ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor)
+ ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor)
]
peerLeftRevealOptions = []
} else if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData {
peerLeftRevealOptions = []
if customMessageListData.commandPrefix != nil {
peerRevealOptions = [
- ItemListRevealOption(key: RevealOptionKey.edit.rawValue, title: item.presentationData.strings.ChatList_ItemMenuEdit, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.neutral2.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral2.foregroundColor),
- ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.ChatList_ItemMenuDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)
+ ItemListRevealOption(key: RevealOptionKey.edit.rawValue, title: item.presentationData.strings.ChatList_ItemMenuEdit, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.neutral2.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.neutral2.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor),
+ ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.ChatList_ItemMenuDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor)
]
} else {
peerRevealOptions = []
@@ -3800,13 +3824,13 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
if actions.contains(.toggleUnread) {
if unreadCount.unread {
- peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Read, icon: readIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor))
+ peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Read, icon: readIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor))
} else {
- peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Unread, icon: unreadIcon, color: item.presentationData.theme.list.itemDisclosureActions.accent.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor))
+ peerLeftRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.toggleMarkedUnread.rawValue, title: item.presentationData.strings.DialogList_Unread, icon: unreadIcon, color: item.presentationData.theme.list.itemDisclosureActions.accent.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor))
}
}
if actions.contains(.delete) {
- peerRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor))
+ peerRevealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.chatList.dateTextColor))
}
}
} else {
@@ -3864,7 +3888,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
return (layout, { [weak self] synchronousLoads, animated in
if let strongSelf = self {
- strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize)
+ strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, nextHasActiveRevealControls, params, countersSize)
+ strongSelf.nextHasActiveRevealControls = nextHasActiveRevealControls
strongSelf.currentItemHeight = itemHeight
strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult
@@ -3998,7 +4023,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0)
- let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
+ let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + 6.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
var avatarScaleOffset: CGFloat = 0.0
var avatarScale: CGFloat = 1.0
if let inlineNavigationLocation = item.interaction.inlineNavigationLocation {
@@ -4291,7 +4316,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let _ = mentionBadgeApply(animateBadges, true)
let _ = onlineApply(animateContent && animateOnline)
- var dateFrame = CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateLayout.size.width, y: contentRect.origin.y + 2.0), size: dateLayout.size)
+ var dateFrame = CGRect(origin: CGPoint(x: contentRect.maxX - dateLayout.size.width, y: contentRect.origin.y + 2.0), size: dateLayout.size)
if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData, customMessageListData.messageCount != nil, customMessageListData.commandPrefix == nil {
dateFrame.origin.x -= 10.0
@@ -4355,16 +4380,17 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.statusNode.fontSize = item.presentationData.fontSize.itemListBaseFontSize
let _ = strongSelf.statusNode.transitionToState(statusState, animated: animateContent)
+ let rightAccessoryVerticalOffset: CGFloat = -2.0
var nextBadgeX: CGFloat = contentRect.maxX
if let _ = currentBadgeBackgroundImage {
- let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0, width: badgeLayout.width, height: badgeLayout.height)
+ let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0 + rightAccessoryVerticalOffset, width: badgeLayout.width, height: badgeLayout.height)
transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame)
nextBadgeX -= badgeLayout.width + 6.0
}
if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil {
- let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height - 2.0, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height)
+ let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height - 2.0 + rightAccessoryVerticalOffset, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height)
transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame)
nextBadgeX -= mentionBadgeLayout.width + 6.0
@@ -4375,7 +4401,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.pinnedIconNode.isHidden = false
let pinnedIconSize = currentPinnedIconImage.size
- let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height - 2.0, width: pinnedIconSize.width, height: pinnedIconSize.height)
+ let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height - 2.0 + rightAccessoryVerticalOffset, width: pinnedIconSize.width, height: pinnedIconSize.height)
strongSelf.pinnedIconNode.frame = pinnedIconFrame
nextBadgeX -= pinnedIconSize.width + 6.0
@@ -4392,11 +4418,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let actionButtonSize = CGSize(width: actionButtonTitleNodeLayout.size.width + actionButtonSideInset * 2.0, height: actionButtonTitleNodeLayout.size.height + actionButtonTopInset + actionButtonBottomInset)
var actionButtonFrame = CGRect(x: nextBadgeX - actionButtonSize.width, y: contentRect.minY + floor((contentRect.height - actionButtonSize.height) * 0.5), width: actionButtonSize.width, height: actionButtonSize.height)
actionButtonFrame.origin.y = max(actionButtonFrame.origin.y, dateFrame.maxY + floor(item.presentationData.fontSize.itemListBaseFontSize * 4.0 / 17.0))
+ actionButtonFrame.origin.y += 4.0
let actionButtonNode: HighlightableButtonNode
+ var animateActionButtonIn = false
if let current = strongSelf.actionButtonNode {
actionButtonNode = current
} else {
+ animateActionButtonIn = true
actionButtonNode = HighlightableButtonNode()
strongSelf.actionButtonNode = actionButtonNode
strongSelf.mainContentContainerNode.addSubnode(actionButtonNode)
@@ -4425,23 +4454,40 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
actionButtonNode.addSubnode(actionButtonTitleNode)
}
+ actionButtonNode.isUserInteractionEnabled = true
actionButtonNode.frame = actionButtonFrame
actionButtonBackgroundView.frame = CGRect(origin: CGPoint(), size: actionButtonFrame.size)
actionButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((actionButtonFrame.width - actionButtonTitleNodeLayout.size.width) * 0.5), y: actionButtonTopInset), size: actionButtonTitleNodeLayout.size)
+ if animateActionButtonIn {
+ actionButtonNode.alpha = 0.0
+ }
+ transition.updateAlpha(node: actionButtonNode, alpha: 1.0)
nextBadgeX -= actionButtonSize.width + 6.0
} else {
- if let actionButtonTitleNode = strongSelf.actionButtonTitleNode {
- actionButtonTitleNode.removeFromSupernode()
- strongSelf.actionButtonTitleNode = nil
- }
- if let actionButtonBackgroundView = strongSelf.actionButtonBackgroundView {
- actionButtonBackgroundView.removeFromSuperview()
- strongSelf.actionButtonBackgroundView = nil
- }
if let actionButtonNode = strongSelf.actionButtonNode {
- actionButtonNode.removeFromSupernode()
+ let actionButtonTitleNode = strongSelf.actionButtonTitleNode
+ let actionButtonBackgroundView = strongSelf.actionButtonBackgroundView
+ actionButtonNode.isUserInteractionEnabled = false
+
+ strongSelf.actionButtonTitleNode = nil
+ strongSelf.actionButtonBackgroundView = nil
strongSelf.actionButtonNode = nil
+
+ transition.updateAlpha(node: actionButtonNode, alpha: 0.0, completion: { [weak actionButtonNode, weak actionButtonTitleNode, weak actionButtonBackgroundView] _ in
+ actionButtonTitleNode?.removeFromSupernode()
+ actionButtonBackgroundView?.removeFromSuperview()
+ actionButtonNode?.removeFromSupernode()
+ })
+ } else {
+ if let actionButtonTitleNode = strongSelf.actionButtonTitleNode {
+ actionButtonTitleNode.removeFromSupernode()
+ strongSelf.actionButtonTitleNode = nil
+ }
+ if let actionButtonBackgroundView = strongSelf.actionButtonBackgroundView {
+ actionButtonBackgroundView.removeFromSuperview()
+ strongSelf.actionButtonBackgroundView = nil
+ }
}
}
@@ -4470,9 +4516,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let titleFrame = CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: contentRect.origin.y + UIScreenPixel), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
- let authorNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height), size: authorLayout)
+ let authorNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 2.0), size: authorLayout)
strongSelf.authorNode.frame = authorNodeFrame
- let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 1.0 + UIScreenPixel + (authorLayout.height.isZero ? 0.0 : (authorLayout.height - 3.0))), size: textLayout.size)
+ let textNodeFrame = CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: contentRect.minY + titleLayout.size.height - 2.0 + (authorLayout.height.isZero ? 0.0 : (authorLayout.height - 3.0))), size: textLayout.size)
if let topForumTopicRect, !isSearching {
let compoundHighlightingNode: LinkHighlightingNode
@@ -5063,20 +5109,24 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
titleBadge.textNode.removeFromSupernode()
}
- let separatorInset: CGFloat
+ let leftSeparatorInset: CGFloat
+ let rightSeparatorInset: CGFloat
if case let .groupReference(groupReferenceData) = item.content, groupReferenceData.hiddenByDefault {
- separatorInset = 0.0
+ leftSeparatorInset = 0.0
+ rightSeparatorInset = 0.0
} else if (!nextIsPinned && isPinned) || last {
- separatorInset = 0.0
+ leftSeparatorInset = 0.0
+ rightSeparatorInset = 0.0
} else {
- separatorInset = editingOffset + leftInset + rawContentRect.origin.x
+ leftSeparatorInset = editingOffset + leftInset + rawContentRect.origin.x
+ rightSeparatorInset = 16.0
}
- transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight)))
+ transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftSeparatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - leftSeparatorInset - rightSeparatorInset, height: separatorHeight)))
if let inlineNavigationLocation = item.interaction.inlineNavigationLocation {
- transition.updateAlpha(node: strongSelf.separatorNode, alpha: 1.0 - inlineNavigationLocation.progress)
+ strongSelf.updateSeparatorAlpha(transition: transition, inlineNavigationProgress: inlineNavigationLocation.progress)
} else {
- transition.updateAlpha(node: strongSelf.separatorNode, alpha: 1.0)
+ strongSelf.updateSeparatorAlpha(transition: transition)
}
if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData {
@@ -5122,7 +5172,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.highlightedBackgroundNode.backgroundColor = highlightedBackgroundColor
let topNegativeInset: CGFloat = 0.0
- strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layoutOffset - separatorHeight - topNegativeInset), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height + separatorHeight + topNegativeInset))
+ strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: strongSelf.revealOffset, y: layoutOffset - separatorHeight - topNegativeInset), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height + separatorHeight + topNegativeInset))
+ transition.updateCornerRadius(node: strongSelf.highlightedBackgroundNode, cornerRadius: strongSelf.isRevealOptionsActive ? 26.0 : 0.0)
if let peerPresence = peerPresence {
strongSelf.peerPresenceManager?.reset(presence: EnginePeer.Presence(status: peerPresence.status, lastActivity: 0), isOnline: online)
@@ -5266,8 +5317,23 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition)
-
+
transition.updateBounds(node: self.contextContainer, bounds: self.contextContainer.frame.offsetBy(dx: -offset, dy: 0.0))
+
+ let highlightedBackgroundFrame = self.highlightedBackgroundNode.frame
+ transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: offset, y: highlightedBackgroundFrame.minY), size: highlightedBackgroundFrame.size))
+ }
+
+ override public func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateIsHighlighted(transition: transition)
+ }
+
+ override public func nextRevealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.nextRevealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateSeparatorAlpha(transition: transition)
}
override public func touchesToOtherItemsPrevented() {
@@ -5471,7 +5537,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
@objc private func performLocalAccessibilityCustomAction(_ action: UIAccessibilityCustomAction) {
if let action = action as? ChatListItemAccessibilityCustomAction {
- self.revealOptionSelected(ItemListRevealOption(key: action.key, title: "", icon: .none, color: .black, textColor: .white), animated: false)
+ self.revealOptionSelected(ItemListRevealOption(key: action.key, title: "", icon: .none, color: .black, iconColor: .white, textColor: .white), animated: false)
}
}
diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift
index a835020531..2dc44bb69b 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift
@@ -106,6 +106,7 @@ public final class ChatListNodeInteraction {
let openActiveSessions: () -> Void
let openBirthdaySetup: () -> Void
let performActiveSessionAction: (NewSessionReview, Bool) -> Void
+ let performBotConnectionReviewAction: (NewBotConnectionReview, Bool) -> Void
let openChatFolderUpdates: () -> Void
let hideChatFolderUpdates: () -> Void
let openStories: (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void
@@ -166,6 +167,7 @@ public final class ChatListNodeInteraction {
openActiveSessions: @escaping () -> Void,
openBirthdaySetup: @escaping () -> Void,
performActiveSessionAction: @escaping (NewSessionReview, Bool) -> Void,
+ performBotConnectionReviewAction: @escaping (NewBotConnectionReview, Bool) -> Void,
openChatFolderUpdates: @escaping () -> Void,
hideChatFolderUpdates: @escaping () -> Void,
openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void,
@@ -213,6 +215,7 @@ public final class ChatListNodeInteraction {
self.openActiveSessions = openActiveSessions
self.openBirthdaySetup = openBirthdaySetup
self.performActiveSessionAction = performActiveSessionAction
+ self.performBotConnectionReviewAction = performBotConnectionReviewAction
self.openChatFolderUpdates = openChatFolderUpdates
self.hideChatFolderUpdates = hideChatFolderUpdates
self.openStories = openStories
@@ -1796,6 +1799,17 @@ public final class ChatListNode: ListViewImpl {
let _ = self.context.engine.privacy.terminateAnotherSession(id: newSessionReview.id).startStandalone()
#endif
}
+ }, performBotConnectionReviewAction: { [weak self] newBotConnectionReview, isPositive in
+ guard let self else {
+ return
+ }
+
+ if isPositive {
+ let _ = self.context.engine.accountData.confirmBotConnectionReview(botId: newBotConnectionReview.botId).startStandalone()
+ } else {
+ let _ = removeNewBotConnectionReviews(postbox: self.context.account.postbox, botIds: [newBotConnectionReview.botId]).startStandalone()
+ let _ = self.context.engine.accountData.setAccountConnectedBot(bot: nil).startStandalone()
+ }
}, openChatFolderUpdates: { [weak self] in
guard let self else {
return
diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift
index 5eea683612..02e3c62d25 100644
--- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift
+++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift
@@ -152,7 +152,6 @@ public final class ChatPanelInterfaceInteraction {
public let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void
public let openScheduledMessages: () -> Void
public let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
- public let openPeersNearby: () -> Void
public let unarchivePeer: () -> Void
public let scrollToTop: () -> Void
public let viewReplies: (EngineMessage.Id?, ChatReplyThreadMessage) -> Void
@@ -284,7 +283,6 @@ public final class ChatPanelInterfaceInteraction {
displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void,
displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void,
openScheduledMessages: @escaping () -> Void,
- openPeersNearby: @escaping () -> Void,
displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void,
unarchivePeer: @escaping () -> Void,
scrollToTop: @escaping () -> Void,
@@ -416,7 +414,6 @@ public final class ChatPanelInterfaceInteraction {
self.displaySlowmodeTooltip = displaySlowmodeTooltip
self.displaySendMessageOptions = displaySendMessageOptions
self.openScheduledMessages = openScheduledMessages
- self.openPeersNearby = openPeersNearby
self.displaySearchResultsTooltip = displaySearchResultsTooltip
self.unarchivePeer = unarchivePeer
self.scrollToTop = scrollToTop
@@ -556,7 +553,6 @@ public final class ChatPanelInterfaceInteraction {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { _, _ in
}, openScheduledMessages: {
- }, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, unarchivePeer: {
}, scrollToTop: {
diff --git a/submodules/ChatTextLinkEditUI/BUILD b/submodules/ChatTextLinkEditUI/BUILD
index be81825492..a49af0567e 100644
--- a/submodules/ChatTextLinkEditUI/BUILD
+++ b/submodules/ChatTextLinkEditUI/BUILD
@@ -20,6 +20,7 @@ swift_library(
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertMultilineInputFieldComponent",
+ "//submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent",
],
visibility = [
"//visibility:public",
diff --git a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift
index 87e357334b..a6e2c31202 100644
--- a/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift
+++ b/submodules/ChatTextLinkEditUI/Sources/ChatTextLinkEditController.swift
@@ -10,81 +10,142 @@ import UrlEscaping
import ComponentFlow
import AlertComponent
import AlertMultilineInputFieldComponent
+import AlertWebpagePreviewComponent
public func chatTextLinkEditController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil,
text: String,
link: String?,
- apply: @escaping (String?) -> Void
+ preview: Bool = false,
+ apply: @escaping (String?, TelegramMediaWebpage?) -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
-
+
let inputState = AlertMultilineInputFieldComponent.ExternalState()
-
- var content: [AnyComponentWithIdentity] = []
- content.append(AnyComponentWithIdentity(
- id: "title",
- component: AnyComponent(
- AlertTitleComponent(title: link != nil ? strings.TextFormat_EditLinkTitle : strings.TextFormat_AddLinkTitle)
- )
- ))
- content.append(AnyComponentWithIdentity(
- id: "text",
- component: AnyComponent(
- AlertTextComponent(content: .plain(strings.TextFormat_AddLinkText(text).string))
- )
- ))
-
+
var applyImpl: (() -> Void)?
- content.append(AnyComponentWithIdentity(
- id: "input",
- component: AnyComponent(
- AlertMultilineInputFieldComponent(
- context: context,
- initialValue: link.flatMap { NSAttributedString(string: $0) },
- placeholder: strings.TextFormat_AddLinkPlaceholder,
- returnKeyType: .done,
- keyboardType: .URL,
- autocapitalizationType: .none,
- autocorrectionType: .no,
- isInitiallyFocused: true,
- externalState: inputState,
- returnKeyAction: {
- applyImpl?()
- }
- )
- )
- ))
-
+
var effectiveUpdatedPresentationData: (PresentationData, Signal)
if let updatedPresentationData {
effectiveUpdatedPresentationData = updatedPresentationData
} else {
effectiveUpdatedPresentationData = (presentationData, context.sharedContext.presentationData)
}
-
+
+ let makeContent: (TelegramMediaWebpage?) -> [AnyComponentWithIdentity] = { webpage in
+ var content: [AnyComponentWithIdentity] = []
+ content.append(AnyComponentWithIdentity(
+ id: "title",
+ component: AnyComponent(
+ AlertTitleComponent(title: link != nil ? strings.TextFormat_EditLinkTitle : strings.TextFormat_AddLinkTitle)
+ )
+ ))
+ content.append(AnyComponentWithIdentity(
+ id: "text",
+ component: AnyComponent(
+ AlertTextComponent(content: .plain(text))
+ )
+ ))
+ content.append(AnyComponentWithIdentity(
+ id: "input",
+ component: AnyComponent(
+ AlertMultilineInputFieldComponent(
+ context: context,
+ initialValue: link.flatMap { NSAttributedString(string: $0) },
+ placeholder: strings.TextFormat_AddLinkPlaceholder,
+ returnKeyType: .done,
+ keyboardType: .URL,
+ autocapitalizationType: .none,
+ autocorrectionType: .no,
+ isInitiallyFocused: true,
+ externalState: inputState,
+ returnKeyAction: {
+ applyImpl?()
+ }
+ )
+ )
+ ))
+ if let webpage {
+ content.append(AnyComponentWithIdentity(
+ id: "webpagePreview",
+ component: AnyComponent(AlertWebpagePreviewComponent(
+ context: context,
+ presentationData: effectiveUpdatedPresentationData.0,
+ webpage: webpage
+ ))
+ ))
+ }
+ return content
+ }
+
+ let contentPromise = Promise<[AnyComponentWithIdentity]>()
+ contentPromise.set(.single(makeContent(nil)))
+
var dismissImpl: (() -> Void)?
let alertController = AlertScreen(
configuration: AlertScreen.Configuration(allowInputInset: true),
- content: content,
- actions: [
+ contentSignal: contentPromise.get(),
+ actionsSignal: .single([
.init(title: strings.Common_Cancel),
.init(title: strings.Common_Done, type: .default, action: {
applyImpl?()
}, autoDismiss: false)
- ],
+ ]),
updatedPresentationData: effectiveUpdatedPresentationData
)
+
+ let previewDisposable = MetaDisposable()
+ var currentPreview: (link: String, webpage: TelegramMediaWebpage)?
+ if preview {
+ var currentDisplayedPreview: (link: String, webpage: TelegramMediaWebpage?)?
+ previewDisposable.set((inputState.valueSignal
+ |> map { value -> String in
+ return explicitUrl(value.string)
+ }
+ |> distinctUntilChanged
+ |> mapToSignal { link -> Signal<(String, TelegramMediaWebpage?), NoError> in
+ guard !link.isEmpty && isValidUrl(link, validSchemes: ["http": true, "https": true, "tg": false, "ton": false, "tonsite": true]) else {
+ return .single((link, nil))
+ }
+
+ let previewSignal = webpagePreview(account: context.account, urls: [link])
+ |> map { result -> (String, TelegramMediaWebpage?) in
+ guard case let .result(result) = result, let webpage = result?.webpage, case .Loaded = webpage.content else {
+ return (link, nil)
+ }
+ return (link, webpage)
+ }
+
+ return .single((link, nil))
+ |> then((.complete() |> delay(1.0, queue: Queue.mainQueue())) |> then(previewSignal))
+ }
+ |> deliverOnMainQueue).startStrict(next: { link, webpage in
+ if let currentDisplayedPreview, currentDisplayedPreview.link == link && currentDisplayedPreview.webpage == webpage {
+ return
+ }
+ currentDisplayedPreview = (link, webpage)
+ if let webpage {
+ currentPreview = (link, webpage)
+ } else {
+ currentPreview = nil
+ }
+ contentPromise.set(.single(makeContent(webpage)))
+ }))
+ }
+ alertController.dismissed = { _ in
+ previewDisposable.dispose()
+ }
+
applyImpl = {
let updatedLink = explicitUrl(inputState.value.string)
if !updatedLink.isEmpty && isValidUrl(updatedLink, validSchemes: ["http": true, "https": true, "tg": false, "ton": false, "tonsite": true]) {
dismissImpl?()
- apply(updatedLink)
+ apply(updatedLink, currentPreview?.link == updatedLink ? currentPreview?.webpage : nil)
} else if inputState.value.string.isEmpty {
dismissImpl?()
- apply("")
+ apply("", nil)
} else {
inputState.animateError()
}
diff --git a/submodules/ComponentFlow/Source/Components/Image.swift b/submodules/ComponentFlow/Source/Components/Image.swift
index f18aba2e18..3641cf4467 100644
--- a/submodules/ComponentFlow/Source/Components/Image.swift
+++ b/submodules/ComponentFlow/Source/Components/Image.swift
@@ -7,19 +7,22 @@ public final class Image: Component {
public let size: CGSize?
public let contentMode: UIImageView.ContentMode
public let cornerRadius: CGFloat
+ public let flipHorizontally: Bool
public init(
image: UIImage?,
tintColor: UIColor? = nil,
size: CGSize? = nil,
contentMode: UIImageView.ContentMode = .scaleToFill,
- cornerRadius: CGFloat = 0.0
+ cornerRadius: CGFloat = 0.0,
+ flipHorizontally: Bool = false
) {
self.image = image
self.tintColor = tintColor
self.size = size
self.contentMode = contentMode
self.cornerRadius = cornerRadius
+ self.flipHorizontally = flipHorizontally
}
public static func ==(lhs: Image, rhs: Image) -> Bool {
@@ -38,6 +41,9 @@ public final class Image: Component {
if lhs.cornerRadius != rhs.cornerRadius {
return false
}
+ if lhs.flipHorizontally != rhs.flipHorizontally {
+ return false
+ }
return true
}
@@ -51,7 +57,11 @@ public final class Image: Component {
}
func update(component: Image, availableSize: CGSize, environment: Environment, transition: ComponentTransition) -> CGSize {
- self.image = component.image
+ if component.flipHorizontally, let cgImage = component.image?.cgImage {
+ self.image = UIImage(cgImage: cgImage, scale: component.image?.scale ?? 0.0, orientation: .upMirrored)
+ } else {
+ self.image = component.image
+ }
self.contentMode = component.contentMode
self.clipsToBounds = component.cornerRadius > 0.0
diff --git a/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift b/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift
index c48f851ca9..36cc4388f6 100644
--- a/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift
+++ b/submodules/Components/BundleIconComponent/Sources/BundleIconComponent.swift
@@ -11,15 +11,17 @@ public final class BundleIconComponent: Component {
public let scaleFactor: CGFloat
public let shadowColor: UIColor?
public let shadowBlur: CGFloat
+ public let flipHorizontally: Bool
public let flipVertically: Bool
- public init(name: String, tintColor: UIColor?, maxSize: CGSize? = nil, scaleFactor: CGFloat = 1.0, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0, flipVertically: Bool = false) {
+ public init(name: String, tintColor: UIColor?, maxSize: CGSize? = nil, scaleFactor: CGFloat = 1.0, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0, flipHorizontally: Bool = false, flipVertically: Bool = false) {
self.name = name
self.tintColor = tintColor
self.maxSize = maxSize
self.scaleFactor = scaleFactor
self.shadowColor = shadowColor
self.shadowBlur = shadowBlur
+ self.flipHorizontally = flipHorizontally
self.flipVertically = flipVertically
}
@@ -42,6 +44,9 @@ public final class BundleIconComponent: Component {
if lhs.shadowBlur != rhs.shadowBlur {
return false
}
+ if lhs.flipHorizontally != rhs.flipHorizontally {
+ return false
+ }
if lhs.flipVertically != rhs.flipVertically {
return false
}
@@ -77,7 +82,9 @@ public final class BundleIconComponent: Component {
}
})
}
- if component.flipVertically, let cgImage = image?.cgImage {
+ if component.flipHorizontally, let cgImage = image?.cgImage {
+ self.image = UIImage(cgImage: cgImage, scale: image?.scale ?? 0.0, orientation: .upMirrored)
+ } else if component.flipVertically, let cgImage = image?.cgImage {
self.image = UIImage(cgImage: cgImage, scale: image?.scale ?? 0.0, orientation: .down)
} else {
self.image = image
diff --git a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift
index 980ca8e473..7d0c4b173f 100644
--- a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift
+++ b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift
@@ -291,6 +291,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
+ self.scrollNode.view.scrollsToTop = false
self.itemNodes = reactions.map { reaction, count in
return ItemNode(context: context, availableReactions: availableReactions, reaction: reaction, animationCache: animationCache, animationRenderer: animationRenderer, count: count)
@@ -897,6 +898,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.scrollNode.clipsToBounds = false
+ self.scrollNode.view.scrollsToTop = false
super.init()
diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift
index 33844f7452..35500162d5 100644
--- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift
+++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift
@@ -184,7 +184,7 @@ public final class SheetComponent: C
private let dimView: UIView
private let scrollView: ScrollView
private let backgroundView: SheetBackgroundView
- private var effectView: UIVisualEffectView?
+ private var effectView: SheetBackgroundBlurView?
private let clipView: SheetBackgroundView
private let contentView: ComponentView
private var headerView: ComponentView?
@@ -411,13 +411,13 @@ public final class SheetComponent: C
}
var backgroundColor: UIColor = .clear
+ var blurEffectStyle: UIBlurEffect.Style?
switch component.backgroundColor {
case let .blur(style):
+ blurEffectStyle = style == .dark ? .dark : .light
self.backgroundView.isHidden = true
if self.effectView == nil {
- let effectView = UIVisualEffectView(effect: UIBlurEffect(style: style == .dark ? .dark : .light))
- effectView.layer.cornerRadius = self.backgroundView.layer.cornerRadius
- effectView.layer.masksToBounds = true
+ let effectView = SheetBackgroundBlurView()
self.backgroundView.superview?.insertSubview(effectView, aboveSubview: self.backgroundView)
self.effectView = effectView
}
@@ -471,8 +471,9 @@ public final class SheetComponent: C
transition.setFrame(view: self.clipView, frame: clipFrame, completion: nil)
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: clipFrame.size), completion: nil)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil)
- if let effectView = self.effectView {
+ if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle {
transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil)
+ effectView.update(style: blurEffectStyle, size: contentSize, topCornerRadius: topCornerRadius, bottomCornerRadius: topCornerRadius, transition: transition)
}
self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius, bottomCornerRadius: topCornerRadius, transition: transition)
} else {
@@ -483,14 +484,19 @@ public final class SheetComponent: C
transition.setFrame(view: self.clipView, frame: clipFrame)
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, 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)
+ if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle {
+ transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil)
+ effectView.update(style: blurEffectStyle, size: contentSize, topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition)
+ }
case .legacy:
let clipFrame = CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 100.0))
self.clipView.update(size: clipFrame.size, color: .clear, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition)
transition.setFrame(view: self.clipView, frame: clipFrame)
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: clipFrame.size), 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 {
+ if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle {
transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil)
+ effectView.update(style: blurEffectStyle, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0), topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition)
}
}
self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition)
@@ -614,3 +620,31 @@ public final class SheetBackgroundView: UIView {
transition.setBackgroundColor(view: self.bottomCornersView, color: color)
}
}
+
+private final class SheetBackgroundBlurView: UIView {
+ private let clipView = SheetBackgroundView()
+ private let effectView = UIVisualEffectView()
+ private var currentStyle: UIBlurEffect.Style?
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ self.addSubview(self.clipView)
+ self.clipView.bottomCornersView.addSubview(self.effectView)
+ }
+
+ required init?(coder: NSCoder) {
+ preconditionFailure()
+ }
+
+ func update(style: UIBlurEffect.Style, size: CGSize, topCornerRadius: CGFloat, bottomCornerRadius: CGFloat, transition: ComponentTransition) {
+ if self.currentStyle != style {
+ self.currentStyle = style
+ self.effectView.effect = UIBlurEffect(style: style)
+ }
+
+ self.clipView.update(size: size, color: .clear, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition)
+ transition.setFrame(view: self.clipView, frame: CGRect(origin: .zero, size: size))
+ transition.setFrame(view: self.effectView, frame: CGRect(origin: .zero, size: size))
+ }
+}
diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD
index fa3d0c4468..ff00a21cb1 100644
--- a/submodules/ContactListUI/BUILD
+++ b/submodules/ContactListUI/BUILD
@@ -47,6 +47,7 @@ swift_library(
"//submodules/ContextUI",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
+ "//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/SearchBarNode",
],
diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift
index dfee64ae07..d74bf3fad2 100644
--- a/submodules/ContactListUI/Sources/ContactsController.swift
+++ b/submodules/ContactListUI/Sources/ContactsController.swift
@@ -375,49 +375,6 @@ public class ContactsController: ViewController {
}
}
- self.contactsNode.openPeopleNearby = { [weak self] in
- let _ = (DeviceAccess.authorizationStatus(subject: .location(.tracking))
- |> take(1)
- |> deliverOnMainQueue).start(next: { [weak self] status in
- guard let strongSelf = self else {
- return
- }
- let presentPeersNearby = {
- let controller = strongSelf.context.sharedContext.makePeersNearbyController(context: strongSelf.context)
- controller.navigationPresentation = .master
- if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
- var controllers = navigationController.viewControllers.filter { !($0 is PermissionController) }
- controllers.append(controller)
- navigationController.setViewControllers(controllers, animated: true)
- strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
- }
- }
-
- switch status {
- case .allowed:
- presentPeersNearby()
- default:
- let controller = PermissionController(context: strongSelf.context, splashScreen: false)
- controller.setState(.permission(.nearbyLocation(status: PermissionRequestStatus(accessType: status))), animated: false)
- controller.navigationPresentation = .master
- controller.proceed = { result in
- if result {
- presentPeersNearby()
- } else {
- let _ = (strongSelf.navigationController as? NavigationController)?.popViewController(animated: true)
- }
- }
- if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
- navigationController.pushViewController(controller, completion: { [weak self] in
- if let strongSelf = self {
- strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
- }
- })
- }
- }
- })
- }
-
self.contactsNode.openInvite = { [weak self] in
let _ = (DeviceAccess.authorizationStatus(subject: .contacts)
|> take(1)
diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift
index 363ddfa3ea..4496c9e8ff 100644
--- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift
+++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift
@@ -64,7 +64,6 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
var requestOpenDisabledPeerFromSearch: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
var requestAddContact: ((String) -> Void)?
- var openPeopleNearby: (() -> Void)?
var openInvite: (() -> Void)?
var openQrScan: (() -> Void)?
var openStories: ((EnginePeer, ASDisplayNode) -> Void)?
diff --git a/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift b/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift
index 021c4acf53..af68a8ea35 100644
--- a/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift
+++ b/submodules/ContactListUI/Sources/InviteContactsCountPanelNode.swift
@@ -5,6 +5,7 @@ import Display
import TelegramPresentationData
import ComponentFlow
import ButtonComponent
+import AnimatedTextComponent
import EdgeEffect
final class InviteContactsCountPanelNode: ASDisplayNode {
@@ -69,15 +70,11 @@ final class InviteContactsCountPanelNode: ASDisplayNode {
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
- component: AnyComponent(ButtonTextContentComponent(
- text: self.strings.Contacts_InviteContacts(Int32(self.count)),
- badge: 0,
- textColor: self.theme.list.itemCheckColors.foregroundColor,
- badgeBackground: self.theme.list.itemCheckColors.foregroundColor,
- badgeForeground: self.theme.list.itemCheckColors.fillColor,
- badgeStyle: .roundedRectangle,
- badgeIconName: nil,
- combinedAlignment: true
+ component: AnyComponent(AnimatedTextComponent(
+ font: Font.with(size: 17.0, weight: .semibold, traits: .monospacedNumbers),
+ color: self.theme.list.itemCheckColors.foregroundColor,
+ items: [AnimatedTextComponent.Item(id: "text", content: .text(self.strings.Contacts_InviteContacts(Int32(max(1, self.count)))))],
+ noDelay: true
))
),
isEnabled: true,
diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift
index a6a17f1b19..2ba7c55eb9 100644
--- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift
+++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift
@@ -1236,7 +1236,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
color = item.presentationData.theme.list.itemDisclosureActions.accent.fillColor
textColor = item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor
}
- mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor))
+ mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, iconColor: textColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor))
index += 1
}
peerRevealOptions = mappedOptions
@@ -2002,7 +2002,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
if item.editing.editable {
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]))
strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated)
} else {
strongSelf.setRevealOptions((left: [], right: peerRevealOptions))
diff --git a/submodules/DateSelectionUI/Sources/DateSelectionActionSheetController.swift b/submodules/DateSelectionUI/Sources/DateSelectionActionSheetController.swift
deleted file mode 100644
index ef88e03178..0000000000
--- a/submodules/DateSelectionUI/Sources/DateSelectionActionSheetController.swift
+++ /dev/null
@@ -1,146 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import TelegramPresentationData
-import TelegramStringFormatting
-import SwiftSignalKit
-import AccountContext
-import UIKitRuntimeUtils
-
-public final class DateSelectionActionSheetController: ActionSheetController {
- private var presentationDisposable: Disposable?
-
- private let _ready = Promise()
- override public var ready: Promise {
- return self._ready
- }
-
- public init(context: AccountContext, title: String?, currentValue: Int32, minimumDate: Date? = nil, maximumDate: Date? = nil, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) {
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- let strings = presentationData.strings
-
- super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
-
- self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in
- if let strongSelf = self {
- strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
- }
- }).strict()
-
- self._ready.set(.single(true))
-
- var updatedValue = currentValue
- var items: [ActionSheetItem] = []
- if let title = title {
- items.append(ActionSheetTextItem(title: title))
- }
- items.append(DateSelectionActionSheetItem(strings: strings, currentValue: currentValue, minimumDate: minimumDate, maximumDate: maximumDate, valueChanged: { value in
- updatedValue = value
- }))
- if let emptyTitle = emptyTitle {
- items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in
- self?.dismissAnimated()
- applyValue(nil)
- }))
- }
- items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in
- self?.dismissAnimated()
- applyValue(updatedValue)
- }))
- self.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
- self?.dismissAnimated()
- }),
- ])
- ])
- }
-
- required public init(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- deinit {
- self.presentationDisposable?.dispose()
- }
-}
-
-private final class DateSelectionActionSheetItem: ActionSheetItem {
- let strings: PresentationStrings
-
- let currentValue: Int32
- let minimumDate: Date?
- let maximumDate: Date?
- let valueChanged: (Int32) -> Void
-
- init(strings: PresentationStrings, currentValue: Int32, minimumDate: Date?, maximumDate: Date?, valueChanged: @escaping (Int32) -> Void) {
- self.strings = strings
- self.currentValue = roundDateToDays(currentValue)
- self.minimumDate = minimumDate
- self.maximumDate = maximumDate
- self.valueChanged = valueChanged
- }
-
- func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- return DateSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, minimumDate: self.minimumDate, maximumDate: self.maximumDate, valueChanged: self.valueChanged)
- }
-
- func updateNode(_ node: ActionSheetItemNode) {
- }
-}
-
-private final class DateSelectionActionSheetItemNode: ActionSheetItemNode {
- private let theme: ActionSheetControllerTheme
- private let strings: PresentationStrings
-
- private let valueChanged: (Int32) -> Void
- private let pickerView: UIDatePicker
-
- init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, minimumDate: Date?, maximumDate: Date?, valueChanged: @escaping (Int32) -> Void) {
- self.theme = theme
- self.strings = strings
- self.valueChanged = valueChanged
-
- UILabel.setDateLabel(theme.primaryTextColor)
-
- self.pickerView = UIDatePicker()
- self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
- self.pickerView.datePickerMode = .countDownTimer
- self.pickerView.datePickerMode = .date
- self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
- self.pickerView.locale = localeWithStrings(strings)
- if #available(iOS 13.4, *) {
- self.pickerView.preferredDatePickerStyle = .wheels
- }
- if let minimumDate = minimumDate {
- self.pickerView.minimumDate = minimumDate
- }
- if let maximumDate = maximumDate {
- self.pickerView.maximumDate = maximumDate
- } else {
- self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
- }
- self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
-
- super.init(theme: theme)
-
- self.view.addSubview(self.pickerView)
- self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
- }
-
- public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let size = CGSize(width: constrainedSize.width, height: 216.0)
-
- self.pickerView.frame = CGRect(origin: CGPoint(), size: size)
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-
- @objc private func datePickerUpdated() {
- self.valueChanged(roundDateToDays(Int32(self.pickerView.date.timeIntervalSince1970)))
- }
-}
-
diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift
index 3fa40d32dc..49e543eae8 100644
--- a/submodules/DebugSettingsUI/Sources/DebugController.swift
+++ b/submodules/DebugSettingsUI/Sources/DebugController.swift
@@ -1709,7 +1709,7 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
var leftNavigationButton: ItemListNavigationButton?
if modal {
- leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
}
diff --git a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift
index a3fcbc3fad..15288011d5 100644
--- a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift
+++ b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift
@@ -104,19 +104,19 @@ public final class DeviceLocationManager: NSObject {
self.currentTopMode = topMode
if let topMode = topMode {
self.log?("setting mode \(topMode)")
+ switch topMode {
+ case .preciseForeground:
+ self.manager.allowsBackgroundLocationUpdates = false
+ case .preciseAlways:
+ self.manager.allowsBackgroundLocationUpdates = true
+ }
+
if previousTopMode == nil {
if !self.requestedAuthorization {
self.requestedAuthorization = true
self.manager.requestAlwaysAuthorization()
}
-
- switch topMode {
- case .preciseForeground:
- self.manager.allowsBackgroundLocationUpdates = false
- case .preciseAlways:
- self.manager.allowsBackgroundLocationUpdates = true
- }
-
+
self.manager.startUpdatingLocation()
self.manager.startUpdatingHeading()
}
diff --git a/submodules/Display/Source/ActionSheetControllerNode.swift b/submodules/Display/Source/ActionSheetControllerNode.swift
index c27cc399f8..fc4d61c752 100644
--- a/submodules/Display/Source/ActionSheetControllerNode.swift
+++ b/submodules/Display/Source/ActionSheetControllerNode.swift
@@ -88,6 +88,12 @@ final class ActionSheetControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
+ override func didLoad() {
+ super.didLoad()
+
+ self.scrollNode.view.scrollsToTop = false
+ }
+
func performHighlightedAction() {
self.itemGroupsContainerNode.performHighlightedAction()
}
diff --git a/submodules/Display/Source/ActionSheetItemGroupNode.swift b/submodules/Display/Source/ActionSheetItemGroupNode.swift
index cee228963b..af9d79fc27 100644
--- a/submodules/Display/Source/ActionSheetItemGroupNode.swift
+++ b/submodules/Display/Source/ActionSheetItemGroupNode.swift
@@ -52,6 +52,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, ASScrollViewDelegate {
self.scrollNode.view.canCancelContentTouches = true
self.scrollNode.view.showsVerticalScrollIndicator = false
self.scrollNode.view.showsHorizontalScrollIndicator = false
+ self.scrollNode.view.scrollsToTop = false
super.init()
diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift
index cd2a2a5198..613de4eaf7 100644
--- a/submodules/Display/Source/CAAnimationUtils.swift
+++ b/submodules/Display/Source/CAAnimationUtils.swift
@@ -72,8 +72,21 @@ public extension CALayer {
func makeAnimation(from: Any?, to: Any, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation {
if timingFunction.hasPrefix(kCAMediaTimingFunctionCustomSpringPrefix) {
let components = timingFunction.components(separatedBy: "_")
- let damping = Float(components[1]) ?? 100.0
- let initialVelocity = Float(components[2]) ?? 0.0
+ let mass: Float
+ let stiffness: Float
+ let damping: Float
+ let initialVelocity: Float
+ if components.count >= 5 {
+ mass = Float(components[1]) ?? 5.0
+ stiffness = Float(components[2]) ?? 900.0
+ damping = Float(components[3]) ?? 100.0
+ initialVelocity = Float(components[4]) ?? 0.0
+ } else {
+ mass = 5.0
+ stiffness = 900.0
+ damping = components.count > 1 ? (Float(components[1]) ?? 100.0) : 100.0
+ initialVelocity = components.count > 2 ? (Float(components[2]) ?? 0.0) : 0.0
+ }
let animation = CASpringAnimation(keyPath: keyPath)
animation.fromValue = from
@@ -83,10 +96,10 @@ public extension CALayer {
if let completion = completion {
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
}
+ animation.mass = CGFloat(mass)
+ animation.stiffness = CGFloat(stiffness)
animation.damping = CGFloat(damping)
animation.initialVelocity = CGFloat(initialVelocity)
- animation.mass = 5.0
- animation.stiffness = 900.0
animation.duration = animation.settlingDuration
animation.timingFunction = CAMediaTimingFunction.init(name: .linear)
let k = Float(UIView.animationDurationFactor())
diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift
index 17abeb224c..ef5b78b06b 100644
--- a/submodules/Display/Source/ContainedViewLayoutTransition.swift
+++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift
@@ -14,7 +14,7 @@ public enum ContainedViewLayoutTransitionCurve: Equatable, Hashable {
case easeInOut
case easeIn
case spring
- case customSpring(damping: CGFloat, initialVelocity: CGFloat)
+ case customSpring(mass: CGFloat = 5.0, stiffness: CGFloat = 900.0, damping: CGFloat, initialVelocity: CGFloat)
case custom(Float, Float, Float, Float)
public static var slide: ContainedViewLayoutTransitionCurve {
@@ -52,8 +52,8 @@ public extension ContainedViewLayoutTransitionCurve {
return CAMediaTimingFunctionName.easeIn.rawValue
case .spring:
return kCAMediaTimingFunctionSpring
- case let .customSpring(damping, initialVelocity):
- return "\(kCAMediaTimingFunctionCustomSpringPrefix)_\(damping)_\(initialVelocity)"
+ case let .customSpring(mass, stiffness, damping, initialVelocity):
+ return "\(kCAMediaTimingFunctionCustomSpringPrefix)_\(mass)_\(stiffness)_\(damping)_\(initialVelocity)"
case .custom:
return CAMediaTimingFunctionName.easeInEaseOut.rawValue
}
@@ -124,8 +124,8 @@ private extension CALayer {
let timingFunction: String
let mediaTimingFunction: CAMediaTimingFunction?
switch curve {
- case .spring:
- timingFunction = kCAMediaTimingFunctionSpring
+ case .spring, .customSpring:
+ timingFunction = curve.timingFunction
mediaTimingFunction = nil
default:
timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue
diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift
index 05cd1827a1..9378f7a4ce 100644
--- a/submodules/Display/Source/ListView.swift
+++ b/submodules/Display/Source/ListView.swift
@@ -509,6 +509,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
self.scroller.contentSize = CGSize(width: 0.0, height: infiniteScrollSize * 2.0)
self.scroller.isHidden = true
self.scroller.delegate = self.wrappedScrollViewDelegate
+ self.scroller.scrollsToTop = false
self.view.addSubview(self.scroller)
self.scroller.panGestureRecognizer.cancelsTouchesInView = true
self.view.addGestureRecognizer(self.scroller.panGestureRecognizer)
diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift
index d8a004fcd0..452418ad57 100644
--- a/submodules/Display/Source/Navigation/NavigationController.swift
+++ b/submodules/Display/Source/Navigation/NavigationController.swift
@@ -172,6 +172,7 @@ open class NavigationController: UINavigationController, ContainableController,
private var inCallStatusBar: StatusBar?
private var updateInCallStatusBarState: CallStatusBarNode?
private var globalScrollToTopNode: ScrollToTopNode?
+ private var windowScrollToTopProxyViews: WindowScrollToTopProxyViews?
private var rootContainer: RootContainer?
private var rootModalFrame: NavigationModalFrame?
private var modalContainers: [NavigationModalContainer] = []
@@ -331,6 +332,54 @@ open class NavigationController: UINavigationController, ContainableController,
}
deinit {
+ self.windowScrollToTopProxyViews?.update(window: nil, mode: .disabled, referenceView: nil)
+ }
+
+ private func getWindowScrollToTopProxyViews() -> WindowScrollToTopProxyViews {
+ if let windowScrollToTopProxyViews = self.windowScrollToTopProxyViews {
+ return windowScrollToTopProxyViews
+ }
+
+ let windowScrollToTopProxyViews = WindowScrollToTopProxyViews(scrollToTop: { [weak self] subject in
+ self?.scrollToTop(subject)
+ })
+ self.windowScrollToTopProxyViews = windowScrollToTopProxyViews
+ return windowScrollToTopProxyViews
+ }
+
+ private func windowScrollToTopReferenceView(window: UIWindow) -> UIView? {
+ var view: UIView? = self.view
+ while let currentView = view {
+ if currentView.superview === window {
+ return currentView
+ }
+ view = currentView.superview
+ }
+ return nil
+ }
+
+ private func updateWindowScrollToTopProxyViews(layout: ContainerViewLayout) {
+ guard let window = self.view.window, let rootContainer = self.rootContainer else {
+ (self.globalScrollToTopNode?.view as? ScrollToTopView)?.scrollsToTop = true
+ self.windowScrollToTopProxyViews?.update(window: nil, mode: .disabled, referenceView: nil)
+ return
+ }
+
+ (self.globalScrollToTopNode?.view as? ScrollToTopView)?.scrollsToTop = false
+ let referenceView = self.windowScrollToTopReferenceView(window: window)
+ let proxyViews = self.getWindowScrollToTopProxyViews()
+ switch rootContainer {
+ case .flat:
+ let scrollToTopHeight = max(layout.statusBarHeight ?? layout.safeInsets.top, 1.0)
+ let frame = self.view.convert(CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: scrollToTopHeight)), to: window)
+ proxyViews.update(window: window, mode: .flat(frame: frame), referenceView: referenceView)
+ case let .split(container):
+ let frames = container.scrollToTopProxyFrames(layout: layout)
+ proxyViews.update(window: window, mode: .split(
+ masterFrame: container.view.convert(frames.master, to: window),
+ detailFrame: container.view.convert(frames.detail, to: window)
+ ), referenceView: referenceView)
+ }
}
public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
@@ -498,7 +547,7 @@ open class NavigationController: UINavigationController, ContainableController,
}
if let globalScrollToTopNode = self.globalScrollToTopNode {
- globalScrollToTopNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: layout.size.width, height: 1))
+ globalScrollToTopNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: layout.size.width, height: 1.0))
}
var overlayContainerLayout = layout
@@ -1018,8 +1067,6 @@ open class NavigationController: UINavigationController, ContainableController,
case let .flat(flatContainer):
let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in
self?.controllerRemoved(controller)
- }, scrollToTop: { [weak self] subject in
- self?.scrollToTop(subject)
})
if let detailsPlaceholderNode = self.detailsPlaceholderNode {
self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode)
@@ -1053,8 +1100,6 @@ open class NavigationController: UINavigationController, ContainableController,
} else {
let splitContainer = NavigationSplitContainer(theme: self.theme, controllerRemoved: { [weak self] controller in
self?.controllerRemoved(controller)
- }, scrollToTop: { [weak self] subject in
- self?.scrollToTop(subject)
})
if let detailsPlaceholderNode = self.detailsPlaceholderNode {
self.displayNode.insertSubnode(splitContainer, aboveSubnode: detailsPlaceholderNode)
@@ -1385,6 +1430,8 @@ open class NavigationController: UINavigationController, ContainableController,
}
}
+ self.updateWindowScrollToTopProxyViews(layout: layout)
+
self.isUpdatingContainers = false
if notifyGlobalOverlayControllersUpdated {
diff --git a/submodules/Display/Source/Navigation/NavigationModalContainer.swift b/submodules/Display/Source/Navigation/NavigationModalContainer.swift
index f9120ce6fe..7fd3ed95ba 100644
--- a/submodules/Display/Source/Navigation/NavigationModalContainer.swift
+++ b/submodules/Display/Source/Navigation/NavigationModalContainer.swift
@@ -91,6 +91,7 @@ final class NavigationModalContainer: ASDisplayNode, ASScrollViewDelegate, ASGes
self.scrollNode.view.clipsToBounds = false
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
self.scrollNode.view.tag = 0x5C4011
+ self.scrollNode.view.scrollsToTop = false
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] _ in
guard let strongSelf = self, !strongSelf.isDismissed else {
diff --git a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift
index 42192baf34..b3900d15c5 100644
--- a/submodules/Display/Source/Navigation/NavigationSplitContainer.swift
+++ b/submodules/Display/Source/Navigation/NavigationSplitContainer.swift
@@ -11,8 +11,6 @@ enum NavigationSplitContainerScrollToTop {
final class NavigationSplitContainer: ASDisplayNode {
private var theme: NavigationControllerTheme
- private let masterScrollToTopView: ScrollToTopView
- private let detailScrollToTopView: ScrollToTopView
private let masterContainer: NavigationContainer
private let detailContainer: NavigationContainer
private let separator: ASDisplayNode
@@ -39,18 +37,9 @@ final class NavigationSplitContainer: ASDisplayNode {
self.detailContainer.isInFocus = isInFocus
}
- init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void, scrollToTop: @escaping (NavigationSplitContainerScrollToTop) -> Void) {
+ init(theme: NavigationControllerTheme, controllerRemoved: @escaping (ViewController) -> Void) {
self.theme = theme
- self.masterScrollToTopView = ScrollToTopView(frame: CGRect())
- self.masterScrollToTopView.action = {
- scrollToTop(.master)
- }
- self.detailScrollToTopView = ScrollToTopView(frame: CGRect())
- self.detailScrollToTopView.action = {
- scrollToTop(.detail)
- }
-
self.masterContainer = NavigationContainer(isFlat: false, controllerRemoved: controllerRemoved)
self.masterContainer.clipsToBounds = true
@@ -65,8 +54,6 @@ final class NavigationSplitContainer: ASDisplayNode {
self.addSubnode(self.masterContainer)
self.addSubnode(self.detailContainer)
self.addSubnode(self.separator)
- self.view.addSubview(self.masterScrollToTopView)
- self.view.addSubview(self.detailScrollToTopView)
}
func hasNonReadyControllers() -> Bool {
@@ -83,13 +70,21 @@ final class NavigationSplitContainer: ASDisplayNode {
self.separator.backgroundColor = theme.navigationBar.separatorColor
}
+ func scrollToTopProxyFrames(layout: ContainerViewLayout) -> (master: CGRect, detail: CGRect) {
+ let masterWidth: CGFloat = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
+ let detailWidth = layout.size.width - masterWidth
+ let scrollToTopHeight = max(layout.statusBarHeight ?? layout.safeInsets.top, 1.0)
+
+ return (
+ master: CGRect(origin: CGPoint(), size: CGSize(width: masterWidth, height: scrollToTopHeight)),
+ detail: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: scrollToTopHeight))
+ )
+ }
+
func update(layout: ContainerViewLayout, masterControllers: [ViewController], detailControllers: [ViewController], detailsPlaceholderNode: NavigationDetailsPlaceholderNode?, transition: ContainedViewLayoutTransition) {
let masterWidth: CGFloat = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
let detailWidth = layout.size.width - masterWidth
- self.masterScrollToTopView.frame = CGRect(origin: CGPoint(x: 0.0, y: -1.0), size: CGSize(width: masterWidth, height: 10.0))
- self.detailScrollToTopView.frame = CGRect(origin: CGPoint(x: masterWidth, y: -1.0), size: CGSize(width: detailWidth, height: 1.0))
-
transition.updateFrame(node: self.masterContainer, frame: CGRect(origin: CGPoint(), size: CGSize(width: masterWidth, height: layout.size.height)))
transition.updateFrame(node: self.detailContainer, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height)))
transition.updateFrame(node: self.separator, frame: CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)))
diff --git a/submodules/Display/Source/ScrollToTopProxyView.swift b/submodules/Display/Source/ScrollToTopProxyView.swift
index ba727f4d11..ee95519900 100644
--- a/submodules/Display/Source/ScrollToTopProxyView.swift
+++ b/submodules/Display/Source/ScrollToTopProxyView.swift
@@ -35,10 +35,6 @@ class ScrollToTopView: UIScrollView, UIScrollViewDelegate {
return false
}
-
- func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
- print("scrollViewDidScrollToTop")
- }
}
class ScrollToTopNode: ASDisplayNode {
@@ -52,3 +48,83 @@ class ScrollToTopNode: ASDisplayNode {
})
}
}
+
+final class WindowScrollToTopProxyViews {
+ enum Mode {
+ case disabled
+ case flat(frame: CGRect)
+ case split(masterFrame: CGRect, detailFrame: CGRect)
+ }
+
+ private let flatView: ScrollToTopView
+ private let masterView: ScrollToTopView
+ private let detailView: ScrollToTopView
+
+ init(scrollToTop: @escaping (NavigationSplitContainerScrollToTop) -> Void) {
+ self.flatView = ScrollToTopView(frame: CGRect())
+ self.masterView = ScrollToTopView(frame: CGRect())
+ self.detailView = ScrollToTopView(frame: CGRect())
+
+ self.flatView.action = {
+ scrollToTop(.master)
+ }
+ self.masterView.action = {
+ scrollToTop(.master)
+ }
+ self.detailView.action = {
+ scrollToTop(.detail)
+ }
+ }
+
+ deinit {
+ self.disable(self.flatView)
+ self.disable(self.masterView)
+ self.disable(self.detailView)
+ }
+
+ func update(window: UIWindow?, mode: Mode, referenceView: UIView?) {
+ guard let window else {
+ self.disable(self.flatView)
+ self.disable(self.masterView)
+ self.disable(self.detailView)
+ return
+ }
+
+ switch mode {
+ case .disabled:
+ self.disable(self.flatView)
+ self.disable(self.masterView)
+ self.disable(self.detailView)
+ case let .flat(frame):
+ self.activate(self.flatView, in: window, frame: frame, referenceView: referenceView)
+ self.disable(self.masterView)
+ self.disable(self.detailView)
+ case let .split(masterFrame, detailFrame):
+ self.disable(self.flatView)
+ self.activate(self.masterView, in: window, frame: masterFrame, referenceView: referenceView)
+ self.activate(self.detailView, in: window, frame: detailFrame, referenceView: referenceView)
+ }
+ }
+
+ private func activate(_ view: ScrollToTopView, in window: UIWindow, frame: CGRect, referenceView: UIView?) {
+ if let referenceView, referenceView.superview === window {
+ if view.superview !== window {
+ view.removeFromSuperview()
+ }
+ window.insertSubview(view, aboveSubview: referenceView)
+ } else if view.superview !== window {
+ view.removeFromSuperview()
+ window.addSubview(view)
+ }
+
+ view.isHidden = false
+ view.scrollsToTop = true
+ view.frame = frame
+ }
+
+ private func disable(_ view: ScrollToTopView) {
+ view.scrollsToTop = false
+ view.isHidden = true
+ view.removeFromSuperview()
+ }
+}
diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD
index e95073b967..499389abb4 100644
--- a/submodules/DrawingUI/BUILD
+++ b/submodules/DrawingUI/BUILD
@@ -107,6 +107,11 @@ swift_library(
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
"//submodules/TelegramUI/Components/StickerPickerScreen",
"//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation",
+ "//submodules/TelegramUI/Components/SegmentControlComponent",
+ "//submodules/TelegramUI/Components/GlassBarButtonComponent",
+ "//submodules/TelegramUI/Components/LiquidLens",
+ "//submodules/TelegramUI/Components/TabSelectionRecognizer",
+ "//submodules/TelegramUI/Components/GlassControls",
],
visibility = [
"//visibility:public",
diff --git a/submodules/DrawingUI/Sources/ColorPickerScreen.swift b/submodules/DrawingUI/Sources/ColorPickerScreen.swift
index dfcdf6bef8..206563fa1b 100644
--- a/submodules/DrawingUI/Sources/ColorPickerScreen.swift
+++ b/submodules/DrawingUI/Sources/ColorPickerScreen.swift
@@ -10,10 +10,12 @@ import TelegramPresentationData
import SheetComponent
import ViewControllerComponent
import BlurredBackgroundComponent
-import SegmentedControlNode
+import SegmentControlComponent
import MultilineTextComponent
import HexColor
import MediaEditor
+import GlassBarButtonComponent
+import BundleIconComponent
private let palleteColors: [UInt32] = [
0xffffff, 0xebebeb, 0xd6d6d6, 0xc2c2c2, 0xadadad, 0x999999, 0x858585, 0x707070, 0x5c5c5c, 0x474747, 0x333333, 0x000000,
@@ -1494,73 +1496,6 @@ private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor:
})
}
-private class SegmentedControlComponent: Component {
- let values: [String]
- let selectedIndex: Int
- let selectionChanged: (Int) -> Void
-
- init(values: [String], selectedIndex: Int, selectionChanged: @escaping (Int) -> Void) {
- self.values = values
- self.selectedIndex = selectedIndex
- self.selectionChanged = selectionChanged
- }
-
- static func ==(lhs: SegmentedControlComponent, rhs: SegmentedControlComponent) -> Bool {
- if lhs.values != rhs.values {
- return false
- }
- if lhs.selectedIndex != rhs.selectedIndex {
- return false
- }
- return true
- }
-
- final class View: UIView {
- private let backgroundNode: NavigationBackgroundNode
- private let node: SegmentedControlNode
-
- init() {
- self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x888888, alpha: 0.1))
- self.node = SegmentedControlNode(theme: SegmentedControlTheme(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0x6f7075, alpha: 0.6), shadowColor: .black, textColor: UIColor(rgb: 0xffffff), dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)), items: [], selectedIndex: 0)
-
- super.init(frame: CGRect())
-
- self.addSubview(self.backgroundNode.view)
- self.addSubview(self.node.view)
- }
-
- required init?(coder aDecoder: NSCoder) {
- preconditionFailure()
- }
-
- func update(component: SegmentedControlComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
- self.node.items = component.values.map { SegmentedControlItem(title: $0) }
- self.node.selectedIndex = component.selectedIndex
- let selectionChanged = component.selectionChanged
- self.node.selectedIndexChanged = { [weak self] index in
- self?.window?.endEditing(true)
- selectionChanged(index)
- }
-
- let size = self.node.updateLayout(.stretchToFill(width: availableSize.width), transition: transition.containedViewLayoutTransition)
- transition.setFrame(view: self.node.view, frame: CGRect(origin: CGPoint(), size: size))
-
- transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
- self.backgroundNode.update(size: size, cornerRadius: 10.0, transition: .immediate)
-
- return size
- }
- }
-
- 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, transition: transition)
- }
-}
-
final class ColorSwatchComponent: Component {
enum SwatchType: Equatable {
case main
@@ -1886,30 +1821,6 @@ private final class ColorPickerContent: CombinedComponent {
}
final class State: ComponentState {
- var cachedEyedropperImage: UIImage?
- var eyedropperImage: UIImage {
- let eyedropperImage: UIImage
- if let image = self.cachedEyedropperImage {
- eyedropperImage = image
- } else {
- eyedropperImage = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Eyedropper"), color: .white)!
- self.cachedEyedropperImage = eyedropperImage
- }
- return eyedropperImage
- }
-
- var cachedCloseImage: UIImage?
- var closeImage: UIImage {
- let closeImage: UIImage
- if let image = self.cachedCloseImage {
- closeImage = image
- } else {
- closeImage = generateCloseButtonImage(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xa8aab1))!
- self.cachedCloseImage = closeImage
- }
- return closeImage
- }
-
var selectedMode: Int = 0
var selectedColor: DrawingColor
@@ -1951,10 +1862,10 @@ private final class ColorPickerContent: CombinedComponent {
}
static var body: Body {
- let eyedropperButton = Child(Button.self)
- let closeButton = Child(Button.self)
+ let eyedropperButton = Child(GlassBarButtonComponent.self)
+ let closeButton = Child(GlassBarButtonComponent.self)
let title = Child(MultilineTextComponent.self)
- let modeControl = Child(SegmentedControlComponent.self)
+ let modeControl = Child(SegmentControlComponent.self)
let colorGrid = Child(ColorGridComponent.self)
let colorSpectrum = Child(ColorSpectrumComponent.self)
@@ -1985,50 +1896,45 @@ private final class ColorPickerContent: CombinedComponent {
let sideInset: CGFloat = 16.0
let eyedropperButton = eyedropperButton.update(
- component: Button(
- content: AnyComponent(
- Image(image: state.eyedropperImage)
+ component: GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ component: AnyComponentWithIdentity(
+ id: "icon",
+ component: AnyComponent(BundleIconComponent(name: "Media Editor/Eyedropper", tintColor: .white))
),
- action: { [weak component] in
- component?.eyedropper()
+ action: { _ in
+ component.eyedropper()
}
- ).minSize(CGSize(width: 30.0, height: 30.0)),
- availableSize: CGSize(width: 19.0, height: 19.0),
+ ),
+ availableSize: CGSize(width: 44.0, height: 44.0),
transition: .immediate
)
context.add(eyedropperButton
- .position(CGPoint(x: environment.safeInsets.left + eyedropperButton.size.width + 1.0, y: 29.0))
+ .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - eyedropperButton.size.width / 2.0 - 16.0, y: 16.0 + eyedropperButton.size.height / 2.0))
)
let closeButton = closeButton.update(
- component: Button(
- content: AnyComponent(ZStack([
- AnyComponentWithIdentity(
- id: "background",
- component: AnyComponent(
- BlurredBackgroundComponent(
- color: UIColor(rgb: 0x888888, alpha: 0.1)
- )
- )
- ),
- AnyComponentWithIdentity(
- id: "icon",
- component: AnyComponent(
- Image(image: state.closeImage)
- )
- ),
- ])),
- action: { [weak component] in
- component?.dismiss()
+ component: GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ component: AnyComponentWithIdentity(
+ id: "icon",
+ component: AnyComponent(BundleIconComponent(name: "Navigation/Close", tintColor: .white))
+ ),
+ action: { _ in
+ component.dismiss()
}
),
availableSize: CGSize(width: 30.0, height: 30.0),
transition: .immediate
)
context.add(closeButton
- .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - closeButton.size.width - 1.0, y: 29.0))
- .clipsToBounds(true)
- .cornerRadius(15.0)
+ .position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
)
let title = title.update(
@@ -2046,17 +1952,24 @@ private final class ColorPickerContent: CombinedComponent {
transition: .immediate
)
context.add(title
- .position(CGPoint(x: context.availableSize.width / 2.0, y: 29.0))
+ .position(CGPoint(x: context.availableSize.width / 2.0, y: 16.0 + 22.0))
)
- var contentHeight: CGFloat = 58.0
+ var contentHeight: CGFloat = 76.0
let modeControl = modeControl.update(
- component: SegmentedControlComponent(
- values: [strings.Paint_ColorGrid, strings.Paint_ColorSpectrum, strings.Paint_ColorSliders],
- selectedIndex: 0,
- selectionChanged: { [weak state] index in
- state?.updateSelectedMode(index)
+ component: SegmentControlComponent(
+ theme: SegmentControlComponent.Theme(backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.07), legacyBackgroundColor: .clear, foregroundColor: UIColor(rgb: 0x6f7075, alpha: 0.6), textColor: .white, dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)),
+ items: [
+ .init(id: 0, title: strings.Paint_ColorGrid),
+ .init(id: 1, title: strings.Paint_ColorSpectrum),
+ .init(id: 2, title: strings.Paint_ColorSliders),
+ ],
+ selectedId: state.selectedMode,
+ action: { [weak state] index in
+ if let value = index.base as? Int {
+ state?.updateSelectedMode(value)
+ }
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
@@ -2379,6 +2292,7 @@ private final class ColorPickerSheetComponent: CombinedComponent {
})
}
)),
+ style: .glass,
backgroundColor: .blur(.dark),
animateOut: animateOut
),
diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift
index 2678fbe440..aa578ffa70 100644
--- a/submodules/DrawingUI/Sources/DrawingScreen.swift
+++ b/submodules/DrawingUI/Sources/DrawingScreen.swift
@@ -25,6 +25,9 @@ import FastBlur
import MediaEditor
import StickerPickerScreen
import ImageObjectSeparation
+import GlassBarButtonComponent
+import GlassControls
+import BundleIconComponent
public struct DrawingResultData {
public let data: Data?
@@ -363,15 +366,17 @@ private final class ReferenceContentSource: ContextReferenceContentSource {
private let sourceView: UIView
private let contentArea: CGRect
private let customPosition: CGPoint
+ private let actionsPosition: ContextControllerReferenceViewInfo.ActionsPosition
- init(sourceView: UIView, contentArea: CGRect, customPosition: CGPoint) {
+ init(sourceView: UIView, contentArea: CGRect, customPosition: CGPoint, actionsPosition: ContextControllerReferenceViewInfo.ActionsPosition = .top) {
self.sourceView = sourceView
self.contentArea = contentArea
self.customPosition = customPosition
+ self.actionsPosition = actionsPosition
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
- return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: self.contentArea, customPosition: self.customPosition, actionsPosition: .top)
+ return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: self.contentArea, customPosition: self.customPosition, actionsPosition: self.actionsPosition)
}
}
@@ -467,13 +472,11 @@ private let bottomGradientTag = GenericComponentViewTag()
private let undoButtonTag = GenericComponentViewTag()
private let redoButtonTag = GenericComponentViewTag()
private let clearAllButtonTag = GenericComponentViewTag()
+private let topButtonsTag = GenericComponentViewTag()
private let colorButtonTag = GenericComponentViewTag()
private let addButtonTag = GenericComponentViewTag()
private let toolsTag = GenericComponentViewTag()
private let modeTag = GenericComponentViewTag()
-private let flipButtonTag = GenericComponentViewTag()
-private let fillButtonTag = GenericComponentViewTag()
-private let zoomOutButtonTag = GenericComponentViewTag()
private let textSettingsTag = GenericComponentViewTag()
private let sizeSliderTag = GenericComponentViewTag()
private let fontTag = GenericComponentViewTag()
@@ -489,6 +492,12 @@ private let colorTags = [color1Tag, color2Tag, color3Tag, color4Tag, color5Tag,
private let cancelButtonTag = GenericComponentViewTag()
private let doneButtonTag = GenericComponentViewTag()
+enum DrawingMode: Int {
+ case drawing
+ case sticker
+ case text
+}
+
private final class DrawingScreenComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@@ -616,7 +625,6 @@ private final class DrawingScreenComponent: CombinedComponent {
final class State: ComponentState {
enum ImageKey: Hashable {
case undo
- case redo
case done
case add
case fill
@@ -633,8 +641,6 @@ private final class DrawingScreenComponent: CombinedComponent {
switch key {
case .undo:
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Undo"), color: .white)!
- case .redo:
- image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Redo"), color: .white)!
case .done:
image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Done"), color: .white)!
case .add:
@@ -652,13 +658,7 @@ private final class DrawingScreenComponent: CombinedComponent {
return image
}
}
-
- enum Mode {
- case drawing
- case sticker
- case text
- }
-
+
private let context: AccountContext
private let updateToolState: ActionSlot
private let insertEntity: ActionSlot
@@ -675,7 +675,7 @@ private final class DrawingScreenComponent: CombinedComponent {
private let entityViewForEntity: (DrawingEntity) -> DrawingEntityView?
private let present: (ViewController) -> Void
- var currentMode: Mode
+ var currentMode: DrawingMode
var drawingState: DrawingState
var drawingViewState: DrawingView.NavigationState
var currentColor: DrawingColor
@@ -992,7 +992,7 @@ private final class DrawingScreenComponent: CombinedComponent {
self.present(contextController)
}
- func updateCurrentMode(_ mode: Mode, update: Bool = true) {
+ func updateCurrentMode(_ mode: DrawingMode, update: Bool = true) {
self.currentMode = mode
if let selectedEntity = self.selectedEntity {
if selectedEntity is DrawingStickerEntity || selectedEntity is DrawingTextEntity {
@@ -1058,15 +1058,13 @@ private final class DrawingScreenComponent: CombinedComponent {
let topGradient = Child(BlurredGradientComponent.self)
let bottomGradient = Child(BlurredGradientComponent.self)
- let undoButton = Child(Button.self)
+ let undoButton = Child(GlassBarButtonComponent.self)
+ let redoButton = Child(GlassBarButtonComponent.self)
+ let clearAllButton = Child(GlassBarButtonComponent.self)
+ let topButtons = Child(GlassControlPanelComponent.self)
- let redoButton = Child(Button.self)
- let clearAllButton = Child(Button.self)
-
- let zoomOutButton = Child(Button.self)
-
let tools = Child(ToolsComponent.self)
- let modeAndSize = Child(ModeAndSizeComponent.self)
+ let mode = Child(ModeComponent.self)
let colorButton = Child(ColorSwatchComponent.self)
@@ -1082,16 +1080,11 @@ private final class DrawingScreenComponent: CombinedComponent {
let swatch8Button = Child(ColorSwatchComponent.self)
let addButton = Child(Button.self)
-
- let flipButton = Child(Button.self)
- let fillButton = Child(Button.self)
-
- let backButton = Child(Button.self)
- let doneButton = Child(Button.self)
+
+ let backButton = Child(GlassBarButtonComponent.self)
+ let doneButton = Child(GlassBarButtonComponent.self)
let textSize = Child(TextSizeSliderComponent.self)
- let textCancelButton = Child(Button.self)
- let textDoneButton = Child(Button.self)
let presetColors: [DrawingColor] = [
DrawingColor(rgb: 0xff453a),
@@ -1220,6 +1213,8 @@ private final class DrawingScreenComponent: CombinedComponent {
var additionalBottomInset: CGFloat = 0.0
if component.sourceHint == .storyEditor {
additionalBottomInset = max(0.0, previewBottomInset - environment.safeInsets.bottom - 49.0)
+ } else {
+ additionalBottomInset = 8.0
}
if let textEntity = state.selectedEntity as? DrawingTextEntity {
@@ -1318,8 +1313,8 @@ private final class DrawingScreenComponent: CombinedComponent {
)
}
- let rightButtonPosition = rightEdge - 24.0
- var offsetX: CGFloat = leftEdge + 24.0
+ let rightButtonPosition = rightEdge - 28.0
+ var offsetX: CGFloat = leftEdge + 28.0
let delta: CGFloat = (rightButtonPosition - offsetX) / 7.0
let applySwatchColor: (DrawingColor) -> Void = { [weak state] color in
@@ -1608,6 +1603,7 @@ private final class DrawingScreenComponent: CombinedComponent {
}
var hasTopButtons = false
+ var centerItems: [GlassControlGroupComponent.Item] = []
if let entity = state.selectedEntity {
var isFilled: Bool?
if let entity = entity as? DrawingSimpleShapeEntity {
@@ -1625,12 +1621,13 @@ private final class DrawingScreenComponent: CombinedComponent {
hasTopButtons = isFilled != nil || hasFlip
- if let isFilled = isFilled {
- let fillButton = fillButton.update(
- component: Button(
- content: AnyComponent(
- Image(image: state.image(isFilled ? .fill : .stroke))
- ),
+ if controlsAreVisible {
+ if let isFilled = isFilled {
+ centerItems.append(GlassControlGroupComponent.Item(
+ id: AnyHashable("fill"),
+ content: .customIcon(id: AnyHashable("fill"), component: AnyComponent(
+ Image(image: state.image(isFilled ? .fill : .stroke), size: CGSize(width: 30.0, height: 30.0))
+ ), insets: .zero),
action: { [weak state] in
guard let state = state else {
return
@@ -1661,25 +1658,15 @@ private final class DrawingScreenComponent: CombinedComponent {
}
state.updated(transition: .easeInOut(duration: 0.2))
}
- ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(fillButtonTag),
- availableSize: CGSize(width: 30.0, height: 30.0),
- transition: .immediate
- )
- context.add(fillButton
- .position(CGPoint(x: context.availableSize.width / 2.0 - (hasFlip ? 46.0 : 0.0), y: topInset))
- .appear(.default(scale: true))
- .disappear(.default(scale: true))
- .opacity(!controlsAreVisible ? 0.0 : 1.0)
- .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil)
- )
- }
-
- if hasFlip {
- let flipButton = flipButton.update(
- component: Button(
- content: AnyComponent(
- Image(image: state.image(.flip))
- ),
+ ))
+ }
+
+ if hasFlip {
+ centerItems.append(GlassControlGroupComponent.Item(
+ id: AnyHashable("flip"),
+ content: .customIcon(id: AnyHashable("flip"), component: AnyComponent(
+ Image(image: state.image(.flip), size: CGSize(width: 30.0, height: 30.0))
+ ), insets: .zero),
action: { [weak state] in
guard let state = state else {
return
@@ -1695,17 +1682,8 @@ private final class DrawingScreenComponent: CombinedComponent {
}
state.updated(transition: .easeInOut(duration: 0.2))
}
- ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(flipButtonTag),
- availableSize: CGSize(width: 30.0, height: 30.0),
- transition: .immediate
- )
- context.add(flipButton
- .position(CGPoint(x: context.availableSize.width / 2.0 + (isFilled != nil ? 46.0 : 0.0), y: topInset))
- .appear(.default(scale: true))
- .disappear(.default(scale: true))
- .opacity(!controlsAreVisible ? 0.0 : 1.0)
- .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil)
- )
+ ))
+ }
}
}
@@ -1730,28 +1708,24 @@ private final class DrawingScreenComponent: CombinedComponent {
sizeValue = entity.lineWidth
}
}
- if state.drawingViewState.canZoomOut && !hasTopButtons {
- let zoomOutButton = zoomOutButton.update(
- component: Button(
- content: AnyComponent(
- ZoomOutButtonContent(
- title: strings.Paint_ZoomOut,
- image: state.image(.zoomOut)
- )
- ),
- action: {
- dismissEyedropper.invoke(Void())
- performAction.invoke(.zoomOut)
- }
- ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(zoomOutButtonTag),
- availableSize: CGSize(width: 120.0, height: 33.0),
- transition: .immediate
- )
- context.add(zoomOutButton
- .position(CGPoint(x: context.availableSize.width / 2.0, y: environment.safeInsets.top + 32.0 - UIScreenPixel))
- .appear(.default(scale: true, alpha: true))
- .disappear(.default(scale: true, alpha: true))
- )
+ if state.drawingViewState.canZoomOut && !hasTopButtons && controlsAreVisible {
+ centerItems.append(GlassControlGroupComponent.Item(
+ id: AnyHashable("zoomOut"),
+ content: .customIcon(id: AnyHashable("zoomOut"), component: AnyComponent(
+ HStack([
+ AnyComponentWithIdentity(id: "icon", component: AnyComponent(
+ Image(image: state.image(.zoomOut), size: CGSize(width: 24.0, height: 24.0))
+ )),
+ AnyComponentWithIdentity(id: "label", component: AnyComponent(
+ Text(text: environment.strings.Paint_ZoomOut, font: Font.regular(17.0), color: .white)
+ )),
+ ], spacing: 2.0)
+ ), insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)),
+ action: {
+ dismissEyedropper.invoke(Void())
+ performAction.invoke(.zoomOut)
+ }
+ ))
}
}
if let sizeValue {
@@ -1784,111 +1758,170 @@ private final class DrawingScreenComponent: CombinedComponent {
.position(CGPoint(x: textSize.size.width / 2.0, y: topInset + (context.availableSize.height - topInset - bottomInset) / 2.0))
.opacity(sizeSliderVisible && controlsAreVisible ? 1.0 : 0.0)
)
-
- let undoButton = undoButton.update(
- component: Button(
- content: AnyComponent(
- Image(image: state.image(.undo))
- ),
- isEnabled: state.drawingViewState.canUndo,
- action: {
+
+ var leftItems: [GlassControlGroupComponent.Item] = []
+ var rightItems: [GlassControlGroupComponent.Item] = []
+
+ if !isEditingText && controlsAreVisible {
+ leftItems.append(GlassControlGroupComponent.Item(
+ id: "undo",
+ content: .customIcon(id: "undo", component: AnyComponent(Image(image: state.image(.undo), tintColor: state.drawingViewState.canUndo ? .white : .white.withAlphaComponent(0.4), size: CGSize(width: 30.0, height: 30.0))), insets: .zero),
+ action: state.drawingViewState.canUndo ? {
dismissEyedropper.invoke(Void())
performAction.invoke(.undo)
- }
- ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(undoButtonTag),
- availableSize: CGSize(width: 24.0, height: 24.0),
+ } : nil
+ ))
+
+ if state.drawingViewState.canRedo {
+ leftItems.append(GlassControlGroupComponent.Item(
+ id: "redo",
+ content: .customIcon(id: "redo", component: AnyComponent(Image(image: state.image(.undo), tintColor: state.drawingViewState.canRedo ? .white : .white.withAlphaComponent(0.4), size: CGSize(width: 30.0, height: 30.0), flipHorizontally: true)), insets: .zero),
+ action: state.drawingViewState.canRedo ? {
+ dismissEyedropper.invoke(Void())
+ performAction.invoke(.redo)
+ } : nil
+ ))
+ }
+ }
+
+ let undoButton = undoButton.update(
+ component: GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ component: AnyComponentWithIdentity(
+ id: "icon",
+ component: AnyComponent(Image(image: state.image(.undo)))
+ ),
+ action: { _ in
+ dismissEyedropper.invoke(Void())
+ performAction.invoke(.undo)
+ },
+ tag: undoButtonTag
+ ),
+ availableSize: CGSize(width: 44.0, height: 44.0),
transition: context.transition
)
context.add(undoButton
- .position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 2.0, y: topInset))
+ .position(CGPoint(x: environment.safeInsets.left + undoButton.size.width / 2.0 + 16.0, y: topInset))
.scale(isEditingText ? 0.01 : 1.0)
- .opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0)
- .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil)
+ .opacity(0.0)
+ //.opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0)
)
-
let redoButton = redoButton.update(
- component: Button(
- content: AnyComponent(
- Image(image: state.image(.redo))
+ component: GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ component: AnyComponentWithIdentity(
+ id: "icon",
+ component: AnyComponent(Image(image: state.image(.undo), flipHorizontally: true))
),
- action: {
+ action: { _ in
dismissEyedropper.invoke(Void())
performAction.invoke(.redo)
- }
- ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(redoButtonTag),
- availableSize: CGSize(width: 24.0, height: 24.0),
+ },
+ tag: redoButtonTag
+ ),
+ availableSize: CGSize(width: 44.0, height: 44.0),
transition: context.transition
)
context.add(redoButton
.position(CGPoint(x: environment.safeInsets.left + undoButton.size.width + 2.0 + redoButton.size.width / 2.0, y: topInset))
.scale(state.drawingViewState.canRedo && !isEditingText ? 1.0 : 0.01)
- .opacity(state.drawingViewState.canRedo && !isEditingText && controlsAreVisible ? 1.0 : 0.0)
- .shadow(component.sourceHint == .storyEditor ? Shadow(color: UIColor(rgb: 0x000000, alpha: 0.35), radius: 2.0, offset: .zero) : nil)
+ .opacity(0.0)
+ //.opacity(state.drawingViewState.canRedo && !isEditingText && controlsAreVisible ? 1.0 : 0.0)
)
let clearAllButton = clearAllButton.update(
- component: Button(
- content: AnyComponent(
- MultilineTextComponent(
- text: .plain(NSAttributedString(string: strings.Paint_Clear, font: Font.regular(17.0), textColor: .white)),
- textShadowColor: component.sourceHint == .storyEditor ? UIColor(rgb: 0x000000, alpha: 0.35) : nil,
- textShadowBlur: 2.0
- )
+ component: GlassBarButtonComponent(
+ size: nil,
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ component: AnyComponentWithIdentity(
+ id: "label",
+ component: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(string: strings.Paint_Clear, font: Font.regular(17.0), textColor: .white))
+ ))
),
- isEnabled: state.drawingViewState.canClear,
- action: {
+ action: { _ in
dismissEyedropper.invoke(Void())
performAction.invoke(.clear)
- }
- ).tagged(clearAllButtonTag),
+ },
+ tag: clearAllButtonTag
+ ),
availableSize: CGSize(width: 180.0, height: 30.0),
transition: context.transition
)
context.add(clearAllButton
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - clearAllButton.size.width / 2.0 - 13.0, y: topInset))
.scale(isEditingText ? 0.01 : 1.0)
- .opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0)
+ .opacity(0.0)
+ //.opacity(isEditingText || !controlsAreVisible ? 0.0 : 1.0)
)
- let textCancelButton = textCancelButton.update(
- component: Button(
- content: AnyComponent(
- Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: .white)
- ),
+ if !isEditingText && controlsAreVisible {
+ rightItems.append(GlassControlGroupComponent.Item(
+ id: "clearAll",
+ content: .text(strings.Paint_Clear),
+ action: state.drawingViewState.canClear ? {
+ dismissEyedropper.invoke(Void())
+ performAction.invoke(.clear)
+ } : nil
+ ))
+ }
+
+ if isEditingText {
+ leftItems.append(GlassControlGroupComponent.Item(
+ id: "cancel",
+ content: .text(strings.Common_Cancel),
action: { [weak state] in
if let entity = state?.selectedEntity as? DrawingTextEntity {
endEditingTextEntityView.invoke((entity.uuid, true))
}
}
- ),
- availableSize: CGSize(width: 100.0, height: 30.0),
- transition: context.transition
- )
- context.add(textCancelButton
- .position(CGPoint(x: environment.safeInsets.left + textCancelButton.size.width / 2.0 + 13.0, y: topInset))
- .scale(isEditingText ? 1.0 : 0.01)
- .opacity(isEditingText ? 1.0 : 0.0)
- )
-
- let textDoneButton = textDoneButton.update(
- component: Button(
- content: AnyComponent(
- Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: .white)
- ),
+ ))
+
+ rightItems.append(GlassControlGroupComponent.Item(
+ id: "done",
+ content: .text(strings.Common_Done),
action: { [weak state] in
if let entity = state?.selectedEntity as? DrawingTextEntity {
endEditingTextEntityView.invoke((entity.uuid, false))
}
}
+ ))
+ }
+
+ let topButtons = topButtons.update(
+ component: GlassControlPanelComponent(
+ theme: defaultDarkPresentationTheme,
+ leftItem: leftItems.isEmpty ? nil : GlassControlPanelComponent.Item(
+ items: leftItems,
+ background: .panel
+ ),
+ centralItem: centerItems.isEmpty ? nil : GlassControlPanelComponent.Item(
+ items: centerItems,
+ background: .panel
+ ),
+ rightItem: rightItems.isEmpty ? nil : GlassControlPanelComponent.Item(
+ items: rightItems,
+ background: .panel
+ ),
+ centerAlignmentIfPossible: true,
+ isDark: true,
+ tag: topButtonsTag
),
- availableSize: CGSize(width: 100.0, height: 30.0),
+ availableSize: CGSize(width: context.availableSize.width - 32.0, height: 44.0),
transition: context.transition
)
- context.add(textDoneButton
- .position(CGPoint(x: context.availableSize.width - environment.safeInsets.right - textDoneButton.size.width / 2.0 - 13.0, y: topInset))
- .scale(isEditingText ? 1.0 : 0.01)
- .opacity(isEditingText ? 1.0 : 0.0)
+ context.add(topButtons
+ .position(CGPoint(x: context.availableSize.width / 2.0, y: topInset))
+ //.opacity(isEditingText ? 1.0 : 0.0)
)
var color: DrawingColor?
@@ -1930,7 +1963,7 @@ private final class DrawingScreenComponent: CombinedComponent {
transition: context.transition
)
context.add(colorButton
- .position(CGPoint(x: leftEdge + colorButton.size.width / 2.0 + 2.0, y: context.availableSize.height - environment.safeInsets.bottom - colorButton.size.height / 2.0 - 89.0 - additionalBottomInset))
+ .position(CGPoint(x: leftEdge + colorButton.size.width / 2.0 + 6.0, y: context.availableSize.height - environment.safeInsets.bottom - colorButton.size.height / 2.0 - 89.0 - additionalBottomInset))
.appear(.default(scale: true))
.disappear(.default(scale: true))
.opacity(controlsAreVisible ? 1.0 : 0.0)
@@ -1979,7 +2012,7 @@ private final class DrawingScreenComponent: CombinedComponent {
transition: .immediate
)
context.add(addButton
- .position(CGPoint(x: rightEdge - addButton.size.width / 2.0 - 2.0, y: context.availableSize.height - environment.safeInsets.bottom - addButton.size.height / 2.0 - 89.0 - additionalBottomInset))
+ .position(CGPoint(x: rightEdge - addButton.size.width / 2.0 - 6.0, y: context.availableSize.height - environment.safeInsets.bottom - addButton.size.height / 2.0 - 89.0 - additionalBottomInset))
.appear(.default(scale: true))
.disappear(.default(scale: true))
.cornerRadius(12.0)
@@ -1987,27 +2020,38 @@ private final class DrawingScreenComponent: CombinedComponent {
)
let doneButton = doneButton.update(
- component: Button(
- content: AnyComponent(
- Image(image: state.image(.done))
+ component: GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: UIColor(rgb: 0x0088ff),
+ isDark: true,
+ state: .tintedGlass,
+ component: AnyComponentWithIdentity(
+ id: "icon",
+ component: AnyComponent(
+ BundleIconComponent(name: "Navigation/Done", tintColor: .white)
+ )
),
- action: { [weak state] in
+ action: { [weak state] _ in
dismissEyedropper.invoke(Void())
state?.saveToolState()
apply.invoke(Void())
- }
- ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(doneButtonTag),
- availableSize: CGSize(width: 33.0, height: 33.0),
+ },
+ tag: doneButtonTag
+ ),
+ availableSize: CGSize(width: 44.0, height: 44.0),
transition: .immediate
)
- var doneButtonPosition = CGPoint(x: context.availableSize.width - environment.safeInsets.right - doneButton.size.width / 2.0 - 3.0, y: context.availableSize.height - environment.safeInsets.bottom - doneButton.size.height / 2.0 - 2.0 - UIScreenPixel)
+ var doneButtonPosition = CGPoint(x: context.availableSize.width - environment.safeInsets.right - doneButton.size.width / 2.0 - 14.0, y: context.availableSize.height - environment.safeInsets.bottom - doneButton.size.height / 2.0 - 2.0 - UIScreenPixel)
if component.sourceHint == .storyEditor {
doneButtonPosition.x = doneButtonPosition.x - 2.0
if case .regular = environment.metrics.widthClass {
doneButtonPosition.x -= 20.0
}
- doneButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + doneButton.size.height / 2.0) + controlsBottomInset
+ doneButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + doneButton.size.height / 2.0) + controlsBottomInset + 5.0
+ } else {
+ doneButtonPosition.x = doneButtonPosition.x - 12.0
+ doneButtonPosition.y -= 3.0 - UIScreenPixel
}
context.add(doneButton
.position(doneButtonPosition)
@@ -2026,117 +2070,82 @@ private final class DrawingScreenComponent: CombinedComponent {
})
.opacity(controlsAreVisible ? 1.0 : 0.0)
)
-
- let selectedIndex: Int
- switch state.currentMode {
- case .drawing:
- selectedIndex = 0
- case .sticker:
- selectedIndex = 1
- case .text:
- selectedIndex = 2
- }
-
- var selectedSize: CGFloat = 0.0
- if let entity = state.selectedEntity {
- selectedSize = entity.lineWidth
- } else {
- selectedSize = state.drawingState.toolState(for: state.drawingState.selectedTool).size ?? 0.0
- }
-
- let modeAndSize = modeAndSize.update(
- component: ModeAndSizeComponent(
- values: [ strings.Paint_Draw, strings.Paint_Sticker, strings.Paint_Text],
- sizeValue: selectedSize,
- isEditing: false,
- isEnabled: true,
- rightInset: modeRightInset - 57.0,
- tag: modeTag,
- selectedIndex: selectedIndex,
- selectionChanged: { [weak state] index in
- dismissEyedropper.invoke(Void())
- guard let state = state else {
- return
- }
- switch index {
- case 1:
- state.presentStickerPicker()
- case 2:
- state.addTextEntity()
- default:
- state.updateCurrentMode(.drawing)
- }
- },
- sizeUpdated: { [weak state] size in
- if let state = state {
+
+ let mode = mode.update(
+ component: ModeComponent(
+ isTablet: false,
+ strings: environment.strings,
+ tintColor: .white,
+ availableModes: [.drawing, .sticker, .text],
+ currentMode: state.currentMode,
+ updatedMode: { [weak state] mode in
+ if let state {
dismissEyedropper.invoke(Void())
- state.updateBrushSize(size)
- if state.selectedEntity == nil {
- previewBrushSize.invoke(size)
+ switch mode {
+ case .drawing:
+ state.updateCurrentMode(.drawing)
+ case .sticker:
+ state.presentStickerPicker()
+ case .text:
+ state.addTextEntity()
}
}
},
- sizeReleased: {
- previewBrushSize.invoke(nil)
- }
+ tag: modeTag
),
- availableSize: CGSize(width: availableWidth - 57.0 - modeRightInset, height: context.availableSize.height),
+ availableSize: CGSize(width: context.availableSize.width - 66.0 * 2.0, height: 44.0),
transition: context.transition
)
- var modeAndSizePosition = CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - modeAndSize.size.height / 2.0 - 9.0)
+ var modePosition = CGPoint(x: context.availableSize.width / 2.0 - (modeRightInset - 57.0) / 2.0, y: context.availableSize.height - environment.safeInsets.bottom - mode.size.height / 2.0 - 9.0)
if component.sourceHint == .storyEditor {
- modeAndSizePosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 8.0 + modeAndSize.size.height / 2.0) + controlsBottomInset
+ modePosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 8.0 + mode.size.height / 2.0) + controlsBottomInset
+ } else {
+ modePosition.y += 4.0
}
- context.add(modeAndSize
- .position(modeAndSizePosition)
+ context.add(mode
+ .position(modePosition)
.opacity(controlsAreVisible ? 1.0 : 0.0)
)
- var animatingOut = false
- if let appearanceTransition = context.transition.userData(DrawingScreenTransition.self), case .animateOut = appearanceTransition {
- animatingOut = true
- }
-
- if animatingOut && component.sourceHint == .storyEditor {
-
- } else {
- let backButton = backButton.update(
- component: Button(
- content: AnyComponent(
- LottieAnimationComponent(
- animation: LottieAnimationComponent.AnimationItem(
- name: "media_backToCancel",
- mode: .animating(loop: false),
- range: animatingOut || component.isAvatar ? (0.5, 1.0) : (0.0, 0.5)
- ),
- colors: ["__allcolors__": .white],
- size: CGSize(width: 33.0, height: 33.0)
- )
- ),
- action: { [weak state] in
- if let state = state {
- dismissEyedropper.invoke(Void())
- state.saveToolState()
- dismiss.invoke(Void())
- }
+ let backButton = backButton.update(
+ component: GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ component: AnyComponentWithIdentity(
+ id: "icon",
+ component: AnyComponent(
+ BundleIconComponent(name: "Navigation/Close", tintColor: .white)
+ )
+ ),
+ action: { [weak state] _ in
+ if let state {
+ dismissEyedropper.invoke(Void())
+ state.saveToolState()
+ dismiss.invoke(Void())
}
- ).minSize(CGSize(width: 44.0, height: 44.0)).tagged(cancelButtonTag),
- availableSize: CGSize(width: 33.0, height: 33.0),
- transition: .immediate
- )
- var backButtonPosition = CGPoint(x: environment.safeInsets.left + backButton.size.width / 2.0 + 3.0, y: context.availableSize.height - environment.safeInsets.bottom - backButton.size.height / 2.0 - 2.0 - UIScreenPixel)
- if component.sourceHint == .storyEditor {
- backButtonPosition.x = backButtonPosition.x + 2.0
- if case .regular = environment.metrics.widthClass {
- backButtonPosition.x += 20.0
- }
- backButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + backButton.size.height / 2.0) + controlsBottomInset
+ },
+ tag: cancelButtonTag
+ ),
+ availableSize: CGSize(width: 44.0, height: 44.0),
+ transition: .immediate
+ )
+ var backButtonPosition = CGPoint(x: environment.safeInsets.left + backButton.size.width / 2.0 + 14.0, y: context.availableSize.height - environment.safeInsets.bottom - backButton.size.height / 2.0 - 2.0 - UIScreenPixel)
+ if component.sourceHint == .storyEditor {
+ backButtonPosition.x = backButtonPosition.x + 2.0
+ if case .regular = environment.metrics.widthClass {
+ backButtonPosition.x += 20.0
}
- context.add(backButton
- .position(backButtonPosition)
- .opacity(controlsAreVisible ? 1.0 : 0.0)
- )
+ backButtonPosition.y = floorToScreenPixels(context.availableSize.height - previewBottomInset + 3.0 + backButton.size.height / 2.0) + controlsBottomInset + 5.0
+ } else {
+ backButtonPosition.x = backButtonPosition.x + 12.0
+ backButtonPosition.y -= 3.0 - UIScreenPixel
}
+ context.add(backButton
+ .position(backButtonPosition)
+ .opacity(controlsAreVisible ? 1.0 : 0.0)
+ )
return context.availableSize
}
@@ -2224,22 +2233,24 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
self.performAction.connect { [weak self] action in
if let self {
if case .clear = action {
- let actionSheet = ActionSheetController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme))
- actionSheet.setItemGroups([
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: self.presentationData.strings.Paint_ClearConfirm, color: .destructive, action: { [weak actionSheet, weak self] in
- actionSheet?.dismissAnimated()
-
- self?._drawingView?.performAction(action)
- })
- ]),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- })
- ])
- ])
- self.controller?.present(actionSheet, in: .window(.root))
+ let sourceView: UIView
+ if let topButtonsView = self.componentHost.findTaggedView(tag: topButtonsTag) as? GlassControlPanelComponent.View, let rightItemView = topButtonsView.rightItemView, let clearAllView = rightItemView.itemView(id: AnyHashable("clearAll")) {
+ sourceView = clearAllView
+ } else {
+ sourceView = self.view
+ }
+
+ let items: [ContextMenuItem] = [
+ .action(ContextMenuActionItem(text: self.presentationData.strings.Paint_ClearConfirm, textColor: .destructive, icon: { _ in
+ return nil
+ }, action: { [weak self] f in
+ f.dismissWithResult(.default)
+ self?._drawingView?.performAction(.clear)
+ }))
+ ]
+ let presentationData = self.presentationData.withUpdated(theme: defaultDarkPresentationTheme)
+ let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint(), actionsPosition: .bottom)), items: .single(ContextController.Items(content: .list(items))))
+ self.controller?.present(contextController, in: .window(.root))
} else {
self._drawingView?.performAction(action)
}
@@ -2403,22 +2414,18 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
self.dismiss.connect { [weak self] _ in
if let strongSelf = self {
if strongSelf.drawingView.canUndo || strongSelf.entitiesView.hasChanges {
- let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme))
- actionSheet.setItemGroups([
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.PhotoEditor_DiscardChanges, color: .accent, action: { [weak actionSheet, weak self] in
- actionSheet?.dismissAnimated()
-
- self?.controller?.requestDismiss()
- })
- ]),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- })
- ])
- ])
- strongSelf.controller?.present(actionSheet, in: .window(.root))
+ let sourceView = strongSelf.componentHost.findTaggedView(tag: cancelButtonTag) ?? strongSelf.view
+ let items: [ContextMenuItem] = [
+ .action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PhotoEditor_DiscardChanges, textColor: .destructive, icon: { _ in
+ return nil
+ }, action: { [weak self] f in
+ f.dismissWithResult(.default)
+ self?.controller?.requestDismiss()
+ }))
+ ]
+ let presentationData = strongSelf.presentationData.withUpdated(theme: defaultDarkPresentationTheme)
+ let contextController = makeContextController(presentationData: presentationData, source: .reference(ReferenceContentSource(sourceView: sourceView, contentArea: UIScreen.main.bounds, customPosition: CGPoint())), items: .single(ContextController.Items(content: .list(items))))
+ strongSelf.controller?.present(contextController, in: .window(.root))
} else {
strongSelf.controller?.requestDismiss()
}
@@ -2497,17 +2504,24 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
if let view = self.componentHost.findTaggedView(tag: bottomGradientTag) {
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
- if let buttonView = self.componentHost.findTaggedView(tag: undoButtonTag) {
- buttonView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
- buttonView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
+ if let topButtonsView = self.componentHost.findTaggedView(tag: topButtonsTag) as? GlassControlPanelComponent.View {
+ topButtonsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
+
+ if let leftItemView = topButtonsView.leftItemView {
+ leftItemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
+ leftItemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
+ }
+ if let rightItemView = topButtonsView.rightItemView {
+ rightItemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
+ rightItemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
+ }
}
- if let buttonView = self.componentHost.findTaggedView(tag: clearAllButtonTag) {
- buttonView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
- buttonView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
+ if let view = self.componentHost.findTaggedView(tag: modeTag) {
+ view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
- if let buttonView = self.componentHost.findTaggedView(tag: addButtonTag) {
- buttonView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
- buttonView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
+ if let view = self.componentHost.findTaggedView(tag: addButtonTag) {
+ view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
+ view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
}
var delay: Double = 0.0
for tag in colorTags {
@@ -2520,6 +2534,15 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) {
view.layer.animatePosition(from: CGPoint(x: -33.0, y: 0.0), to: CGPoint(), duration: 0.3, additive: true)
}
+
+ if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) {
+ view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
+ view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
+ }
+ if let view = self.componentHost.findTaggedView(tag: doneButtonTag) {
+ view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
+ view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.3)
+ }
}
func animateOut(completion: @escaping () -> Void) {
@@ -2536,43 +2559,24 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
}
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
}
- if let buttonView = self.componentHost.findTaggedView(tag: undoButtonTag) {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
- }
- if let buttonView = self.componentHost.findTaggedView(tag: redoButtonTag), buttonView.alpha > 0.0 {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
- }
- if let buttonView = self.componentHost.findTaggedView(tag: clearAllButtonTag) {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
+ if let topButtonsView = self.componentHost.findTaggedView(tag: topButtonsTag) as? GlassControlPanelComponent.View {
+ topButtonsView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
+
+ if let view = topButtonsView.leftItemView {
+ view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
+ view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false)
+ }
+ if let view = topButtonsView.rightItemView {
+ view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
+ view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false)
+ }
}
if let view = self.componentHost.findTaggedView(tag: colorButtonTag) as? ColorSwatchComponent.View {
view.animateOut()
}
- if let buttonView = self.componentHost.findTaggedView(tag: addButtonTag) {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
- }
- if let buttonView = self.componentHost.findTaggedView(tag: flipButtonTag) {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
- }
- if let buttonView = self.componentHost.findTaggedView(tag: fillButtonTag) {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
- }
- if let buttonView = self.componentHost.findTaggedView(tag: zoomOutButtonTag) {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- buttonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
+ if let view = self.componentHost.findTaggedView(tag: addButtonTag) {
+ view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
+ view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false)
}
if let view = self.componentHost.findTaggedView(tag: sizeSliderTag) {
view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -33.0, y: 0.0), duration: 0.3, removeOnCompletion: false, additive: true)
@@ -2596,12 +2600,18 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
})
}
- if let view = self.componentHost.findTaggedView(tag: modeTag) as? ModeAndSizeComponent.View {
- view.animateOut()
+ if let view = self.componentHost.findTaggedView(tag: modeTag) {
+ view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
}
- if let buttonView = self.componentHost.findTaggedView(tag: doneButtonTag) {
- buttonView.alpha = 0.0
- buttonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
+
+ if let view = self.componentHost.findTaggedView(tag: cancelButtonTag) {
+ view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
+ view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, removeOnCompletion: false)
+ }
+
+ if let view = self.componentHost.findTaggedView(tag: doneButtonTag) {
+ view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
+ view.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3)
}
}
diff --git a/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift b/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift
index 5bd68600bd..6cd3b91c06 100644
--- a/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift
+++ b/submodules/DrawingUI/Sources/DrawingVideoRecorder.swift
@@ -8,7 +8,7 @@ import AVFoundation
public final class EntityVideoRecorder {
private weak var mediaEditor: MediaEditor?
private weak var entitiesView: DrawingEntitiesView?
-
+
private let maxDuration: Double
private let camera: Camera
@@ -21,6 +21,9 @@ public final class EntityVideoRecorder {
private let micLevelPromise = Promise()
private var changingPositionDisposable: Disposable?
+ private var positionDisposable: Disposable?
+ private var currentCameraPosition: Camera.Position = .front
+ private var recordingInitialPosition: Camera.Position = .front
public var duration: Signal {
return self.durationPromise.get()
@@ -35,7 +38,7 @@ public final class EntityVideoRecorder {
public init(mediaEditor: MediaEditor, entitiesView: DrawingEntitiesView) {
self.mediaEditor = mediaEditor
self.entitiesView = entitiesView
-
+
self.maxDuration = min(60.0, mediaEditor.duration ?? 60.0)
self.previewView = CameraSimplePreviewView(frame: .zero, main: true)
@@ -86,6 +89,10 @@ public final class EntityVideoRecorder {
}
self.micLevelPromise.set(camera.audioLevel)
+ self.positionDisposable = (camera.position
+ |> deliverOnMainQueue).start(next: { [weak self] position in
+ self?.currentCameraPosition = position
+ })
let start = mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0
mediaEditor.stop()
@@ -107,6 +114,7 @@ public final class EntityVideoRecorder {
deinit {
self.recordingDisposable.dispose()
self.changingPositionDisposable?.dispose()
+ self.positionDisposable?.dispose()
}
public func setup(
@@ -145,6 +153,7 @@ public final class EntityVideoRecorder {
mediaEditor.maybeMuteVideo()
mediaEditor.play()
+ self.recordingInitialPosition = self.currentCameraPosition
self.start = CACurrentMediaTime()
self.recordingDisposable.set((self.camera.startRecording()
|> deliverOnMainQueue).startStrict(next: { [weak self] recordingData in
@@ -169,16 +178,20 @@ public final class EntityVideoRecorder {
}
self.recordingDisposable.set((self.camera.stopRecording()
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
- guard let self, let mediaEditor = self.mediaEditor, let entitiesView = self.entitiesView, case let .finished(mainResult, _, _, _, _) = result else {
+ guard let self, let mediaEditor = self.mediaEditor, let entitiesView = self.entitiesView, case let .finished(mainResult, _, _, positionChangeTimestamps, _) = result else {
return
}
if save {
let duration = AVURLAsset(url: URL(fileURLWithPath: mainResult.path)).duration
+ let mirroringChanges = self.additionalVideoMirroringChanges(
+ initialPosition: self.recordingInitialPosition,
+ positionChangeTimestamps: positionChangeTimestamps
+ )
let start = mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0
mediaEditor.setAdditionalVideoOffset(-start, apply: false)
mediaEditor.setAdditionalVideoTrimRange(0 ..< duration.seconds, apply: true)
- mediaEditor.setAdditionalVideo(mainResult.path, positionChanges: [])
+ mediaEditor.setAdditionalVideo(mainResult.path, mirroringChanges: mirroringChanges, positionChanges: [])
mediaEditor.stop()
Queue.mainQueue().justDispatch {
@@ -217,7 +230,7 @@ public final class EntityVideoRecorder {
self.mediaEditor?.maybeUnmuteVideo()
self.entitiesView?.remove(uuid: self.entity.uuid, animated: true)
- self.mediaEditor?.setAdditionalVideo(nil, positionChanges: [])
+ self.mediaEditor?.setAdditionalVideo(nil, mirroringChanges: [], positionChanges: [])
completion()
}
@@ -226,4 +239,22 @@ public final class EntityVideoRecorder {
public func togglePosition() {
self.camera.togglePosition()
}
+
+ private func additionalVideoMirroringChanges(initialPosition: Camera.Position, positionChangeTimestamps: [(Bool, Double)]) -> [VideoMirroringChange] {
+ var result: [VideoMirroringChange] = [
+ VideoMirroringChange(isMirrored: initialPosition == .front, timestamp: 0.0)
+ ]
+ for (isMirrored, timestamp) in positionChangeTimestamps.sorted(by: { $0.1 < $1.1 }) {
+ result.append(VideoMirroringChange(isMirrored: isMirrored, timestamp: timestamp))
+ }
+
+ var deduplicated: [VideoMirroringChange] = []
+ for change in result {
+ if deduplicated.last?.isMirrored == change.isMirrored {
+ continue
+ }
+ deduplicated.append(change)
+ }
+ return deduplicated
+ }
}
diff --git a/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift b/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift
index a8822e5230..0872e00b73 100644
--- a/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift
+++ b/submodules/DrawingUI/Sources/ModeAndSizeComponent.swift
@@ -2,95 +2,123 @@ import Foundation
import UIKit
import Display
import ComponentFlow
-import LegacyComponents
-import TelegramCore
-import SegmentedControlNode
+import MultilineTextComponent
+import TelegramPresentationData
+import GlassBackgroundComponent
+import LiquidLens
+import TabSelectionRecognizer
-private func generateMaskPath(size: CGSize, leftRadius: CGFloat, rightRadius: CGFloat) -> UIBezierPath {
- let path = UIBezierPath()
- path.addArc(withCenter: CGPoint(x: leftRadius, y: size.height / 2.0), radius: leftRadius, startAngle: .pi * 0.5, endAngle: -.pi * 0.5, clockwise: true)
- path.addArc(withCenter: CGPoint(x: size.width - rightRadius, y: size.height / 2.0), radius: rightRadius, startAngle: -.pi * 0.5, endAngle: .pi * 0.5, clockwise: true)
- path.close()
- return path
+private let buttonSize = CGSize(width: 55.0, height: 44.0)
+private let tabletButtonSize = CGSize(width: 55.0, height: 44.0)
+
+extension DrawingMode {
+ func title(strings: PresentationStrings) -> String {
+ switch self {
+ case .drawing:
+ return strings.Paint_Draw
+ case .sticker:
+ return strings.Paint_Sticker
+ case .text:
+ return strings.Paint_Text
+ }
+ }
}
-private func generateKnobImage() -> UIImage? {
- let side: CGFloat = 28.0
- let margin: CGFloat = 10.0
-
- let image = generateImage(CGSize(width: side + margin * 2.0, height: side + margin * 2.0), opaque: false, rotatedContext: { size, context in
- context.clear(CGRect(origin: .zero, size: size))
-
- context.setShadow(offset: CGSize(width: 0.0, height: 0.0), blur: 9.0, color: UIColor(rgb: 0x000000, alpha: 0.3).cgColor)
- context.setFillColor(UIColor.white.cgColor)
- context.fillEllipse(in: CGRect(origin: CGPoint(x: margin, y: margin), size: CGSize(width: side, height: side)))
- })
- return image?.stretchableImage(withLeftCapWidth: Int(margin + side * 0.5), topCapHeight: Int(margin + side * 0.5))
-}
-
-final class ModeAndSizeComponent: Component {
- let values: [String]
- let sizeValue: CGFloat
- let isEditing: Bool
- let isEnabled: Bool
- let rightInset: CGFloat
+final class ModeComponent: Component {
+ let isTablet: Bool
+ let strings: PresentationStrings
+ let tintColor: UIColor
+ let availableModes: [DrawingMode]
+ let currentMode: DrawingMode
+ let updatedMode: (DrawingMode) -> Void
let tag: AnyObject?
- let selectedIndex: Int
- let selectionChanged: (Int) -> Void
- let sizeUpdated: (CGFloat) -> Void
- let sizeReleased: () -> Void
- init(values: [String], sizeValue: CGFloat, isEditing: Bool, isEnabled: Bool, rightInset: CGFloat, tag: AnyObject?, selectedIndex: Int, selectionChanged: @escaping (Int) -> Void, sizeUpdated: @escaping (CGFloat) -> Void, sizeReleased: @escaping () -> Void) {
- self.values = values
- self.sizeValue = sizeValue
- self.isEditing = isEditing
- self.isEnabled = isEnabled
- self.rightInset = rightInset
+ init(
+ isTablet: Bool,
+ strings: PresentationStrings,
+ tintColor: UIColor,
+ availableModes: [DrawingMode],
+ currentMode: DrawingMode,
+ updatedMode: @escaping (DrawingMode) -> Void,
+ tag: AnyObject?
+ ) {
+ self.isTablet = isTablet
+ self.strings = strings
+ self.tintColor = tintColor
+ self.availableModes = availableModes
+ self.currentMode = currentMode
+ self.updatedMode = updatedMode
self.tag = tag
- self.selectedIndex = selectedIndex
- self.selectionChanged = selectionChanged
- self.sizeUpdated = sizeUpdated
- self.sizeReleased = sizeReleased
}
- static func ==(lhs: ModeAndSizeComponent, rhs: ModeAndSizeComponent) -> Bool {
- if lhs.values != rhs.values {
+ static func ==(lhs: ModeComponent, rhs: ModeComponent) -> Bool {
+ if lhs.isTablet != rhs.isTablet {
return false
}
- if lhs.sizeValue != rhs.sizeValue {
+ if lhs.strings !== rhs.strings {
return false
}
- if lhs.isEditing != rhs.isEditing {
+ if lhs.tintColor != rhs.tintColor {
return false
}
- if lhs.isEnabled != rhs.isEnabled {
+ if lhs.availableModes != rhs.availableModes {
return false
}
- if lhs.rightInset != rhs.rightInset {
- return false
- }
- if lhs.selectedIndex != rhs.selectedIndex {
+ if lhs.currentMode != rhs.currentMode {
return false
}
return true
}
-
- final class View: UIView, UIGestureRecognizerDelegate, ComponentTaggedView {
- private let backgroundNode: NavigationBackgroundNode
- private let node: SegmentedControlNode
+
+ final class View: UIView, ComponentTaggedView, UIScrollViewDelegate, UIGestureRecognizerDelegate {
+ private final class ScrollView: UIScrollView {
+ override func touchesShouldCancel(in view: UIView) -> Bool {
+ return true
+ }
+ }
- private var knob: UIImageView
+ private struct LayoutData {
+ var containerSize: CGSize
+ var selectedFrame: CGRect
+ var cornerRadius: CGFloat?
+ var isTablet: Bool
+ }
- private let maskLayer = SimpleShapeLayer()
+ private var component: ModeComponent?
+ private var state: EmptyComponentState?
- private var isEditing: Bool?
- private var isControlEnabled: Bool?
- private var sliderWidth: CGFloat = 0.0
+ final class ItemView: HighlightTrackingButton {
+ init() {
+ super.init(frame: .zero)
+ }
+
+ required init(coder: NSCoder) {
+ preconditionFailure()
+ }
+
+ func update(isTablet: Bool, value: String, selected: Bool, tintColor: UIColor) -> CGSize {
+ let title = NSMutableAttributedString(string: value, font: Font.with(size: 15.0, design: .regular, weight: .medium), textColor: UIColor(rgb: 0xffffff), paragraphAlignment: .center)
+ self.setAttributedTitle(title, for: .normal)
+ self.sizeToFit()
+ return CGSize(width: self.titleLabel?.bounds.size.width ?? 0.0, height: buttonSize.height)
+ }
+ }
- fileprivate var updated: (CGFloat) -> Void = { _ in }
- fileprivate var released: () -> Void = { }
+ private var backgroundView = UIView()
+ private var backgroundContainer = GlassBackgroundContainerView()
+
+ private var liquidLensView: LiquidLensView?
+ private let scrollView = ScrollView()
+ private let selectedScrollView = UIView()
+ private var ignoreScrolling = false
+ private var layoutData: LayoutData?
+
+ private var itemViews: [AnyHashable: ItemView] = [:]
+ private var selectedItemViews: [AnyHashable: ItemView] = [:]
+
+ private var tabSelectionRecognizer: TabSelectionRecognizer?
+ private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
- private var component: ModeAndSizeComponent?
public func matches(tag: Any) -> Bool {
if let component = self.component, let componentTag = component.tag {
let tag = tag as AnyObject
@@ -102,164 +130,350 @@ final class ModeAndSizeComponent: Component {
}
init() {
- self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x888888, alpha: 0.3))
- self.node = SegmentedControlNode(theme: SegmentedControlTheme(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shadowColor: .black, textColor: UIColor(rgb: 0xffffff), dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)), items: [], selectedIndex: 0, cornerRadius: 16.0)
-
- self.knob = UIImageView(image: generateKnobImage())
-
super.init(frame: CGRect())
+ self.backgroundView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.09)
+ self.backgroundView.layer.cornerRadius = 22.0
+
self.layer.allowsGroupOpacity = true
-
- self.addSubview(self.backgroundNode.view)
- self.addSubview(self.node.view)
- self.addSubview(self.knob)
- self.backgroundNode.layer.mask = self.maskLayer
+ self.scrollView.delaysContentTouches = false
+ self.scrollView.canCancelContentTouches = true
+ self.scrollView.contentInsetAdjustmentBehavior = .never
+ self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
+ self.scrollView.showsVerticalScrollIndicator = false
+ self.scrollView.showsHorizontalScrollIndicator = false
+ self.scrollView.alwaysBounceHorizontal = false
+ self.scrollView.alwaysBounceVertical = false
+ self.scrollView.scrollsToTop = false
+ self.scrollView.clipsToBounds = true
+ self.scrollView.delegate = self
+ self.scrollView.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
+ guard let self else {
+ return false
+ }
+ return self.scrollView.contentOffset.x > .ulpOfOne
+ }
- let pressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handlePress(_:)))
- pressGestureRecognizer.minimumPressDuration = 0.01
- pressGestureRecognizer.delegate = self
- self.addGestureRecognizer(pressGestureRecognizer)
+ self.selectedScrollView.clipsToBounds = true
+ self.selectedScrollView.isUserInteractionEnabled = false
- let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.handlePan(_:)))
- panGestureRecognizer.delegate = self
- self.addGestureRecognizer(panGestureRecognizer)
+ self.addSubview(self.backgroundView)
+ self.backgroundView.addSubview(self.backgroundContainer)
}
required init?(coder aDecoder: NSCoder) {
preconditionFailure()
}
- @objc func handlePress(_ gestureRecognizer: UILongPressGestureRecognizer) {
- let location = gestureRecognizer.location(in: self).offsetBy(dx: -12.0, dy: 0.0)
- guard self.frame.width > 0.0, case .began = gestureRecognizer.state else {
- return
- }
- let value = max(0.0, min(1.0, location.x / (self.frame.width - 24.0)))
- self.updated(value)
+ private var animatedOut = false
+ func animateOutToEditor(transition: ComponentTransition) {
+ self.animatedOut = true
+
+ transition.setAlpha(view: self.backgroundView, alpha: 0.0)
+ transition.setSublayerTransform(view: self, transform: CATransform3DMakeTranslation(0.0, -buttonSize.height, 0.0))
}
- @objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
- switch gestureRecognizer.state {
- case .changed:
- let location = gestureRecognizer.location(in: self).offsetBy(dx: -12.0, dy: 0.0)
- guard self.frame.width > 0.0 else {
- return
+ func animateInFromEditor(transition: ComponentTransition) {
+ self.animatedOut = false
+
+ transition.setAlpha(view: self.backgroundView, alpha: 1.0)
+ transition.setSublayerTransform(view: self, transform: CATransform3DIdentity)
+ }
+
+ override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
+ return self.backgroundView.frame.contains(point)
+ }
+
+ private func item(at point: CGPoint, in view: UIView) -> AnyHashable? {
+ var closestItem: (AnyHashable, CGFloat)?
+ for (id, itemView) in self.itemViews {
+ let itemFrame = itemView.convert(itemView.bounds, to: view)
+ if itemFrame.contains(point) {
+ return id
+ } else {
+ let distance = abs(point.x - itemFrame.midX)
+ if let closestItemValue = closestItem {
+ if closestItemValue.1 > distance {
+ closestItem = (id, distance)
+ }
+ } else {
+ closestItem = (id, distance)
+ }
+ }
+ }
+ return closestItem?.0
+ }
+
+ func scrollViewDidScroll(_ scrollView: UIScrollView) {
+ if self.ignoreScrolling {
+ return
+ }
+ self.updateScrolling(transition: .immediate)
+ }
+
+ func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+ if gestureRecognizer === self.tabSelectionRecognizer && otherGestureRecognizer === self.scrollView.panGestureRecognizer {
+ return true
+ }
+ if otherGestureRecognizer === self.tabSelectionRecognizer && gestureRecognizer === self.scrollView.panGestureRecognizer {
+ return true
+ }
+ return false
+ }
+
+ @objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
+ guard let component = self.component else {
+ return
+ }
+ let location = recognizer.location(in: self)
+ switch recognizer.state {
+ case .began:
+ if let itemId = self.item(at: location, in: self), let itemView = self.itemViews[itemId] {
+ let startX = itemView.frame.minX - 4.0
+ self.selectionGestureState = (startX, startX, itemId)
+ self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
+ }
+ case .changed:
+ if var selectionGestureState = self.selectionGestureState {
+ let translation = recognizer.translation(in: self)
+ if !component.isTablet && self.scrollView.isScrollEnabled && abs(translation.x) > 6.0 && abs(translation.x) > abs(translation.y) {
+ self.selectionGestureState = nil
+ recognizer.state = .cancelled
+ self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
+ return
+ }
+ selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x
+ if let itemId = self.item(at: location, in: self) {
+ selectionGestureState.itemId = itemId
+ }
+ self.selectionGestureState = selectionGestureState
+ self.state?.updated(transition: .immediate, isLocal: true)
}
- let value = max(0.0, min(1.0, location.x / (self.frame.width - 24.0)))
- self.updated(value)
case .ended, .cancelled:
- self.released()
+ if let selectionGestureState = self.selectionGestureState {
+ self.selectionGestureState = nil
+ if case .ended = recognizer.state {
+ guard let item = component.availableModes.first(where: { AnyHashable($0.rawValue) == selectionGestureState.itemId }) else {
+ return
+ }
+ component.updatedMode(item)
+ }
+ self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
+ }
default:
break
}
}
- override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
- if let isEditing = self.isEditing, let isControlEnabled = self.isControlEnabled {
- return isEditing && isControlEnabled
+ private func updateScrolling(transition: ComponentTransition) {
+ guard let component = self.component, let liquidLensView = self.liquidLensView, let layoutData = self.layoutData else {
+ return
+ }
+
+ let contentOffsetX = layoutData.isTablet ? 0.0 : self.scrollView.bounds.minX
+ var lensSelection = (origin: layoutData.selectedFrame.origin, size: layoutData.selectedFrame.size)
+ if let selectionGestureState = self.selectionGestureState, !layoutData.isTablet {
+ lensSelection.origin = CGPoint(x: selectionGestureState.currentX, y: 0.0)
+ }
+
+ if layoutData.isTablet {
+ lensSelection.size.width = layoutData.containerSize.width
} else {
- return false
+ lensSelection.origin.x -= contentOffsetX
+ lensSelection.origin.y = 0.0
+ lensSelection.size.height = layoutData.containerSize.height
}
+
+ let maxSelectionOriginX = max(0.0, layoutData.containerSize.width - lensSelection.size.width)
+ transition.setFrame(view: self.selectedScrollView, frame: CGRect(origin: .zero, size: layoutData.containerSize))
+ transition.setBounds(view: self.selectedScrollView, bounds: CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: layoutData.containerSize))
+
+ liquidLensView.update(size: layoutData.containerSize, cornerRadius: layoutData.cornerRadius, selectionOrigin: CGPoint(x: max(0.0, min(lensSelection.origin.x, maxSelectionOriginX)), y: lensSelection.origin.y), selectionSize: lensSelection.size, inset: 3.0, isDark: true, isLifted: self.selectionGestureState != nil && !layoutData.isTablet, isCollapsed: false, transition: transition)
+ self.backgroundContainer.update(size: layoutData.containerSize, isDark: true, transition: .immediate)
+
+ self.scrollView.isScrollEnabled = !component.isTablet && self.scrollView.contentSize.width > self.scrollView.bounds.width + .ulpOfOne
}
-
- func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
- return true
- }
-
- func animateIn() {
- self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
- self.node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
- }
-
- func animateOut() {
- self.node.alpha = 0.0
- self.node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
-
- self.backgroundNode.alpha = 0.0
- self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
- }
-
- func update(component: ModeAndSizeComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
- self.component = component
-
- self.updated = component.sizeUpdated
- self.released = component.sizeReleased
-
- let previousIsEditing = self.isEditing
- self.isEditing = component.isEditing
- self.isControlEnabled = component.isEnabled
-
- if component.isEditing {
- self.sliderWidth = availableSize.width
- }
-
- self.node.items = component.values.map { SegmentedControlItem(title: $0) }
- self.node.setSelectedIndex(component.selectedIndex, animated: !transition.animation.isImmediate)
- let selectionChanged = component.selectionChanged
- self.node.selectedIndexChanged = { [weak self] index in
- self?.window?.endEditing(true)
- selectionChanged(index)
- }
-
- let nodeSize = self.node.updateLayout(.stretchToFill(width: availableSize.width + component.rightInset), transition: transition.containedViewLayoutTransition)
- let size = CGSize(width: availableSize.width, height: nodeSize.height)
- transition.setFrame(view: self.node.view, frame: CGRect(origin: CGPoint(), size: nodeSize))
-
- var isDismissingEditing = false
- if component.isEditing != previousIsEditing && !component.isEditing {
- isDismissingEditing = true
- }
-
- self.knob.alpha = component.isEditing ? 1.0 : 0.0
- if !isDismissingEditing {
- self.knob.frame = CGRect(origin: CGPoint(x: -12.0 + floorToScreenPixels((self.sliderWidth + 24.0 - self.knob.frame.size.width) * component.sizeValue), y: floorToScreenPixels((size.height - self.knob.frame.size.height) / 2.0)), size: self.knob.frame.size)
- }
- if component.isEditing != previousIsEditing {
- let containedTransition = transition.containedViewLayoutTransition
- let maskPath: UIBezierPath
- if component.isEditing {
- maskPath = generateMaskPath(size: size, leftRadius: 2.0, rightRadius: 11.5)
- let selectionFrame = self.node.animateSelection(to: self.knob.center, transition: containedTransition)
- containedTransition.animateFrame(layer: self.knob.layer, from: selectionFrame.insetBy(dx: -9.0, dy: -9.0))
-
- self.knob.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ func update(component: ModeComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize {
+ let previousComponent = self.component
+ self.component = component
+ self.state = state
+
+ let isTablet = component.isTablet
+
+ let liquidLensView: LiquidLensView
+ if let current = self.liquidLensView {
+ liquidLensView = current
+ } else {
+ liquidLensView = LiquidLensView(kind: isTablet ? .noContainer : .externalContainer)
+ self.liquidLensView = liquidLensView
+ self.backgroundContainer.contentView.addSubview(liquidLensView)
+ liquidLensView.contentView.addSubview(self.scrollView)
+ liquidLensView.selectedContentView.addSubview(self.selectedScrollView)
+
+ let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
+ tabSelectionRecognizer.delegate = self
+ tabSelectionRecognizer.cancelsTouchesInView = false
+ self.tabSelectionRecognizer = tabSelectionRecognizer
+ liquidLensView.addGestureRecognizer(tabSelectionRecognizer)
+ }
+ if self.scrollView.superview == nil {
+ liquidLensView.contentView.addSubview(self.scrollView)
+ }
+ if self.selectedScrollView.superview == nil {
+ liquidLensView.selectedContentView.addSubview(self.selectedScrollView)
+ }
+
+ self.backgroundView.backgroundColor = component.isTablet ? .clear : UIColor(rgb: 0xffffff, alpha: 0.11)
+
+ var inset: CGFloat = 23.0
+ let spacing: CGFloat
+ if isTablet {
+ spacing = 9.0
+ } else {
+ if availableSize.width < 200.0 {
+ inset = 20.0
+ spacing = 24.0
} else {
- maskPath = generateMaskPath(size: size, leftRadius: 16.0, rightRadius: 16.0)
- if previousIsEditing != nil {
- let selectionFrame = self.node.animateSelection(from: self.knob.center, transition: containedTransition)
- containedTransition.animateFrame(layer: self.knob.layer, from: self.knob.frame, to: selectionFrame.insetBy(dx: -9.0, dy: -9.0))
- self.knob.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
+ spacing = 30.0
+ }
+ }
+
+ var i = 0
+ var itemFrame = CGRect(origin: isTablet ? .zero : CGPoint(x: inset, y: 0.0), size: buttonSize)
+ var selectedFrame = itemFrame
+
+ var validKeys: Set = Set()
+ for mode in component.availableModes {
+ let id = mode.rawValue
+ validKeys.insert(id)
+
+ let itemView: ItemView
+ let selectedItemView: ItemView
+ if let current = self.itemViews[id], let currentSelected = self.selectedItemViews[id] {
+ itemView = current
+ selectedItemView = currentSelected
+ } else {
+ itemView = ItemView()
+ itemView.isUserInteractionEnabled = false
+ self.itemViews[id] = itemView
+
+ selectedItemView = ItemView()
+ selectedItemView.isUserInteractionEnabled = false
+ self.selectedItemViews[id] = selectedItemView
+ }
+ if itemView.superview !== self.scrollView {
+ self.scrollView.addSubview(itemView)
+ }
+ if selectedItemView.superview !== self.selectedScrollView {
+ self.selectedScrollView.addSubview(selectedItemView)
+ }
+
+ let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: false, tintColor: component.tintColor)
+ itemView.bounds = CGRect(origin: .zero, size: itemSize)
+
+ let _ = selectedItemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: true, tintColor: component.tintColor)
+ selectedItemView.bounds = CGRect(origin: .zero, size: itemSize)
+
+ itemFrame = CGRect(origin: itemFrame.origin, size: itemSize)
+
+ if mode == component.currentMode {
+ selectedFrame = itemFrame
+ }
+
+ if isTablet {
+ itemView.center = CGPoint(x: availableSize.width / 2.0, y: itemFrame.midY)
+ selectedItemView.center = itemView.center
+ itemFrame = itemFrame.offsetBy(dx: 0.0, dy: tabletButtonSize.height + spacing)
+ } else {
+ itemView.center = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
+ selectedItemView.center = itemView.center
+ itemFrame = itemFrame.offsetBy(dx: itemFrame.width + spacing, dy: 0.0)
+ }
+ i += 1
+ }
+
+ var removeKeys: [AnyHashable] = []
+ for (id, itemView) in self.itemViews {
+ if !validKeys.contains(id) {
+ removeKeys.append(id)
+
+ transition.setAlpha(view: itemView, alpha: 0.0, completion: { _ in
+ itemView.removeFromSuperview()
+ })
+
+ if let selectedItemView = self.selectedItemViews[id] {
+ transition.setAlpha(view: selectedItemView, alpha: 0.0, completion: { _ in
+ selectedItemView.removeFromSuperview()
+ })
}
}
- transition.setShapeLayerPath(layer: self.maskLayer, path: maskPath.cgPath)
}
-
- transition.setFrame(layer: self.maskLayer, frame: CGRect(origin: .zero, size: nodeSize))
+ for id in removeKeys {
+ self.itemViews.removeValue(forKey: id)
+ self.selectedItemViews.removeValue(forKey: id)
+ }
- transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
- self.backgroundNode.update(size: size, transition: transition.containedViewLayoutTransition)
+ let totalSize: CGSize
+ let size: CGSize
+ let contentSize: CGSize
+ var cornerRadius: CGFloat?
+ if isTablet {
+ totalSize = CGSize(width: availableSize.width, height: tabletButtonSize.height * CGFloat(component.availableModes.count) + spacing * CGFloat(component.availableModes.count - 1))
+ size = CGSize(width: availableSize.width, height: availableSize.height)
+ transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: totalSize))
+ contentSize = totalSize
+ cornerRadius = 20.0
+ } else {
+ size = CGSize(width: availableSize.width, height: buttonSize.height)
+ totalSize = CGSize(width: itemFrame.minX - spacing + inset, height: buttonSize.height)
+ let visibleSize = CGSize(width: min(availableSize.width, totalSize.width), height: totalSize.height)
+ transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - visibleSize.width) / 2.0), y: 0.0), size: visibleSize))
+ contentSize = totalSize
+ }
- if let screenTransition = transition.userData(DrawingScreenTransition.self) {
- switch screenTransition {
- case .animateIn:
- self.animateIn()
- case .animateOut:
- self.animateOut()
+ let containerFrame = CGRect(origin: .zero, size: self.backgroundView.frame.size)
+ transition.setFrame(view: self.backgroundContainer, frame: containerFrame)
+ transition.setFrame(view: liquidLensView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerFrame.size))
+
+ let scrollViewFrame = CGRect(origin: .zero, size: containerFrame.size)
+ transition.setFrame(view: self.scrollView, frame: scrollViewFrame)
+ if self.scrollView.contentSize != contentSize {
+ self.scrollView.contentSize = contentSize
+ }
+ self.scrollView.isScrollEnabled = !isTablet && contentSize.width > scrollViewFrame.width + .ulpOfOne
+
+ self.layoutData = LayoutData(containerSize: containerFrame.size, selectedFrame: selectedFrame.insetBy(dx: -inset, dy: 3.0), cornerRadius: cornerRadius, isTablet: isTablet)
+
+ self.ignoreScrolling = true
+ var scrollViewBounds = CGRect(origin: self.scrollView.bounds.origin, size: scrollViewFrame.size)
+ let maxContentOffsetX = max(0.0, contentSize.width - scrollViewFrame.width)
+ let shouldFocusOnSelectedItem = previousComponent?.currentMode != component.currentMode || previousComponent?.availableModes != component.availableModes || self.scrollView.bounds.size != scrollViewFrame.size
+ if self.scrollView.isScrollEnabled && shouldFocusOnSelectedItem {
+ let scrollLookahead = min(60.0, scrollViewBounds.width * 0.25)
+ if scrollViewBounds.minX + scrollViewBounds.width - scrollLookahead < selectedFrame.maxX {
+ scrollViewBounds.origin.x = selectedFrame.maxX - scrollViewBounds.width + scrollLookahead
+ }
+ if scrollViewBounds.minX > selectedFrame.minX - scrollLookahead {
+ scrollViewBounds.origin.x = selectedFrame.minX - scrollLookahead
}
}
+ scrollViewBounds.origin.x = max(0.0, min(scrollViewBounds.origin.x, maxContentOffsetX))
+ transition.setBounds(view: self.scrollView, bounds: scrollViewBounds)
+ self.ignoreScrolling = false
+
+ self.updateScrolling(transition: transition)
return size
}
}
-
+
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, transition: transition)
+ return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
}
}
diff --git a/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift
index 580693d0f1..e9d3014d93 100644
--- a/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift
+++ b/submodules/FeaturedStickersScreen/Sources/ChatMediaInputTrendingPane.swift
@@ -254,6 +254,70 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane {
self.disposable?.dispose()
self.installDisposable.dispose()
}
+
+ private func presentStickerPackActionOverlay(_ actions: [StickerPackScreenActionResult]) {
+ guard let action = actions.first else {
+ return
+ }
+
+ var animateInAsReplacement = false
+ if let navigationController = self.interaction.getNavigationController() {
+ for controller in navigationController.overlayControllers {
+ if let controller = controller as? UndoOverlayController {
+ controller.dismissWithCommitActionAndReplacementAnimation()
+ animateInAsReplacement = true
+ }
+ }
+ }
+
+ var presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
+ if let forceTheme = self.forceTheme {
+ presentationData = presentationData.withUpdated(theme: forceTheme)
+ }
+
+ let controller: UndoOverlayController
+ switch action.action {
+ case .add:
+ controller = UndoOverlayController(
+ presentationData: presentationData,
+ content: .stickersModified(
+ title: presentationData.strings.StickerPackActionInfo_AddedTitle,
+ text: presentationData.strings.StickerPackActionInfo_AddedText(action.info.title).string,
+ undo: false,
+ info: action.info,
+ topItem: action.items.first,
+ context: self.context
+ ),
+ elevatedLayout: false,
+ animateInAsReplacement: animateInAsReplacement,
+ action: { _ in
+ return true
+ }
+ )
+ case let .remove(positionInList):
+ controller = UndoOverlayController(
+ presentationData: presentationData,
+ content: .stickersModified(
+ title: presentationData.strings.StickerPackActionInfo_RemovedTitle,
+ text: presentationData.strings.StickerPackActionInfo_RemovedText(action.info.title).string,
+ undo: true,
+ info: action.info,
+ topItem: action.items.first,
+ context: self.context
+ ),
+ elevatedLayout: false,
+ animateInAsReplacement: animateInAsReplacement,
+ action: { [weak self] overlayAction in
+ if case .undo = overlayAction {
+ let _ = self?.context.engine.stickers.addStickerPackInteractively(info: action.info, items: action.items, positionInList: positionInList).start()
+ }
+ return true
+ }
+ )
+ }
+
+ self.interaction.getNavigationController()?.presentOverlay(controller: controller)
+ }
public func activate() {
if self.isActivated {
@@ -373,7 +437,9 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane {
return false
}
},
- actionPerformed: nil
+ actionPerformed: { [weak self] actions in
+ self?.presentStickerPackActionOverlay(actions)
+ }
)
strongSelf.interaction.presentController(controller, nil)
}
diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift
index 88e6dc1925..ccf2c4a8dd 100644
--- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift
+++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift
@@ -675,6 +675,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
super.didLoad()
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
self.scrollNode.view.showsVerticalScrollIndicator = false
+ self.scrollNode.view.scrollsToTop = false
let backwardLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.seekBackwardLongPress(_:)))
backwardLongPressGestureRecognizer.minimumPressDuration = 0.3
@@ -1356,7 +1357,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
speed: settingsButtonState.speed,
quality: settingsButtonState.quality,
isOpen: false
- ))),
+ )), insets: .zero),
action: { [weak self] in
guard let self, let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View else {
return
@@ -1819,12 +1820,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
if availableOpenInOptions(context: strongSelf.context, item: item).count > 1 {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Conversation_FileOpenIn, action: { [weak self] in
if let strongSelf = self {
- let openInController = OpenInActionSheetController(context: strongSelf.context, forceTheme: defaultDarkColorPresentationTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in
+ let openInController = OpenInOptionsScreen(context: strongSelf.context, forceTheme: defaultDarkColorPresentationTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in
if let strongSelf = self {
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
})
- strongSelf.controllerInteraction?.presentController(openInController, nil)
+ strongSelf.controllerInteraction?.pushController(openInController)
}
}))
} else {
@@ -2124,12 +2125,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
if availableOpenInOptions(context: self.context, item: item).count > 1 {
preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Conversation_FileOpenIn, action: { [weak self] in
if let strongSelf = self {
- let openInController = OpenInActionSheetController(context: strongSelf.context, forceTheme: forceTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in
+ let openInController = OpenInOptionsScreen(context: strongSelf.context, forceTheme: forceTheme, item: item, additionalAction: nil, openUrl: { [weak self] url in
if let strongSelf = self {
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
})
- strongSelf.controllerInteraction?.presentController(openInController, nil)
+ strongSelf.controllerInteraction?.pushController(openInController)
}
}))
} else {
diff --git a/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift b/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift
index 1f7e9247e8..1c5ef1273b 100644
--- a/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift
+++ b/submodules/GalleryUI/Sources/GalleryThumbnailContainerNode.swift
@@ -83,6 +83,7 @@ public final class GalleryThumbnailContainerNode: ASDisplayNode, ASScrollViewDel
self.scrollNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.showsVerticalScrollIndicator = false
+ self.scrollNode.view.scrollsToTop = false
self.addSubnode(self.scrollNode)
}
diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift
index 5919706cff..a70cc1f360 100644
--- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift
+++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift
@@ -419,6 +419,9 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
adView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
adView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 64.0), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
adView.removeFromSuperview()
+ if self.adView.view === adView {
+ self.adView = ComponentView()
+ }
Queue.mainQueue().after(0.1) {
adView.layer.removeAllAnimations()
}
@@ -444,7 +447,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
return result
}
}
- if let adView = self.adView.view, adView.frame.contains(point) {
+ if let adView = self.adView.view, adView.superview === self.view, !self.isAnimatingOut, adView.frame.contains(point) {
return super.hitTest(point, with: event)
}
return nil
@@ -3892,12 +3895,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if !presentationData.theme.overallDarkAppearance {
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
}
- let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in
+ let actionSheet = OpenInOptionsScreen(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in
if let strongSelf = self {
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {})
}
})
- controller.present(actionSheet, in: .window(.root))
+ controller.push(actionSheet)
}
})))
break
diff --git a/submodules/GlassButtonNode/BUILD b/submodules/GlassButtonNode/BUILD
deleted file mode 100644
index bab4d6718e..0000000000
--- a/submodules/GlassButtonNode/BUILD
+++ /dev/null
@@ -1,19 +0,0 @@
-load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
-
-swift_library(
- name = "GlassButtonNode",
- module_name = "GlassButtonNode",
- srcs = glob([
- "Sources/**/*.swift",
- ]),
- copts = [
- "-warnings-as-errors",
- ],
- deps = [
- "//submodules/AsyncDisplayKit:AsyncDisplayKit",
- "//submodules/Display:Display",
- ],
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/submodules/GlassButtonNode/Info.plist b/submodules/GlassButtonNode/Info.plist
deleted file mode 100644
index e1fe4cfb7b..0000000000
--- a/submodules/GlassButtonNode/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- $(CURRENT_PROJECT_VERSION)
-
-
diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift
index 4e395aaf5a..b2d7db0d72 100644
--- a/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift
+++ b/submodules/HashtagSearchUI/Sources/HashtagSearchRecentListNode.swift
@@ -307,7 +307,7 @@ final class HashtagSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
if item.clear {
strongSelf.setRevealOptions((left: [], right: []))
} else {
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.theme.list.itemSecondaryTextColor)]))
}
}
})
diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift
index a00b0f3073..31ea269639 100644
--- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift
+++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift
@@ -110,6 +110,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll
self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
+ self.wrappingScrollNode.view.scrollsToTop = false
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift
index 2c3bd0525c..69f1f2b0ba 100644
--- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift
+++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift
@@ -139,6 +139,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.addSubnode(self.navigationBar)
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
+ self.scrollNode.view.scrollsToTop = false
self.navigationBar.back = navigateBack
self.navigationBar.share = { [weak self] in
@@ -1787,12 +1788,12 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
private func openUrlIn(_ url: InstantPageUrlItem) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
- let actionSheet = OpenInActionSheetController(context: self.context, item: .url(url: url.url), openUrl: { [weak self] url in
+ let actionSheet = OpenInOptionsScreen(context: self.context, item: .url(url: url.url), openUrl: { [weak self] url in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
}
})
- self.present(actionSheet, nil)
+ self.pushController(actionSheet)
}
private func mediasFromItems(_ items: [InstantPageItem]) -> [InstantPageMedia] {
diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift
index dee69b4aac..8fe23019a6 100644
--- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift
+++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift
@@ -309,7 +309,21 @@ public final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode
@objc func joinPressed() {
if let peer = self.peer, case .notJoined = self.joinState {
self.updateJoinState(.inProgress)
- self.joinDisposable.set((self.context.engine.peers.joinChannel(peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(error: { [weak self] _ in
+ self.joinDisposable.set((self.context.engine.peers.joinChannel(peerId: peer.id, hash: nil) |> deliverOnMainQueue).start(next: { [weak self] result in
+ guard let strongSelf = self else {
+ return
+ }
+ switch result {
+ case .joined:
+ break
+ case let .webView(webView):
+ if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController, let controller = navigationController.viewControllers.last as? ViewController {
+ strongSelf.context.sharedContext.openJoinChatWebView(context: strongSelf.context, parentController: controller, updatedPresentationData: nil, webView: webView)
+ } else if case .inProgress = strongSelf.joinState {
+ strongSelf.updateJoinState(.notJoined)
+ }
+ }
+ }, error: { [weak self] _ in
if let strongSelf = self {
if case .inProgress = strongSelf.joinState {
strongSelf.updateJoinState(.notJoined)
diff --git a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift
index 50c1222805..c60553a9c8 100644
--- a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift
+++ b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift
@@ -52,6 +52,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie
self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
+ self.wrappingScrollNode.view.scrollsToTop = false
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
diff --git a/submodules/InviteLinksUI/BUILD b/submodules/InviteLinksUI/BUILD
index bed130117c..f5c6a4fb6b 100644
--- a/submodules/InviteLinksUI/BUILD
+++ b/submodules/InviteLinksUI/BUILD
@@ -60,6 +60,7 @@ swift_library(
"//submodules/PromptUI",
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
+ "//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/ComponentFlow",
"//submodules/Components/MultilineTextComponent",
],
diff --git a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift
index bde2b78d3a..d37959cda9 100644
--- a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift
+++ b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift
@@ -711,7 +711,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
saveEnabled = true
}
- doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Save), style: .bold, enabled: !state.selectedPeerIds.isEmpty && saveEnabled, action: {
+ doneButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: !state.selectedPeerIds.isEmpty && saveEnabled, action: {
applyChangesImpl?()
})
}
diff --git a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift
index 8cbb4e2389..e0c4440b86 100644
--- a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift
+++ b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift
@@ -479,7 +479,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
}
}
-private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: InviteLinkEditControllerState, isGroup: Bool, isPublic: Bool, presentationData: PresentationData, configuration: StarsSubscriptionConfiguration) -> [InviteLinksEditEntry] {
+private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: InviteLinkEditControllerState, isGroup: Bool, isPublic: Bool, globalRequestApproval: Bool, presentationData: PresentationData, configuration: StarsSubscriptionConfiguration) -> [InviteLinksEditEntry] {
var entries: [InviteLinksEditEntry] = []
entries.append(.titleHeader(presentationData.theme, presentationData.strings.InviteLink_Create_LinkNameTitle.uppercased()))
@@ -507,17 +507,14 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
entries.append(.subscriptionFeeInfo(presentationData.theme, infoText))
}
- if !isPublic {
- entries.append(.requestApproval(presentationData.theme, presentationData.strings.InviteLink_Create_RequestApproval, state.requestApproval, isEditingEnabled && !isSubscription))
- var requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel
+ if !isPublic || isGroup {
+ let requestApprovalEnabled = isEditingEnabled && !isSubscription && !(isPublic && isGroup && !globalRequestApproval)
+ entries.append(.requestApproval(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers, state.requestApproval, requestApprovalEnabled))
+ var requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalInfo
if isSubscription {
requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalFeeUnavailable
- } else {
- if state.requestApproval {
- requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOnInfoChannel
- } else {
- requestApprovalInfoText = isGroup ? presentationData.strings.InviteLink_Create_RequestApprovalOnInfoGroup : presentationData.strings.InviteLink_Create_RequestApprovalOffInfoChannel
- }
+ } else if isPublic && isGroup && !globalRequestApproval {
+ requestApprovalInfoText = presentationData.strings.InviteLink_Create_RequestApprovalPublicGroupUnavailable
}
entries.append(.requestApprovalInfo(presentationData.theme, requestApprovalInfoText))
}
@@ -538,7 +535,7 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
}
entries.append(.timeInfo(presentationData.theme, presentationData.strings.InviteLink_Create_TimeLimitInfo))
- if !state.requestApproval || isPublic {
+ if !state.requestApproval {
entries.append(.usageHeader(presentationData.theme, presentationData.strings.InviteLink_Create_UsersLimit.uppercased()))
entries.append(.usagePicker(presentationData.theme, presentationData.dateTimeFormat, state.usage, isEditingEnabled))
@@ -696,7 +693,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|> map { presentationData, state, peer -> (ItemListControllerState, (ItemListNodeState, Any)) in
let isPublic = !(peer?.addressName?.isEmpty ?? true)
- let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
@@ -707,7 +704,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
}
}
- let rightNavigationButton = ItemListNavigationButton(content: .text(invite == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Save), style: state.updating ? .activity : .bold, enabled: doneIsEnabled, action: {
+ let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: state.updating ? .activity : .bold, enabled: doneIsEnabled, action: {
updateState { state in
var updatedState = state
updatedState.updating = true
@@ -727,7 +724,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
let titleString = state.title.trimmingCharacters(in: .whitespacesAndNewlines)
let title = titleString.isEmpty ? nil : titleString
var usageLimit = state.usage.value
- var requestNeeded: Bool? = state.requestApproval && !isPublic
+ var requestNeeded: Bool? = state.requestApproval
if invite == nil {
let subscriptionPricing: StarsSubscriptionPricing?
@@ -792,14 +789,20 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
}
let isGroup: Bool
+ let globalRequestApproval: Bool
if case let .channel(channel) = peer, case .broadcast = channel.info {
isGroup = false
+ globalRequestApproval = channel.flags.contains(.requestToJoin)
+ } else if case let .channel(channel) = peer {
+ isGroup = true
+ globalRequestApproval = channel.flags.contains(.requestToJoin)
} else {
isGroup = true
+ globalRequestApproval = false
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(invite == nil ? presentationData.strings.InviteLink_Create_Title : presentationData.strings.InviteLink_Create_EditTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
- let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, isGroup: isGroup, isPublic: isPublic, presentationData: presentationData, configuration: configuration), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges)
+ let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, isGroup: isGroup, isPublic: isPublic, globalRequestApproval: globalRequestApproval, presentationData: presentationData, configuration: configuration), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
}
diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift
index 580562f972..b338627fe6 100644
--- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift
+++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift
@@ -7,14 +7,12 @@ import AsyncDisplayKit
import TelegramCore
import Display
import AccountContext
-import SolidRoundedButtonNode
import ItemListUI
import ItemListPeerItem
import SectionHeaderItem
import TelegramStringFormatting
import MergeLists
import ContextUI
-
import OverlayStatusController
import PresentationDataUtils
import DirectionalPanGesture
diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift
index 25df8e383c..a5908e95b8 100644
--- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift
+++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift
@@ -7,14 +7,12 @@ import AsyncDisplayKit
import TelegramCore
import Display
import AccountContext
-import SolidRoundedButtonNode
import ItemListUI
import ItemListPeerItem
import SectionHeaderItem
import TelegramStringFormatting
import MergeLists
import ContextUI
-
import OverlayStatusController
import PresentationDataUtils
import DirectionalPanGesture
diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift
index 5382dfed9c..04c68fdc70 100644
--- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift
+++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkItem.swift
@@ -6,10 +6,11 @@ import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import ItemListUI
-import SolidRoundedButtonNode
import AnimatedAvatarSetNode
import ShimmerEffect
import TelegramCore
+import ComponentFlow
+import ButtonComponent
private func actionButtonImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 24.0, height: 24.0), contextGenerator: { size, context in
@@ -141,8 +142,8 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod
private let addressButtonNode: HighlightTrackingButtonNode
private let addressButtonIconNode: ASImageNode
private var addressShimmerNode: ShimmerEffectNode?
- private var shareButtonNode: SolidRoundedButtonNode?
- private var secondaryButtonNode: SolidRoundedButtonNode?
+ private var shareButton: ComponentView?
+ private var secondaryButton: ComponentView?
private let avatarsButtonNode: HighlightTrackingButtonNode
private let avatarsContext: AnimatedAvatarSetContext
@@ -250,11 +251,6 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod
}
}
}
- self.shareButtonNode?.pressed = { [weak self] in
- if let strongSelf = self, let item = strongSelf.item {
- item.shareAction?()
- }
- }
self.avatarsButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
@@ -458,64 +454,94 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod
strongSelf.referenceContainerNode.frame = strongSelf.containerNode.bounds
strongSelf.addressButtonIconNode.frame = strongSelf.containerNode.bounds
- let shareButtonNode: SolidRoundedButtonNode
- if let currentShareButtonNode = strongSelf.shareButtonNode {
- shareButtonNode = currentShareButtonNode
- } else {
- let buttonTheme: SolidRoundedButtonTheme
- if let buttonColor = item.buttonColor {
- buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
- } else {
- buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
- }
- shareButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: buttonHeight, cornerRadius: buttonHeight * 0.5)
- shareButtonNode.pressed = { [weak self] in
- self?.item?.shareAction?()
- }
- strongSelf.addSubnode(shareButtonNode)
- strongSelf.shareButtonNode = shareButtonNode
- }
- shareButtonNode.title = item.buttonTitle
+ let buttonBackgroundColor = item.buttonColor ?? item.presentationData.theme.list.itemCheckColors.fillColor
+ let buttonForegroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor
+ let buttonBackground = ButtonComponent.Background(
+ style: item.systemStyle == .glass ? .glass : .legacy,
+ color: buttonBackgroundColor,
+ foreground: buttonForegroundColor,
+ pressedColor: buttonBackgroundColor.withMultipliedAlpha(0.8),
+ cornerRadius: buttonHeight * 0.5
+ )
- if let secondaryButtonTitle = item.secondaryButtonTitle {
- let secondaryButtonNode: SolidRoundedButtonNode
- if let current = strongSelf.secondaryButtonNode {
- secondaryButtonNode = current
- } else {
- let buttonTheme: SolidRoundedButtonTheme
- if let buttonColor = item.buttonColor {
- buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
- } else {
- buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
- }
- secondaryButtonNode = SolidRoundedButtonNode(theme: buttonTheme, glass: item.systemStyle == .glass, height: buttonHeight, cornerRadius: buttonHeight * 0.5)
- secondaryButtonNode.pressed = { [weak self] in
- self?.item?.secondaryAction?()
- }
- strongSelf.addSubnode(secondaryButtonNode)
- strongSelf.secondaryButtonNode = secondaryButtonNode
- }
- secondaryButtonNode.title = secondaryButtonTitle
+ let shareButton: ComponentView
+ if let currentShareButton = strongSelf.shareButton {
+ shareButton = currentShareButton
} else {
- if let secondaryButtonNode = strongSelf.secondaryButtonNode {
- strongSelf.secondaryButtonNode = nil
- secondaryButtonNode.removeFromSupernode()
+ shareButton = ComponentView()
+ strongSelf.shareButton = shareButton
+ }
+
+ if item.secondaryButtonTitle != nil {
+ if strongSelf.secondaryButton == nil {
+ strongSelf.secondaryButton = ComponentView()
+ }
+ } else {
+ if let secondaryButton = strongSelf.secondaryButton {
+ strongSelf.secondaryButton = nil
+ secondaryButton.view?.removeFromSuperview()
}
}
var buttonWidth = contentSize.width - leftInset - rightInset
let totalButtonWidth = buttonWidth
let buttonSpacing: CGFloat = 8.0
- if strongSelf.secondaryButtonNode != nil {
+ if strongSelf.secondaryButton != nil {
buttonWidth = floor((buttonWidth - 8.0) / 2.0)
}
- let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
- shareButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
+ let shareButtonSize = shareButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(id: AnyHashable(item.buttonTitle), component: AnyComponent(Text(text: item.buttonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))),
+ isEnabled: item.enableButton,
+ tintWhenDisabled: false,
+ action: { [weak self] in
+ self?.item?.shareAction?()
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: buttonWidth, height: buttonHeight)
+ )
+ if let shareButtonView = shareButton.view {
+ if shareButtonView.superview == nil {
+ strongSelf.view.addSubview(shareButtonView)
+ }
+ shareButtonView.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing), size: shareButtonSize)
+ shareButtonView.isHidden = !item.displayButton
+ shareButtonView.alpha = item.enableButton ? 1.0 : 0.4
+ shareButtonView.isAccessibilityElement = true
+ shareButtonView.accessibilityLabel = item.buttonTitle
+ shareButtonView.accessibilityTraits = item.enableButton ? [.button] : [.button, .notEnabled]
+ }
- if let secondaryButtonNode = strongSelf.secondaryButtonNode {
- let _ = secondaryButtonNode.updateLayout(width: totalButtonWidth - buttonWidth - buttonSpacing, transition: .immediate)
- secondaryButtonNode.frame = CGRect(x: leftInset + buttonWidth + buttonSpacing, y: verticalInset + fieldHeight + fieldSpacing, width: totalButtonWidth - buttonWidth - buttonSpacing, height: buttonHeight)
+ if let secondaryButton = strongSelf.secondaryButton, let secondaryButtonTitle = item.secondaryButtonTitle {
+ let secondaryButtonWidth = totalButtonWidth - buttonWidth - buttonSpacing
+ let secondaryButtonSize = secondaryButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(id: AnyHashable(secondaryButtonTitle), component: AnyComponent(Text(text: secondaryButtonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))),
+ tintWhenDisabled: false,
+ action: { [weak self] in
+ self?.item?.secondaryAction?()
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: secondaryButtonWidth, height: buttonHeight)
+ )
+ if let secondaryButtonView = secondaryButton.view {
+ if secondaryButtonView.superview == nil {
+ strongSelf.view.addSubview(secondaryButtonView)
+ }
+ secondaryButtonView.frame = CGRect(origin: CGPoint(x: leftInset + buttonWidth + buttonSpacing, y: verticalInset + fieldHeight + fieldSpacing), size: secondaryButtonSize)
+ secondaryButtonView.isHidden = !item.displayButton
+ secondaryButtonView.alpha = 1.0
+ secondaryButtonView.isAccessibilityElement = true
+ secondaryButtonView.accessibilityLabel = secondaryButtonTitle
+ secondaryButtonView.accessibilityTraits = [.button]
+ }
}
var totalWidth = invitedPeersLayout.size.width
@@ -544,9 +570,6 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod
strongSelf.fieldButtonNode.isUserInteractionEnabled = item.invite != nil
strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0
- strongSelf.shareButtonNode?.isUserInteractionEnabled = item.enableButton
- strongSelf.shareButtonNode?.alpha = item.enableButton ? 1.0 : 0.4
- strongSelf.shareButtonNode?.isHidden = !item.displayButton
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil
strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil
diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift
index 6ccdd7c2b1..36238ff383 100644
--- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift
+++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift
@@ -521,7 +521,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
if item.removeAction != nil {
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.ChatListFilter_LinkActionDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.ChatListFilter_LinkActionDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]))
} else {
strongSelf.setRevealOptions((left: [], right: []))
}
diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift
index a00ee4df31..3ca0541df6 100644
--- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift
+++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift
@@ -377,7 +377,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
var pricingAttributedText: NSMutableAttributedString?
var timerValue: TimerNode.Value?
- if let invite = item.invite, case let .link(_, title, _, _, _, _, date, startDate, expireDate, usageLimit, count, requestedCount, subscriptionPricing) = invite {
+ if let invite = item.invite, case let .link(_, title, _, requestApproval, _, _, date, startDate, expireDate, usageLimit, count, requestedCount, subscriptionPricing) = invite {
if let title = title, !title.isEmpty {
titleText = title
}
@@ -405,6 +405,12 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
}
subtitleText += item.presentationData.strings.MemberRequests_PeopleRequestedShort(requestedCount)
}
+ if requestApproval {
+ if !subtitleText.isEmpty {
+ subtitleText += " • "
+ }
+ subtitleText += item.presentationData.strings.InviteLink_ApprovalRequired
+ }
if let subscriptionPricing {
let text = NSMutableAttributedString()
diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift
index 6e389b97ed..8a1e59dea5 100644
--- a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift
+++ b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift
@@ -721,7 +721,7 @@ public class ItemListInviteRequestItemNode: 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: true) : 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/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift
index 1761e1f681..8563d9dab8 100644
--- a/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift
+++ b/submodules/InviteLinksUI/Sources/ItemListPermanentInviteLinkItem.swift
@@ -6,13 +6,13 @@ import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import ItemListUI
-import SolidRoundedButtonNode
import AnimatedAvatarSetNode
import ShimmerEffect
import TelegramCore
import Markdown
import TextFormat
import ComponentFlow
+import ButtonComponent
import MultilineTextComponent
import TextNodeWithEntities
@@ -143,8 +143,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
private let addressButtonNode: HighlightTrackingButtonNode
private let addressButtonIconNode: ASImageNode
private var addressShimmerNode: ShimmerEffectNode?
- private var copyButtonNode: SolidRoundedButtonNode?
- private var shareButtonNode: SolidRoundedButtonNode?
+ private var copyButton: ComponentView?
+ private var shareButton: ComponentView?
private let avatarsButtonNode: HighlightTrackingButtonNode
private let avatarsContext: AnimatedAvatarSetContext
@@ -257,16 +257,6 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
}
}
}
- self.copyButtonNode?.pressed = { [weak self] in
- if let strongSelf = self, let item = strongSelf.item {
- item.copyAction?()
- }
- }
- self.shareButtonNode?.pressed = { [weak self] in
- if let strongSelf = self, let item = strongSelf.item {
- item.shareAction?()
- }
- }
self.avatarsButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
@@ -535,46 +525,38 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
effectiveSeparateButtons = false
}
- let copyButtonNode: SolidRoundedButtonNode
- if let currentCopyButtonNode = strongSelf.copyButtonNode {
- copyButtonNode = currentCopyButtonNode
+ let buttonBackgroundColor = item.buttonColor ?? item.presentationData.theme.list.itemCheckColors.fillColor
+ let buttonForegroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor
+ let buttonBackground = ButtonComponent.Background(
+ style: item.systemStyle == .glass ? .glass : .legacy,
+ color: buttonBackgroundColor,
+ foreground: buttonForegroundColor,
+ pressedColor: buttonBackgroundColor.withMultipliedAlpha(0.8),
+ cornerRadius: item.systemStyle == .glass ? 26.0 : 11.0
+ )
+
+ let copyButtonTitle = item.presentationData.strings.InviteLink_CopyShort
+ let copyButton: ComponentView
+ if let currentCopyButton = strongSelf.copyButton {
+ copyButton = currentCopyButton
} else {
- let buttonTheme: SolidRoundedButtonTheme
- if let buttonColor = item.buttonColor {
- buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
- } else {
- buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
- }
- 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?()
- }
- strongSelf.addSubnode(copyButtonNode)
- strongSelf.copyButtonNode = copyButtonNode
+ copyButton = ComponentView()
+ strongSelf.copyButton = copyButton
}
- let shareButtonNode: SolidRoundedButtonNode
- if let currentShareButtonNode = strongSelf.shareButtonNode {
- shareButtonNode = currentShareButtonNode
+ let shareButtonTitle: String
+ if let invite = item.invite, invitationAvailability(invite).isZero {
+ shareButtonTitle = item.presentationData.strings.InviteLink_ReactivateLink
} else {
- let buttonTheme: SolidRoundedButtonTheme
- if let buttonColor = item.buttonColor {
- buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
- } else {
- buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
- }
- 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 {
- shareButtonNode.title = effectiveSeparateButtons ? item.presentationData.strings.InviteLink_ShareShort : item.presentationData.strings.InviteLink_Share
- }
- shareButtonNode.pressed = { [weak self] in
- self?.item?.shareAction?()
- }
- strongSelf.addSubnode(shareButtonNode)
- strongSelf.shareButtonNode = shareButtonNode
+ shareButtonTitle = effectiveSeparateButtons ? item.presentationData.strings.InviteLink_ShareShort : item.presentationData.strings.InviteLink_Share
+ }
+
+ let shareButton: ComponentView
+ if let currentShareButton = strongSelf.shareButton {
+ shareButton = currentShareButton
+ } else {
+ shareButton = ComponentView()
+ strongSelf.shareButton = shareButton
}
let buttonSpacing: CGFloat = 8.0
@@ -585,11 +567,57 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
shareButtonOriginX = leftInset + buttonWidth + buttonSpacing
}
- let _ = copyButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
- copyButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
+ let copyButtonSize = copyButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(id: AnyHashable(copyButtonTitle), component: AnyComponent(Text(text: copyButtonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))),
+ isEnabled: item.invite != nil,
+ tintWhenDisabled: false,
+ action: { [weak self] in
+ self?.item?.copyAction?()
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: buttonWidth, height: buttonHeight)
+ )
+ if let copyButtonView = copyButton.view {
+ if copyButtonView.superview == nil {
+ strongSelf.view.addSubview(copyButtonView)
+ }
+ copyButtonView.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing), size: copyButtonSize)
+ copyButtonView.isHidden = !item.displayButton || !effectiveSeparateButtons
+ copyButtonView.alpha = item.invite != nil ? 1.0 : 0.4
+ copyButtonView.isAccessibilityElement = true
+ copyButtonView.accessibilityLabel = copyButtonTitle
+ copyButtonView.accessibilityTraits = item.invite != nil ? [.button] : [.button, .notEnabled]
+ }
- let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
- shareButtonNode.frame = CGRect(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
+ let shareButtonSize = shareButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(id: AnyHashable(shareButtonTitle), component: AnyComponent(Text(text: shareButtonTitle, font: Font.semibold(17.0), color: buttonForegroundColor))),
+ isEnabled: item.invite != nil,
+ tintWhenDisabled: false,
+ action: { [weak self] in
+ self?.item?.shareAction?()
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: buttonWidth, height: buttonHeight)
+ )
+ if let shareButtonView = shareButton.view {
+ if shareButtonView.superview == nil {
+ strongSelf.view.addSubview(shareButtonView)
+ }
+ shareButtonView.frame = CGRect(origin: CGPoint(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing), size: shareButtonSize)
+ shareButtonView.isHidden = !item.displayButton
+ shareButtonView.alpha = item.invite != nil ? 1.0 : 0.4
+ shareButtonView.isAccessibilityElement = true
+ shareButtonView.accessibilityLabel = shareButtonTitle
+ shareButtonView.accessibilityTraits = item.invite != nil ? [.button] : [.button, .notEnabled]
+ }
if let justCreatedCallTextNodeLayout {
if let justCreatedCallTextNode = justCreatedCallTextNodeLayout.1(TextNodeWithEntities.Arguments(
@@ -608,7 +636,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
justCreatedCallTextNode.textNode.view.addGestureRecognizer(UITapGestureRecognizer(target: strongSelf, action: #selector(strongSelf.justCreatedCallTextTap(_:))))
}
- let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: shareButtonNode.frame.maxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height))
+ let buttonMaxY = verticalInset + fieldHeight + fieldSpacing + buttonHeight
+ let justCreatedCallTextNodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - justCreatedCallTextNodeLayout.0.size.width) / 2.0), y: buttonMaxY + justCreatedCallTextSpacing), size: CGSize(width: justCreatedCallTextNodeLayout.0.size.width, height: justCreatedCallTextNodeLayout.0.size.height))
justCreatedCallTextNode.textNode.frame = justCreatedCallTextNodeFrame
let justCreatedCallSeparatorText: ComponentView
@@ -648,7 +677,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
environment: {},
containerSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0)
)
- let justCreatedCallSeparatorTextFrame = CGRect(origin: CGPoint(x: floor((params.width - justCreatedCallSeparatorTextSize.width) * 0.5), y: shareButtonNode.frame.maxY + justCreatedCallSeparatorSpacing), size: justCreatedCallSeparatorTextSize)
+ let justCreatedCallSeparatorTextFrame = CGRect(origin: CGPoint(x: floor((params.width - justCreatedCallSeparatorTextSize.width) * 0.5), y: buttonMaxY + justCreatedCallSeparatorSpacing), size: justCreatedCallSeparatorTextSize)
if let justCreatedCallSeparatorTextView = justCreatedCallSeparatorText.view {
if justCreatedCallSeparatorTextView.superview == nil {
strongSelf.view.addSubview(justCreatedCallSeparatorTextView)
@@ -703,14 +732,6 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0
- strongSelf.copyButtonNode?.isUserInteractionEnabled = item.invite != nil
- strongSelf.copyButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
- strongSelf.copyButtonNode?.isHidden = !item.displayButton || !effectiveSeparateButtons
-
- strongSelf.shareButtonNode?.isUserInteractionEnabled = item.invite != nil
- strongSelf.shareButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
- strongSelf.shareButtonNode?.isHidden = !item.displayButton
-
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil
strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil
diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift
index 11e9c2d5a2..e1f339963a 100644
--- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift
+++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift
@@ -321,7 +321,7 @@ public struct ItemListPeerItemShimmering {
}
}
-public final class ItemListPeerItem: ListViewItem, ItemListItem {
+public final class ItemListPeerItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem {
public enum Context {
public final class Custom {
public let accountPeerId: EnginePeer.Id
@@ -468,6 +468,10 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
let disableInteractiveTransitionIfNecessary: Bool
let storyStats: EnginePeerStoryStats?
let openStories: ((UIView) -> Void)?
+
+ public var hasActiveRevealOptions: Bool {
+ return self.editing.revealed == true
+ }
public init(
presentationData: ItemListPresentationData,
@@ -1013,12 +1017,12 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
color = item.presentationData.theme.list.itemDisclosureActions.accent.fillColor
textColor = item.presentationData.theme.list.itemDisclosureActions.accent.foregroundColor
}
- mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor))
+ mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, iconColor: textColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor))
index += 1
}
peerRevealOptions = mappedOptions
} else {
- peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]
+ peerRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]
}
} else {
peerRevealOptions = []
@@ -1434,26 +1438,29 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noCorners
var hasTopCorners = false
var hasBottomCorners = false
+ let topStripeIsHidden: Bool
switch neighbors.top {
case .sameSection(false):
- strongSelf.topStripeNode.isHidden = true
+ topStripeIsHidden = true
default:
hasTopCorners = true
- strongSelf.topStripeNode.isHidden = !item.displayDecorations || hasCorners || !item.hasTopStripe
+ topStripeIsHidden = !item.displayDecorations || hasCorners || !item.hasTopStripe
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
+ let bottomStripeIsHidden: Bool
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset + editingOffset
bottomStripeOffset = -separatorHeight
- strongSelf.bottomStripeNode.isHidden = !item.displayDecorations
+ bottomStripeIsHidden = !item.displayDecorations
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
- strongSelf.bottomStripeNode.isHidden = hasCorners || !item.displayDecorations
+ bottomStripeIsHidden = hasCorners || !item.displayDecorations
}
+ strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions)
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
@@ -1778,7 +1785,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
}
}
- strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
+ strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition)
if let presence = item.presence {
strongSelf.peerPresenceManager?.reset(presence: presence)
@@ -1860,7 +1867,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
var isHighlighted = false
var reallyHighlighted: Bool {
- var reallyHighlighted = self.isHighlighted
+ var reallyHighlighted = self.isHighlighted || self.isRevealOptionsActive
if let (item, _, _, _) = self.layoutParams, item.highlighted {
reallyHighlighted = true
}
@@ -1868,39 +1875,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
}
func updateIsHighlighted(transition: ContainedViewLayoutTransition) {
- if self.reallyHighlighted {
- self.highlightedBackgroundNode.alpha = 1.0
- if self.highlightedBackgroundNode.supernode == nil {
- var anchorNode: ASDisplayNode?
- if self.bottomStripeNode.supernode != nil {
- anchorNode = self.bottomStripeNode
- } else if self.topStripeNode.supernode != nil {
- anchorNode = self.topStripeNode
- } else if self.backgroundNode.supernode != nil {
- anchorNode = self.backgroundNode
- }
- if let anchorNode = anchorNode {
- self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
- } else {
- self.addSubnode(self.highlightedBackgroundNode)
- }
- }
- } else {
- if self.highlightedBackgroundNode.supernode != nil {
- if transition.isAnimated {
- self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
- if let strongSelf = self {
- if completed {
- strongSelf.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- })
- self.highlightedBackgroundNode.alpha = 0.0
- } else {
- self.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- }
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.reallyHighlighted, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
}
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
@@ -2002,6 +1977,12 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
transition.updateFrame(view: avatarIconComponentView, frame: threadIconFrame)
}
}
+
+ override public func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateIsHighlighted(transition: transition)
+ }
override public func revealOptionsInteractivelyOpened() {
if let (item, _, _, _) = self.layoutParams {
diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift
index 73a61b8757..daf52b4de4 100644
--- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift
+++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift
@@ -36,7 +36,7 @@ public enum ItemListStickerPackItemControl: Equatable {
case check(checked: Bool)
}
-public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
+public final class ItemListStickerPackItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem {
let presentationData: ItemListPresentationData
let context: AccountContext
let systemStyle: ItemListSystemStyle
@@ -56,6 +56,10 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
let removePack: () -> Void
let toggleSelected: () -> Void
+ public var hasActiveRevealOptions: Bool {
+ return self.editing.revealed
+ }
+
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 (EngineItemCollectionId?, EngineItemCollectionId?) -> Void, addPack: @escaping () -> Void, removePack: @escaping () -> Void, toggleSelected: @escaping () -> Void) {
self.presentationData = presentationData
self.context = context
@@ -180,6 +184,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
private let activateArea: AccessibilityAreaNode
private let fetchDisposable = MetaDisposable()
+ private var isHighlighted: Bool = false
override var canBeSelected: Bool {
if self.selectableControlNode != nil || self.editableControlNode != nil || self.disabledOverlayNode != nil {
@@ -372,7 +377,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
let packRevealOptions: [ItemListRevealOption]
if item.editing.editable && item.enabled && !item.editing.editing {
- packRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]
+ packRevealOptions = [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]
} else {
packRevealOptions = []
}
@@ -410,14 +415,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = 65.0 + params.leftInset
- let verticalInset: CGFloat
- switch item.systemStyle {
- case .glass:
- verticalInset = 13.0
- case .legacy:
- verticalInset = 11.0
- }
-
+ let verticalInset: CGFloat = 11.0
let titleSpacing: CGFloat = 2.0
let separatorHeight = UIScreenPixel
@@ -764,26 +762,29 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
+ let topStripeIsHidden: Bool
switch neighbors.top {
case .sameSection(false):
- strongSelf.topStripeNode.isHidden = true
+ topStripeIsHidden = true
default:
hasTopCorners = true
- strongSelf.topStripeNode.isHidden = hasCorners
+ topStripeIsHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
+ let bottomStripeIsHidden: Bool
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset + editingOffset
bottomStripeOffset = -separatorHeight
- strongSelf.bottomStripeNode.isHidden = false
+ bottomStripeIsHidden = false
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
- strongSelf.bottomStripeNode.isHidden = hasCorners
+ bottomStripeIsHidden = hasCorners
}
+ strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions)
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
@@ -877,7 +878,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
strongSelf.imageNode.setSignal(updatedImageSignal)
}
- strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
+ strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel)), transition: transition)
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
@@ -895,39 +896,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
- if highlighted {
- self.highlightedBackgroundNode.alpha = 1.0
- if self.highlightedBackgroundNode.supernode == nil {
- var anchorNode: ASDisplayNode?
- if self.bottomStripeNode.supernode != nil {
- anchorNode = self.bottomStripeNode
- } else if self.topStripeNode.supernode != nil {
- anchorNode = self.topStripeNode
- } else if self.backgroundNode.supernode != nil {
- anchorNode = self.backgroundNode
- }
- if let anchorNode = anchorNode {
- self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
- } else {
- self.addSubnode(self.highlightedBackgroundNode)
- }
- }
- } else {
- if self.highlightedBackgroundNode.supernode != nil {
- if animated {
- self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
- if let strongSelf = self {
- if completed {
- strongSelf.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- })
- self.highlightedBackgroundNode.alpha = 0.0
- } else {
- self.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- }
+ self.isHighlighted = highlighted
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
@@ -967,6 +937,12 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: animationNode, frame: CGRect(origin: CGPoint(x: params.leftInset + self.revealOffset + editingOffset + 15.0 + floor((boundingSize.width - animationNode.frame.size.width) / 2.0), y: animationNode.frame.minY), size: animationNode.frame.size))
}
}
+
+ override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
+ }
override func revealOptionsInteractivelyOpened() {
if let (item, _, _) = self.layoutParams {
diff --git a/submodules/ItemListUI/BUILD b/submodules/ItemListUI/BUILD
index cbd9e07683..0b641c47f8 100644
--- a/submodules/ItemListUI/BUILD
+++ b/submodules/ItemListUI/BUILD
@@ -35,6 +35,7 @@ swift_library(
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
+ "//submodules/TelegramUI/Components/GlassControls",
"//submodules/TelegramUI/Components/HorizontalTabsComponent",
],
visibility = [
diff --git a/submodules/ItemListUI/Sources/ItemListController.swift b/submodules/ItemListUI/Sources/ItemListController.swift
index dbc2384f58..9b83218c2d 100644
--- a/submodules/ItemListUI/Sources/ItemListController.swift
+++ b/submodules/ItemListUI/Sources/ItemListController.swift
@@ -25,6 +25,8 @@ public enum ItemListNavigationButtonContentIcon {
case search
case add
case action
+ case close
+ case done
}
public enum ItemListNavigationButtonContent: Equatable {
@@ -426,16 +428,27 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
case let .text(value):
item = UIBarButtonItem(title: value, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
case let .icon(icon):
- var image: UIImage?
- switch icon {
+ if [.close, .done].contains(icon) {
+ switch icon {
+ case .done:
+ item = UIBarButtonItem(title: "___done", style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
+ default:
+ item = UIBarButtonItem(title: "___close", style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
+ }
+ } else {
+ var image: UIImage?
+ switch icon {
case .search:
image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.presentationData.theme)
case .add:
image = PresentationResourcesRootController.navigationAddIcon(controllerState.presentationData.theme)
case .action:
image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme)
+ default:
+ image = nil
+ }
+ item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
}
- item = UIBarButtonItem(image: image, style: leftNavigationButton.style.barButtonItemStyle, target: strongSelf, action: #selector(strongSelf.leftNavigationButtonPressed))
case let .node(node):
item = UIBarButtonItem(customDisplayNode: node)
item.setCustomAction({ [weak self] in
@@ -488,16 +501,27 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
case let .text(value):
item = UIBarButtonItem(title: value, style: style.barButtonItemStyle, target: strongSelf, action: action)
case let .icon(icon):
- var image: UIImage?
- switch icon {
+ if [.close, .done].contains(icon) {
+ switch icon {
+ case .done:
+ item = UIBarButtonItem(title: "___done", style: style.barButtonItemStyle, target: strongSelf, action: action)
+ default:
+ item = UIBarButtonItem(title: "___close", style: style.barButtonItemStyle, target: strongSelf, action: action)
+ }
+ } else {
+ var image: UIImage?
+ switch icon {
case .search:
image = PresentationResourcesRootController.navigationCompactSearchIcon(controllerState.presentationData.theme)
case .add:
image = PresentationResourcesRootController.navigationAddIcon(controllerState.presentationData.theme)
case .action:
image = PresentationResourcesRootController.navigationShareIcon(controllerState.presentationData.theme)
+ default:
+ image = nil
+ }
+ item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action)
}
- item = UIBarButtonItem(image: image, style: style.barButtonItemStyle, target: strongSelf, action: action)
case let .node(node):
item = UIBarButtonItem(customDisplayNode: node)
item.setCustomAction({ [weak self] in
@@ -665,6 +689,16 @@ open class ItemListController: ViewController, KeyShortcutResponder, Presentable
}
}
+ public func itemNode(forTag tag: ItemListItemTag) -> ListViewItemNode? {
+ var result: ListViewItemNode?
+ self.forEachItemNode { itemNode in
+ if result == nil, let taggedItemNode = itemNode as? ItemListItemNode, let itemTag = taggedItemNode.tag, itemTag.isEqual(to: tag) {
+ result = itemNode
+ }
+ }
+ return result
+ }
+
public func ensureItemNodeVisible(_ itemNode: ListViewItemNode, animated: Bool = true, overflow: CGFloat = 0.0, atTop: Bool = false, curve: ListViewAnimationCurve = .Default(duration: 0.25)) {
self.controllerNode.listNode.ensureItemNodeVisible(itemNode, animated: animated, overflow: overflow, atTop: atTop, curve: curve)
}
diff --git a/submodules/ItemListUI/Sources/ItemListControllerNode.swift b/submodules/ItemListUI/Sources/ItemListControllerNode.swift
index 76901d6706..6b9efaf342 100644
--- a/submodules/ItemListUI/Sources/ItemListControllerNode.swift
+++ b/submodules/ItemListUI/Sources/ItemListControllerNode.swift
@@ -6,6 +6,8 @@ import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import MergeLists
+import ComponentFlow
+import GlassControls
public protocol ItemListHeaderItemNode: AnyObject {
func updateTheme(theme: PresentationTheme)
@@ -255,7 +257,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
private var emptyStateItem: ItemListControllerEmptyStateItem?
private var emptyStateNode: ItemListControllerEmptyStateItemNode?
- private var toolbarNode: ToolbarNode?
+ private var toolbar: ComponentView?
private var searchItem: ItemListControllerSearch?
private var searchNode: ItemListControllerSearchNode?
@@ -654,7 +656,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
insets.bottom = max(insets.bottom, additionalInsets.bottom)
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
- if layout.size.width >= 375.0 {
+ if layout.size.width >= 320.0 {
insets.left += inset
insets.right += inset
}
@@ -666,60 +668,165 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.listNodeContainer.insertSubnode(self.leftOverlayNode, aboveSubnode: self.listNode)
}
- if let toolbarItem = self.toolbarItem {
- var tabBarHeight: CGFloat
- let bottomInset: CGFloat = insets.bottom
- if !layout.safeInsets.left.isZero {
- tabBarHeight = 34.0 + bottomInset
- insets.bottom += 34.0
+ if let toolbarData = self.toolbarItem, let theme = self.theme {
+ var panelsBottomInset: CGFloat = layout.insets(options: []).bottom
+ if layout.metrics.widthClass == .regular, let inputHeight = layout.inputHeight, inputHeight != 0.0 {
+ panelsBottomInset = inputHeight + 8.0
+ }
+ if panelsBottomInset == 0.0 {
+ panelsBottomInset = 8.0
} else {
- tabBarHeight = 49.0 + bottomInset
- insets.bottom += 49.0
+ panelsBottomInset = max(panelsBottomInset, 8.0)
}
- let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))
+ let sideInset: CGFloat = 20.0
+ let toolbarHeight = 44.0
+ let toolbarFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - panelsBottomInset - toolbarHeight), size: CGSize(width: layout.size.width - sideInset * 2.0, height: toolbarHeight))
- if let toolbarNode = self.toolbarNode {
- transition.updateFrame(node: toolbarNode, frame: toolbarFrame)
- toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: transition)
- } else if let theme = self.theme {
- let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: theme), displaySeparator: true)
- toolbarNode.frame = toolbarFrame
- toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: .immediate)
- self.addSubnode(toolbarNode)
- self.toolbarNode = toolbarNode
- if case let .animated(duration, curve) = transition {
- toolbarNode.layer.animatePosition(from: CGPoint(x: 0.0, y: toolbarFrame.height), to: CGPoint(), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, additive: true)
+ let toolbar: ComponentView
+ var toolbarTransition = ComponentTransition(transition)
+ if let current = self.toolbar {
+ toolbar = current
+ } else {
+ toolbar = ComponentView()
+ self.toolbar = toolbar
+ toolbarTransition = .immediate
+ }
+
+ let _ = toolbar.update(
+ transition: toolbarTransition,
+ component: AnyComponent(GlassControlPanelComponent(
+ theme: theme,
+ leftItem: toolbarData.toolbar.leftAction.flatMap { value in
+ return GlassControlPanelComponent.Item(
+ items: [GlassControlGroupComponent.Item(
+ id: "left_" + value.title,
+ content: .text(value.title),
+ action: value.isEnabled ? { [weak self] in
+ guard let self, let toolbarData = self.toolbarItem else {
+ return
+ }
+ toolbarData.actions[0].action()
+ } : nil
+ )],
+ background: .panel
+ )
+ },
+ centralItem: toolbarData.toolbar.middleAction.flatMap { value in
+ return GlassControlPanelComponent.Item(
+ items: [GlassControlGroupComponent.Item(
+ id: "right_" + value.title,
+ content: .text(value.title),
+ action: value.isEnabled ? { [weak self] in
+ guard let self, let toolbarData = self.toolbarItem else {
+ return
+ }
+ if toolbarData.actions.count == 1 {
+ toolbarData.actions[0].action()
+ } else if toolbarData.actions.count == 3 {
+ toolbarData.actions[1].action()
+ }
+ } : nil
+ )],
+ background: .panel
+ )
+ },
+ rightItem: toolbarData.toolbar.rightAction.flatMap { value in
+ return GlassControlPanelComponent.Item(
+ items: [GlassControlGroupComponent.Item(
+ id: "right_" + value.title,
+ content: .text(value.title),
+ action: value.isEnabled ? { [weak self] in
+ guard let self, let toolbarData = self.toolbarItem else {
+ return
+ }
+ if toolbarData.actions.count == 2 {
+ toolbarData.actions[1].action()
+ } else if toolbarData.actions.count == 3 {
+ toolbarData.actions[2].action()
+ }
+ } : nil
+ )],
+ background: .panel
+ )
+ },
+ centerAlignmentIfPossible: true
+ )),
+ environment: {},
+ containerSize: toolbarFrame.size
+ )
+
+ if let toolbarView = toolbar.view {
+ if toolbarView.superview == nil {
+ self.view.addSubview(toolbarView)
+ toolbarView.alpha = 0.0
}
+ toolbarTransition.setFrame(view: toolbarView, frame: toolbarFrame)
+ ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 1.0)
}
-
- self.toolbarNode?.left = {
- toolbarItem.actions[0].action()
- }
- self.toolbarNode?.right = {
- if toolbarItem.actions.count == 2 {
- toolbarItem.actions[1].action()
- } else if toolbarItem.actions.count == 3 {
- toolbarItem.actions[2].action()
- }
- }
- self.toolbarNode?.middle = {
- if toolbarItem.actions.count == 1 {
- toolbarItem.actions[0].action()
- } else if toolbarItem.actions.count == 3 {
- toolbarItem.actions[1].action()
- }
- }
- } else if let toolbarNode = self.toolbarNode {
- self.toolbarNode = nil
- if case let .animated(duration, curve) = transition {
- toolbarNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: toolbarNode.frame.size.height), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: false, additive: true, completion: { [weak toolbarNode] _ in
- toolbarNode?.removeFromSupernode()
+ } else if let toolbar = self.toolbar {
+ self.toolbar = nil
+ if let toolbarView = toolbar.view {
+ ComponentTransition(transition).setAlpha(view: toolbarView, alpha: 0.0, completion: { [weak toolbarView] _ in
+ toolbarView?.removeFromSuperview()
})
- } else {
- toolbarNode.removeFromSupernode()
}
}
+
+// if let toolbarItem = self.toolbarItem {
+// var tabBarHeight: CGFloat
+// let bottomInset: CGFloat = insets.bottom
+// if !layout.safeInsets.left.isZero {
+// tabBarHeight = 34.0 + bottomInset
+// insets.bottom += 34.0
+// } else {
+// tabBarHeight = 49.0 + bottomInset
+// insets.bottom += 49.0
+// }
+//
+// let toolbarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))
+//
+// if let toolbarNode = self.toolbarNode {
+// transition.updateFrame(node: toolbarNode, frame: toolbarFrame)
+// toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: transition)
+// } else if let theme = self.theme {
+// let toolbarNode = ToolbarNode(theme: ToolbarTheme(rootControllerTheme: theme), displaySeparator: true)
+// toolbarNode.frame = toolbarFrame
+// toolbarNode.updateLayout(size: toolbarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: layout.intrinsicInsets.bottom, toolbar: toolbarItem.toolbar, transition: .immediate)
+// self.addSubnode(toolbarNode)
+// self.toolbarNode = toolbarNode
+// if case let .animated(duration, curve) = transition {
+// toolbarNode.layer.animatePosition(from: CGPoint(x: 0.0, y: toolbarFrame.height), to: CGPoint(), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, additive: true)
+// }
+// }
+//
+// self.toolbarNode?.left = {
+// toolbarItem.actions[0].action()
+// }
+// self.toolbarNode?.right = {
+// if toolbarItem.actions.count == 2 {
+// toolbarItem.actions[1].action()
+// } else if toolbarItem.actions.count == 3 {
+// toolbarItem.actions[2].action()
+// }
+// }
+// self.toolbarNode?.middle = {
+// if toolbarItem.actions.count == 1 {
+// toolbarItem.actions[0].action()
+// } else if toolbarItem.actions.count == 3 {
+// toolbarItem.actions[1].action()
+// }
+// }
+// } else if let toolbarNode = self.toolbarNode {
+// self.toolbarNode = nil
+// if case let .animated(duration, curve) = transition {
+// toolbarNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: toolbarNode.frame.size.height), duration: duration, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: false, additive: true, completion: { [weak toolbarNode] _ in
+// toolbarNode?.removeFromSupernode()
+// })
+// } else {
+// toolbarNode.removeFromSupernode()
+// }
+// }
if let headerItemNode = self.headerItemNode {
let headerHeight = headerItemNode.updateLayout(layout: layout, transition: transition)
@@ -977,7 +1084,7 @@ open class ItemListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
insets.bottom = footerHeight
let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
- if layout.size.width >= 375.0 {
+ if layout.size.width >= 320.0 {
insets.left += inset
insets.right += inset
}
diff --git a/submodules/ItemListUI/Sources/ItemListItem.swift b/submodules/ItemListUI/Sources/ItemListItem.swift
index bbf501135c..6f4f3d824b 100644
--- a/submodules/ItemListUI/Sources/ItemListItem.swift
+++ b/submodules/ItemListUI/Sources/ItemListItem.swift
@@ -15,6 +15,10 @@ public protocol ItemListItem {
var requestsNoInset: Bool { get }
}
+public protocol ItemListRevealOptionsStatefulItem: ItemListItem {
+ var hasActiveRevealOptions: Bool { get }
+}
+
public extension ItemListItem {
//let accessoryItem: ListViewAccessoryItem?
@@ -62,10 +66,14 @@ public enum ItemListNeighbor {
public struct ItemListNeighbors {
public var top: ItemListNeighbor
public var bottom: ItemListNeighbor
+ public var topHasActiveRevealOptions: Bool
+ public var bottomHasActiveRevealOptions: Bool
- public init(top: ItemListNeighbor, bottom: ItemListNeighbor) {
+ public init(top: ItemListNeighbor, bottom: ItemListNeighbor, topHasActiveRevealOptions: Bool = false, bottomHasActiveRevealOptions: Bool = false) {
self.top = top
self.bottom = bottom
+ self.topHasActiveRevealOptions = topHasActiveRevealOptions
+ self.bottomHasActiveRevealOptions = bottomHasActiveRevealOptions
}
}
@@ -108,7 +116,12 @@ public func itemListNeighbors(item: ItemListItem, topItem: ItemListItem?, bottom
bottomNeighbor = .none
}
- return ItemListNeighbors(top: topNeighbor, bottom: bottomNeighbor)
+ return ItemListNeighbors(
+ top: topNeighbor,
+ bottom: bottomNeighbor,
+ topHasActiveRevealOptions: (topItem as? ItemListRevealOptionsStatefulItem)?.hasActiveRevealOptions ?? false,
+ bottomHasActiveRevealOptions: (bottomItem as? ItemListRevealOptionsStatefulItem)?.hasActiveRevealOptions ?? false
+ )
}
public func itemListNeighborsPlainInsets(_ neighbors: ItemListNeighbors) -> UIEdgeInsets {
@@ -164,7 +177,7 @@ public func itemListNeighborsGroupedInsets(_ neighbors: ItemListNeighbors, _ par
}
public func itemListHasRoundedBlockLayout(_ params: ListViewItemLayoutParams) -> Bool {
- return params.width >= 350.0
+ return params.width >= 320.0
}
public final class ItemListPresentationData: Equatable {
diff --git a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift
index 0554cbac28..654626b0cd 100644
--- a/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift
+++ b/submodules/ItemListUI/Sources/ItemListRevealOptionsNode.swift
@@ -8,7 +8,7 @@ public enum ItemListRevealOptionIcon: Equatable {
case none
case image(image: UIImage)
case animation(animation: String, scale: CGFloat, offset: CGFloat, replaceColors: [UInt32]?, flip: Bool)
-
+
public static func ==(lhs: ItemListRevealOptionIcon, rhs: ItemListRevealOptionIcon) -> Bool {
switch lhs {
case .none:
@@ -38,16 +38,18 @@ public struct ItemListRevealOption: Equatable {
public let title: String
public let icon: ItemListRevealOptionIcon
public let color: UIColor
+ public let iconColor: UIColor
public let textColor: UIColor
-
- public init(key: Int32, title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor) {
+
+ public init(key: Int32, title: String, icon: ItemListRevealOptionIcon, color: UIColor, iconColor: UIColor, textColor: UIColor) {
self.key = key
self.title = title
self.icon = icon
self.color = color
+ self.iconColor = iconColor
self.textColor = textColor
}
-
+
public static func ==(lhs: ItemListRevealOption, rhs: ItemListRevealOption) -> Bool {
if lhs.key != rhs.key {
return false
@@ -58,6 +60,9 @@ public struct ItemListRevealOption: Equatable {
if !lhs.color.isEqual(rhs.color) {
return false
}
+ if !lhs.iconColor.isEqual(rhs.iconColor) {
+ return false
+ }
if !lhs.textColor.isEqual(rhs.textColor) {
return false
}
@@ -68,45 +73,128 @@ public struct ItemListRevealOption: Equatable {
}
}
-private let titleFontWithIcon = Font.medium(13.0)
-private let titleFontWithoutIcon = Font.regular(17.0)
+private let titleFont = Font.regular(11.0)
+private let iconlessTitleFont = Font.regular(13.0)
-private enum ItemListRevealOptionAlignment {
- case left
- case right
+private let optionSpacing: CGFloat = 10.0
+private let optionEdgeInset: CGFloat = 10.0
+private let optionTitleSpacing: CGFloat = 4.0
+private let optionRevealStartOverlap: CGFloat = 12.0
+private let optionRevealEndDistance: CGFloat = 10.0
+private let optionExpandedActivationWidthFactor: CGFloat = 3.0
+private let optionExpandedTransitionDistance: CGFloat = 16.0
+private let optionIconlessTitleHorizontalInset: CGFloat = 10.0
+private let optionIconAnimationResponse: CGFloat = 18.0
+private let optionIconAnimationSnapDistance: CGFloat = 0.5
+
+private extension ItemListRevealOptionIcon {
+ var hasVisualIcon: Bool {
+ switch self {
+ case .none:
+ return false
+ case .image, .animation:
+ return true
+ }
+ }
+}
+
+private struct ItemListRevealOptionLayoutMetrics {
+ let shapeSize: CGSize
+ let slotWidth: CGFloat
+ let titleWidth: CGFloat
+ let iconMaxSide: CGFloat
+ let cornerRadius: CGFloat
+ let expandedIconInset: CGFloat
+
+ var contentHeight: CGFloat {
+ return self.shapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight)
+ }
+
+ var slotShapeInset: CGFloat {
+ return floor((self.slotWidth - self.shapeSize.width) / 2.0)
+ }
+
+ static func metrics(for height: CGFloat, hasVisualIcons: Bool) -> ItemListRevealOptionLayoutMetrics {
+ let regularShapeSize = CGSize(width: 50.0, height: 50.0)
+ let compactShapeSize = CGSize(width: 60.0, height: 32.0)
+ let regularContentHeight = regularShapeSize.height + optionTitleSpacing + ceil(titleFont.lineHeight)
+ if height < regularContentHeight || !hasVisualIcons {
+ return ItemListRevealOptionLayoutMetrics(shapeSize: compactShapeSize, slotWidth: 70.0, titleWidth: 70.0, iconMaxSide: 20.0, cornerRadius: 16.0, expandedIconInset: 16.0)
+ } else {
+ return ItemListRevealOptionLayoutMetrics(shapeSize: regularShapeSize, slotWidth: 60.0, titleWidth: 60.0, iconMaxSide: 40.0, cornerRadius: 25.0, expandedIconInset: 20.0)
+ }
+ }
+
+ func revealWidth(count: Int) -> CGFloat {
+ if count == 0 {
+ return 0.0
+ }
+ return optionEdgeInset * 2.0 + self.shapeSize.width * CGFloat(count) + optionSpacing * CGFloat(count - 1)
+ }
+}
+
+private func clampToUnitInterval(_ value: CGFloat) -> CGFloat {
+ return max(0.0, min(1.0, value))
+}
+
+private func frameCenter(_ frame: CGRect) -> CGPoint {
+ return CGPoint(x: frame.midX, y: frame.midY)
}
private final class ItemListRevealOptionNode: ASDisplayNode {
+ private let contentContainerNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let highlightNode: ASDisplayNode
private let titleNode: ASTextNode
private let iconNode: ASImageNode?
private let animationNode: SimpleAnimationNode?
-
+
private let enableAnimations: Bool
-
+ private let displaysTitleInsidePill: Bool
+
private var animationScale: CGFloat = 1.0
private var animationNodeOffset: CGFloat = 0.0
private var animationNodeFlip = false
- var alignment: ItemListRevealOptionAlignment?
+
+ private var iconAnimationLink: SharedDisplayLinkDriver.Link?
+ private weak var manuallyAnimatedIconNode: ASDisplayNode?
+ private var currentIconCenter: CGPoint?
+ private var targetIconCenter: CGPoint?
+
+ private var didApplyLayout = false
var isExpanded: Bool = false
-
- init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor, enableAnimations: Bool) {
+
+ var hasAppliedLayout: Bool {
+ return self.didApplyLayout
+ }
+
+ init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, iconColor: UIColor, textColor: UIColor, enableAnimations: Bool) {
+ self.contentContainerNode = ASDisplayNode()
self.backgroundNode = ASDisplayNode()
self.highlightNode = ASDisplayNode()
-
+
self.titleNode = ASTextNode()
- self.titleNode.attributedText = NSAttributedString(string: title, font: icon == .none ? titleFontWithoutIcon : titleFontWithIcon, textColor: textColor)
-
+ self.titleNode.maximumNumberOfLines = 1
+ self.titleNode.truncationMode = .byTruncatingTail
+
+ let displaysTitleInsidePill: Bool
+ if case .none = icon {
+ displaysTitleInsidePill = true
+ } else {
+ displaysTitleInsidePill = false
+ }
+ self.displaysTitleInsidePill = displaysTitleInsidePill
+ self.titleNode.attributedText = NSAttributedString(string: title, font: displaysTitleInsidePill ? iconlessTitleFont : titleFont, textColor: displaysTitleInsidePill ? iconColor : textColor)
+
self.enableAnimations = enableAnimations
-
+
switch icon {
case let .image(image):
let iconNode = ASImageNode()
- iconNode.image = generateTintedImage(image: image, color: textColor)
+ iconNode.image = generateTintedImage(image: image, color: iconColor)
self.iconNode = iconNode
self.animationNode = nil
-
+
case let .animation(animation, scale, offset, replaceColors, flip):
self.animationScale = scale
self.iconNode = nil
@@ -116,7 +204,7 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
colors[colorToReplace] = color.rgb
}
}
- self.animationNode = SimpleAnimationNode(animationName: animation, replaceColors: colors, size: CGSize(width: 79.0, height: 79.0), playOnce: true)
+ self.animationNode = SimpleAnimationNode(animationName: animation, replaceColors: colors, size: CGSize(width: 66.0, height: 66.0), playOnce: true)
if !enableAnimations {
self.animationNode!.seekToEnd()
}
@@ -126,28 +214,34 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
self.animationNodeOffset = offset
self.animationNodeFlip = flip
break
-
+
case .none:
self.iconNode = nil
self.animationNode = nil
}
-
+
super.init()
-
- self.addSubnode(self.backgroundNode)
- self.addSubnode(self.titleNode)
+
+ self.contentContainerNode.layer.allowsGroupOpacity = true
+ self.addSubnode(self.contentContainerNode)
+ self.contentContainerNode.addSubnode(self.backgroundNode)
+ self.contentContainerNode.addSubnode(self.titleNode)
if let iconNode = self.iconNode {
- self.addSubnode(iconNode)
+ self.contentContainerNode.addSubnode(iconNode)
} else if let animationNode = self.animationNode {
- self.addSubnode(animationNode)
+ self.contentContainerNode.addSubnode(animationNode)
}
self.backgroundNode.backgroundColor = color
self.highlightNode.backgroundColor = color.withMultipliedBrightnessBy(0.9)
}
-
+
+ deinit {
+ self.stopManualIconAnimation()
+ }
+
func setHighlighted(_ highlighted: Bool) {
if highlighted {
- self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
+ self.contentContainerNode.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
self.highlightNode.layer.animate(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "opacity", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.3)
self.highlightNode.alpha = 1.0
} else {
@@ -155,145 +249,264 @@ private final class ItemListRevealOptionNode: ASDisplayNode {
self.highlightNode.alpha = 0.0
}
}
-
+
func resetAnimation() {
self.animationNode?.reset()
+ self.stopManualIconAnimation()
}
-
- func updateLayout(isFirst: Bool, isLeft: Bool, baseSize: CGSize, alignment: ItemListRevealOptionAlignment, isExpanded: Bool, extendedWidth: CGFloat, sideInset: CGFloat, transition: ContainedViewLayoutTransition, additive: Bool, revealFactor: CGFloat, animateIconMovement: Bool) {
- self.highlightNode.frame = CGRect(origin: CGPoint(), size: baseSize)
-
- var animateAdditive = false
- if additive && transition.isAnimated && self.isExpanded != isExpanded {
- animateAdditive = true
+
+ private func currentIconPresentationCenter(iconNode: ASDisplayNode) -> CGPoint {
+ return iconNode.layer.presentation()?.position ?? iconNode.position
+ }
+
+ private func isManualIconAnimationAtTarget(center: CGPoint) -> Bool {
+ guard let targetIconCenter = self.targetIconCenter else {
+ return true
}
-
+ let centerDeltaX = targetIconCenter.x - center.x
+ let centerDeltaY = targetIconCenter.y - center.y
+ let centerDistance = sqrt(centerDeltaX * centerDeltaX + centerDeltaY * centerDeltaY)
+ return centerDistance <= optionIconAnimationSnapDistance
+ }
+
+ private func stopManualIconAnimation() {
+ self.iconAnimationLink?.isPaused = true
+ self.iconAnimationLink?.invalidate()
+ self.iconAnimationLink = nil
+ self.manuallyAnimatedIconNode = nil
+ self.currentIconCenter = nil
+ self.targetIconCenter = nil
+ }
+
+ private func updateManualIconCenter(iconNode: ASDisplayNode, targetCenter: CGPoint, forceImmediate: Bool) {
+ iconNode.layer.removeAnimation(forKey: "position")
+
+ if self.manuallyAnimatedIconNode !== iconNode || self.currentIconCenter == nil {
+ self.currentIconCenter = self.currentIconPresentationCenter(iconNode: iconNode)
+ self.manuallyAnimatedIconNode = iconNode
+ }
+
+ self.targetIconCenter = targetCenter
+
+ if forceImmediate {
+ iconNode.position = targetCenter
+ self.stopManualIconAnimation()
+ return
+ }
+
+ if let currentIconCenter = self.currentIconCenter, self.isManualIconAnimationAtTarget(center: currentIconCenter) {
+ iconNode.position = targetCenter
+ self.stopManualIconAnimation()
+ return
+ }
+
+ if self.iconAnimationLink == nil {
+ self.iconAnimationLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
+ self?.tickManualIconAnimation(deltaTime: deltaTime)
+ })
+ self.iconAnimationLink?.isPaused = false
+ }
+ }
+
+ private func tickManualIconAnimation(deltaTime: CGFloat) {
+ guard let iconNode = self.manuallyAnimatedIconNode, let currentIconCenter = self.currentIconCenter, let targetIconCenter = self.targetIconCenter else {
+ self.stopManualIconAnimation()
+ return
+ }
+
+ let clampedDeltaTime = min(0.05, max(0.0, deltaTime))
+ let progress = 1.0 - exp(-clampedDeltaTime * optionIconAnimationResponse)
+ let updatedCenter = CGPoint(
+ x: currentIconCenter.x + (targetIconCenter.x - currentIconCenter.x) * progress,
+ y: currentIconCenter.y + (targetIconCenter.y - currentIconCenter.y) * progress
+ )
+
+ if self.isManualIconAnimationAtTarget(center: updatedCenter) {
+ iconNode.position = targetIconCenter
+ self.stopManualIconAnimation()
+ } else {
+ self.currentIconCenter = updatedCenter
+ iconNode.position = updatedCenter
+ }
+ }
+
+ func updateLayout(isLeft: Bool, isPrimary: Bool, metrics: ItemListRevealOptionLayoutMetrics, revealProgress: CGFloat, overswipeProgress: CGFloat, expandedProgress: CGFloat, isStretched: Bool, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
+ let didApplyLayout = self.didApplyLayout
+ let bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
+ transition.updateFrame(node: self.contentContainerNode, frame: bounds)
+
+ let titleSize = self.titleNode.measure(CGSize(width: metrics.titleWidth, height: CGFloat.greatestFiniteMagnitude))
+ let pillSize: CGSize
+ if self.displaysTitleInsidePill {
+ let pillWidth = max(metrics.shapeSize.width, min(metrics.slotWidth, titleSize.width + optionIconlessTitleHorizontalInset * 2.0))
+ pillSize = CGSize(width: pillWidth, height: metrics.shapeSize.height)
+ } else {
+ pillSize = metrics.shapeSize
+ }
+ let shapeY: CGFloat
+ if self.displaysTitleInsidePill {
+ shapeY = floor((bounds.height - pillSize.height) / 2.0)
+ } else {
+ shapeY = floor((bounds.height - metrics.contentHeight) / 2.0)
+ }
+
+ let shapeFrameX: CGFloat
+ if isStretched {
+ shapeFrameX = isLeft ? 0.0 : bounds.width - pillSize.width
+ } else {
+ shapeFrameX = floor((metrics.slotWidth - pillSize.width) / 2.0)
+ }
+ let shapeFrame = CGRect(origin: CGPoint(x: shapeFrameX, y: shapeY), size: pillSize)
let backgroundFrame: CGRect
- if isFirst {
- backgroundFrame = CGRect(origin: CGPoint(x: isLeft ? -400.0 : 0.0, y: 0.0), size: CGSize(width: extendedWidth + 400.0, height: baseSize.height))
+ if isStretched {
+ backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: shapeY), size: CGSize(width: bounds.width, height: pillSize.height))
} else {
- backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: extendedWidth, height: baseSize.height))
+ backgroundFrame = shapeFrame
}
- let deltaX: CGFloat
- if animateAdditive {
- let previousFrame = self.backgroundNode.frame
- self.backgroundNode.frame = backgroundFrame
- if isLeft {
- deltaX = previousFrame.width - backgroundFrame.width
- } else {
- deltaX = -(previousFrame.width - backgroundFrame.width)
- }
- if !animateIconMovement {
- transition.animatePositionAdditive(node: self.backgroundNode, offset: CGPoint(x: deltaX, y: 0.0))
- }
- } else {
- deltaX = 0.0
- transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
- }
-
- self.alignment = alignment
+
+ transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
+ transition.updateFrame(node: self.highlightNode, frame: backgroundFrame)
+ transition.updateCornerRadius(node: self.backgroundNode, cornerRadius: metrics.cornerRadius)
+ transition.updateCornerRadius(node: self.highlightNode, cornerRadius: metrics.cornerRadius)
+
+ let wasExpanded = self.isExpanded
self.isExpanded = isExpanded
- let titleSize = self.titleNode.calculatedSize
- var contentRect = CGRect(origin: CGPoint(), size: baseSize)
- switch alignment {
- case .left:
- contentRect.origin.x = 0.0
- case .right:
- contentRect.origin.x = extendedWidth - contentRect.width
+ self.didApplyLayout = true
+ let contentAlpha: CGFloat
+ if isPrimary {
+ contentAlpha = revealProgress
+ } else {
+ contentAlpha = revealProgress * (1.0 - 0.3 * overswipeProgress)
}
-
+ let contentScale = 0.3 + 0.7 * revealProgress
+ transition.updateAlpha(node: self.contentContainerNode, alpha: contentAlpha)
+ transition.updateTransform(node: self.contentContainerNode, transform: CGAffineTransform(scaleX: contentScale, y: contentScale))
+
+ let titleAlpha: CGFloat = isPrimary && !self.displaysTitleInsidePill ? (1.0 - expandedProgress) : 1.0
+ var didApplyManualIconCenter = false
+
+ let centeredIconCenterX = isPrimary ? backgroundFrame.midX : shapeFrame.midX
+ let iconCenterX: CGFloat
+ if isPrimary && expandedProgress > 0.0 {
+ let expandedIconCenterX: CGFloat
+ if isLeft {
+ expandedIconCenterX = backgroundFrame.maxX - metrics.expandedIconInset
+ } else {
+ expandedIconCenterX = backgroundFrame.minX + metrics.expandedIconInset
+ }
+ iconCenterX = centeredIconCenterX + (expandedIconCenterX - centeredIconCenterX) * expandedProgress
+ } else {
+ iconCenterX = centeredIconCenterX
+ }
+ let iconCenterY = backgroundFrame.midY
+
if let animationNode = self.animationNode {
- let imageSize = CGSize(width: animationNode.size.width * self.animationScale, height: animationNode.size.height * self.animationScale)
- let iconOffset: CGFloat = -2.0 + self.animationNodeOffset
- let titleIconSpacing: CGFloat = 11.0
- let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width + sideInset) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize)
- if animateAdditive {
- let iconOffsetX = animateIconMovement ? animationNode.frame.minX - iconFrame.minX : deltaX
- animationNode.frame = iconFrame
- transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: iconOffsetX, y: 0.0))
+ var imageSize = CGSize(width: animationNode.size.width * self.animationScale, height: animationNode.size.height * self.animationScale)
+ let imageMaxSide = max(imageSize.width, imageSize.height)
+ if imageMaxSide > metrics.iconMaxSide {
+ let imageScale = metrics.iconMaxSide / imageMaxSide * 1.4
+ imageSize = CGSize(width: floorToScreenPixels(imageSize.width * imageScale), height: floorToScreenPixels(imageSize.height * imageScale))
+ }
+ let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - imageSize.width / 2.0), y: floorToScreenPixels(iconCenterY - imageSize.height / 2.0) + 6.0 + self.animationNodeOffset), size: imageSize)
+
+ if isPrimary {
+ didApplyManualIconCenter = true
+ transition.updateBounds(node: animationNode, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
+ let targetCenter = frameCenter(iconFrame)
+ if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne {
+ self.updateManualIconCenter(iconNode: animationNode, targetCenter: targetCenter, forceImmediate: false)
+ } else if self.manuallyAnimatedIconNode === animationNode && self.iconAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne {
+ self.updateManualIconCenter(iconNode: animationNode, targetCenter: targetCenter, forceImmediate: false)
+ } else {
+ self.stopManualIconAnimation()
+ transition.updatePosition(node: animationNode, position: targetCenter)
+ }
} else {
transition.updateFrame(node: animationNode, frame: iconFrame)
}
-
- let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize)
- if animateAdditive {
- let titleOffsetX = animateIconMovement ? self.titleNode.frame.minX - titleFrame.minX : deltaX
- self.titleNode.frame = titleFrame
- transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: titleOffsetX, y: 0.0))
- } else {
- transition.updateFrame(node: self.titleNode, frame: titleFrame)
- }
-
if self.enableAnimations {
- if (abs(revealFactor) >= 0.4) {
+ if revealProgress >= 0.4 {
animationNode.play()
- } else if abs(revealFactor) < CGFloat.ulpOfOne && !transition.isAnimated {
+ } else if revealProgress < CGFloat.ulpOfOne && !transition.isAnimated {
animationNode.reset()
}
}
} else if let iconNode = self.iconNode, let imageSize = iconNode.image?.size {
- let iconOffset: CGFloat = -9.0
- let titleIconSpacing: CGFloat = 11.0
- let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width + sideInset) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize)
- if animateAdditive {
- let iconOffsetX = animateIconMovement ? iconNode.frame.minX - iconFrame.minX : deltaX
- iconNode.frame = iconFrame
- transition.animatePositionAdditive(node: iconNode, offset: CGPoint(x: iconOffsetX, y: 0.0))
+ var fittedSize = imageSize
+ let imageMaxSide = max(fittedSize.width, fittedSize.height)
+ if imageMaxSide > metrics.iconMaxSide {
+ let imageScale = metrics.iconMaxSide / imageMaxSide
+ fittedSize = CGSize(width: floorToScreenPixels(fittedSize.width * imageScale), height: floorToScreenPixels(fittedSize.height * imageScale))
+ }
+ let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(iconCenterX - fittedSize.width / 2.0), y: floorToScreenPixels(iconCenterY - fittedSize.height / 2.0)), size: fittedSize)
+ if isPrimary {
+ didApplyManualIconCenter = true
+ transition.updateBounds(node: iconNode, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
+ let targetCenter = frameCenter(iconFrame)
+ if didApplyLayout && wasExpanded != isExpanded && revealProgress >= CGFloat.ulpOfOne {
+ self.updateManualIconCenter(iconNode: iconNode, targetCenter: targetCenter, forceImmediate: false)
+ } else if self.manuallyAnimatedIconNode === iconNode && self.iconAnimationLink != nil && revealProgress >= CGFloat.ulpOfOne {
+ self.updateManualIconCenter(iconNode: iconNode, targetCenter: targetCenter, forceImmediate: false)
+ } else {
+ self.stopManualIconAnimation()
+ transition.updatePosition(node: iconNode, position: targetCenter)
+ }
} else {
transition.updateFrame(node: iconNode, frame: iconFrame)
}
-
- let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize)
- if animateAdditive {
- let titleOffsetX = animateIconMovement ? self.titleNode.frame.minX - titleFrame.minX : deltaX
- self.titleNode.frame = titleFrame
- transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: titleOffsetX, y: 0.0))
- } else {
- transition.updateFrame(node: self.titleNode, frame: titleFrame)
- }
+ }
+
+ if !didApplyManualIconCenter {
+ self.stopManualIconAnimation()
+ }
+ transition.updateAlpha(node: self.titleNode, alpha: titleAlpha)
+
+ let titleFrame: CGRect
+ if self.displaysTitleInsidePill {
+ titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - titleSize.width / 2.0), y: floorToScreenPixels(backgroundFrame.midY - titleSize.height / 2.0)), size: titleSize)
} else {
- let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width + sideInset) / 2.0), y: contentRect.minY + floor((baseSize.height - titleSize.height) / 2.0)), size: titleSize)
- if animateAdditive {
- let titleOffsetX = animateIconMovement ? self.titleNode.frame.minX - titleFrame.minX : deltaX
- self.titleNode.frame = titleFrame
- transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: titleOffsetX, y: 0.0))
- } else {
- transition.updateFrame(node: self.titleNode, frame: titleFrame)
- }
+ let titleCenterX = isPrimary ? backgroundFrame.midX : shapeFrame.midX
+ titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(titleCenterX - titleSize.width / 2.0), y: shapeFrame.maxY + optionTitleSpacing), size: titleSize)
}
+ transition.updateFrame(node: self.titleNode, frame: titleFrame)
}
-
+
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
- let titleSize = self.titleNode.measure(constrainedSize)
- var maxWidth = titleSize.width
- if let iconNode = self.iconNode, let image = iconNode.image {
- maxWidth = max(image.size.width, maxWidth)
- }
- return CGSize(width: max(74.0, maxWidth + 20.0), height: constrainedSize.height)
+ let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: constrainedSize.height, hasVisualIcons: !self.displaysTitleInsidePill)
+ let _ = self.titleNode.measure(CGSize(width: metrics.titleWidth, height: CGFloat.greatestFiniteMagnitude))
+ return CGSize(width: metrics.slotWidth, height: constrainedSize.height)
}
}
public final class ItemListRevealOptionsNode: ASDisplayNode {
private let optionSelected: (ItemListRevealOption) -> Void
private let tapticAction: () -> Void
-
+ private let clippingContainerNode: ASDisplayNode
+ private let optionsContainerNode: ASDisplayNode
+
private var options: [ItemListRevealOption] = []
private var isLeft: Bool = false
-
+
private var optionNodes: [ItemListRevealOptionNode] = []
private var revealOffset: CGFloat = 0.0
private var sideInset: CGFloat = 0.0
-
+
public init(optionSelected: @escaping (ItemListRevealOption) -> Void, tapticAction: @escaping () -> Void) {
self.optionSelected = optionSelected
self.tapticAction = tapticAction
-
+ self.clippingContainerNode = ASDisplayNode()
+ self.optionsContainerNode = ASDisplayNode()
+
super.init()
+
+ self.clippingContainerNode.clipsToBounds = true
+ self.addSubnode(self.clippingContainerNode)
+ self.clippingContainerNode.addSubnode(self.optionsContainerNode)
}
-
+
override public func didLoad() {
super.didLoad()
-
+
let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
gestureRecognizer.highlight = { [weak self] location in
guard let strongSelf = self, let location = location else {
@@ -311,7 +524,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
}
self.view.addGestureRecognizer(gestureRecognizer)
}
-
+
public func setOptions(_ options: [ItemListRevealOption], isLeft: Bool, enableAnimations: Bool) {
if self.options != options || self.isLeft != isLeft {
self.options = options
@@ -320,124 +533,132 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
node.removeFromSupernode()
}
self.optionNodes = options.map { option in
- return ItemListRevealOptionNode(title: option.title, icon: option.icon, color: option.color, textColor: option.textColor, enableAnimations: enableAnimations)
+ return ItemListRevealOptionNode(title: option.title, icon: option.icon, color: option.color, iconColor: option.iconColor, textColor: option.textColor, enableAnimations: enableAnimations)
}
if isLeft {
for node in self.optionNodes.reversed() {
- self.addSubnode(node)
+ self.optionsContainerNode.addSubnode(node)
}
} else {
for node in self.optionNodes {
- self.addSubnode(node)
+ self.optionsContainerNode.addSubnode(node)
}
}
self.invalidateCalculatedLayout()
}
}
-
+
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
- var maxWidth: CGFloat = 0.0
+ let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: constrainedSize.height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon }))
for node in self.optionNodes {
- let nodeSize = node.measure(constrainedSize)
- maxWidth = max(nodeSize.width, maxWidth)
+ let _ = node.measure(constrainedSize)
}
- return CGSize(width: maxWidth * CGFloat(self.optionNodes.count), height: constrainedSize.height)
+ return CGSize(width: metrics.revealWidth(count: self.optionNodes.count), height: constrainedSize.height)
}
-
+
public func updateRevealOffset(offset: CGFloat, sideInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.revealOffset = offset
self.sideInset = sideInset
self.updateNodesLayout(transition: transition)
}
-
+
private func updateNodesLayout(transition: ContainedViewLayoutTransition) {
let size = self.bounds.size
if size.width.isLessThanOrEqualTo(0.0) || self.optionNodes.isEmpty {
return
}
- let basicNodeWidth = floor((size.width - abs(self.sideInset)) / CGFloat(self.optionNodes.count))
- let lastNodeWidth = size.width - basicNodeWidth * CGFloat(self.optionNodes.count - 1)
- let revealFactor = self.revealOffset / size.width
- let boundaryRevealFactor: CGFloat
- if self.optionNodes.count > 2 {
- boundaryRevealFactor = 1.0 + 16.0 / size.width
- } else {
- boundaryRevealFactor = 1.0 + basicNodeWidth / size.width
- }
- let startingOffset: CGFloat
+ let metrics = ItemListRevealOptionLayoutMetrics.metrics(for: size.height, hasVisualIcons: self.options.contains(where: { $0.icon.hasVisualIcon }))
+ let revealedDistance = abs(self.revealOffset)
+ let boundedRevealedDistance = min(revealedDistance, size.width)
+ let overswipeDistance = max(0.0, revealedDistance - size.width)
+ let overswipeProgress = clampToUnitInterval(overswipeDistance / optionExpandedTransitionDistance)
+ let expandedActivationDistance = metrics.shapeSize.width * (optionExpandedActivationWidthFactor - 1.0)
+ let primaryIndex = self.isLeft ? 0 : self.optionNodes.count - 1
+ let stride = metrics.shapeSize.width + optionSpacing
+
+ let clippingFrameX: CGFloat
if self.isLeft {
- startingOffset = size.width + max(0.0, abs(revealFactor) - 1.0) * size.width
+ clippingFrameX = max(0.0, size.width - revealedDistance)
} else {
- startingOffset = 0.0
+ clippingFrameX = 0.0
}
-
+ let clippingFrame = CGRect(origin: CGPoint(x: clippingFrameX, y: 0.0), size: CGSize(width: revealedDistance, height: size.height))
+ transition.updateFrame(node: self.clippingContainerNode, frame: clippingFrame)
+ transition.updateFrame(node: self.optionsContainerNode, frame: CGRect(origin: CGPoint(x: -clippingFrameX, y: 0.0), size: CGSize(width: max(size.width, revealedDistance), height: size.height)))
+
let animated = transition.isAnimated
var completionCount = self.optionNodes.count
let intermediateCompletion = {
- if completionCount == 0 && animated && abs(revealFactor) < CGFloat.ulpOfOne {
+ if completionCount == 0 && animated && revealedDistance < CGFloat.ulpOfOne {
for node in self.optionNodes {
node.resetAnimation()
}
}
}
-
+
var i = self.isLeft ? (self.optionNodes.count - 1) : 0
while i >= 0 && i < self.optionNodes.count {
let node = self.optionNodes[i]
- let nodeWidth = i == (self.optionNodes.count - 1) ? lastNodeWidth : basicNodeWidth
- var nodeTransition = transition
- var isExpanded = false
- if (self.isLeft && i == 0) || (!self.isLeft && i == self.optionNodes.count - 1) {
- if abs(revealFactor) > boundaryRevealFactor {
- isExpanded = true
+ let isPrimary = i == primaryIndex
+ let isStretched = isPrimary && overswipeDistance > CGFloat.ulpOfOne
+ let isExpanded = isPrimary && overswipeDistance > expandedActivationDistance
+ let expandedProgress: CGFloat = isExpanded ? 1.0 : 0.0
+ if node.hasAppliedLayout && node.isExpanded != isExpanded && !transition.isAnimated {
+ self.tapticAction()
+ }
+
+ let baseCircleFrame: CGRect
+ let nodeFrame: CGRect
+ let revealProgress: CGFloat
+
+ if self.isLeft {
+ let baseCircleLeft = size.width - boundedRevealedDistance + self.sideInset + optionEdgeInset + CGFloat(i) * stride
+ baseCircleFrame = CGRect(origin: CGPoint(x: baseCircleLeft, y: 0.0), size: metrics.shapeSize)
+ let distanceFromShutterEdge = size.width - baseCircleFrame.maxX
+ revealProgress = clampToUnitInterval((distanceFromShutterEdge + optionRevealStartOverlap) / (optionRevealStartOverlap + optionRevealEndDistance))
+
+ if isStretched {
+ let primaryLeft = size.width - boundedRevealedDistance + self.sideInset + optionEdgeInset
+ let primaryRight: CGFloat
+ if self.optionNodes.count > 1 {
+ let neighborLeft = primaryLeft + stride + overswipeDistance
+ primaryRight = max(primaryLeft + metrics.shapeSize.width, neighborLeft - optionSpacing)
+ } else {
+ primaryRight = primaryLeft + metrics.shapeSize.width + overswipeDistance
+ }
+ nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(primaryLeft), y: 0.0), size: CGSize(width: max(metrics.shapeSize.width, primaryRight - primaryLeft), height: size.height))
+ } else {
+ let circleLeft = baseCircleLeft + (isPrimary ? 0.0 : overswipeDistance)
+ nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(circleLeft - metrics.slotShapeInset), y: 0.0), size: CGSize(width: metrics.slotWidth, height: size.height))
}
- }
- if let _ = node.alignment, node.isExpanded != isExpanded {
- nodeTransition = transition.isAnimated ? transition : .animated(duration: 0.2, curve: .easeInOut)
- if !transition.isAnimated {
- self.tapticAction()
- }
- }
-
- var sideInset: CGFloat = 0.0
- if i == self.optionNodes.count - 1 {
- sideInset = self.sideInset
- }
-
- let extendedWidth: CGFloat
- let nodeLeftOffset: CGFloat
- if isExpanded {
- nodeLeftOffset = 0.0
- extendedWidth = size.width * max(1.0, abs(revealFactor))
- } else if self.isLeft {
- let offset = basicNodeWidth * CGFloat(self.optionNodes.count - 1 - i)
- extendedWidth = (size.width - offset) * max(1.0, abs(revealFactor))
- nodeLeftOffset = startingOffset - extendedWidth - floorToScreenPixels(offset * abs(revealFactor))
} else {
- let offset = basicNodeWidth * CGFloat(i)
- extendedWidth = (size.width - offset) * max(1.0, abs(revealFactor))
- nodeLeftOffset = startingOffset + floorToScreenPixels(offset * abs(revealFactor))
+ let baseCircleRight = revealedDistance + self.sideInset - optionEdgeInset - CGFloat(self.optionNodes.count - 1 - i) * stride
+ baseCircleFrame = CGRect(origin: CGPoint(x: baseCircleRight - metrics.shapeSize.width, y: 0.0), size: metrics.shapeSize)
+ revealProgress = clampToUnitInterval((baseCircleFrame.minX + optionRevealStartOverlap) / (optionRevealStartOverlap + optionRevealEndDistance))
+
+ if isStretched {
+ let primaryRight = revealedDistance + self.sideInset - optionEdgeInset
+ let primaryLeft: CGFloat
+ if self.optionNodes.count > 1 {
+ let neighborRight = primaryRight - stride - overswipeDistance
+ primaryLeft = min(primaryRight - metrics.shapeSize.width, neighborRight + optionSpacing)
+ } else {
+ primaryLeft = primaryRight - metrics.shapeSize.width - overswipeDistance
+ }
+ nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(primaryLeft), y: 0.0), size: CGSize(width: max(metrics.shapeSize.width, primaryRight - primaryLeft), height: size.height))
+ } else {
+ let circleLeft = baseCircleFrame.minX - (isPrimary ? 0.0 : overswipeDistance)
+ nodeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(circleLeft - metrics.slotShapeInset), y: 0.0), size: CGSize(width: metrics.slotWidth, height: size.height))
+ }
}
-
- transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: nodeLeftOffset, y: 0.0), size: CGSize(width: extendedWidth, height: size.height)), completion: { _ in
+
+ transition.updateFrame(node: node, frame: nodeFrame, completion: { _ in
completionCount -= 1
intermediateCompletion()
-
})
-
- var nodeAlignment: ItemListRevealOptionAlignment
- if (self.optionNodes.count > 1) {
- nodeAlignment = self.isLeft ? .right : .left
- } else {
- if (self.isLeft) {
- nodeAlignment = isExpanded ? .right : .left
- } else {
- nodeAlignment = isExpanded ? .left : .right
- }
- }
- let animateIconMovement = self.optionNodes.count == 1
- node.updateLayout(isFirst: (self.isLeft && i == 0) || (!self.isLeft && i == self.optionNodes.count - 1), isLeft: self.isLeft, baseSize: CGSize(width: nodeWidth, height: size.height), alignment: nodeAlignment, isExpanded: isExpanded, extendedWidth: extendedWidth, sideInset: sideInset, transition: nodeTransition, additive: !transition.isAnimated, revealFactor: revealFactor, animateIconMovement: animateIconMovement)
-
+
+ node.updateLayout(isLeft: self.isLeft, isPrimary: isPrimary, metrics: metrics, revealProgress: revealProgress, overswipeProgress: overswipeProgress, expandedProgress: expandedProgress, isStretched: isStretched, isExpanded: isExpanded, transition: transition)
+
if self.isLeft {
i -= 1
} else {
@@ -445,12 +666,12 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
}
}
}
-
+
@objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
if case .ended = recognizer.state, let gesture = recognizer.lastRecognizedGestureAndLocation?.0, case .tap = gesture {
let location = recognizer.location(in: self.view)
var selectedOption: Int?
-
+
var i = self.isLeft ? 0 : (self.optionNodes.count - 1)
while i >= 0 && i < self.optionNodes.count {
self.optionNodes[i].setHighlighted(false)
@@ -469,7 +690,7 @@ public final class ItemListRevealOptionsNode: ASDisplayNode {
}
}
}
-
+
public func isDisplayingExtendedAction() -> Bool {
return self.optionNodes.contains(where: { $0.isExpanded })
}
diff --git a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift
index 5017682d7b..d59871b99b 100644
--- a/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift
+++ b/submodules/ItemListUI/Sources/Items/ItemListCheckboxItem.swift
@@ -124,6 +124,7 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
private let subtitleNode: TextNode
private var item: ItemListCheckboxItem?
+ private var isHighlighted = false
override public var controlsContainer: ASDisplayNode {
return self.contentParentNode
@@ -336,27 +337,31 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
+ let topStripeIsHidden: Bool
switch neighbors.top {
case .sameSection(false):
- strongSelf.topStripeNode.isHidden = true
+ topStripeIsHidden = true
default:
hasTopCorners = true
- strongSelf.topStripeNode.isHidden = hasCorners
+ topStripeIsHidden = hasCorners
}
let bottomStripeInset: CGFloat
+ let bottomStripeIsHidden: Bool
if item.zeroSeparatorInsets {
bottomStripeInset = 0.0
+ bottomStripeIsHidden = false
} else {
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset
- strongSelf.bottomStripeNode.isHidden = false
+ bottomStripeIsHidden = false
default:
bottomStripeInset = 0.0
hasBottomCorners = true
- strongSelf.bottomStripeNode.isHidden = hasCorners
+ bottomStripeIsHidden = hasCorners
}
}
+ strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions)
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
@@ -384,12 +389,12 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
strongSelf.imageNode.image = nil
}
- strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
+ strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel)))
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
if item.deleteAction != nil {
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]))
} else {
strongSelf.setRevealOptions((left: [], right: []))
}
@@ -401,39 +406,8 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
- if highlighted && (self.item?.enabled ?? false) {
- self.highlightedBackgroundNode.alpha = 1.0
- if self.highlightedBackgroundNode.supernode == nil {
- var anchorNode: ASDisplayNode?
- if self.bottomStripeNode.supernode != nil {
- anchorNode = self.bottomStripeNode
- } else if self.topStripeNode.supernode != nil {
- anchorNode = self.topStripeNode
- } else if self.backgroundNode.supernode != nil {
- anchorNode = self.backgroundNode
- }
- if let anchorNode = anchorNode {
- self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
- } else {
- self.addSubnode(self.highlightedBackgroundNode)
- }
- }
- } else {
- if self.highlightedBackgroundNode.supernode != nil {
- if animated {
- self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
- if let strongSelf = self {
- if completed {
- strongSelf.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- })
- self.highlightedBackgroundNode.alpha = 0.0
- } else {
- self.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- }
+ self.isHighlighted = highlighted && (self.item?.enabled ?? false)
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.4, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
}
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
@@ -446,10 +420,16 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition)
-
+
transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: CGPoint(x: offset, y: self.contentContainerNode.frame.minY), size: self.contentContainerNode.bounds.size))
}
+ override public func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
+ }
+
override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed()
diff --git a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift
index fc62243b86..ab096a8865 100644
--- a/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift
+++ b/submodules/ItemListUI/Sources/Items/ItemListDisclosureItem.swift
@@ -74,7 +74,33 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemCompone
public let tag: ItemListItemTag?
public let shimmeringIndex: Int?
- 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) {
+ 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
@@ -709,7 +735,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
centralContentHeight += additionalDetailLabelInfo.0.size.height
}
- let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - centralContentHeight) / 2.0)), size: titleLayout.size)
+ let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((height - centralContentHeight) / 2.0) + 1.0), size: titleLayout.size)
strongSelf.titleNode.textNode.frame = titleFrame
if let updateBadgeImage = updatedLabelBadgeImage {
@@ -739,7 +765,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
case .detailText, .multilineDetailText:
labelFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
default:
- labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: floor((height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
+ labelFrame = CGRect(origin: CGPoint(x: params.width - rightInset - labelLayout.size.width, y: floorToScreenPixels((height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
}
strongSelf.labelNode.frame = labelFrame
diff --git a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift
index df2372c151..955a0d6201 100644
--- a/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift
+++ b/submodules/ItemListUI/Sources/Items/ItemListEditableItem.swift
@@ -67,17 +67,173 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
private var enableAnimations: Bool = true
private var initialRevealOffset: CGFloat = 0.0
+ private var hasActiveRevealGestureOffset: Bool = false
public private(set) var revealOffset: CGFloat = 0.0
-
+ public private(set) var isRevealOptionsActive: Bool = false
+ public private(set) var isNextRevealOptionsActive: Bool = false
+ private weak var revealOptionsActivePreviousItemNode: ItemListRevealOptionsItemNode?
+
+ private weak var revealOptionsTopSeparatorNode: ASDisplayNode?
+ private weak var revealOptionsBottomSeparatorNode: ASDisplayNode?
+ private var revealOptionsTopSeparatorBaseIsHidden: Bool = false
+ private var revealOptionsBottomSeparatorBaseIsHidden: Bool = false
+ private var revealOptionsTopSeparatorHiddenByPreviousRevealOptions: Bool = false
+ private var revealOptionsBottomSeparatorHiddenByNextRevealOptions: Bool = false
+
+ private weak var revealOptionsHighlightedBackgroundNode: ASDisplayNode?
+ private var revealOptionsHighlightedBackgroundBaseFrame: CGRect?
+ private var revealOptionsHighlightedBackgroundTracksRevealOffset: Bool = false
+ private var revealOptionsHighlightedBackgroundActiveCornerRadius: CGFloat = 0.0
+
private var recognizer: ItemListRevealOptionsGestureRecognizer?
private var tapRecognizer: UITapGestureRecognizer?
private var hapticFeedback: HapticFeedback?
private var allowAnyDirection = false
-
+
public var isDisplayingRevealedOptions: Bool {
return !self.revealOffset.isZero
}
+
+ private func updateRevealOptionsActive(_ isActive: Bool, transition: ContainedViewLayoutTransition) {
+ if self.isRevealOptionsActive != isActive {
+ self.isRevealOptionsActive = isActive
+ self.updatePreviousRevealOptionsItemNode(isActive: isActive, transition: transition)
+ self.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+ }
+ }
+
+ private func updateNextRevealOptionsActive(_ isActive: Bool, transition: ContainedViewLayoutTransition) {
+ if self.isNextRevealOptionsActive != isActive {
+ self.isNextRevealOptionsActive = isActive
+ self.nextRevealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+ }
+ }
+
+ private func previousRevealOptionsItemNode() -> ItemListRevealOptionsItemNode? {
+ guard let subnodes = self.supernode?.subnodes else {
+ return nil
+ }
+
+ if let index = self.index {
+ var candidate: ItemListRevealOptionsItemNode?
+ for subnode in subnodes {
+ guard let itemNode = subnode as? ItemListRevealOptionsItemNode, itemNode !== self, let itemIndex = itemNode.index, itemIndex < index else {
+ continue
+ }
+ if let currentCandidate = candidate, let candidateIndex = currentCandidate.index {
+ if itemIndex > candidateIndex {
+ candidate = itemNode
+ }
+ } else {
+ candidate = itemNode
+ }
+ }
+ if let candidate = candidate {
+ return candidate
+ }
+ }
+
+ var candidate: ItemListRevealOptionsItemNode?
+ for subnode in subnodes {
+ guard let itemNode = subnode as? ItemListRevealOptionsItemNode, itemNode !== self else {
+ continue
+ }
+ if itemNode.frame.maxY <= self.frame.minY + UIScreenPixel {
+ if let currentCandidate = candidate {
+ if itemNode.frame.maxY > currentCandidate.frame.maxY {
+ candidate = itemNode
+ }
+ } else {
+ candidate = itemNode
+ }
+ }
+ }
+ return candidate
+ }
+
+ private func updatePreviousRevealOptionsItemNode(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ let previousItemNode = isActive ? self.previousRevealOptionsItemNode() : nil
+ if self.revealOptionsActivePreviousItemNode !== previousItemNode {
+ self.revealOptionsActivePreviousItemNode?.updateNextRevealOptionsActive(false, transition: transition)
+ self.revealOptionsActivePreviousItemNode = previousItemNode
+ }
+ previousItemNode?.updateNextRevealOptionsActive(isActive, transition: transition)
+ if !isActive {
+ self.revealOptionsActivePreviousItemNode = nil
+ }
+ }
+
+ private func updateRevealOptionsSeparatorVisibility() {
+ self.revealOptionsTopSeparatorNode?.isHidden = self.revealOptionsTopSeparatorBaseIsHidden || self.revealOptionsTopSeparatorHiddenByPreviousRevealOptions || self.isRevealOptionsActive
+ self.revealOptionsBottomSeparatorNode?.isHidden = self.revealOptionsBottomSeparatorBaseIsHidden || self.revealOptionsBottomSeparatorHiddenByNextRevealOptions || self.isRevealOptionsActive || self.isNextRevealOptionsActive
+ }
+
+ private func updateRevealOptionsHighlightedBackgroundGeometry(transition: ContainedViewLayoutTransition) {
+ guard let highlightedBackgroundNode = self.revealOptionsHighlightedBackgroundNode, let baseFrame = self.revealOptionsHighlightedBackgroundBaseFrame else {
+ return
+ }
+
+ let frame: CGRect
+ if self.revealOptionsHighlightedBackgroundTracksRevealOffset && self.isRevealOptionsActive {
+ frame = baseFrame.offsetBy(dx: self.revealOffset, dy: 0.0)
+ } else {
+ frame = baseFrame
+ }
+ transition.updateFrame(node: highlightedBackgroundNode, frame: frame)
+ transition.updateCornerRadius(node: highlightedBackgroundNode, cornerRadius: self.isRevealOptionsActive ? self.revealOptionsHighlightedBackgroundActiveCornerRadius : 0.0)
+ }
+
+ public func updateRevealOptionsSeparatorNodes(top: ASDisplayNode?, bottom: ASDisplayNode?, topIsHidden: Bool, bottomIsHidden: Bool, topHiddenByPreviousRevealOptions: Bool = false, bottomHiddenByNextRevealOptions: Bool = false) {
+ self.revealOptionsTopSeparatorNode = top
+ self.revealOptionsBottomSeparatorNode = bottom
+ self.revealOptionsTopSeparatorBaseIsHidden = topIsHidden
+ self.revealOptionsBottomSeparatorBaseIsHidden = bottomIsHidden
+ self.revealOptionsTopSeparatorHiddenByPreviousRevealOptions = topHiddenByPreviousRevealOptions
+ self.revealOptionsBottomSeparatorHiddenByNextRevealOptions = bottomHiddenByNextRevealOptions
+ self.updateRevealOptionsSeparatorVisibility()
+ }
+
+ public func updateRevealOptionsHighlightedBackgroundFrame(_ highlightedBackgroundNode: ASDisplayNode, frame: CGRect, tracksRevealOffset: Bool = true, activeCornerRadius: CGFloat = 26.0, transition: ContainedViewLayoutTransition = .immediate) {
+ self.revealOptionsHighlightedBackgroundNode = highlightedBackgroundNode
+ self.revealOptionsHighlightedBackgroundBaseFrame = frame
+ self.revealOptionsHighlightedBackgroundTracksRevealOffset = tracksRevealOffset
+ self.revealOptionsHighlightedBackgroundActiveCornerRadius = activeCornerRadius
+ self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition)
+ }
+
+ public func updateRevealOptionsHighlightedBackgroundNode(_ highlightedBackgroundNode: ASDisplayNode, isHighlighted: Bool, transition: ContainedViewLayoutTransition, aboveNodes: [ASDisplayNode?]) {
+ self.revealOptionsHighlightedBackgroundNode = highlightedBackgroundNode
+ if isHighlighted {
+ highlightedBackgroundNode.alpha = 1.0
+ if highlightedBackgroundNode.supernode == nil {
+ var anchorNode: ASDisplayNode?
+ for node in aboveNodes {
+ if let node = node, node.supernode != nil {
+ anchorNode = node
+ break
+ }
+ }
+ if let anchorNode = anchorNode {
+ self.insertSubnode(highlightedBackgroundNode, aboveSubnode: anchorNode)
+ } else {
+ self.addSubnode(highlightedBackgroundNode)
+ }
+ }
+ } else if highlightedBackgroundNode.supernode != nil {
+ if transition.isAnimated {
+ highlightedBackgroundNode.layer.animateAlpha(from: highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak highlightedBackgroundNode] completed in
+ if completed {
+ highlightedBackgroundNode?.removeFromSupernode()
+ }
+ })
+ highlightedBackgroundNode.alpha = 0.0
+ } else {
+ highlightedBackgroundNode.removeFromSupernode()
+ }
+ }
+ self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition)
+ }
override open var canBeSelected: Bool {
return !self.isDisplayingRevealedOptions
@@ -86,6 +242,10 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
override public init(layerBacked: Bool, rotated: Bool, seeThrough: Bool) {
super.init(layerBacked: layerBacked, rotated: rotated, seeThrough: seeThrough)
}
+
+ deinit {
+ self.revealOptionsActivePreviousItemNode?.updateNextRevealOptionsActive(false, transition: .immediate)
+ }
open var controlsContainer: ASDisplayNode {
return self
@@ -185,25 +345,27 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
}
open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
- /*if gestureRecognizer === self.recognizer && otherGestureRecognizer is InteractiveTransitionGestureRecognizer {
- return true
- }*/
return false
}
@objc private func revealTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
- self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.3, curve: .spring))
+ self.updateRevealOffsetInternal(offset: 0.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0)))
self.revealOptionsInteractivelyClosed()
}
}
+ private func revealSpringCurve(initialVelocity: CGFloat = 0.0) -> ContainedViewLayoutTransitionCurve {
+ return .customSpring(mass: 2.0, stiffness: 200.0, damping: 100.0, initialVelocity: initialVelocity)
+ }
+
@objc private func revealGesture(_ recognizer: ItemListRevealOptionsGestureRecognizer) {
guard let (size, _, _) = self.validLayout else {
return
}
switch recognizer.state {
case .began:
+ self.hasActiveRevealGestureOffset = !self.revealOffset.isZero
if let leftRevealNode = self.leftRevealNode {
let revealSize = leftRevealNode.bounds.size
let location = recognizer.location(in: self.view)
@@ -235,16 +397,26 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
if self.leftRevealNode == nil && CGFloat(0.0).isLess(than: translation.x) {
self.setupAndAddLeftRevealNode()
self.revealOptionsInteractivelyOpened()
+ self.hasActiveRevealGestureOffset = true
} else if self.rightRevealNode == nil && translation.x.isLess(than: 0.0) {
self.setupAndAddRightRevealNode()
self.revealOptionsInteractivelyOpened()
+ self.hasActiveRevealGestureOffset = true
+ }
+ if !translation.x.isZero {
+ self.hasActiveRevealGestureOffset = true
}
self.updateRevealOffsetInternal(offset: translation.x, transition: .immediate)
- if self.leftRevealNode == nil && self.rightRevealNode == nil {
+ if self.leftRevealNode == nil && self.rightRevealNode == nil && !self.hasActiveRevealGestureOffset {
self.revealOptionsInteractivelyClosed()
}
case .ended, .cancelled:
+ let hasActiveRevealGestureOffset = self.hasActiveRevealGestureOffset
+ self.hasActiveRevealGestureOffset = false
guard let recognizer = self.recognizer else {
+ if hasActiveRevealGestureOffset {
+ self.revealOptionsInteractivelyClosed()
+ }
break
}
@@ -273,7 +445,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
reveal = false
selectedOption = self.revealOptions.left.first
} else {
- self.updateRevealOffsetInternal(offset: reveal ?revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring))
+ self.updateRevealOffsetInternal(offset: reveal ?revealSize.width : 0.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0)))
}
if let selectedOption = selectedOption {
@@ -308,7 +480,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
reveal = false
selectedOption = self.revealOptions.right.last
} else {
- self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.3, curve: .spring))
+ self.updateRevealOffsetInternal(offset: reveal ? -revealSize.width : 0.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0)))
}
if let selectedOption = selectedOption {
@@ -318,6 +490,8 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
self.revealOptionsInteractivelyClosed()
}
}
+ } else if hasActiveRevealGestureOffset {
+ self.revealOptionsInteractivelyClosed()
}
default:
break
@@ -370,6 +544,9 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.validLayout = (size, leftInset, rightInset)
+ if self.isRevealOptionsActive {
+ self.updatePreviousRevealOptionsItemNode(isActive: true, transition: .immediate)
+ }
if let leftRevealNode = self.leftRevealNode {
var revealSize = leftRevealNode.measure(CGSize(width: CGFloat.greatestFiniteMagnitude, height: size.height))
@@ -386,6 +563,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
open func updateRevealOffsetInternal(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
self.revealOffset = offset
+ self.updateRevealOptionsActive(!offset.isZero, transition: transition)
guard let (size, leftInset, rightInset) = self.validLayout else {
return
}
@@ -455,11 +633,21 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
}
self.updateRevealOffset(offset: offset, transition: transition)
+ self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition)
}
open func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
}
+
+ open func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ self.updateRevealOptionsSeparatorVisibility()
+ self.updateRevealOptionsHighlightedBackgroundGeometry(transition: transition)
+ }
+
+ open func nextRevealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ self.updateRevealOptionsSeparatorVisibility()
+ }
open func revealOptionsInteractivelyOpened() {
@@ -476,7 +664,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
}
let transition: ContainedViewLayoutTransition
if animated {
- transition = .animated(duration: 0.3, curve: .spring)
+ transition = .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0))
} else {
transition = .immediate
}
@@ -498,7 +686,7 @@ open class ItemListRevealOptionsItemNode: ListViewItemNode, ASGestureRecognizerD
open func animateRevealOptionsFill(completion: (() -> Void)? = nil) {
if let validLayout = self.validLayout {
self.layer.allowsGroupOpacity = true
- self.updateRevealOffsetInternal(offset: -validLayout.0.width - 74.0, transition: .animated(duration: 0.3, curve: .spring), completion: {
+ self.updateRevealOffsetInternal(offset: -validLayout.0.width - 74.0, transition: .animated(duration: 0.6, curve: self.revealSpringCurve(initialVelocity: 0.0)), completion: {
self.layer.allowsGroupOpacity = false
completion?()
})
diff --git a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift
index b21392f866..c238ddb3a3 100644
--- a/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift
+++ b/submodules/ItemListUI/Sources/Items/ItemListInfoItem.swift
@@ -174,12 +174,15 @@ public class InfoItemNode: ListViewItemNode {
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
+ self.labelNode.displaysAsynchronously = false
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
+ self.titleNode.displaysAsynchronously = false
self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
+ self.textNode.displaysAsynchronously = false
self.activateArea = AccessibilityAreaNode()
self.activateArea.accessibilityTraits = .staticText
diff --git a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift
index bb6552e96f..e9f93e82c2 100644
--- a/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift
+++ b/submodules/ItemListUI/Sources/Items/ItemListSwitchItem.swift
@@ -171,6 +171,10 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
public var tag: ItemListItemTag? {
return self.item?.tag
}
+
+ public var contextSourceView: UIView {
+ return self.textNode?.view ?? self.switchNode.view
+ }
public init(type: ItemListSwitchItemNodeType) {
self.backgroundNode = ASDisplayNode()
@@ -251,6 +255,20 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
})
})
}
+
+ public func updateHasContextMenu(hasContextMenu: Bool) {
+ let transition: ContainedViewLayoutTransition
+ if hasContextMenu {
+ transition = .immediate
+ } else {
+ transition = .animated(duration: 0.3, curve: .easeInOut)
+ }
+ if let textNode = self.textNode {
+ transition.updateAlpha(node: textNode, alpha: hasContextMenu ? 0.5 : 1.0)
+ } else {
+ transition.updateAlpha(node: self.switchNode, alpha: hasContextMenu ? 0.5 : 1.0)
+ }
+ }
func asyncLayout() -> (_ item: ItemListSwitchItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
@@ -488,7 +506,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
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)
+ let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: topInset + 1.0), size: titleLayout.size)
transition.updatePosition(node: strongSelf.titleNode, position: titleFrame.origin)
strongSelf.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift
index 3eed8b32db..63c7349d2d 100644
--- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift
+++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewController.swift
@@ -141,14 +141,19 @@ public final class LegacyJoinLinkPreviewController: ViewController {
private func join() {
self.disposable.set((self.context.engine.peers.joinChatInteractively(with: self.link) |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
- if strongSelf.isRequest {
- strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .inviteRequestSent(title: strongSelf.presentationData.strings.MemberRequests_RequestToJoinSent, text: strongSelf.isGroup ? strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
- } else {
- if case let .joined(peer) = result, let peer {
- strongSelf.navigateToPeer(peer, nil)
+ switch result {
+ case let .joined(peer):
+ if strongSelf.isRequest {
+ strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .inviteRequestSent(title: strongSelf.presentationData.strings.MemberRequests_RequestToJoinSent, text: strongSelf.isGroup ? strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionGroup : strongSelf.presentationData.strings.MemberRequests_RequestToJoinSentDescriptionChannel ), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
+ } else {
+ if let peer {
+ strongSelf.navigateToPeer(peer, nil)
+ }
}
+ strongSelf.dismiss()
+ case let .webView(webView):
+ strongSelf.context.sharedContext.openJoinChatWebView(context: strongSelf.context, parentController: strongSelf, updatedPresentationData: nil, webView: webView)
}
- strongSelf.dismiss()
}
}, error: { [weak self] error in
if let strongSelf = self {
diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift
index 7516769aef..302c8ceae6 100644
--- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift
+++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewControllerNode.swift
@@ -81,6 +81,7 @@ final class JoinLinkPreviewControllerNode: ViewControllerTracingNode, ASScrollVi
self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
+ self.wrappingScrollNode.view.scrollsToTop = false
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift
index a0ce070a00..2a3f2b576b 100644
--- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift
+++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift
@@ -133,6 +133,7 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer
self.descriptionNode.textAlignment = .center
self.peersScrollNode = ASScrollNode()
self.peersScrollNode.view.showsHorizontalScrollIndicator = false
+ self.peersScrollNode.view.scrollsToTop = false
self.actionButtonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 11.0)
diff --git a/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift b/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift
index 95fd3d3982..53382b7d12 100644
--- a/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift
+++ b/submodules/LanguageLinkPreviewUI/Sources/LanguageLinkPreviewControllerNode.swift
@@ -76,6 +76,7 @@ final class LanguageLinkPreviewControllerNode: ViewControllerTracingNode, ASScro
self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
+ self.wrappingScrollNode.view.scrollsToTop = false
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h
index 847e2b5c7c..d0d054545a 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryInterfaceView.h
@@ -14,7 +14,7 @@
@property (nonatomic, copy) void (^captionSet)(id, NSAttributedString *);
@property (nonatomic, copy) void (^donePressed)(id);
-@property (nonatomic, copy) void (^doneLongPressed)(id);
+@property (nonatomic, copy) void (^doneLongPressed)(id, UIView *);
@property (nonatomic, copy) void (^photoStripItemSelected)(NSInteger index);
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h
index 4afdca957e..1e8c90aa47 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h
@@ -1,6 +1,7 @@
#import
#import
#import
+#import
@class TGPaintingData;
@class TGStickerMaskDescription;
@@ -142,6 +143,8 @@
@property (nonatomic, copy) id _Nullable(^ _Nullable captionPanelView)(void);
@property (nonatomic, copy) id _Nullable(^ _Nullable livePhotoButton)(void);
+@property (nonatomic, copy) UIView *_Nullable(^ _Nullable photoToolbarView)(TGPhotoEditorBackButton backButton, TGPhotoEditorDoneButton doneButton, bool solidBackground, bool hasSendStarsButton);
+@property (nonatomic, copy) bool (^ _Nullable presentMediaPickerSendActionMenu)(UIView * _Nonnull sourceView, bool canSendSilently, bool canSendWhenOnline, bool canSchedule, bool reminder, bool hasTimer, void (^ _Nonnull sendSilently)(void), void (^ _Nonnull sendWhenOnline)(void), void (^ _Nonnull schedule)(void), void (^ _Nonnull sendWithTimer)(void));
@property (nonatomic, copy) void (^ _Nullable editCover)(CGSize dimensions, void(^_Nonnull completion)(UIImage * _Nonnull));
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h
index 4860d82e10..90e8982d23 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h
@@ -1,44 +1,14 @@
#import
+#import
#import
@protocol TGPhotoPaintStickersContext;
-typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) {
- TGPhotoEditorNoneTab = 0,
- TGPhotoEditorCropTab = 1 << 0,
- TGPhotoEditorRotateTab = 1 << 1,
- TGPhotoEditorMirrorTab = 1 << 2,
- TGPhotoEditorPaintTab = 1 << 3,
- TGPhotoEditorEraserTab = 1 << 4,
- TGPhotoEditorStickerTab = 1 << 5,
- TGPhotoEditorTextTab = 1 << 6,
- TGPhotoEditorToolsTab = 1 << 7,
- TGPhotoEditorQualityTab = 1 << 8,
- TGPhotoEditorTimerTab = 1 << 9,
- TGPhotoEditorAspectRatioTab = 1 << 10,
- TGPhotoEditorTintTab = 1 << 11,
- TGPhotoEditorBlurTab = 1 << 12,
- TGPhotoEditorCurvesTab = 1 << 13
-};
-
-typedef enum
-{
- TGPhotoEditorBackButtonBack,
- TGPhotoEditorBackButtonCancel
-} TGPhotoEditorBackButton;
-
-typedef enum
-{
- TGPhotoEditorDoneButtonSend,
- TGPhotoEditorDoneButtonCheck,
- TGPhotoEditorDoneButtonDone,
- TGPhotoEditorDoneButtonSchedule
-} TGPhotoEditorDoneButton;
-
-@interface TGPhotoToolbarView : UIView
+@interface TGPhotoToolbarView : UIView
@property (nonatomic, assign) UIInterfaceOrientation interfaceOrientation;
+@property (nonatomic, assign) CGFloat bottomInset;
@property (nonatomic, readonly) UIButton *doneButton;
@@ -79,8 +49,12 @@ typedef enum
- (void)setActiveTab:(TGPhotoEditorTab)tab;
+- (void)setQualityButtonIsPhoto:(bool)isPhoto highQuality:(bool)highQuality videoPreset:(NSInteger)videoPreset;
+- (void)setTimerButtonValue:(NSInteger)value;
+
- (void)setInfoString:(NSString *)string;
+- (UIView *)viewForTab:(TGPhotoEditorTab)tab;
- (TGPhotoEditorButton *)buttonForTab:(TGPhotoEditorTab)tab;
@end
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarViewProtocol.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarViewProtocol.h
new file mode 100644
index 0000000000..043207594e
--- /dev/null
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarViewProtocol.h
@@ -0,0 +1,83 @@
+#import
+#import
+
+typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) {
+ TGPhotoEditorNoneTab = 0,
+ TGPhotoEditorCropTab = 1 << 0,
+ TGPhotoEditorRotateTab = 1 << 1,
+ TGPhotoEditorMirrorTab = 1 << 2,
+ TGPhotoEditorPaintTab = 1 << 3,
+ TGPhotoEditorEraserTab = 1 << 4,
+ TGPhotoEditorStickerTab = 1 << 5,
+ TGPhotoEditorTextTab = 1 << 6,
+ TGPhotoEditorToolsTab = 1 << 7,
+ TGPhotoEditorQualityTab = 1 << 8,
+ TGPhotoEditorTimerTab = 1 << 9,
+ TGPhotoEditorAspectRatioTab = 1 << 10,
+ TGPhotoEditorTintTab = 1 << 11,
+ TGPhotoEditorBlurTab = 1 << 12,
+ TGPhotoEditorCurvesTab = 1 << 13
+};
+
+typedef enum
+{
+ TGPhotoEditorBackButtonBack,
+ TGPhotoEditorBackButtonCancel
+} TGPhotoEditorBackButton;
+
+typedef enum
+{
+ TGPhotoEditorDoneButtonSend,
+ TGPhotoEditorDoneButtonCheck,
+ TGPhotoEditorDoneButtonDone,
+ TGPhotoEditorDoneButtonSchedule
+} TGPhotoEditorDoneButton;
+
+@protocol TGPhotoToolbarViewProtocol
+
+@property (nonatomic, assign) UIInterfaceOrientation interfaceOrientation;
+@property (nonatomic, assign) CGFloat bottomInset;
+
+@property (nonatomic, readonly) UIView *doneButton;
+
+@property (nonatomic, copy) void(^cancelPressed)(void);
+@property (nonatomic, copy) void(^donePressed)(void);
+@property (nonatomic, copy) void(^doneLongPressed)(id sender);
+@property (nonatomic, copy) void(^tabPressed)(TGPhotoEditorTab tab);
+
+@property (nonatomic, readonly) CGRect cancelButtonFrame;
+@property (nonatomic, readonly) CGRect doneButtonFrame;
+
+@property (nonatomic, assign) TGPhotoEditorBackButton backButtonType;
+@property (nonatomic, assign) TGPhotoEditorDoneButton doneButtonType;
+
+@property (nonatomic, assign) int64_t sendPaidMessageStars;
+
+@property (nonatomic, readonly) TGPhotoEditorTab currentTabs;
+
+- (void)transitionInAnimated:(bool)animated;
+- (void)transitionInAnimated:(bool)animated transparent:(bool)transparent;
+- (void)transitionOutAnimated:(bool)animated;
+- (void)transitionOutAnimated:(bool)animated transparent:(bool)transparent hideOnCompletion:(bool)hideOnCompletion;
+
+- (void)setDoneButtonEnabled:(bool)enabled animated:(bool)animated;
+- (void)setEditButtonsEnabled:(bool)enabled animated:(bool)animated;
+- (void)setEditButtonsHidden:(bool)hidden animated:(bool)animated;
+- (void)setEditButtonsHighlighted:(TGPhotoEditorTab)buttons;
+- (void)setEditButtonsDisabled:(TGPhotoEditorTab)buttons;
+
+- (void)setCenterButtonsHidden:(bool)hidden animated:(bool)animated;
+- (void)setAllButtonsHidden:(bool)hidden animated:(bool)animated;
+- (void)setCancelDoneButtonsHidden:(bool)hidden animated:(bool)animated;
+
+- (void)setToolbarTabs:(TGPhotoEditorTab)tabs animated:(bool)animated;
+- (void)setActiveTab:(TGPhotoEditorTab)tab;
+
+- (void)setQualityButtonIsPhoto:(bool)isPhoto highQuality:(bool)highQuality videoPreset:(NSInteger)videoPreset;
+- (void)setTimerButtonValue:(NSInteger)value;
+
+- (void)setInfoString:(NSString *)string;
+
+- (UIView *)viewForTab:(TGPhotoEditorTab)tab;
+
+@end
diff --git a/submodules/LegacyComponents/Sources/TGCameraController.m b/submodules/LegacyComponents/Sources/TGCameraController.m
index ee9724b20e..10f823ceda 100644
--- a/submodules/LegacyComponents/Sources/TGCameraController.m
+++ b/submodules/LegacyComponents/Sources/TGCameraController.m
@@ -1548,7 +1548,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
__weak TGModernGalleryController *weakGalleryController = galleryController;
__weak TGMediaPickerGalleryModel *weakModel = model;
- model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) {
+ model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item, UIView *sourceView) {
__strong TGCameraController *strongSelf = weakSelf;
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
if (strongSelf == nil || !(strongSelf.hasSilentPosting || strongSelf.hasSchedule) || strongSelf->_shortcut)
@@ -1720,6 +1720,21 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
[strongSelf _dismissTransitionForResultController:strongController];
});
};
+ if (sourceView != nil && strongSelf.stickersContext.presentMediaPickerSendActionMenu != nil && strongSelf.stickersContext.presentMediaPickerSendActionMenu(sourceView, strongSelf->_hasSilentPosting, effectiveHasSchedule, effectiveHasSchedule, strongSelf->_reminder, strongSelf->_hasTimer, ^{
+ if (controller.sendSilently != nil)
+ controller.sendSilently();
+ }, ^{
+ if (controller.sendWhenOnline != nil)
+ controller.sendWhenOnline();
+ }, ^{
+ if (controller.schedule != nil)
+ controller.schedule();
+ }, ^{
+ if (controller.sendWithTimer != nil)
+ controller.sendWithTimer();
+ })) {
+ return;
+ }
id windowManager = nil;
windowManager = [strongSelf->_context makeOverlayWindowManager];
diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m
index 28564775a5..84a2531334 100644
--- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m
+++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m
@@ -42,6 +42,19 @@
#import
#import
+#import
+
+static UIView *TGMediaPickerCreatePhotoToolbarView(id context, TGPhotoEditorBackButton backButton, TGPhotoEditorDoneButton doneButton, bool solidBackground, id stickersContext, bool hasSendStarsButton)
+{
+ if (stickersContext.photoToolbarView != nil)
+ {
+ UIView *toolbarView = stickersContext.photoToolbarView(backButton, doneButton, solidBackground, hasSendStarsButton);
+ if (toolbarView != nil)
+ return toolbarView;
+ }
+
+ return [[TGPhotoToolbarView alloc] initWithContext:context backButton:backButton doneButton:doneButton solidBackground:solidBackground stickersContext:hasSendStarsButton ? stickersContext : nil];
+}
static TGMediaAsset *TGMediaPickerGalleryLivePhotoAsset(id editableMediaItem)
{
@@ -101,8 +114,8 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
UIView *_wrapperView;
UIView *_headerWrapperView;
- TGPhotoToolbarView *_portraitToolbarView;
- TGPhotoToolbarView *_landscapeToolbarView;
+ UIView *_portraitToolbarView;
+ UIView *_landscapeToolbarView;
UIImageView *_arrowView;
UILabel *_recipientLabel;
@@ -224,8 +237,10 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
return;
[strongSelf.window endEditing:true];
- if (strongSelf->_doneLongPressed != nil)
- strongSelf->_doneLongPressed(strongSelf->_currentItem);
+ if (strongSelf->_doneLongPressed != nil) {
+ UIView *sourceView = [sender isKindOfClass:[UIView class]] ? (UIView *)sender : nil;
+ strongSelf->_doneLongPressed(strongSelf->_currentItem, sourceView);
+ }
[[NSUserDefaults standardUserDefaults] setObject:@(3) forKey:@"TG_displayedMediaTimerTooltip_v3"];
};
@@ -471,13 +486,13 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
TGPhotoEditorDoneButton doneButton = isScheduledMessages ? TGPhotoEditorDoneButtonSchedule : TGPhotoEditorDoneButtonSend;
- _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false stickersContext:editingContext.sendPaidMessageStars > 0 ? stickersContext : nil];
+ _portraitToolbarView = TGMediaPickerCreatePhotoToolbarView(_context, TGPhotoEditorBackButtonBack, doneButton, false, stickersContext, editingContext.sendPaidMessageStars > 0);
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
_portraitToolbarView.donePressed = toolbarDonePressed;
_portraitToolbarView.doneLongPressed = toolbarDoneLongPressed;
[_wrapperView addSubview:_portraitToolbarView];
- _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false stickersContext:nil];
+ _landscapeToolbarView = TGMediaPickerCreatePhotoToolbarView(_context, TGPhotoEditorBackButtonBack, doneButton, false, stickersContext, false);
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
_landscapeToolbarView.donePressed = toolbarDonePressed;
_landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed;
@@ -641,17 +656,17 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
- (UIView *)timerButton
{
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))
- return [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab];
+ return [_portraitToolbarView viewForTab:TGPhotoEditorTimerTab];
else
- return [_landscapeToolbarView buttonForTab:TGPhotoEditorTimerTab];
+ return [_landscapeToolbarView viewForTab:TGPhotoEditorTimerTab];
}
- (UIView *)qualityButton
{
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))
- return [_portraitToolbarView buttonForTab:TGPhotoEditorQualityTab];
+ return [_portraitToolbarView viewForTab:TGPhotoEditorQualityTab];
else
- return [_landscapeToolbarView buttonForTab:TGPhotoEditorQualityTab];
+ return [_landscapeToolbarView viewForTab:TGPhotoEditorQualityTab];
}
- (void)setSelectedItemsModel:(TGMediaPickerGallerySelectedItemsModel *)selectedItemsModel
@@ -1188,19 +1203,15 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
_muteButton.selected = adjustments.sendAsGif;
- TGPhotoEditorButton *qualityButton = [_portraitToolbarView buttonForTab:TGPhotoEditorQualityTab];
+ UIView *qualityButton = [_portraitToolbarView viewForTab:TGPhotoEditorQualityTab];
if (qualityButton != nil)
{
bool isPhoto = [_currentItemView isKindOfClass:[TGMediaPickerGalleryPhotoItemView class]] || [_currentItem isKindOfClass:[TGCameraCapturedPhoto class]];
+ bool isHd = false;
+ TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetCompressedMedium;
if (isPhoto) {
- bool isHd = _editingContext.isHighQualityPhoto;
- UIImage *icon = [TGPhotoEditorInterfaceAssets qualityIconForHighQuality:isHd filled: false];
- qualityButton.iconImage = icon;
-
- qualityButton = [_landscapeToolbarView buttonForTab:TGPhotoEditorQualityTab];
- qualityButton.iconImage = icon;
+ isHd = _editingContext.isHighQualityPhoto;
} else {
- TGMediaVideoConversionPreset preset = 0;
TGMediaVideoConversionPreset adjustmentsPreset = TGMediaVideoConversionPresetCompressedDefault;
if ([adjustments isKindOfClass:[TGMediaVideoEditAdjustments class]])
adjustmentsPreset = ((TGMediaVideoEditAdjustments *)adjustments).preset;
@@ -1221,28 +1232,19 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
TGMediaVideoConversionPreset bestPreset = [TGMediaVideoConverter bestAvailablePresetForDimensions:dimensions];
if (preset > bestPreset)
preset = bestPreset;
-
- UIImage *icon = [TGPhotoEditorInterfaceAssets qualityIconForPreset:preset];
- qualityButton.iconImage = icon;
-
- qualityButton = [_landscapeToolbarView buttonForTab:TGPhotoEditorQualityTab];
- qualityButton.iconImage = icon;
}
+
+ [_portraitToolbarView setQualityButtonIsPhoto:isPhoto highQuality:isHd videoPreset:preset];
+ [_landscapeToolbarView setQualityButtonIsPhoto:isPhoto highQuality:isHd videoPreset:preset];
}
- TGPhotoEditorButton *timerButton = [_portraitToolbarView buttonForTab:TGPhotoEditorTimerTab];
+ UIView *timerButton = [_portraitToolbarView viewForTab:TGPhotoEditorTimerTab];
if (timerButton != nil)
{
NSInteger value = [timer integerValue];
- UIImage *defaultIcon = [TGPhotoEditorInterfaceAssets timerIconForValue:0];
- UIImage *icon = [TGPhotoEditorInterfaceAssets timerIconForValue:value];
- [timerButton setIconImage:defaultIcon activeIconImage:icon];
-
- TGPhotoEditorButton *landscapeTimerButton = [_landscapeToolbarView buttonForTab:TGPhotoEditorTimerTab];
-
- timerButton = landscapeTimerButton;
- [timerButton setIconImage:defaultIcon activeIconImage:icon];
+ [_portraitToolbarView setTimerButtonValue:value];
+ [_landscapeToolbarView setTimerButtonValue:value];
if (value > 0)
highlightedButtons |= TGPhotoEditorTimerTab;
@@ -1413,6 +1415,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
_coverButton.alpha = alpha;
_arrowView.alpha = alpha * 0.6f;
_recipientLabel.alpha = alpha * 0.6;
+ _captionMixin.livePhotoButtonView.alpha = alpha;
} completion:^(BOOL finished)
{
if (finished)
@@ -1420,6 +1423,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
_checkButton.userInteractionEnabled = !hidden;
_muteButton.userInteractionEnabled = !hidden;
_coverButton.userInteractionEnabled = !hidden;
+ _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden;
}
}];
@@ -1448,6 +1452,9 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
_arrowView.alpha = alpha * 0.6f;
_recipientLabel.alpha = alpha * 0.6;
+
+ _captionMixin.livePhotoButtonView.alpha = alpha;
+ _captionMixin.livePhotoButtonView.userInteractionEnabled = !hidden;
}
if (hidden)
@@ -1735,11 +1742,11 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
{
[self setSelectionInterfaceHidden:true animated:true];
- [UIView animateWithDuration:0.2 animations:^
+ [UIView animateWithDuration:0.3 animations:^
{
_captionMixin.inputPanelView.alpha = 0.0f;
- _portraitToolbarView.doneButton.alpha = 0.0f;
- _landscapeToolbarView.doneButton.alpha = 0.0f;
+ _portraitToolbarView.alpha = 0.0f;
+ _landscapeToolbarView.alpha = 0.0f;
}];
}
@@ -1747,11 +1754,11 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
{
[self setSelectionInterfaceHidden:false animated:true];
- [UIView animateWithDuration:0.3 animations:^
+ [UIView animateWithDuration:0.2 animations:^
{
_captionMixin.inputPanelView.alpha = 1.0f;
- _portraitToolbarView.doneButton.alpha = 1.0f;
- _landscapeToolbarView.doneButton.alpha = 1.0f;
+ _portraitToolbarView.alpha = 1.0f;
+ _landscapeToolbarView.alpha = 1.0f;
}];
}
@@ -1861,7 +1868,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
break;
default:
- frame = CGRectMake(screenEdges.left + 5, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 26 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), _muteButton.frame.size.width, _muteButton.frame.size.height);
+ frame = CGRectMake(screenEdges.left + 5, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 26 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 74.0 : 0.0), _muteButton.frame.size.width, _muteButton.frame.size.height);
break;
}
@@ -1972,7 +1979,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
break;
default:
- frame = CGRectMake(screenEdges.right - 46 - _safeAreaInset.right - buttonInset, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 45 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), 44, 44);
+ frame = CGRectMake(screenEdges.right - 46 - _safeAreaInset.right - buttonInset, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 50 - _safeAreaInset.bottom - panelInset - (hasHeaderView ? 64.0 : 0.0), 44, 44);
break;
}
@@ -2085,6 +2092,8 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
CGFloat screenSide = MAX(screenSize.width, screenSize.height);
UIEdgeInsets screenEdges = UIEdgeInsetsZero;
+ _portraitToolbarView.bottomInset = _safeAreaInset.bottom;
+
if (TGIsPad())
{
_landscapeToolbarView.hidden = true;
@@ -2118,7 +2127,7 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
_coverTitleLabel.frame = CGRectMake(screenEdges.left + floor((self.frame.size.width - _coverTitleLabel.frame.size.width) / 2.0), coverTitleTopY + 26, _coverTitleLabel.frame.size.width, _coverTitleLabel.frame.size.height);
UIEdgeInsets captionEdgeInsets = screenEdges;
- captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height;
+ captionEdgeInsets.bottom = _portraitToolbarView.frame.size.height + 10.0;
[_captionMixin updateLayoutWithFrame:self.bounds edgeInsets:captionEdgeInsets animated:false];
switch (orientation)
@@ -2157,14 +2166,14 @@ static TGMediaLivePhotoMode TGMediaPickerGalleryResolvedLivePhotoMode(NSNumber *
{
[UIView performWithoutAnimation:^
{
- _photoCounterButton.frame = CGRectMake(screenEdges.right - 56 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 40 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), 64, 38);
+ _photoCounterButton.frame = CGRectMake(screenEdges.right - 64 - _safeAreaInset.right, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - 50 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), 64, 38);
- _selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 54 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize);
+ _selectedPhotosView.frame = CGRectMake(screenEdges.left + 4, screenEdges.bottom - TGPhotoEditorToolbarSize - [_captionMixin.inputPanel baseHeight] - photosViewSize - 64 - _safeAreaInset.bottom - (hasHeaderView ? 46.0 : 0.0), self.frame.size.width - 4 * 2 - _safeAreaInset.right, photosViewSize);
}];
_landscapeToolbarView.frame = CGRectMake(_landscapeToolbarView.frame.origin.x, screenEdges.top, TGPhotoEditorToolbarSize, self.frame.size.height);
- _headerWrapperView.frame = CGRectMake(screenEdges.left, _portraitToolbarView.frame.origin.y - 64.0 - [_captionMixin.inputPanel baseHeight], self.frame.size.width, 72.0);
+ _headerWrapperView.frame = CGRectMake(screenEdges.left, _portraitToolbarView.frame.origin.y - 74.0 - [_captionMixin.inputPanel baseHeight], self.frame.size.width, 72.0);
}
break;
}
diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m
index 35a79b3278..12edfa97bd 100644
--- a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m
+++ b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m
@@ -142,7 +142,7 @@
strongSelf.completeWithItem(item, false, 0);
};
- model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item) {
+ model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item, UIView *sourceView) {
__strong TGMediaPickerModernGalleryMixin *strongSelf = weakSelf;
if (strongSelf == nil || !(hasSilentPosting || hasSchedule))
return;
@@ -236,6 +236,19 @@
strongSelf.completeWithItem(item, false, 0);
});
};
+ if (sourceView != nil && strongSelf->_stickersContext.presentMediaPickerSendActionMenu != nil && strongSelf->_stickersContext.presentMediaPickerSendActionMenu(sourceView, hasSilentPosting, effectiveHasSchedule, effectiveHasSchedule, reminder, false, ^{
+ if (controller.sendSilently != nil)
+ controller.sendSilently();
+ }, ^{
+ if (controller.sendWhenOnline != nil)
+ controller.sendWhenOnline();
+ }, ^{
+ if (controller.schedule != nil)
+ controller.schedule();
+ }, ^{
+ })) {
+ return;
+ }
TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:[strongSelf->_context makeOverlayWindowManager] parentController:strongSelf->_parentController contentController:controller];
controllerWindow.hidden = false;
diff --git a/submodules/LegacyComponents/Sources/TGPhotoCropController.h b/submodules/LegacyComponents/Sources/TGPhotoCropController.h
index 1a32504b05..6d6a873680 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoCropController.h
+++ b/submodules/LegacyComponents/Sources/TGPhotoCropController.h
@@ -21,6 +21,7 @@
- (void)rotate;
- (void)mirror;
- (void)aspectRatioButtonPressed;
+- (void)aspectRatioButtonPressedWithSourceView:(UIView *)sourceView;
- (void)setImage:(UIImage *)image;
- (void)setSnapshotImage:(UIImage *)snapshotImage;
diff --git a/submodules/LegacyComponents/Sources/TGPhotoCropController.m b/submodules/LegacyComponents/Sources/TGPhotoCropController.m
index f197b0418f..eb854e0fa7 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoCropController.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoCropController.m
@@ -22,8 +22,6 @@
#import "TGPhotoCropView.h"
#import
-#import
-
const CGFloat TGPhotoCropButtonsWrapperSize = 61.0f;
const CGSize TGPhotoCropAreaInsetSize = { 9, 9 };
@@ -535,6 +533,11 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original";
}
- (void)aspectRatioButtonPressed
+{
+ [self aspectRatioButtonPressedWithSourceView:nil];
+}
+
+- (void)aspectRatioButtonPressedWithSourceView:(UIView *)sourceView
{
if (_cropView.isAnimating)
return;
@@ -547,16 +550,11 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original";
{
[_cropView performConfirmAnimated:true];
- TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
- controller.dismissesByOutsideTap = true;
- controller.hasSwipeGesture = true;
- __weak TGMenuSheetController *weakController = controller;
__weak TGPhotoCropController *weakSelf = self;
void (^action)(NSString *) = ^(NSString *ratioString)
{
__strong TGPhotoCropController *strongSelf = weakSelf;
- __strong TGMenuSheetController *strongController = weakController;
if (strongSelf == nil)
return;
@@ -569,7 +567,7 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original";
else
{
aspectRatio = [ratioString floatValue];
- if (_cropView.cropOrientation == UIImageOrientationLeft || _cropView.cropOrientation == UIImageOrientationRight)
+ if (strongSelf->_cropView.cropOrientation == UIImageOrientationLeft || strongSelf->_cropView.cropOrientation == UIImageOrientationRight)
aspectRatio = 1.0f / aspectRatio;
}
@@ -588,13 +586,11 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original";
else
TGDispatchAfter(0.1f, dispatch_get_main_queue(), setAspectRatioBlock);
#pragma clang diagnostic pop
-
- [strongController dismissAnimated:true];
};
- NSMutableArray *items = [[NSMutableArray alloc] init];
- [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioOriginal") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^{ action(TGPhotoCropOriginalAspectRatio); }]];
- [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioSquare") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^{ action(@"1.0"); }]];
+ NSMutableArray *actions = [[NSMutableArray alloc] init];
+ [actions addObject:[[LegacyComponentsActionSheetAction alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioOriginal") action:TGPhotoCropOriginalAspectRatio]];
+ [actions addObject:[[LegacyComponentsActionSheetAction alloc] initWithTitle:TGLocalized(@"PhotoEditor.CropAspectRatioSquare") action:@"1.0"]];
CGSize croppedImageSize = _cropView.cropRect.size;
if (_cropView.cropOrientation == UIImageOrientationLeft || _cropView.cropOrientation == UIImageOrientationRight)
@@ -634,26 +630,14 @@ NSString * const TGPhotoCropOriginalAspectRatio = @"original";
ratio = heightComponent / widthComponent;
- [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:[NSString stringWithFormat:@"%d:%d", (int)widthComponent, (int)heightComponent] type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^{ action([NSString stringWithFormat:@"%f", ratio]); }]];
+ [actions addObject:[[LegacyComponentsActionSheetAction alloc] initWithTitle:[NSString stringWithFormat:@"%d:%d", (int)widthComponent, (int)heightComponent] action:[NSString stringWithFormat:@"%f", ratio]]];
}
- [items addObject:[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^
+ [_context presentActionSheet:actions view:(sourceView ?: self.view) completion:^(LegacyComponentsActionSheetAction *selectedAction)
{
- __strong TGMenuSheetController *strongController = weakController;
- if (strongController != nil)
- [strongController dismissAnimated:true];
- }]];
-
- [controller setItemViews:items];
-// controller.sourceRect = ^CGRect
-// {
-// __strong TGPhotoCropController *strongSelf = weakSelf;
-// if (strongSelf != nil)
-// return [strongSelf.view convertRect:strongSelf->_aspectRatioButton.frame fromView:strongSelf->_aspectRatioButton.superview];
-//
-// return CGRectZero;
-// };
- [controller presentInViewController:self.parentViewController sourceView:self.view animated:true];
+ if (selectedAction != nil)
+ action(selectedAction.action);
+ }];
}
[self _updateTabs];
diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m
index c85605aee8..0fdaa96527 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoEditorBlurToolView.m
@@ -169,25 +169,7 @@
- (void)blurButtonPressed:(TGPhotoEditorBlurTypeButton *)sender
{
-// if (sender.tag != 0 && sender.tag == _currentType)
-// {
-// _editingIntensity = true;
-// _startIntensity = [(PGBlurToolValue *)self.value intensity];
-//
-// PGBlurToolValue *value = [(PGBlurToolValue *)self.value copy];
-// value.editingIntensity = true;
-//
-// _value = value;
-//
-// if (self.valueChanged != nil)
-// self.valueChanged(value);
-//
-// [self setIntensitySliderHidden:false animated:true];
-// }
-// else
-// {
- [self setSelectedBlurType:(PGBlurToolType)sender.tag update:true];
-// }
+ [self setSelectedBlurType:(PGBlurToolType)sender.tag update:true];
}
- (void)setValue:(id)value
diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m
index f61c9a9f19..c35be4ef10 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m
@@ -27,6 +27,7 @@
#import
#import
+#import
#import "TGPhotoEditorPreviewView.h"
#import
@@ -46,11 +47,21 @@
#import "TGMediaPickerGalleryVideoScrubber.h"
#import "TGMediaPickerGalleryVideoScrubberThumbnailView.h"
-#import
-
#import
#import
+static UIView *TGPhotoEditorCreatePhotoToolbarView(id context, TGPhotoEditorBackButton backButton, TGPhotoEditorDoneButton doneButton, bool solidBackground, id stickersContext)
+{
+ if (stickersContext.photoToolbarView != nil)
+ {
+ UIView *toolbarView = stickersContext.photoToolbarView(backButton, doneButton, solidBackground, false);
+ if (toolbarView != nil)
+ return toolbarView;
+ }
+
+ return [[TGPhotoToolbarView alloc] initWithContext:context backButton:backButton doneButton:doneButton solidBackground:solidBackground stickersContext:nil];
+}
+
@interface TGPhotoEditorController ()
{
bool _switchingTab;
@@ -64,8 +75,8 @@
UIView *_containerView;
UIView *_wrapperView;
UIView *_transitionWrapperView;
- TGPhotoToolbarView *_portraitToolbarView;
- TGPhotoToolbarView *_landscapeToolbarView;
+ UIView *_portraitToolbarView;
+ UIView *_landscapeToolbarView;
TGPhotoEditorPreviewView *_previewView;
PGPhotoEditorView *_fullPreviewView;
UIView *_fullEntitiesView;
@@ -285,16 +296,27 @@
case TGPhotoEditorRotateTab:
case TGPhotoEditorMirrorTab:
- case TGPhotoEditorAspectRatioTab:
if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoCropController class]] || [strongSelf->_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]])
[strongSelf->_currentTabController handleTabAction:tab];
break;
+
+ case TGPhotoEditorAspectRatioTab:
+ if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoCropController class]])
+ {
+ UIView *toolbarView = UIInterfaceOrientationIsPortrait(strongSelf.effectiveOrientation) ? strongSelf->_portraitToolbarView : strongSelf->_landscapeToolbarView;
+ [(TGPhotoCropController *)strongSelf->_currentTabController aspectRatioButtonPressedWithSourceView:[toolbarView viewForTab:TGPhotoEditorAspectRatioTab]];
+ }
+ else if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]])
+ {
+ [strongSelf->_currentTabController handleTabAction:tab];
+ }
+ break;
}
};
TGPhotoEditorBackButton backButton = TGPhotoEditorBackButtonCancel;
TGPhotoEditorDoneButton doneButton = TGPhotoEditorDoneButtonCheck;
- _portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true stickersContext:nil];
+ _portraitToolbarView = TGPhotoEditorCreatePhotoToolbarView(_context, backButton, doneButton, true, _stickersContext);
[_portraitToolbarView setToolbarTabs:_availableTabs animated:false];
[_portraitToolbarView setActiveTab:_currentTab];
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
@@ -303,7 +325,7 @@
_portraitToolbarView.tabPressed = toolbarTabPressed;
[_wrapperView addSubview:_portraitToolbarView];
- _landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true stickersContext:nil];
+ _landscapeToolbarView = TGPhotoEditorCreatePhotoToolbarView(_context, backButton, doneButton, true, _stickersContext);
[_landscapeToolbarView setToolbarTabs:_availableTabs animated:false];
[_landscapeToolbarView setActiveTab:_currentTab];
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
@@ -1974,34 +1996,12 @@
if ((_initialAdjustments == nil && (![editorValues isDefaultValuesForAvatar:[self presentedForAvatarCreation]] || editorValues.cropOrientation != UIImageOrientationUp)) || (_initialAdjustments != nil && ![editorValues isEqual:_initialAdjustments]))
{
- TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
- controller.dismissesByOutsideTap = true;
- controller.narrowInLandscape = true;
- __weak TGMenuSheetController *weakController = controller;
-
- NSArray *items = @
+ NSArray *actions = @
[
- [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"PhotoEditor.DiscardChanges") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
- {
- __strong TGMenuSheetController *strongController = weakController;
- if (strongController == nil)
- return;
-
- [strongController dismissAnimated:true manual:false completion:^
- {
- dismiss();
- }];
- }],
- [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^
- {
- __strong TGMenuSheetController *strongController = weakController;
- if (strongController != nil)
- [strongController dismissAnimated:true];
- }]
+ [[LegacyComponentsActionSheetAction alloc] initWithTitle:TGLocalized(@"PhotoEditor.DiscardChanges") action:@"discard" type:LegacyComponentsActionSheetActionTypeDestructive]
];
- [controller setItemViews:items];
- controller.sourceRect = ^
+ [_context presentActionSheet:actions view:self.view sourceRect:^CGRect
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
@@ -2011,8 +2011,11 @@
return [strongSelf.view convertRect:strongSelf->_portraitToolbarView.cancelButtonFrame fromView:strongSelf->_portraitToolbarView];
else
return [strongSelf.view convertRect:strongSelf->_landscapeToolbarView.cancelButtonFrame fromView:strongSelf->_landscapeToolbarView];
- };
- [controller presentInViewController:self sourceView:self.view animated:true];
+ } completion:^(LegacyComponentsActionSheetAction *selectedAction)
+ {
+ if ([selectedAction.action isEqualToString:@"discard"])
+ dismiss();
+ }];
}
else
{
diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m
index 9ab2a97410..8a22a46a4c 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m
@@ -30,11 +30,11 @@
+ (UIColor *)accentColor
{
- TGMediaAssetsPallete *pallete = nil;
- if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)])
- pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete];
-
- return pallete.maybeAccentColor ?: UIColorRGB(0x65b3ff);
+// TGMediaAssetsPallete *pallete = nil;
+// if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)])
+// pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete];
+//
+ return UIColorRGB(0xffd300); //pallete.maybeAccentColor ?: UIColorRGB(0x65b3ff);
}
+ (UIColor *)panelBackgroundColor
@@ -374,7 +374,7 @@
+ (UIFont *)editorItemTitleFont
{
- return [TGFont systemFontOfSize:14];
+ return [TGFont boldSystemFontOfSize:13]; //[TGFont systemFontOfSize:1];
}
+ (UIColor *)filterSelectionColor
diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m
index 8b519b5860..2c98f01c3b 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m
@@ -31,6 +31,7 @@
bool _animatingCancelDoneButtons;
}
+
@end
@implementation TGPhotoToolbarView
@@ -635,6 +636,34 @@
return nil;
}
+- (UIView *)viewForTab:(TGPhotoEditorTab)tab
+{
+ return [self buttonForTab:tab];
+}
+
+- (void)setQualityButtonIsPhoto:(bool)isPhoto highQuality:(bool)highQuality videoPreset:(NSInteger)videoPreset
+{
+ TGPhotoEditorButton *qualityButton = [self buttonForTab:TGPhotoEditorQualityTab];
+ if (qualityButton == nil)
+ return;
+
+ if (isPhoto)
+ qualityButton.iconImage = [TGPhotoEditorInterfaceAssets qualityIconForHighQuality:highQuality filled:false];
+ else
+ qualityButton.iconImage = [TGPhotoEditorInterfaceAssets qualityIconForPreset:(TGMediaVideoConversionPreset)videoPreset];
+}
+
+- (void)setTimerButtonValue:(NSInteger)value
+{
+ TGPhotoEditorButton *timerButton = [self buttonForTab:TGPhotoEditorTimerTab];
+ if (timerButton == nil)
+ return;
+
+ UIImage *defaultIcon = [TGPhotoEditorInterfaceAssets timerIconForValue:0];
+ UIImage *activeIcon = [TGPhotoEditorInterfaceAssets timerIconForValue:value];
+ [timerButton setIconImage:defaultIcon activeIconImage:activeIcon];
+}
+
- (void)layoutSubviews
{
CGRect backgroundFrame = self.bounds;
diff --git a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m
index c407675ed9..b894c687d6 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m
@@ -236,7 +236,7 @@
[strongController dismissWhenReadyAnimated:true];
};
- model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item)
+ model.interfaceView.doneLongPressed = ^(TGMediaPickerGalleryItem *item, UIView *sourceView)
{
__strong TGModernGalleryController *strongController = weakGalleryController;
__strong TGMediaPickerGalleryModel *strongModel = weakModel;
@@ -286,6 +286,19 @@
complete(silentPosting, time);
});
};
+ if (sourceView != nil && stickersContext.presentMediaPickerSendActionMenu != nil && stickersContext.presentMediaPickerSendActionMenu(sourceView, hasSilentPosting, hasSchedule, hasSchedule, reminder, false, ^{
+ if (sendController.sendSilently != nil)
+ sendController.sendSilently();
+ }, ^{
+ if (sendController.sendWhenOnline != nil)
+ sendController.sendWhenOnline();
+ }, ^{
+ if (sendController.schedule != nil)
+ sendController.schedule();
+ }, ^{
+ })) {
+ return;
+ }
[strongController presentViewController:sendController animated:false completion:nil];
};
diff --git a/submodules/LegacyMediaPickerUI/BUILD b/submodules/LegacyMediaPickerUI/BUILD
index 7cdc998863..951f5a14ff 100644
--- a/submodules/LegacyMediaPickerUI/BUILD
+++ b/submodules/LegacyMediaPickerUI/BUILD
@@ -20,6 +20,7 @@ swift_library(
"//submodules/AccountContext:AccountContext",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/LegacyUI:LegacyUI",
+ "//submodules/ContextUI:ContextUI",
"//submodules/MimeTypes:MimeTypes",
"//submodules/LocalMediaResources:LocalMediaResources",
"//submodules/SearchPeerMembers:SearchPeerMembers",
diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift
index c3e477107c..87c5001af8 100644
--- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift
+++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift
@@ -166,6 +166,7 @@ public func legacyMediaEditor(
snapshots: [UIView],
transitionCompletion: (() -> Void)?,
getCaptionPanelView: @escaping () -> TGCaptionPanelView?,
+ photoToolbarView: ((TGPhotoEditorBackButton, TGPhotoEditorDoneButton, Bool, Bool) -> (UIView & TGPhotoToolbarViewProtocol)?)? = nil,
hasSilentPosting: Bool = false,
hasSchedule: Bool = false,
reminder: Bool = false,
@@ -191,6 +192,7 @@ public func legacyMediaEditor(
paintStickersContext.captionPanelView = {
return getCaptionPanelView()
}
+ paintStickersContext.photoToolbarView = photoToolbarView
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let recipientName: String
@@ -208,15 +210,28 @@ public func legacyMediaEditor(
legacyController.blocksBackgroundWhenInOverlay = true
legacyController.acceptsFocusWhenInOverlay = true
legacyController.statusBar.statusBarStyle = .Ignore
+ paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context, presentationData: presentationData, presentInGlobalOverlay: { [weak legacyController] controller in
+ if let legacyController {
+ legacyController.presentInGlobalOverlay(controller)
+ } else if let mainWindow = context.sharedContext.mainWindow {
+ mainWindow.presentInGlobalOverlay(controller)
+ } else {
+ context.sharedContext.presentGlobalController(controller, nil)
+ }
+ })
legacyController.controllerLoaded = { [weak legacyController] in
legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true
}
-
+ legacyController.presentationCompleted = {
+ Queue.mainQueue().after(0.1) {
+ transitionCompletion?()
+ }
+ }
+
let schedulePicker: (Bool, @escaping (Int32, Bool) -> Void) -> Void = { media, done in
presentSchedulePicker(media, done)
}
let appeared: () -> Void = {
- transitionCompletion?()
}
let completion: (TGMediaEditableItem, TGMediaEditingContext, Bool, Int32) -> Void = { result, editingContext, silentPosting, scheduleTime in
let nativeGenerator = legacyAssetPickerItemGenerator()
@@ -235,64 +250,30 @@ public func legacyMediaEditor(
legacyController.enableSizeClassSignal = true
- if isGif {
- let galleryController = TGPhotoVideoEditor.controller(
- with: legacyController.context,
- caption: initialCaption,
- withItem: item,
- paint: mode == .draw,
- adjustments: mode == .adjustments,
- recipientName: recipientName,
- stickersContext: paintStickersContext,
- from: .zero,
- mainSnapshot: nil,
- snapshots: snapshots as [Any],
- immediate: transitionCompletion != nil,
- activateInput: mode == .caption,
- isGif: true,
- hasSilentPosting: hasSilentPosting,
- hasSchedule: hasSchedule,
- reminder: reminder,
- presentSchedulePicker: schedulePicker,
- appeared: appeared,
- completion: completion,
- dismissed: dismissed
- )
- legacyController.bind(controller: galleryController)
- present(legacyController, nil)
- } else {
- let emptyController = LegacyEmptyController(context: legacyController.context)!
- emptyController.navigationBarShouldBeHidden = true
- let navigationController = makeLegacyNavigationController(rootController: emptyController)
- navigationController.setNavigationBarHidden(true, animated: false)
- legacyController.bind(controller: navigationController)
-
- present(legacyController, nil)
-
- TGPhotoVideoEditor.present(
- with: legacyController.context,
- controller: emptyController,
- caption: initialCaption,
- withItem: item,
- paint: mode == .draw,
- adjustments: mode == .adjustments,
- recipientName: recipientName,
- stickersContext: paintStickersContext,
- from: .zero,
- mainSnapshot: nil,
- snapshots: snapshots as [Any],
- immediate: transitionCompletion != nil,
- activateInput: mode == .caption,
- isGif: false,
- hasSilentPosting: hasSilentPosting,
- hasSchedule: hasSchedule,
- reminder: reminder,
- presentSchedulePicker: schedulePicker,
- appeared: appeared,
- completion: completion,
- dismissed: dismissed
- )
- }
+ let galleryController = TGPhotoVideoEditor.controller(
+ with: legacyController.context,
+ caption: initialCaption,
+ withItem: item,
+ paint: mode == .draw,
+ adjustments: mode == .adjustments,
+ recipientName: recipientName,
+ stickersContext: paintStickersContext,
+ from: .zero,
+ mainSnapshot: nil,
+ snapshots: snapshots as [Any],
+ immediate: transitionCompletion != nil,
+ activateInput: mode == .caption,
+ isGif: isGif,
+ hasSilentPosting: hasSilentPosting,
+ hasSchedule: hasSchedule,
+ reminder: reminder,
+ presentSchedulePicker: schedulePicker,
+ appeared: appeared,
+ completion: completion,
+ dismissed: dismissed
+ )
+ legacyController.bind(controller: galleryController)
+ present(legacyController, nil)
})
}
@@ -389,6 +370,15 @@ public func legacyAttachmentMenu(
}
let paintStickersContext = LegacyPaintStickersContext(context: context)
+ paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context, presentationData: updatedPresentationData.initial, presentInGlobalOverlay: { [weak parentController] controller in
+ if let parentController {
+ parentController.presentInGlobalOverlay(controller)
+ } else if let mainWindow = context.sharedContext.mainWindow {
+ mainWindow.presentInGlobalOverlay(controller)
+ } else {
+ context.sharedContext.presentGlobalController(controller, nil)
+ }
+ })
paintStickersContext.captionPanelView = {
return getCaptionPanelView()
}
diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift
index b11536ead8..4d452670b9 100644
--- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift
+++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift
@@ -21,6 +21,7 @@ public func guessMimeTypeByFileExtension(_ ext: String) -> String {
public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: EngineRawPeer, chatLocation: ChatLocation, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: NSAttributedString, hasSchedule: Bool, presentWebSearch: (() -> Void)?, presentSelectionLimitExceeded: @escaping () -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32, Bool) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?) {
let paintStickersContext = LegacyPaintStickersContext(context: context)
+ paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context)
paintStickersContext.captionPanelView = {
return getCaptionPanelView()
}
diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift
index f10578a5ff..bdf89b9fc7 100644
--- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift
+++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift
@@ -13,6 +13,7 @@ import SolidRoundedButtonNode
import MediaEditor
import DrawingUI
import TelegramPresentationData
+import ContextUI
import AnimatedCountLabelNode
import CoreMedia
@@ -571,15 +572,118 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
}
}
+public typealias LegacyMediaPickerSendActionMenuPresenter = (
+ UIView,
+ Bool,
+ Bool,
+ Bool,
+ Bool,
+ Bool,
+ @escaping () -> Void,
+ @escaping () -> Void,
+ @escaping () -> Void,
+ @escaping () -> Void
+) -> Bool
+
+private final class LegacyMediaPickerSendActionMenuReferenceContentSource: ContextReferenceContentSource {
+ private weak var sourceView: UIView?
+
+ init(sourceView: UIView) {
+ self.sourceView = sourceView
+ }
+
+ func transitionInfo() -> ContextControllerReferenceViewInfo? {
+ guard let sourceView = self.sourceView else {
+ return nil
+ }
+ return ContextControllerReferenceViewInfo(referenceView: sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .top)
+ }
+}
+
+public func makeLegacyMediaPickerSendActionMenuPresenter(
+ context: AccountContext,
+ presentationData: PresentationData? = nil,
+ presentInGlobalOverlay: ((ViewController) -> Void)? = nil
+) -> LegacyMediaPickerSendActionMenuPresenter {
+ return { sourceView, canSendSilently, canSendWhenOnline, canSchedule, reminder, hasTimer, sendSilently, sendWhenOnline, schedule, sendWithTimer in
+ guard sourceView.window != nil else {
+ return false
+ }
+
+ let presentationData = (presentationData ?? context.sharedContext.currentPresentationData.with { $0 }).withUpdated(theme: defaultDarkPresentationTheme)
+ var items: [ContextMenuItem] = []
+
+ if canSendSilently {
+ items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.contextMenu.primaryColor)
+ }, action: { _, f in
+ f(.default)
+ sendSilently()
+ })))
+ }
+
+ if canSendWhenOnline {
+ items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendWhenOnline, icon: { theme in
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/WhenOnlineIcon"), color: theme.contextMenu.primaryColor)
+ }, action: { _, f in
+ f(.default)
+ sendWhenOnline()
+ })))
+ }
+
+ if canSchedule {
+ items.append(.action(ContextMenuActionItem(text: reminder ? presentationData.strings.Conversation_SendMessage_SetReminder : presentationData.strings.Conversation_SendMessage_ScheduleMessage, icon: { theme in
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/ScheduleIcon"), color: theme.contextMenu.primaryColor)
+ }, action: { _, f in
+ f(.default)
+ schedule()
+ })))
+ }
+
+ if hasTimer {
+ items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_Timer_Send, icon: { theme in
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Timer"), color: theme.contextMenu.primaryColor)
+ }, action: { _, f in
+ f(.default)
+ sendWithTimer()
+ })))
+ }
+
+ guard !items.isEmpty else {
+ return false
+ }
+
+ let contextController = makeContextController(
+ presentationData: presentationData,
+ source: .reference(LegacyMediaPickerSendActionMenuReferenceContentSource(sourceView: sourceView)),
+ items: .single(ContextController.Items(content: .list(items))),
+ gesture: nil
+ )
+ if let presentInGlobalOverlay {
+ presentInGlobalOverlay(contextController)
+ } else if let mainWindow = context.sharedContext.mainWindow {
+ mainWindow.presentInGlobalOverlay(contextController)
+ } else {
+ context.sharedContext.presentGlobalController(contextController, nil)
+ }
+ return true
+ }
+}
+
public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext {
public var captionPanelView: (() -> TGCaptionPanelView?)?
public var livePhotoButton: (() -> TGLivePhotoButton?)?
+ public var photoToolbarView: ((TGPhotoEditorBackButton, TGPhotoEditorDoneButton, Bool, Bool) -> (UIView & TGPhotoToolbarViewProtocol)?)?
+ public var presentMediaPickerSendActionMenu: LegacyMediaPickerSendActionMenuPresenter?
public var editCover: ((CGSize, @escaping (UIImage) -> Void) -> Void)?
-
+
private let context: AccountContext
-
+
public init(context: AccountContext) {
self.context = context
+ super.init()
+
+ self.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context)
}
class LegacyDrawingAdapter: NSObject, TGPhotoDrawingAdapter {
diff --git a/submodules/LegacyUI/BUILD b/submodules/LegacyUI/BUILD
index 958e606004..9fd41d27b1 100644
--- a/submodules/LegacyUI/BUILD
+++ b/submodules/LegacyUI/BUILD
@@ -14,6 +14,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
+ "//submodules/ContextUI:ContextUI",
"//submodules/Display:Display",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramAudio:TelegramAudio",
diff --git a/submodules/LegacyUI/Sources/LegacyController.swift b/submodules/LegacyUI/Sources/LegacyController.swift
index 0259dd86b6..7b276de96e 100644
--- a/submodules/LegacyUI/Sources/LegacyController.swift
+++ b/submodules/LegacyUI/Sources/LegacyController.swift
@@ -1,5 +1,6 @@
import Foundation
import UIKit
+import ContextUI
import Display
import SSignalKit
import SwiftSignalKit
@@ -28,6 +29,18 @@ private func passControllerAppearanceAnimated(in: Bool, presentation: LegacyCont
}
}
+private final class LegacyActionSheetContextReferenceContentSource: ContextReferenceContentSource {
+ private let sourceView: UIView
+
+ init(sourceView: UIView) {
+ self.sourceView = sourceView
+ }
+
+ func transitionInfo() -> ContextControllerReferenceViewInfo? {
+ return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .top)
+ }
+}
+
private final class LegacyComponentsOverlayWindowManagerImpl: NSObject, LegacyComponentsOverlayWindowManager {
private weak var contentController: UIViewController?
private weak var parentController: ViewController?
@@ -260,11 +273,80 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
}
public func presentActionSheet(_ actions: [LegacyComponentsActionSheetAction]!, view: UIView!, completion: ((LegacyComponentsActionSheetAction?) -> Void)!) {
-
+ self.presentActionSheet(actions, view: view, sourceRect: nil, completion: completion)
}
public func presentActionSheet(_ actions: [LegacyComponentsActionSheetAction]!, view: UIView!, sourceRect: (() -> CGRect)!, completion: ((LegacyComponentsActionSheetAction?) -> Void)!) {
+ guard let controller = self.controller, let view = view else {
+ completion?(nil)
+ return
+ }
+ let presentationData: PresentationData
+ if let context = legacyContextGet() {
+ presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
+ } else {
+ presentationData = defaultPresentationData().withUpdated(theme: defaultDarkColorPresentationTheme)
+ }
+
+ let anchorView: UIView?
+ let referenceView: UIView
+ if let sourceRect = sourceRect {
+ let anchor = UIView(frame: sourceRect())
+ anchor.isUserInteractionEnabled = false
+ anchor.backgroundColor = .clear
+ view.addSubview(anchor)
+ anchorView = anchor
+ referenceView = anchor
+ } else {
+ anchorView = nil
+ referenceView = view
+ }
+
+ var didSelectAction = false
+ var items: [ContextMenuItem] = []
+ for legacyAction in actions ?? [] {
+ if legacyAction.type == LegacyComponentsActionSheetActionTypeCancel {
+ continue
+ }
+ guard let title = legacyAction.title else {
+ continue
+ }
+ let textColor: ContextMenuActionItemTextColor = legacyAction.type == LegacyComponentsActionSheetActionTypeDestructive ? .destructive : .primary
+ items.append(.action(ContextMenuActionItem(text: title, textColor: textColor, icon: { _ in
+ return nil
+ }, action: { actionContext in
+ didSelectAction = true
+ if let contextController = actionContext.controller {
+ contextController.dismiss(result: .default, completion: {
+ completion?(legacyAction)
+ })
+ } else {
+ anchorView?.removeFromSuperview()
+ completion?(legacyAction)
+ }
+ })))
+ }
+
+ if items.isEmpty {
+ anchorView?.removeFromSuperview()
+ completion?(nil)
+ return
+ }
+
+ let contextController = makeContextController(
+ context: legacyContextGet(),
+ presentationData: presentationData,
+ source: .reference(LegacyActionSheetContextReferenceContentSource(sourceView: referenceView)),
+ items: .single(ContextController.Items(content: .list(items)))
+ )
+ contextController.dismissed = { [weak anchorView] in
+ anchorView?.removeFromSuperview()
+ if !didSelectAction {
+ completion?(nil)
+ }
+ }
+ controller.present(contextController, in: .window(.root))
}
public func presentTooltip(_ text: String!, icon: UIImage!, sourceRect: CGRect) {
diff --git a/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift b/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift
index c8d8e43912..0a5f677cf3 100644
--- a/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift
+++ b/submodules/LiveLocationTimerNode/Sources/ChatMessageLiveLocationTimerNode.swift
@@ -4,6 +4,10 @@ import AsyncDisplayKit
import Display
import TelegramPresentationData
+private let compactInfinityFont = Font.with(size: 14.0, design: .round, weight: .bold)
+private let compactTextFont = Font.with(size: 12.0, design: .round, weight: .bold)
+private let compactSmallTextFont = Font.with(size: 10.0, design: .round, weight: .bold)
+
private let infinityFont = Font.with(size: 15.0, design: .round, weight: .bold)
private let textFont = Font.with(size: 13.0, design: .round, weight: .bold)
private let smallTextFont = Font.with(size: 11.0, design: .round, weight: .bold)
@@ -134,11 +138,11 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
let font: UIFont
if parameters.string == "∞" {
- font = infinityFont
+ font = bounds.width < 28.0 ? compactInfinityFont : infinityFont
} else if parameters.string.count > 2 {
- font = smallTextFont
+ font = bounds.width < 28.0 ? compactSmallTextFont : smallTextFont
} else {
- font = textFont
+ font = bounds.width < 28.0 ? compactTextFont : textFont
}
let attributes: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: parameters.foregroundColor]
@@ -147,7 +151,7 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
var offset = CGPoint()
if parameters.string == "∞" {
- offset = CGPoint(x: 1.0, y: -1.0)
+ offset = bounds.width < 28.0 ? CGPoint(x: 1.0 - UIScreenPixel, y: 0.0) : CGPoint(x: 1.0, y: -1.0)
} else if parameters.string.count > 2 {
offset = CGPoint(x: 0.0, y: UIScreenPixel)
}
diff --git a/submodules/LocationUI/BUILD b/submodules/LocationUI/BUILD
index 321fd7dd3a..39d33fd6d0 100644
--- a/submodules/LocationUI/BUILD
+++ b/submodules/LocationUI/BUILD
@@ -16,6 +16,7 @@ swift_library(
"//submodules/TelegramCore:TelegramCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/LegacyComponents:LegacyComponents",
+ "//submodules/ContextUI:ContextUI",
"//submodules/ShareController:ShareController",
"//submodules/AccountContext:AccountContext",
"//submodules/OpenInExternalAppUI:OpenInExternalAppUI",
@@ -41,14 +42,20 @@ swift_library(
"//submodules/TelegramNotices:TelegramNotices",
"//submodules/TooltipUI:TooltipUI",
"//submodules/UndoUI:UndoUI",
+ "//submodules/Weather",
"//submodules/AttachmentUI:AttachmentUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/ComponentFlow",
"//submodules/Components/BundleIconComponent",
+ "//submodules/Components/SheetComponent",
+ "//submodules/Components/ViewControllerComponent",
+ "//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/GlassControls",
+ "//submodules/TelegramUI/Components/LottieComponent",
+ "//submodules/TelegramUI/Components/LottieComponentResourceContent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift
index ea37651e03..a4c5121a60 100644
--- a/submodules/LocationUI/Sources/LocationActionListItem.swift
+++ b/submodules/LocationUI/Sources/LocationActionListItem.swift
@@ -141,16 +141,18 @@ final class LocationActionListItem: ListViewItem {
let title: String
let subtitle: String
let icon: LocationActionListItemIcon
+ let isOpaque: Bool
let beginTimeAndTimeout: (Double, Double)?
let action: () -> Void
let highlighted: (Bool) -> Void
- public init(presentationData: ItemListPresentationData, engine: TelegramEngine, title: String, subtitle: String, icon: LocationActionListItemIcon, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) {
+ public init(presentationData: ItemListPresentationData, engine: TelegramEngine, title: String, subtitle: String, icon: LocationActionListItemIcon, isOpaque: Bool = true, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) {
self.presentationData = presentationData
self.engine = engine
self.title = title
self.subtitle = subtitle
self.icon = icon
+ self.isOpaque = isOpaque
self.beginTimeAndTimeout = beginTimeAndTimeout
self.action = action
self.highlighted = highlighted
@@ -216,6 +218,7 @@ final class LocationActionListItemNode: ListViewItemNode {
self.separatorNode.isLayerBacked = true
self.highlightedBackgroundNode = ASDisplayNode()
+ self.highlightedBackgroundNode.clipsToBounds = true
self.highlightedBackgroundNode.isLayerBacked = true
self.iconNode = ASImageNode()
@@ -232,6 +235,21 @@ final class LocationActionListItemNode: ListViewItemNode {
self.addSubnode(self.venueIconNode)
}
+ func liveLocationContextSourceView(extend: Bool) -> UIView? {
+ guard let icon = self.item?.icon else {
+ return nil
+ }
+
+ switch icon {
+ case .liveLocation:
+ return extend ? nil : self.view
+ case .extendLiveLocation:
+ return extend ? self.view : nil
+ default:
+ return nil
+ }
+ }
+
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
if let item = self.item {
let makeLayout = self.asyncLayout()
@@ -278,7 +296,7 @@ final class LocationActionListItemNode: ListViewItemNode {
let iconLayout = self.venueIconNode.asyncLayout()
return { [weak self] item, params, hasSeparator in
- let leftInset: CGFloat = 65.0 + params.leftInset
+ let leftInset: CGFloat = (item.isOpaque ? 65.0 : 72.0 ) + params.leftInset
let rightInset: CGFloat = params.rightInset
let verticalInset: CGFloat = 8.0
let iconSize: CGFloat = 40.0
@@ -292,7 +310,7 @@ final class LocationActionListItemNode: ListViewItemNode {
let subtitleAttributedString = NSAttributedString(string: item.subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
- let titleSpacing: CGFloat = 1.0
+ let titleSpacing: CGFloat = 0.0
let bottomInset: CGFloat = hasSeparator ? 0.0 : 4.0
var contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + bottomInset)
if hasSeparator {
@@ -300,6 +318,8 @@ final class LocationActionListItemNode: ListViewItemNode {
}
let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
+ var hasSeparator = hasSeparator
+
return (nodeLayout, { [weak self] in
var updatedTheme: PresentationTheme?
if currentItem?.presentationData.theme !== item.presentationData.theme {
@@ -315,11 +335,15 @@ final class LocationActionListItemNode: ListViewItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.layoutParams = params
-
+
if let _ = updatedTheme {
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
- strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
+ if item.isOpaque {
+ strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
+ } else {
+ strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.contextMenu.itemHighlightedBackgroundColor
+ }
}
var arguments: TransformImageCustomArguments?
@@ -394,15 +418,34 @@ final class LocationActionListItemNode: ListViewItemNode {
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))
+ let contentLeftInset: CGFloat = item.isOpaque ? 0.0 : 7.0
+
+ let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 15.0 + contentLeftInset, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0)), size: CGSize(width: iconSize, height: iconSize))
strongSelf.iconNode.frame = iconNodeFrame
strongSelf.venueIconNode.frame = iconNodeFrame
- strongSelf.wavesNode?.frame = CGRect(origin: CGPoint(x: params.leftInset + 11.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0) - 4.0), size: CGSize(width: 48.0, height: 48.0))
+ strongSelf.wavesNode?.frame = CGRect(origin: CGPoint(x: params.leftInset + 11.0 + contentLeftInset, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0) - 4.0), size: CGSize(width: 48.0, height: 48.0))
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))
+
+ let highlightFrame: CGRect
+ let highlightCornerRadius: CGFloat
+ if item.isOpaque {
+ highlightFrame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
+ highlightCornerRadius = 0.0
+ } else {
+ highlightFrame = CGRect(origin: CGPoint(x: 14.0, y: 2.0), size: CGSize(width: contentSize.width - 14.0 * 2.0, height: 52.0))
+ highlightCornerRadius = highlightFrame.height * 0.5
+ }
+
+ strongSelf.highlightedBackgroundNode.frame = highlightFrame
+ strongSelf.highlightedBackgroundNode.cornerRadius = highlightCornerRadius
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))
+
+ if !item.isOpaque {
+ hasSeparator = false
+ }
+ strongSelf.backgroundNode.isHidden = !item.isOpaque
strongSelf.separatorNode.isHidden = !hasSeparator
if let (beginTimestamp, timeout) = item.beginTimeAndTimeout {
@@ -414,9 +457,9 @@ final class LocationActionListItemNode: ListViewItemNode {
strongSelf.addSubnode(timerNode)
strongSelf.timerNode = timerNode
}
- let timerSize = CGSize(width: 28.0, height: 28.0)
+ let timerSize = CGSize(width: 24.0, height: 24.0)
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: beginTimestamp, timeout: Int32(timeout) == liveLocationIndefinitePeriod ? -1.0 : timeout, strings: item.presentationData.strings)
- timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize)
+ timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 15.0 - contentLeftInset - timerSize.width, y: floorToScreenPixels((contentSize.height - timerSize.height) / 2.0) - 2.0), size: timerSize)
} else if let timerNode = strongSelf.timerNode {
strongSelf.timerNode = nil
timerNode.removeFromSupernode()
diff --git a/submodules/LocationUI/Sources/LocationAnnotation.swift b/submodules/LocationUI/Sources/LocationAnnotation.swift
index a9957db257..207e31cbc6 100644
--- a/submodules/LocationUI/Sources/LocationAnnotation.swift
+++ b/submodules/LocationUI/Sources/LocationAnnotation.swift
@@ -177,6 +177,7 @@ public class LocationPinAnnotationView: MKAnnotationView {
var hasPulse = false
var headingKvoToken: NSKeyValueObservation?
+ private var mapHeading: CGFloat = 0.0
override public class var layerClass: AnyClass {
return LocationPinAnnotationLayer.self
@@ -291,12 +292,10 @@ public class LocationPinAnnotationView: MKAnnotationView {
headingKvoToken.invalidate()
}
- self.headingKvoToken = annotation.observe(\.heading, options: .new) { [weak self] (_, change) in
- guard let heading = change.newValue else {
- return
- }
- self?.updateHeading(heading)
+ self.headingKvoToken = annotation.observe(\.heading, options: .new) { [weak self] (_, _) in
+ self?.updateHeading()
}
+ self.updateHeading()
}
else if let peer = annotation.peer {
self.iconNode.isHidden = true
@@ -310,7 +309,7 @@ public class LocationPinAnnotationView: MKAnnotationView {
self.headingKvoToken = nil
headingKvoToken.invalidate()
}
- self.updateHeading(nil)
+ self.updateHeading()
} else if let location = annotation.location {
let venueType = location.venue?.type ?? ""
let color = venueType.isEmpty ? annotation.theme.list.itemAccentColor : venueIconColor(type: venueType)
@@ -347,16 +346,23 @@ public class LocationPinAnnotationView: MKAnnotationView {
self.headingKvoToken = nil
headingKvoToken.invalidate()
}
- self.updateHeading(nil)
+ self.updateHeading()
}
}
}
}
- private func updateHeading(_ heading: NSNumber?) {
- if let heading = heading?.int32Value {
+ func updateMapHeading(_ mapHeading: CGFloat) {
+ if self.mapHeading != mapHeading {
+ self.mapHeading = mapHeading
+ self.updateHeading()
+ }
+ }
+
+ private func updateHeading() {
+ if let heading = (self.annotation as? LocationPinAnnotation)?.heading?.doubleValue {
self.arrowNode.isHidden = false
- self.arrowNode.transform = CATransform3DMakeRotation(CGFloat(heading) / 180.0 * CGFloat.pi, 0.0, 0.0, 1.0)
+ self.arrowNode.transform = CATransform3DMakeRotation((CGFloat(heading) - self.mapHeading) / 180.0 * CGFloat.pi, 0.0, 0.0, 1.0)
} else {
self.arrowNode.isHidden = true
self.arrowNode.transform = CATransform3DIdentity
diff --git a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift
index 3547383093..8e56deae75 100644
--- a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift
+++ b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift
@@ -1,123 +1,84 @@
import Foundation
import UIKit
import Display
-import AsyncDisplayKit
import TelegramCore
import SwiftSignalKit
import AccountContext
-import SolidRoundedButtonNode
import TelegramPresentationData
import TelegramStringFormatting
import PresentationDataUtils
-import CoreLocation
+import ComponentFlow
+import ViewControllerComponent
+import SheetComponent
+import ButtonComponent
+import GlassBarButtonComponent
+import AnimatedTextComponent
+import BundleIconComponent
enum LocationDistancePickerScreenStyle {
case `default`
case media
}
-final class LocationDistancePickerScreen: ViewController {
- private var controllerNode: LocationDistancePickerScreenNode {
- return self.displayNode as! LocationDistancePickerScreenNode
- }
-
- private var animatedIn = false
-
- private let context: AccountContext
- private let style: LocationDistancePickerScreenStyle
- private let distances: Signal<[Double], NoError>
- private let compactDisplayTitle: String?
- private let updated: (Int32?) -> Void
- private let completion: (Int32, @escaping () -> Void) -> Void
- private let willDismiss: () -> Void
-
- private var presentationDataDisposable: Disposable?
+final class LocationDistancePickerScreen: ViewControllerComponentContainer {
+ private let willDismissImpl: () -> Void
+ private var didCallWillDismiss = false
init(context: AccountContext, style: LocationDistancePickerScreenStyle, compactDisplayTitle: String?, distances: Signal<[Double], NoError>, updated: @escaping (Int32?) -> Void, completion: @escaping (Int32, @escaping () -> Void) -> Void, willDismiss: @escaping () -> Void) {
- self.context = context
- self.style = style
- self.distances = distances
- self.compactDisplayTitle = compactDisplayTitle
- self.updated = updated
- self.completion = completion
- self.willDismiss = willDismiss
+ self.willDismissImpl = willDismiss
- super.init(navigationBarPresentationData: nil)
-
- self.statusBar.statusBarStyle = .Ignore
+ super.init(
+ context: context,
+ component: LocationDistancePickerScreenComponent(
+ context: context,
+ style: style,
+ compactDisplayTitle: compactDisplayTitle,
+ distances: distances,
+ updated: { distance in
+ updated(distance)
+ },
+ completion: completion,
+ willDismiss: willDismiss
+ ),
+ navigationBarAppearance: .none,
+ statusBarStyle: .ignore
+ )
self.blocksBackgroundWhenInOverlay = true
-
- self.presentationDataDisposable = (context.sharedContext.presentationData
- |> deliverOnMainQueue).start(next: { [weak self] presentationData in
- if let strongSelf = self {
- strongSelf.controllerNode.updatePresentationData(presentationData)
- }
- })
-
- self.statusBar.statusBarStyle = .Ignore
+ self.navigationPresentation = .flatModal
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
- deinit {
- self.presentationDataDisposable?.dispose()
- }
-
- override public func loadDisplayNode() {
- self.displayNode = LocationDistancePickerScreenNode(context: self.context, style: self.style, compactDisplayTitle: self.compactDisplayTitle, distances: self.distances)
- self.controllerNode.updated = { [weak self] distance in
- guard let strongSelf = self else {
- return
- }
- strongSelf.updated(distance)
- }
- self.controllerNode.completion = { [weak self] distance in
- guard let strongSelf = self else {
- return
- }
- strongSelf.completion(distance, {
- strongSelf.dismiss()
- })
- }
- self.controllerNode.dismiss = { [weak self] in
- self?.presentingViewController?.dismiss(animated: false, completion: nil)
- }
- self.controllerNode.cancel = { [weak self] in
- self?.dismiss()
- }
-
- let _ = self.controllerNode.update()
- }
-
- override public func loadView() {
- super.loadView()
- }
-
- override public func viewDidAppear(_ animated: Bool) {
- super.viewDidAppear(animated)
-
- if !self.animatedIn {
- self.animatedIn = true
- self.controllerNode.animateIn()
+ fileprivate func performWillDismissOnce() {
+ if self.didCallWillDismiss {
+ return
}
+ self.didCallWillDismiss = true
+ self.willDismissImpl()
}
override public func dismiss(completion: (() -> Void)? = nil) {
- self.willDismiss()
- self.controllerNode.animateOut(completion: completion)
+ if let componentView = self.node.hostView.componentView as? LocationDistancePickerScreenComponent.View {
+ componentView.requestDismiss(completion: completion)
+ } else {
+ self.performWillDismissOnce()
+ super.dismiss(animated: false, 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)
+ override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
+ if flag {
+ self.dismiss(completion: completion)
+ } else {
+ super.dismiss(animated: false, completion: completion)
+ }
}
}
-private class TimerPickerView: UIPickerView {
+private final class TimerPickerView: UIPickerView {
var selectorColor: UIColor? = nil {
didSet {
for subview in self.subviews {
@@ -151,7 +112,7 @@ private class TimerPickerView: UIPickerView {
}
}
-private var unitValues: [Int32] = {
+private let unitValues: [Int32] = {
var values: [Int32] = []
for i in 0 ..< 99 {
values.append(Int32(i))
@@ -159,7 +120,7 @@ private var unitValues: [Int32] = {
return values
}()
-private var smallUnitValues: [Int32] = {
+private let smallUnitValues: [Int32] = {
var values: [Int32] = []
values.append(0)
values.append(5)
@@ -169,213 +130,318 @@ private var smallUnitValues: [Int32] = {
return values
}()
-class LocationDistancePickerScreenNode: ViewControllerTracingNode, ASScrollViewDelegate, UIPickerViewDataSource, UIPickerViewDelegate {
- private let context: AccountContext
- private let controllerStyle: LocationDistancePickerScreenStyle
- private var presentationData: PresentationData
- private var compactDisplayTitle: String?
- private var distances: [Double] = []
+private final class LocationDistancePickerScreenComponent: Component {
+ typealias EnvironmentType = ViewControllerComponentContainer.Environment
- private let dimNode: ASDisplayNode
- private let wrappingScrollNode: ASScrollNode
- private let contentContainerNode: ASDisplayNode
- private let effectNode: ASDisplayNode
- private let backgroundNode: ASDisplayNode
- private let contentBackgroundNode: ASDisplayNode
- private let titleNode: ASTextNode
- private let textNode: ImmediateTextNode
- private let cancelButton: HighlightableButtonNode
- private let doneButton: SolidRoundedButtonNode
+ let context: AccountContext
+ let style: LocationDistancePickerScreenStyle
+ let compactDisplayTitle: String?
+ let distances: Signal<[Double], NoError>
+ let updated: (Int32) -> Void
+ let completion: (Int32, @escaping () -> Void) -> Void
+ let willDismiss: () -> Void
- private let measureButtonTitleNode: ImmediateTextNode
-
- private var pickerView: TimerPickerView?
- private let unitLabelNode: ImmediateTextNode
- private let smallUnitLabelNode: ImmediateTextNode
-
- private var pickerTimer: SwiftSignalKit.Timer?
-
- private var containerLayout: (ContainerViewLayout, CGFloat)?
-
- private var distancesDisposable: Disposable?
-
- var updated: ((Int32) -> Void)?
- var completion: ((Int32) -> Void)?
- var dismiss: (() -> Void)?
- var cancel: (() -> Void)?
-
- init(context: AccountContext, style: LocationDistancePickerScreenStyle, compactDisplayTitle: String?, distances: Signal<[Double], NoError>) {
+ init(
+ context: AccountContext,
+ style: LocationDistancePickerScreenStyle,
+ compactDisplayTitle: String?,
+ distances: Signal<[Double], NoError>,
+ updated: @escaping (Int32) -> Void,
+ completion: @escaping (Int32, @escaping () -> Void) -> Void,
+ willDismiss: @escaping () -> Void
+ ) {
self.context = context
- self.controllerStyle = style
- self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ self.style = style
self.compactDisplayTitle = compactDisplayTitle
-
- self.wrappingScrollNode = ASScrollNode()
- self.wrappingScrollNode.view.alwaysBounceVertical = true
- self.wrappingScrollNode.view.delaysContentTouches = false
- self.wrappingScrollNode.view.canCancelContentTouches = true
-
- self.dimNode = ASDisplayNode()
- self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
-
- self.contentContainerNode = ASDisplayNode()
- self.contentContainerNode.isOpaque = false
-
- self.backgroundNode = ASDisplayNode()
- self.backgroundNode.clipsToBounds = true
- self.backgroundNode.cornerRadius = 16.0
-
- let backgroundColor: UIColor
- let textColor: UIColor
- let accentColor: UIColor
- let blurStyle: UIBlurEffect.Style
- switch style {
- case .default:
- backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor
- textColor = self.presentationData.theme.actionSheet.primaryTextColor
- accentColor = self.presentationData.theme.actionSheet.controlAccentColor
- blurStyle = self.presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark
- case .media:
- backgroundColor = UIColor(rgb: 0x1c1c1e)
- textColor = .white
- accentColor = self.presentationData.theme.actionSheet.controlAccentColor
- blurStyle = .dark
- }
-
- self.effectNode = ASDisplayNode(viewBlock: {
- return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
- })
-
- self.contentBackgroundNode = ASDisplayNode()
- self.contentBackgroundNode.backgroundColor = backgroundColor
-
- self.titleNode = ASTextNode()
- self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Location_ProximityNotification_Title, font: Font.bold(17.0), textColor: textColor)
-
- self.textNode = ImmediateTextNode()
- self.textNode.alpha = 0.0
-
- 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)
- self.doneButton.title = self.presentationData.strings.Conversation_Timer_Send
-
- self.unitLabelNode = ImmediateTextNode()
- self.smallUnitLabelNode = ImmediateTextNode()
-
- self.measureButtonTitleNode = ImmediateTextNode()
-
- super.init()
-
- self.backgroundColor = nil
- self.isOpaque = false
-
- self.unitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceKM : self.presentationData.strings.Location_ProximityNotification_DistanceMI, font: Font.regular(15.0), textColor: textColor)
- self.smallUnitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceM : "", font: Font.regular(15.0), textColor: textColor)
-
- self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
- self.addSubnode(self.dimNode)
-
- self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate
- self.addSubnode(self.wrappingScrollNode)
-
- self.wrappingScrollNode.addSubnode(self.backgroundNode)
- self.wrappingScrollNode.addSubnode(self.contentContainerNode)
-
- self.backgroundNode.addSubnode(self.effectNode)
- self.backgroundNode.addSubnode(self.contentBackgroundNode)
- self.contentContainerNode.addSubnode(self.titleNode)
- self.contentContainerNode.addSubnode(self.textNode)
- self.contentContainerNode.addSubnode(self.cancelButton)
- self.contentContainerNode.addSubnode(self.doneButton)
-
- self.contentContainerNode.addSubnode(self.unitLabelNode)
- self.contentContainerNode.addSubnode(self.smallUnitLabelNode)
-
- self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
- self.doneButton.pressed = { [weak self] in
- if let strongSelf = self, let pickerView = strongSelf.pickerView {
- strongSelf.doneButton.isUserInteractionEnabled = false
-
- let largeValue = unitValues[pickerView.selectedRow(inComponent: 0)]
- let smallValue = smallUnitValues[pickerView.selectedRow(inComponent: 1)]
- var value = largeValue * 1000 + smallValue * 10
- if !strongSelf.usesMetricSystem {
- value = Int32(Double(value) * 1.60934)
- }
- strongSelf.completion?(value)
- }
- }
-
- self.setupPickerView()
-
- self.distancesDisposable = (distances
- |> deliverOnMainQueue).start(next: { [weak self] distances in
- if let strongSelf = self {
- strongSelf.distances = distances
- strongSelf.updateDoneButtonTitle()
- }
- })
+ self.distances = distances
+ self.updated = updated
+ self.completion = completion
+ self.willDismiss = willDismiss
}
- deinit {
- self.distancesDisposable?.dispose()
-
- self.pickerTimer?.invalidate()
- }
-
- func setupPickerView() {
- if let pickerView = self.pickerView {
- pickerView.removeFromSuperview()
- }
-
- let pickerView = TimerPickerView()
- pickerView.selectorColor = UIColor(rgb: 0xffffff, alpha: 0.18)
- pickerView.dataSource = self
- pickerView.delegate = self
- pickerView.selectRow(0, inComponent: 0, animated: false)
-
- if self.usesMetricSystem {
- pickerView.selectRow(6, inComponent: 1, animated: false)
- } else {
- pickerView.selectRow(4, inComponent: 1, animated: false)
- }
- self.contentContainerNode.view.addSubview(pickerView)
- self.pickerView = pickerView
-
- self.contentContainerNode.addSubnode(self.unitLabelNode)
- self.contentContainerNode.addSubnode(self.smallUnitLabelNode)
-
- self.pickerTimer?.invalidate()
-
- let pickerTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: true, completion: { [weak self] in
- if let strongSelf = self {
- if strongSelf.update() {
- strongSelf.updateDoneButtonTitle()
- }
- }
- }, queue: Queue.mainQueue())
- self.pickerTimer = pickerTimer
- pickerTimer.start()
-
- self.updateDoneButtonTitle()
- }
-
- private var usesMetricSystem: Bool {
- let locale = localeWithStrings(self.presentationData.strings)
- if locale.identifier.hasSuffix("GB") {
+ static func ==(lhs: LocationDistancePickerScreenComponent, rhs: LocationDistancePickerScreenComponent) -> Bool {
+ if lhs.context !== rhs.context {
return false
}
- return locale.usesMetricSystem
+ if lhs.style != rhs.style {
+ return false
+ }
+ if lhs.compactDisplayTitle != rhs.compactDisplayTitle {
+ return false
+ }
+ return true
}
- func numberOfComponents(in pickerView: UIPickerView) -> Int {
- return 2
+ final class View: UIView {
+ private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>()
+ private let sheetAnimateOut = ActionSlot>()
+
+ private var component: LocationDistancePickerScreenComponent?
+ private var environment: ViewControllerComponentContainer.Environment?
+ private var isDismissed = false
+ private var didCallWillDismiss = false
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func performWillDismissOnce() {
+ if self.didCallWillDismiss {
+ return
+ }
+ self.didCallWillDismiss = true
+
+ if let controller = self.environment?.controller() as? LocationDistancePickerScreen {
+ controller.performWillDismissOnce()
+ } else {
+ self.component?.willDismiss()
+ }
+ }
+
+ func requestDismiss(completion: (() -> Void)? = nil) {
+ self.performWillDismissOnce()
+
+ if self.isDismissed {
+ completion?()
+ return
+ }
+ self.isDismissed = true
+
+ self.sheetAnimateOut.invoke(Action { [weak self] _ in
+ guard let self else {
+ completion?()
+ return
+ }
+ if let controller = self.environment?.controller() {
+ controller.dismiss(animated: false, completion: completion)
+ } else {
+ completion?()
+ }
+ })
+ }
+
+ func update(component: LocationDistancePickerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ self.component = component
+
+ let environment = environment[EnvironmentType.self].value
+ self.environment = environment
+
+ let sheetEnvironment = SheetComponentEnvironment(
+ metrics: environment.metrics,
+ deviceMetrics: environment.deviceMetrics,
+ isDisplaying: environment.isVisible,
+ isCentered: environment.metrics.widthClass == .regular,
+ hasInputHeight: !environment.inputHeight.isZero,
+ regularMetricsSize: CGSize(width: 430.0, height: 900.0),
+ dismiss: { [weak self] _ in
+ self?.requestDismiss()
+ }
+ )
+
+ let backgroundColor: SheetComponent.BackgroundColor
+ switch component.style {
+ case .default:
+ backgroundColor = .color(environment.theme.list.modalPlainBackgroundColor)
+ case .media:
+ backgroundColor = .color(UIColor(rgb: 0x1c1c1e))
+ }
+
+ let _ = self.sheet.update(
+ transition: transition,
+ component: AnyComponent(
+ SheetComponent(
+ content: AnyComponent(
+ LocationDistancePickerContentComponent(
+ style: component.style,
+ compactDisplayTitle: component.compactDisplayTitle,
+ distances: component.distances,
+ updated: component.updated,
+ completion: { [weak self] distance in
+ guard let self, let component = self.component else {
+ return
+ }
+ component.completion(distance, { [weak self] in
+ self?.requestDismiss()
+ })
+ },
+ dismiss: { [weak self] in
+ self?.requestDismiss()
+ }
+ )
+ ),
+ style: .glass,
+ backgroundColor: backgroundColor,
+ hasDimView: false,
+ animateOut: self.sheetAnimateOut,
+ willDismiss: { [weak self] in
+ self?.performWillDismissOnce()
+ }
+ )
+ ),
+ 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
+ }
}
- private func updateDoneButtonTitle() {
- if let pickerView = self.pickerView {
+ 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 LocationDistancePickerContentComponent: Component {
+ typealias EnvironmentType = ViewControllerComponentContainer.Environment
+
+ let style: LocationDistancePickerScreenStyle
+ let compactDisplayTitle: String?
+ let distances: Signal<[Double], NoError>
+ let updated: (Int32) -> Void
+ let completion: (Int32) -> Void
+ let dismiss: () -> Void
+
+ init(
+ style: LocationDistancePickerScreenStyle,
+ compactDisplayTitle: String?,
+ distances: Signal<[Double], NoError>,
+ updated: @escaping (Int32) -> Void,
+ completion: @escaping (Int32) -> Void,
+ dismiss: @escaping () -> Void
+ ) {
+ self.style = style
+ self.compactDisplayTitle = compactDisplayTitle
+ self.distances = distances
+ self.updated = updated
+ self.completion = completion
+ self.dismiss = dismiss
+ }
+
+ static func ==(lhs: LocationDistancePickerContentComponent, rhs: LocationDistancePickerContentComponent) -> Bool {
+ if lhs.style != rhs.style {
+ return false
+ }
+ if lhs.compactDisplayTitle != rhs.compactDisplayTitle {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView, UIPickerViewDataSource, UIPickerViewDelegate {
+ private let closeButton = ComponentView()
+ private let title = ComponentView()
+ private let unitLabel = ComponentView()
+ private let smallUnitLabel = ComponentView()
+ private let button = ComponentView()
+ private let warningText = ComponentView()
+
+ private var pickerView: TimerPickerView?
+ private var pickerTimer: SwiftSignalKit.Timer?
+ private var distancesDisposable: Disposable?
+
+ private var component: LocationDistancePickerContentComponent?
+ private weak var state: EmptyComponentState?
+ private var environment: EnvironmentType?
+ private var distances: [Double] = []
+ private var previousReportedValue: Int32?
+ private var isCompleting = false
+
+ private var isUpdating = false
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ deinit {
+ self.distancesDisposable?.dispose()
+ self.pickerTimer?.invalidate()
+ }
+
+ private var usesMetricSystem: Bool {
+ guard let environment = self.environment else {
+ return true
+ }
+ let locale = localeWithStrings(environment.strings)
+ if locale.identifier.hasSuffix("GB") {
+ return false
+ }
+ return locale.usesMetricSystem
+ }
+
+ private func setupDistancesIfNeeded(component: LocationDistancePickerContentComponent) {
+ if self.distancesDisposable != nil {
+ return
+ }
+ self.distancesDisposable = (component.distances
+ |> deliverOnMainQueue).start(next: { [weak self] distances in
+ guard let self else {
+ return
+ }
+ self.distances = distances
+ if !self.isUpdating {
+ self.state?.updated(transition: .immediate)
+ }
+ })
+ }
+
+ private func setupPickerViewIfNeeded() {
+ if self.pickerView != nil {
+ return
+ }
+
+ let pickerView = TimerPickerView()
+ pickerView.selectorColor = UIColor(rgb: 0xffffff, alpha: 0.18)
+ pickerView.dataSource = self
+ pickerView.delegate = self
+ pickerView.selectRow(0, inComponent: 0, animated: false)
+ if self.usesMetricSystem {
+ pickerView.selectRow(6, inComponent: 1, animated: false)
+ } else {
+ pickerView.selectRow(4, inComponent: 1, animated: false)
+ }
+ self.addSubview(pickerView)
+ self.pickerView = pickerView
+
+ let pickerTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: true, completion: { [weak self] in
+ guard let self else {
+ return
+ }
+ if self.reportSelectedValue() {
+ self.state?.updated(transition: .immediate)
+ }
+ }, queue: Queue.mainQueue())
+ self.pickerTimer = pickerTimer
+ pickerTimer.start()
+
+ let _ = self.reportSelectedValue()
+ }
+
+ private func selectedDistance() -> (value: Int32, convertedValue: Int32, convertedDistance: Double, distanceText: String)? {
+ guard let pickerView = self.pickerView, let environment = self.environment else {
+ return nil
+ }
+
let selectedLargeRow = pickerView.selectedRow(inComponent: 0)
var selectedSmallRow = pickerView.selectedRow(inComponent: 1)
if selectedLargeRow == 0 && selectedSmallRow == 0 {
@@ -390,275 +456,309 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, ASScrollViewD
if smallValue == 5 {
formattedValue = formattedValue.replacingOccurrences(of: ".1", with: ".05").replacingOccurrences(of: ",1", with: ",05")
}
- let distance = self.usesMetricSystem ? "\(formattedValue) \(self.presentationData.strings.Location_ProximityNotification_DistanceKM)" : "\(formattedValue) \(self.presentationData.strings.Location_ProximityNotification_DistanceMI)"
+ let distanceText = self.usesMetricSystem ? "\(formattedValue) \(environment.strings.Location_ProximityNotification_DistanceKM)" : "\(formattedValue) \(environment.strings.Location_ProximityNotification_DistanceMI)"
- let shortTitle = self.presentationData.strings.Location_ProximityNotification_Notify(distance).string
- var longTitle: String?
- if let displayTitle = self.compactDisplayTitle, let (layout, _) = self.containerLayout {
- let title = self.presentationData.strings.Location_ProximityNotification_NotifyLong(displayTitle, distance).string
- let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
-
- self.measureButtonTitleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: .black)
- let titleSize = self.measureButtonTitleNode.updateLayout(CGSize(width: width * 2.0, height: 50.0))
- if titleSize.width < width - 70.0 {
- longTitle = title
+ var convertedDistance = Double(value)
+ if !self.usesMetricSystem {
+ convertedDistance = convertedDistance * 1.60934
+ }
+
+ return (value, Int32(convertedDistance), convertedDistance, distanceText)
+ }
+
+ private func reportSelectedValue() -> Bool {
+ guard let selectedDistance = self.selectedDistance(), let component = self.component else {
+ return false
+ }
+ if let previousReportedValue = self.previousReportedValue, selectedDistance.convertedValue == previousReportedValue {
+ return false
+ }
+ self.previousReportedValue = selectedDistance.convertedValue
+ component.updated(selectedDistance.convertedValue)
+ return true
+ }
+
+ func update(component: LocationDistancePickerContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ self.isUpdating = true
+ defer {
+ self.isUpdating = false
+ }
+
+ let previousEnvironment = self.environment
+ self.component = component
+ self.state = state
+
+ let environment = environment[EnvironmentType.self].value
+ self.environment = environment
+
+ self.setupDistancesIfNeeded(component: component)
+ self.setupPickerViewIfNeeded()
+
+ if let previousEnvironment, previousEnvironment.strings !== environment.strings || previousEnvironment.theme !== environment.theme {
+ self.pickerView?.reloadAllComponents()
+ }
+
+ let textColor: UIColor
+ let secondaryTextColor: UIColor
+ let buttonFillColor: UIColor
+ let buttonForegroundColor: UIColor
+ switch component.style {
+ case .default:
+ textColor = environment.theme.actionSheet.primaryTextColor
+ secondaryTextColor = environment.theme.actionSheet.secondaryTextColor
+ buttonFillColor = environment.theme.list.itemCheckColors.fillColor
+ buttonForegroundColor = environment.theme.list.itemCheckColors.foregroundColor
+ case .media:
+ textColor = .white
+ secondaryTextColor = UIColor(white: 1.0, alpha: 0.7)
+ buttonFillColor = environment.theme.list.itemCheckColors.fillColor
+ buttonForegroundColor = environment.theme.list.itemCheckColors.foregroundColor
+ }
+
+ let sideInset: CGFloat = 16.0
+ let topInset: CGFloat = 16.0
+ let titleHeight: CGFloat = 54.0
+ let pickerHeight: CGFloat = 216.0
+ let buttonHeight: CGFloat = 52.0
+ let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: buttonHeight, sideInset: 30.0)
+ let buttonWidth = availableSize.width - buttonInsets.left - buttonInsets.right
+
+ let selectedDistance = self.selectedDistance()
+ let distanceText = selectedDistance?.distanceText ?? ""
+ let isTooFar: Bool
+ if let selectedDistance, let maximumDistance = self.distances.last {
+ isTooFar = selectedDistance.convertedDistance > maximumDistance
+ } else {
+ isTooFar = false
+ }
+
+ let closeButtonSize = CGSize(width: 44.0, height: 44.0)
+ let closeSize = self.closeButton.update(
+ transition: transition,
+ component: AnyComponent(
+ GlassBarButtonComponent(
+ size: closeButtonSize,
+ backgroundColor: nil,
+ isDark: component.style == .media ? true : environment.theme.overallDarkAppearance,
+ state: .glass,
+ component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
+ BundleIconComponent(
+ name: "Navigation/Close",
+ tintColor: environment.theme.chat.inputPanel.panelControlColor
+ )
+ )),
+ action: { [weak self] _ in
+ self?.component?.dismiss()
+ }
+ )
+ ),
+ environment: {},
+ containerSize: closeButtonSize
+ )
+ if let closeButtonView = self.closeButton.view {
+ if closeButtonView.superview == nil {
+ self.addSubview(closeButtonView)
+ }
+ transition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: sideInset, y: topInset), size: closeSize))
+ }
+
+ let titleSize = self.title.update(
+ transition: transition,
+ component: AnyComponent(Text(
+ text: environment.strings.Location_ProximityNotification_Title,
+ font: Font.bold(17.0),
+ color: textColor
+ )),
+ environment: {},
+ containerSize: CGSize(width: availableSize.width - 120.0, height: titleHeight)
+ )
+ if let titleView = self.title.view {
+ if titleView.superview == nil {
+ self.addSubview(titleView)
+ }
+ transition.setFrame(view: titleView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) * 0.5), y: topInset + floorToScreenPixels((closeButtonSize.height - titleSize.height) * 0.5)), size: titleSize))
+ }
+
+ let pickerFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + titleHeight), size: CGSize(width: availableSize.width, height: pickerHeight))
+ if let pickerView = self.pickerView {
+ transition.setFrame(view: pickerView, frame: pickerFrame)
+ }
+
+ let unitLabelSize = self.unitLabel.update(
+ transition: transition,
+ component: AnyComponent(Text(
+ text: self.usesMetricSystem ? environment.strings.Location_ProximityNotification_DistanceKM : environment.strings.Location_ProximityNotification_DistanceMI,
+ font: Font.regular(15.0),
+ color: textColor
+ )),
+ environment: {},
+ containerSize: CGSize(width: availableSize.width, height: titleHeight)
+ )
+ if let unitLabelView = self.unitLabel.view {
+ if unitLabelView.superview == nil {
+ self.addSubview(unitLabelView)
+ }
+ transition.setFrame(view: unitLabelView, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0) + 50.0, y: floor(pickerFrame.midY - unitLabelSize.height / 2.0)), size: unitLabelSize))
+ }
+
+ let smallUnitLabelSize = self.smallUnitLabel.update(
+ transition: transition,
+ component: AnyComponent(Text(
+ text: self.usesMetricSystem ? environment.strings.Location_ProximityNotification_DistanceM : "",
+ font: Font.regular(15.0),
+ color: textColor
+ )),
+ environment: {},
+ containerSize: CGSize(width: availableSize.width, height: titleHeight)
+ )
+ if let smallUnitLabelView = self.smallUnitLabel.view {
+ if smallUnitLabelView.superview == nil {
+ self.addSubview(smallUnitLabelView)
+ }
+ transition.setFrame(view: smallUnitLabelView, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0 * 3.0) + 50.0, y: floor(pickerFrame.midY - smallUnitLabelSize.height / 2.0)), size: smallUnitLabelSize))
+ }
+
+ let bottomY = pickerFrame.maxY + 17.0
+ var buttonTitle = environment.strings.Location_ProximityNotification_Notify(distanceText).string
+ if let displayTitle = component.compactDisplayTitle {
+ let longTitle = environment.strings.Location_ProximityNotification_NotifyLong(displayTitle, distanceText).string
+ let titleSize = NSAttributedString(string: longTitle, font: Font.semibold(17.0), textColor: .black).boundingRect(with: CGSize(width: availableSize.width * 2.0, height: 50.0), options: .usesLineFragmentOrigin, context: nil).size
+ if titleSize.width < availableSize.width - 70.0 {
+ buttonTitle = longTitle
}
}
- self.doneButton.title = longTitle ?? shortTitle
-
- self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Location_ProximityNotification_AlreadyClose(distance).string, font: Font.regular(14.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor)
- if let (layout, navigationBarHeight) = self.containerLayout {
- self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
+
+ var buttonTransition = transition
+ if transition.animation.isImmediate {
+ buttonTransition = buttonTransition.withAnimation(.curve(duration: 0.2, curve: .easeInOut))
+ }
+ let buttonSize = self.button.update(
+ transition: buttonTransition,
+ component: AnyComponent(ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: buttonFillColor,
+ foreground: buttonForegroundColor,
+ pressedColor: buttonFillColor.withMultipliedAlpha(0.8)
+ ),
+ content: AnyComponentWithIdentity(id: AnyHashable("title"), component: AnyComponent(
+ AnimatedTextComponent(
+ font: Font.semibold(17.0),
+ color: buttonForegroundColor,
+ items: [
+ AnimatedTextComponent.Item(id: AnyHashable("title"), content: .text(buttonTitle))
+ ],
+ noDelay: true
+ )
+ )),
+ isEnabled: !isTooFar && !self.isCompleting,
+ tintWhenDisabled: false,
+ displaysProgress: false,
+ action: { [weak self] in
+ guard let self, let component = self.component, let selectedDistance = self.selectedDistance() else {
+ return
+ }
+ self.isCompleting = true
+ self.state?.updated(transition: .immediate)
+ component.completion(selectedDistance.convertedValue)
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: buttonWidth, height: buttonHeight)
+ )
+ if let buttonView = self.button.view {
+ if buttonView.superview == nil {
+ self.addSubview(buttonView)
+ }
+ transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonInsets.left, y: bottomY), size: buttonSize))
+ buttonTransition.setAlpha(view: buttonView, alpha: isTooFar ? 0.0 : 1.0)
}
- var convertedValue = Double(value)
- if !self.usesMetricSystem {
- convertedValue = Double(convertedValue) * 1.60934
+ let warningSize = self.warningText.update(
+ transition: transition,
+ component: AnyComponent(Text(
+ text: environment.strings.Location_ProximityNotification_AlreadyClose(distanceText).string,
+ font: Font.regular(14.0),
+ color: secondaryTextColor
+ )),
+ environment: {},
+ containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight)
+ )
+ if let warningTextView = self.warningText.view {
+ if warningTextView.superview == nil {
+ self.addSubview(warningTextView)
+ }
+ transition.setFrame(view: warningTextView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - warningSize.width) * 0.5), y: bottomY + floorToScreenPixels((buttonHeight - warningSize.height) * 0.5)), size: warningSize))
+ buttonTransition.setAlpha(view: warningTextView, alpha: isTooFar ? 1.0 : 0.0)
}
- if let distance = self.distances.last, convertedValue > distance {
- self.doneButton.alpha = 0.0
- self.doneButton.isUserInteractionEnabled = false
- self.textNode.alpha = 1.0
+ return CGSize(width: availableSize.width, height: bottomY + buttonHeight + buttonInsets.bottom)
+ }
+
+ func numberOfComponents(in pickerView: UIPickerView) -> Int {
+ return 2
+ }
+
+ func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+ if component == 0 {
+ return unitValues.count
+ } else if component == 1 {
+ return smallUnitValues.count
} else {
- self.doneButton.alpha = 1.0
- self.doneButton.isUserInteractionEnabled = true
- self.textNode.alpha = 0.0
+ return 1
}
}
- }
-
- var previousReportedValue: Int32?
- fileprivate func update() -> Bool {
- if let pickerView = self.pickerView {
- let selectedLargeRow = pickerView.selectedRow(inComponent: 0)
- var selectedSmallRow = pickerView.selectedRow(inComponent: 1)
- if selectedLargeRow == 0 && selectedSmallRow == 0 {
- selectedSmallRow = 1
+
+ func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
+ guard let environment = self.environment else {
+ return nil
}
- let largeValue = unitValues[selectedLargeRow]
- let smallValue = smallUnitValues[selectedSmallRow]
-
- var value = largeValue * 1000 + smallValue * 10
- if !self.usesMetricSystem {
- value = Int32(Double(value) * 1.60934)
- }
-
- if let previousReportedValue = self.previousReportedValue, value == previousReportedValue {
- return false
- } else {
- self.updated?(value)
- self.previousReportedValue = value
- return true
- }
- } else {
- return false
- }
- }
-
- func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
- if pickerView.selectedRow(inComponent: 0) == 0 && pickerView.selectedRow(inComponent: 1) == 0 {
- pickerView.selectRow(1, inComponent: 1, animated: true)
- }
- self.updateDoneButtonTitle()
- let _ = self.update()
- }
-
- func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
- if component == 0 {
- return unitValues.count
- } else if component == 1 {
- return smallUnitValues.count
- } else {
- return 1
- }
- }
-
- func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
- let font = Font.regular(17.0)
- let string: String
- if component == 0 {
- let value = unitValues[row]
- string = "\(value)"
- } else {
- if self.usesMetricSystem {
- let value = String(format: "%d", smallUnitValues[row] * 10)
+ let font = Font.regular(17.0)
+ let string: String
+ if component == 0 {
+ let value = unitValues[row]
string = "\(value)"
} else {
- let value = smallUnitValues[row]
- if value == 0 {
- string = ".0"
- } else if value == 5 {
- string = ".05"
+ if self.usesMetricSystem {
+ let value = String(format: "%d", smallUnitValues[row] * 10)
+ string = "\(value)"
} else {
- string = ".\(value / 10)"
+ let value = smallUnitValues[row]
+ if value == 0 {
+ string = ".0"
+ } else if value == 5 {
+ string = ".05"
+ } else {
+ string = ".\(value / 10)"
+ }
}
}
- }
- return NSAttributedString(string: string, font: font, textColor: self.presentationData.theme.actionSheet.primaryTextColor)
- }
-
- func updatePresentationData(_ presentationData: PresentationData) {
- let previousTheme = self.presentationData.theme
- self.presentationData = presentationData
-
- guard case .default = self.controllerStyle else {
- return
- }
-
- if let effectView = self.effectNode.view as? UIVisualEffectView {
- effectView.effect = UIBlurEffect(style: presentationData.theme.actionSheet.backgroundType == .light ? .light : .dark)
- }
-
- self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor
- self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
-
- if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout {
- self.setupPickerView()
- self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
- }
-
- self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
- self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme))
-
- self.updateDoneButtonTitle()
-
- self.unitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceKM : self.presentationData.strings.Location_ProximityNotification_DistanceMI, font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
- self.smallUnitLabelNode.attributedText = NSAttributedString(string: self.usesMetricSystem ? self.presentationData.strings.Location_ProximityNotification_DistanceM : "", font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
- }
-
- override func didLoad() {
- super.didLoad()
-
- if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
- self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
- }
- }
-
- @objc func cancelButtonPressed() {
- self.cancel?()
- }
-
- @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
- if case .ended = recognizer.state {
- self.cancelButtonPressed()
- }
- }
-
- func animateIn() {
- self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
-
- let offset = self.contentContainerNode.frame.height
- let position = self.wrappingScrollNode.position
- let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
- self.wrappingScrollNode.position = CGPoint(x: position.x, y: position.y + offset)
- transition.animateView({
- self.wrappingScrollNode.position = position
- })
- }
-
- func animateOut(completion: (() -> Void)? = nil) {
- var dimCompleted = false
- var offsetCompleted = false
-
- let internalCompletion: () -> Void = { [weak self] in
- if let strongSelf = self, dimCompleted && offsetCompleted {
- strongSelf.dismiss?()
+
+ let textColor: UIColor
+ switch self.component?.style {
+ case .media:
+ textColor = .white
+ default:
+ textColor = environment.theme.actionSheet.primaryTextColor
}
- completion?()
+ return NSAttributedString(string: string, font: font, textColor: textColor)
}
- self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
- dimCompleted = true
- internalCompletion()
- })
-
- let offset = self.contentContainerNode.frame.height
- self.wrappingScrollNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { _ in
- offsetCompleted = true
- internalCompletion()
- })
- }
-
- override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
- if self.bounds.contains(point) {
- if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) {
- return self.dimNode.view
+ func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
+ if pickerView.selectedRow(inComponent: 0) == 0 && pickerView.selectedRow(inComponent: 1) == 0 {
+ pickerView.selectRow(1, inComponent: 1, animated: true)
}
- }
- return super.hitTest(point, with: event)
- }
-
- func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
- let contentOffset = scrollView.contentOffset
- let additionalTopHeight = max(0.0, -contentOffset.y)
-
- if additionalTopHeight >= 30.0 {
- self.cancelButtonPressed()
+ let _ = self.reportSelectedValue()
+ self.state?.updated(transition: .immediate)
}
}
- func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
- let hadValidLayout = self.containerLayout != nil
- self.containerLayout = (layout, navigationBarHeight)
-
- var insets = layout.insets(options: [.statusBar, .input])
- let cleanInsets = layout.insets(options: [.statusBar])
- insets.top = max(10.0, insets.top)
-
- let buttonOffset: CGFloat = 0.0
- let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
- let titleHeight: CGFloat = 54.0
- var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
- let pickerHeight: CGFloat = min(216.0, layout.size.height - contentHeight)
- contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + pickerHeight + buttonOffset
-
- let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
-
- let sideInset = floor((layout.size.width - width) / 2.0)
- let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
- let contentFrame = contentContainerFrame
-
- var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0))
- if backgroundFrame.minY < contentFrame.minY {
- backgroundFrame.origin.y = contentFrame.minY
- }
- transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
- transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
- transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
- transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
- transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: insets.top + 66.0 + UIScreenPixel)))
-
- let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight))
- let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize)
- transition.updateFrame(node: self.titleNode, frame: titleFrame)
-
- let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight))
- let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize)
- transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
-
- let buttonInset: CGFloat = 16.0
- let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
- let doneButtonFrame = CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width, height: doneButtonHeight)
- transition.updateFrame(node: self.doneButton, frame: doneButtonFrame)
-
- let textSize = self.textNode.updateLayout(CGSize(width: width, height: titleHeight))
- transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor(doneButtonFrame.center.y - textSize.height / 2.0)), size: textSize))
-
- let pickerFrame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight))
- self.pickerView?.frame = pickerFrame
-
- let unitLabelSize = self.unitLabelNode.updateLayout(CGSize(width: width, height: titleHeight))
- transition.updateFrame(node: self.unitLabelNode, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0) + 50.0, y: floor(pickerFrame.center.y - unitLabelSize.height / 2.0)), size: unitLabelSize))
-
- let smallUnitLabelSize = self.smallUnitLabelNode.updateLayout(CGSize(width: width, height: titleHeight))
- transition.updateFrame(node: self.smallUnitLabelNode, frame: CGRect(origin: CGPoint(x: floor(pickerFrame.width / 4.0 * 3.0) + 50.0, y: floor(pickerFrame.center.y - smallUnitLabelSize.height / 2.0)), size: smallUnitLabelSize))
-
- transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
-
- if !hadValidLayout {
- self.updateDoneButtonTitle()
- }
+ 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)
}
}
diff --git a/submodules/LocationUI/Sources/LocationInfoListItem.swift b/submodules/LocationUI/Sources/LocationInfoListItem.swift
index 032f1f621c..d87f8c99a1 100644
--- a/submodules/LocationUI/Sources/LocationInfoListItem.swift
+++ b/submodules/LocationUI/Sources/LocationInfoListItem.swift
@@ -7,9 +7,10 @@ import TelegramCore
import TelegramPresentationData
import ItemListUI
import LocationResources
-import AppBundle
-import SolidRoundedButtonNode
import ShimmerEffect
+import ComponentFlow
+import ButtonComponent
+import BundleIconComponent
public final class LocationInfoListItem: ListViewItem {
let presentationData: ItemListPresentationData
@@ -18,27 +19,35 @@ public final class LocationInfoListItem: ListViewItem {
let address: String?
let distance: String?
let drivingTime: ExpectedTravelTime
- let transitTime: ExpectedTravelTime
let walkingTime: ExpectedTravelTime
let hasEta: Bool
let action: () -> Void
let drivingAction: () -> Void
- let transitAction: () -> Void
let walkingAction: () -> Void
- public init(presentationData: ItemListPresentationData, engine: TelegramEngine, location: TelegramMediaMap, address: String?, distance: String?, drivingTime: ExpectedTravelTime, transitTime: ExpectedTravelTime, walkingTime: ExpectedTravelTime, hasEta: Bool, action: @escaping () -> Void, drivingAction: @escaping () -> Void, transitAction: @escaping () -> Void, walkingAction: @escaping () -> Void) {
+ public init(
+ presentationData: ItemListPresentationData,
+ engine: TelegramEngine,
+ location: TelegramMediaMap,
+ address: String?,
+ distance: String?,
+ drivingTime: ExpectedTravelTime,
+ walkingTime: ExpectedTravelTime,
+ hasEta: Bool,
+ action: @escaping () -> Void,
+ drivingAction: @escaping () -> Void,
+ walkingAction: @escaping () -> Void
+ ) {
self.presentationData = presentationData
self.engine = engine
self.location = location
self.address = address
self.distance = distance
self.drivingTime = drivingTime
- self.transitTime = transitTime
self.walkingTime = walkingTime
self.hasEta = hasEta
self.action = action
self.drivingAction = drivingAction
- self.transitAction = transitAction
self.walkingAction = walkingAction
}
@@ -76,31 +85,26 @@ public final class LocationInfoListItem: ListViewItem {
}
public final class LocationInfoListItemNode: ListViewItemNode {
- private let backgroundNode: ASDisplayNode
private var titleNode: TextNode?
private var subtitleNode: TextNode?
private let venueIconNode: TransformImageNode
private let buttonNode: HighlightableButtonNode
private var placeholderNode: ShimmerEffectNode?
- private var drivingButtonNode: SolidRoundedButtonNode?
- private var transitButtonNode: SolidRoundedButtonNode?
- private var walkingButtonNode: SolidRoundedButtonNode?
+ private let drivingButton = ComponentView()
+ private let walkingButton = ComponentView()
private var item: LocationInfoListItem?
private var layoutParams: ListViewItemLayoutParams?
private var absoluteLocation: (CGRect, CGSize)?
required public init() {
- self.backgroundNode = ASDisplayNode()
- self.backgroundNode.isLayerBacked = true
self.buttonNode = HighlightableButtonNode()
self.venueIconNode = TransformImageNode()
self.venueIconNode.isUserInteractionEnabled = false
super.init(layerBacked: false, rotated: false, seeThrough: false)
- //self.addSubnode(self.backgroundNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.venueIconNode)
@@ -145,14 +149,15 @@ public final class LocationInfoListItemNode: ListViewItemNode {
let iconLayout = self.venueIconNode.asyncLayout()
return { [weak self] item, params in
- let leftInset: CGFloat = 75.0 + params.leftInset
+ let leftInset: CGFloat = 78.0 + params.leftInset
let rightInset: CGFloat = params.rightInset
let verticalInset: CGFloat = 14.0
- let iconSize: CGFloat = 48.0
- let inset: CGFloat = 15.0
+ let iconSize: CGFloat = 40.0
+ let directionsButtonHeight: CGFloat = 52.0
+ let directionsTopInset: CGFloat = 18.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 title: String
let subtitle: String
@@ -179,10 +184,11 @@ public final class LocationInfoListItemNode: ListViewItemNode {
let subtitleAttributedString = NSAttributedString(string: subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 15.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
- let titleSpacing: CGFloat = 1.0
- let bottomInset: CGFloat = 4.0
+ let titleSpacing: CGFloat = 0.0
+ let bottomInset: CGFloat = 16.0
let textContentSize = verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + bottomInset
- let contentSize = CGSize(width: params.width, height: item.hasEta ? max(100.0, textContentSize) : textContentSize)
+ let etaContentSize = verticalInset + titleLayout.size.height + titleSpacing + subtitleLayout.size.height + directionsTopInset + directionsButtonHeight + bottomInset
+ let contentSize = CGSize(width: params.width, height: item.hasEta ? max(etaContentSize, textContentSize) : textContentSize)
let nodeLayout = ListViewItemNodeLayout(contentSize: contentSize, insets: UIEdgeInsets())
return (nodeLayout, { [weak self] in
@@ -200,13 +206,7 @@ public final class LocationInfoListItemNode: ListViewItemNode {
if let strongSelf = self {
strongSelf.item = item
strongSelf.layoutParams = params
-
- if let _ = updatedTheme {
- strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
- }
-
- strongSelf.backgroundNode.isHidden = params.isStandalone
-
+
let arguments = VenueIconArguments(defaultBackgroundColor: item.presentationData.theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
if let updatedLocation = updatedLocation {
strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: updatedLocation.venue?.type ?? "", background: true))
@@ -229,107 +229,126 @@ public final class LocationInfoListItemNode: ListViewItemNode {
strongSelf.addSubnode(subtitleNode)
}
- let buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
- if strongSelf.drivingButtonNode == nil {
- strongSelf.drivingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
- strongSelf.drivingButtonNode?.iconSpacing = 5.0
- strongSelf.drivingButtonNode?.alpha = 0.0
- strongSelf.drivingButtonNode?.allowsGroupOpacity = true
- strongSelf.drivingButtonNode?.pressed = { [weak self] in
- if let item = self?.item {
- item.drivingAction()
- }
- }
- strongSelf.drivingButtonNode.flatMap { strongSelf.addSubnode($0) }
-
- strongSelf.transitButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
- strongSelf.transitButtonNode?.iconSpacing = 2.0
- strongSelf.transitButtonNode?.alpha = 0.0
- strongSelf.transitButtonNode?.allowsGroupOpacity = true
- strongSelf.transitButtonNode?.pressed = { [weak self] in
- if let item = self?.item {
- item.transitAction()
- }
- }
- strongSelf.transitButtonNode.flatMap { strongSelf.addSubnode($0) }
-
- strongSelf.walkingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
- strongSelf.walkingButtonNode?.iconSpacing = 2.0
- strongSelf.walkingButtonNode?.alpha = 0.0
- strongSelf.walkingButtonNode?.allowsGroupOpacity = true
- strongSelf.walkingButtonNode?.pressed = { [weak self] in
- if let item = self?.item {
- item.walkingAction()
- }
- }
- strongSelf.walkingButtonNode.flatMap { strongSelf.addSubnode($0) }
- } else if let _ = updatedTheme {
- strongSelf.drivingButtonNode?.updateTheme(buttonTheme)
- strongSelf.drivingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
-
- strongSelf.transitButtonNode?.updateTheme(buttonTheme)
- strongSelf.transitButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
-
- strongSelf.walkingButtonNode?.updateTheme(buttonTheme)
- strongSelf.walkingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
- }
-
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
titleNode.frame = titleFrame
let subtitleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: subtitleLayout.size)
subtitleNode.frame = subtitleFrame
- let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + inset, y: 10.0), size: CGSize(width: iconSize, height: iconSize))
+ let iconNodeFrame = CGRect(origin: CGPoint(x: params.leftInset + 26.0, y: 14.0), size: CGSize(width: iconSize, height: iconSize))
strongSelf.venueIconNode.frame = iconNodeFrame
- var directionsWidth: CGFloat = 93.0
+ let glassInset: CGFloat = 6.0
+ let buttonSideInset: CGFloat = 30.0
+ let buttonSpacing: CGFloat = 10.0
+ var directionsWidth: CGFloat = floorToScreenPixels((params.width - glassInset * 2.0 - buttonSideInset * 2.0 - buttonSpacing) / 2.0)
if item.hasEta {
- if item.drivingTime == .unknown && item.transitTime == .unknown && item.walkingTime == .unknown {
- strongSelf.drivingButtonNode?.icon = nil
- strongSelf.drivingButtonNode?.title = item.presentationData.strings.Map_GetDirections
- if let drivingButtonNode = strongSelf.drivingButtonNode {
- let buttonSize = drivingButtonNode.sizeThatFits(contentSize)
- directionsWidth = buttonSize.width
- }
+ let buttonBackground = ButtonComponent.Background(
+ style: .glass,
+ color: item.presentationData.theme.list.itemCheckColors.fillColor,
+ foreground: item.presentationData.theme.list.itemCheckColors.foregroundColor,
+ pressedColor: item.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
+ )
+ let foregroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor
+
+ var drivingButtonTitle = ""
+ var walkingButtonTitle = ""
+ var drivingButtonHasIcon = true
+ var drivingButtonVisible = false
+ var walkingButtonVisible = false
+
+ if item.drivingTime == .unknown && item.walkingTime == .unknown {
+ drivingButtonHasIcon = false
+ drivingButtonTitle = item.presentationData.strings.Map_GetDirections
+ drivingButtonVisible = true
if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime {
- strongSelf.drivingButtonNode?.alpha = 1.0
- strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ strongSelf.drivingButton.view?.alpha = 1.0
+ strongSelf.drivingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
} else {
if case let .ready(drivingTime) = item.drivingTime {
- strongSelf.drivingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 })
+ drivingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) ?? ""
+ drivingButtonVisible = true
if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime {
- strongSelf.drivingButtonNode?.alpha = 1.0
- strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
- }
- }
-
- if case let .ready(transitTime) = item.transitTime {
- strongSelf.transitButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: transitTime, format: { $0 })
-
- if let previousTransitTime = currentItem?.transitTime, case .calculating = previousTransitTime {
- strongSelf.transitButtonNode?.alpha = 1.0
- strongSelf.transitButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ strongSelf.drivingButton.view?.alpha = 1.0
+ strongSelf.drivingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
if case let .ready(walkingTime) = item.walkingTime {
- strongSelf.walkingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 })
+ walkingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) ?? ""
+ walkingButtonVisible = true
if let previousWalkingTime = currentItem?.walkingTime, case .calculating = previousWalkingTime {
- strongSelf.walkingButtonNode?.alpha = 1.0
- strongSelf.walkingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ strongSelf.walkingButton.view?.alpha = 1.0
+ strongSelf.walkingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
}
+
+ let drivingButtonContent: AnyComponent
+ if drivingButtonHasIcon {
+ drivingButtonContent = AnyComponent(
+ HStack([
+ AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsDriving", tintColor: foregroundColor))),
+ AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: drivingButtonTitle, font: Font.semibold(17.0), color: foregroundColor)))
+ ], spacing: 5.0)
+ )
+ } else {
+ drivingButtonContent = AnyComponent(Text(text: drivingButtonTitle, font: Font.semibold(17.0), color: foregroundColor))
+ }
- let directionsSpacing: CGFloat = 8.0
+ let drivingButtonSize = strongSelf.drivingButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("driving-\(drivingButtonHasIcon)-\(drivingButtonTitle)"),
+ component: drivingButtonContent
+ ),
+ action: { [weak self] in
+ if let item = self?.item {
+ item.drivingAction()
+ }
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: drivingButtonHasIcon ? directionsWidth : contentSize.width - glassInset * 2.0 - buttonSideInset * 2.0, height: directionsButtonHeight)
+ )
+ if !drivingButtonHasIcon {
+ directionsWidth = drivingButtonSize.width
+ }
- if case .calculating = item.drivingTime, case .calculating = item.transitTime, case .calculating = item.walkingTime {
+ let walkingButtonSize = strongSelf.walkingButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("walking-\(walkingButtonTitle)"),
+ component: AnyComponent(
+ HStack([
+ AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsWalking", tintColor: foregroundColor))),
+ AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: walkingButtonTitle, font: Font.semibold(17.0), color: foregroundColor)))
+ ], spacing: 2.0)
+ )
+ ),
+ contentInsets: UIEdgeInsets(),
+ action: { [weak self] in
+ if let item = self?.item {
+ item.walkingAction()
+ }
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: directionsWidth, height: directionsButtonHeight)
+ )
+
+ var buttonOrigin = glassInset + buttonSideInset
+
+ if case .calculating = item.drivingTime, case .calculating = item.walkingTime {
let shimmerNode: ShimmerEffectNode
if let current = strongSelf.placeholderNode {
shimmerNode = current
@@ -338,17 +357,17 @@ public final class LocationInfoListItemNode: ListViewItemNode {
strongSelf.placeholderNode = shimmerNode
strongSelf.addSubnode(shimmerNode)
}
- shimmerNode.frame = CGRect(origin: CGPoint(x: leftInset, y: subtitleFrame.maxY + 12.0), size: CGSize(width: contentSize.width - leftInset, height: 32.0))
+ shimmerNode.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + directionsTopInset), size: CGSize(width: contentSize.width - buttonOrigin * 2.0, height: directionsButtonHeight))
if let (rect, size) = strongSelf.absoluteLocation {
shimmerNode.updateAbsoluteRect(rect, within: size)
}
var shapes: [ShimmerEffectNode.Shape] = []
- shapes.append(.roundedRectLine(startPoint: CGPoint(x: 0.0, y: 0.0), width: directionsWidth, diameter: 32.0))
- shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + directionsSpacing, y: 0.0), width: directionsWidth, diameter: 32.0))
- shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + directionsSpacing + directionsWidth + directionsSpacing, y: 0.0), width: directionsWidth, diameter: 32.0))
+ shapes.append(.roundedRectLine(startPoint: CGPoint(x: 0.0, y: 0.0), width: directionsWidth, diameter: directionsButtonHeight))
+ shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + buttonSpacing, y: 0.0), width: directionsWidth, diameter: directionsButtonHeight))
+ shapes.append(.roundedRectLine(startPoint: CGPoint(x: directionsWidth + buttonSpacing + directionsWidth + buttonSpacing, y: 0.0), width: directionsWidth, diameter: directionsButtonHeight))
- shimmerNode.update(backgroundColor: item.presentationData.theme.list.plainBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size)
+ shimmerNode.update(backgroundColor: .clear, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size, mask: true)
} else if let shimmerNode = strongSelf.placeholderNode {
strongSelf.placeholderNode = nil
shimmerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak shimmerNode] _ in
@@ -356,30 +375,42 @@ public final class LocationInfoListItemNode: ListViewItemNode {
})
}
- let drivingHeight = strongSelf.drivingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
- let transitHeight = strongSelf.transitButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
- let walkingHeight = strongSelf.walkingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
- var buttonOrigin = leftInset
- strongSelf.drivingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: drivingHeight))
+ let drivingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + directionsTopInset), size: CGSize(width: directionsWidth, height: drivingButtonSize.height))
+ if let drivingButtonView = strongSelf.drivingButton.view {
+ if drivingButtonView.superview == nil {
+ strongSelf.view.addSubview(drivingButtonView)
+ }
+ drivingButtonView.frame = drivingButtonFrame
+ if drivingButtonView.layer.animation(forKey: "opacity") == nil {
+ drivingButtonView.alpha = drivingButtonVisible ? 1.0 : 0.0
+ }
+ }
if case .ready = item.drivingTime {
- buttonOrigin += directionsWidth + directionsSpacing
+ buttonOrigin += directionsWidth + buttonSpacing
}
- strongSelf.transitButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: transitHeight))
-
- if case .ready = item.transitTime {
- buttonOrigin += directionsWidth + directionsSpacing
+ let walkingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + directionsTopInset), size: CGSize(width: directionsWidth, height: walkingButtonSize.height))
+ if let walkingButtonView = strongSelf.walkingButton.view {
+ if walkingButtonView.superview == nil {
+ strongSelf.view.addSubview(walkingButtonView)
+ }
+ walkingButtonView.frame = walkingButtonFrame
+ if walkingButtonView.layer.animation(forKey: "opacity") == nil {
+ walkingButtonView.alpha = walkingButtonVisible ? 1.0 : 0.0
+ }
}
-
- strongSelf.walkingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: walkingHeight))
} else {
-
+ strongSelf.drivingButton.view?.alpha = 0.0
+ strongSelf.walkingButton.view?.alpha = 0.0
+ if let shimmerNode = strongSelf.placeholderNode {
+ strongSelf.placeholderNode = nil
+ shimmerNode.removeFromSupernode()
+ }
}
strongSelf.buttonNode.frame = CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: 72.0)
- strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
}
})
})
diff --git a/submodules/LocationUI/Sources/LocationLiveListItem.swift b/submodules/LocationUI/Sources/LocationLiveListItem.swift
index 5aaaf511fc..be96523c7c 100644
--- a/submodules/LocationUI/Sources/LocationLiveListItem.swift
+++ b/submodules/LocationUI/Sources/LocationLiveListItem.swift
@@ -10,10 +10,11 @@ import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI
import LocationResources
-import AppBundle
import AvatarNode
import LiveLocationTimerNode
-import SolidRoundedButtonNode
+import ComponentFlow
+import ButtonComponent
+import BundleIconComponent
final class LocationLiveListItem: ListViewItem {
let presentationData: ItemListPresentationData
@@ -24,17 +25,15 @@ final class LocationLiveListItem: ListViewItem {
let distance: Double?
let drivingTime: ExpectedTravelTime
- let transitTime: ExpectedTravelTime
let walkingTime: ExpectedTravelTime
let action: () -> Void
let longTapAction: () -> Void
let drivingAction: () -> Void
- let transitAction: () -> Void
let walkingAction: () -> Void
- public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, message: EngineMessage, distance: Double?, drivingTime: ExpectedTravelTime, transitTime: ExpectedTravelTime, walkingTime: ExpectedTravelTime, action: @escaping () -> Void, longTapAction: @escaping () -> Void = { }, drivingAction: @escaping () -> Void, transitAction: @escaping () -> Void, walkingAction: @escaping () -> Void) {
+ public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, message: EngineMessage, distance: Double?, drivingTime: ExpectedTravelTime, walkingTime: ExpectedTravelTime, action: @escaping () -> Void, longTapAction: @escaping () -> Void = { }, drivingAction: @escaping () -> Void, walkingAction: @escaping () -> Void) {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@@ -42,12 +41,10 @@ final class LocationLiveListItem: ListViewItem {
self.message = message
self.distance = distance
self.drivingTime = drivingTime
- self.transitTime = transitTime
self.walkingTime = walkingTime
self.action = action
self.longTapAction = longTapAction
self.drivingAction = drivingAction
- self.transitAction = transitAction
self.walkingAction = walkingAction
}
@@ -91,28 +88,19 @@ final class LocationLiveListItem: ListViewItem {
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
final class LocationLiveListItemNode: ListViewItemNode {
- private let backgroundNode: ASDisplayNode
- private let separatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private var titleNode: TextNode?
private var subtitleNode: TextNode?
private let avatarNode: AvatarNode
private var timerNode: ChatMessageLiveLocationTimerNode?
- private var drivingButtonNode: SolidRoundedButtonNode?
- private var transitButtonNode: SolidRoundedButtonNode?
- private var walkingButtonNode: SolidRoundedButtonNode?
+ private let drivingButton = ComponentView()
+ private let walkingButton = ComponentView()
private var item: LocationLiveListItem?
private var layoutParams: ListViewItemLayoutParams?
required init() {
- self.backgroundNode = ASDisplayNode()
- self.backgroundNode.isLayerBacked = true
-
- self.separatorNode = ASDisplayNode()
- self.separatorNode.isLayerBacked = true
-
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
@@ -121,8 +109,6 @@ final class LocationLiveListItemNode: ListViewItemNode {
super.init(layerBacked: false, rotated: false, seeThrough: false)
- self.addSubnode(self.backgroundNode)
- self.addSubnode(self.separatorNode)
self.addSubnode(self.avatarNode)
}
@@ -142,7 +128,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
- self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
+ self.insertSubnode(self.highlightedBackgroundNode, at: 0)
}
} else {
if self.highlightedBackgroundNode.supernode != nil {
@@ -169,7 +155,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
return { [weak self] item, params, hasSeparator in
- let leftInset: CGFloat = 65.0 + params.leftInset
+ let leftInset: CGFloat = 72.0 + params.leftInset
let rightInset: CGFloat = params.rightInset
let verticalInset: CGFloat = 8.0
@@ -203,18 +189,17 @@ final class LocationLiveListItemNode: ListViewItemNode {
let subtitleAttributedString = NSAttributedString(string: subtitle, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 54.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
- let titleSpacing: CGFloat = 1.0
+ let titleSpacing: CGFloat = 0.0
var contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + subtitleLayout.size.height)
- let hasEta: Bool
+ var hasEta: Bool
if case .ready = item.drivingTime {
hasEta = true
- } else if case .ready = item.transitTime {
- hasEta = true
} else if case .ready = item.walkingTime {
hasEta = true
} else {
hasEta = false
}
+ hasEta = true
if hasEta {
contentSize.height += 46.0
}
@@ -232,9 +217,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
strongSelf.layoutParams = params
if let _ = updatedTheme {
- strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
- strongSelf.backgroundNode.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
- strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
+ strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.contextMenu.itemHighlightedBackgroundColor
}
let titleNode = titleApply()
@@ -249,72 +232,23 @@ final class LocationLiveListItemNode: ListViewItemNode {
strongSelf.addSubnode(subtitleNode)
}
- let buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
- if strongSelf.drivingButtonNode == nil {
- strongSelf.drivingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
- strongSelf.drivingButtonNode?.alpha = 0.0
- strongSelf.drivingButtonNode?.iconSpacing = 5.0
- strongSelf.drivingButtonNode?.allowsGroupOpacity = true
- strongSelf.drivingButtonNode?.pressed = { [weak self] in
- if let item = self?.item {
- item.drivingAction()
- }
- }
- strongSelf.drivingButtonNode.flatMap { strongSelf.addSubnode($0) }
-
- strongSelf.transitButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
- strongSelf.transitButtonNode?.alpha = 0.0
- strongSelf.transitButtonNode?.iconSpacing = 2.0
- strongSelf.transitButtonNode?.allowsGroupOpacity = true
- strongSelf.transitButtonNode?.pressed = { [weak self] in
- if let item = self?.item {
- item.transitAction()
- }
- }
- strongSelf.transitButtonNode.flatMap { strongSelf.addSubnode($0) }
-
- strongSelf.walkingButtonNode = SolidRoundedButtonNode(icon: generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor), theme: buttonTheme, fontSize: 15.0, height: 32.0, cornerRadius: 16.0)
- strongSelf.walkingButtonNode?.alpha = 0.0
- strongSelf.walkingButtonNode?.iconSpacing = 2.0
- strongSelf.walkingButtonNode?.allowsGroupOpacity = true
- strongSelf.walkingButtonNode?.pressed = { [weak self] in
- if let item = self?.item {
- item.walkingAction()
- }
- }
- strongSelf.walkingButtonNode.flatMap { strongSelf.addSubnode($0) }
- } else if let _ = updatedTheme {
- strongSelf.drivingButtonNode?.updateTheme(buttonTheme)
- strongSelf.drivingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsDriving"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
-
- strongSelf.transitButtonNode?.updateTheme(buttonTheme)
- strongSelf.transitButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsTransit"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
-
- strongSelf.walkingButtonNode?.updateTheme(buttonTheme)
- strongSelf.walkingButtonNode?.icon = generateTintedImage(image: UIImage(bundleImageName: "Location/DirectionsWalking"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
- }
-
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
titleNode.frame = titleFrame
let subtitleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset + titleLayout.size.height + titleSpacing), size: subtitleLayout.size)
subtitleNode.frame = subtitleFrame
- let separatorHeight = UIScreenPixel
- let topHighlightInset: CGFloat = separatorHeight
- let separatorRightInset: CGFloat = 16.0
let avatarSize: CGFloat = 40.0
-
if let peer = item.message.author {
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: nil, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: false)
}
- strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 8.0), size: CGSize(width: avatarSize, height: avatarSize))
+ strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 22.0, y: 8.0), size: CGSize(width: avatarSize, height: avatarSize))
- 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 - leftInset - params.rightInset - separatorRightInset, height: separatorHeight))
- strongSelf.separatorNode.isHidden = !hasSeparator
+ let highlightFrame = CGRect(origin: CGPoint(x: 14.0, y: 2.0), size: CGSize(width: contentSize.width - 14.0 * 2.0, height: 52.0))
+ let highlightCornerRadius = highlightFrame.height * 0.5
+ strongSelf.highlightedBackgroundNode.frame = highlightFrame
+ strongSelf.highlightedBackgroundNode.cornerRadius = highlightCornerRadius
var liveBroadcastingTimeout: Int32 = 0
if let location = getLocation(from: item.message), let timeout = location.liveBroadcastingTimeout {
@@ -338,61 +272,134 @@ final class LocationLiveListItemNode: ListViewItemNode {
strongSelf.addSubnode(timerNode)
strongSelf.timerNode = timerNode
}
- let timerSize = CGSize(width: 28.0, height: 28.0)
+ let timerSize = CGSize(width: 24.0, height: 24.0)
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: Double(item.message.timestamp), timeout: Int32(liveBroadcastingTimeout) == liveLocationIndefinitePeriod ? -1.0 : Double(liveBroadcastingTimeout), strings: item.presentationData.strings)
- timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: 14.0), size: timerSize)
+ timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 26.0 - timerSize.width, y: floorToScreenPixels((56.0 - timerSize.height) / 2.0)), size: timerSize)
} else if let timerNode = strongSelf.timerNode {
strongSelf.timerNode = nil
timerNode.removeFromSupernode()
}
+ let buttonBackground = ButtonComponent.Background(
+ style: .glass,
+ color: item.presentationData.theme.list.itemCheckColors.fillColor,
+ foreground: item.presentationData.theme.list.itemCheckColors.foregroundColor,
+ pressedColor: item.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
+ )
+ let foregroundColor = item.presentationData.theme.list.itemCheckColors.foregroundColor
+ var directionsSize = CGSize(width: 96.0, height: 36.0)
+ let directionsSpacing: CGFloat = 8.0
+
+ var drivingButtonTitle = ""
+ var drivingButtonHasIcon = true
+ var walkingButtonTitle = ""
+ var drivingButtonVisible = false
+ var walkingButtonVisible = false
+
if case let .ready(drivingTime) = item.drivingTime {
- strongSelf.drivingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 })
+ drivingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: drivingTime, format: { $0 }) ?? ""
+ drivingButtonVisible = true
if let previousDrivingTime = currentItem?.drivingTime, case .calculating = previousDrivingTime {
- strongSelf.drivingButtonNode?.alpha = 1.0
- strongSelf.drivingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ strongSelf.drivingButton.view?.alpha = 1.0
+ strongSelf.drivingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
- }
-
- if case let .ready(transitTime) = item.transitTime {
- strongSelf.transitButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: transitTime, format: { $0 })
-
- if let previousTransitTime = currentItem?.transitTime, case .calculating = previousTransitTime {
- strongSelf.transitButtonNode?.alpha = 1.0
- strongSelf.transitButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ } else {
+ drivingButtonVisible = true
+ if case .unknown = item.walkingTime {
+ drivingButtonHasIcon = false
+ drivingButtonTitle = item.presentationData.strings.Map_GetDirections
+ directionsSize.width = contentSize.width - leftInset * 2.0
}
}
if case let .ready(walkingTime) = item.walkingTime {
- strongSelf.walkingButtonNode?.title = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 })
+ walkingButtonTitle = stringForEstimatedDuration(strings: item.presentationData.strings, time: walkingTime, format: { $0 }) ?? ""
+ walkingButtonVisible = true
if let previousWalkingTime = currentItem?.walkingTime, case .calculating = previousWalkingTime {
- strongSelf.walkingButtonNode?.alpha = 1.0
- strongSelf.walkingButtonNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ strongSelf.walkingButton.view?.alpha = 1.0
+ strongSelf.walkingButton.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
- let directionsWidth: CGFloat = 93.0
- let directionsSpacing: CGFloat = 8.0
- let drivingHeight = strongSelf.drivingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
- let transitHeight = strongSelf.transitButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
- let walkingHeight = strongSelf.walkingButtonNode?.updateLayout(width: directionsWidth, transition: .immediate) ?? 0.0
+ var drivingButtonContent: [AnyComponentWithIdentity] = []
+ if drivingButtonHasIcon {
+ drivingButtonContent.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsDriving", tintColor: foregroundColor))))
+ }
+ drivingButtonContent.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: drivingButtonTitle, font: Font.semibold(14.0), color: foregroundColor))))
+
+ let drivingButtonSize = strongSelf.drivingButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("driving-\(drivingButtonTitle)"),
+ component: AnyComponent(
+ HStack(drivingButtonContent, spacing: 2.0)
+ )
+ ),
+ contentInsets: UIEdgeInsets(),
+ action: { [weak self] in
+ if let item = self?.item {
+ item.drivingAction()
+ }
+ }
+ )),
+ environment: {},
+ containerSize: directionsSize
+ )
+
+ let walkingButtonSize = strongSelf.walkingButton.update(
+ transition: .immediate,
+ component: AnyComponent(ButtonComponent(
+ background: buttonBackground,
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("walking-\(walkingButtonTitle)"),
+ component: AnyComponent(
+ HStack([
+ AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Location/DirectionsWalking", tintColor: foregroundColor))),
+ AnyComponentWithIdentity(id: "title", component: AnyComponent(Text(text: walkingButtonTitle, font: Font.semibold(14.0), color: foregroundColor)))
+ ], spacing: 0.0)
+ )
+ ),
+ contentInsets: UIEdgeInsets(),
+ action: { [weak self] in
+ if let item = self?.item {
+ item.walkingAction()
+ }
+ }
+ )),
+ environment: {},
+ containerSize: directionsSize
+ )
var buttonOrigin = leftInset
- strongSelf.drivingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: drivingHeight))
+ let drivingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: drivingButtonSize)
+ if let drivingButtonView = strongSelf.drivingButton.view {
+ if drivingButtonView.superview == nil {
+ strongSelf.view.addSubview(drivingButtonView)
+ }
+ drivingButtonView.frame = drivingButtonFrame
+ if drivingButtonView.layer.animation(forKey: "opacity") == nil {
+ drivingButtonView.alpha = drivingButtonVisible ? 1.0 : 0.0
+ }
+ }
if case .ready = item.drivingTime {
- buttonOrigin += directionsWidth + directionsSpacing
+ buttonOrigin += directionsSize.width + directionsSpacing
}
- strongSelf.transitButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: transitHeight))
-
- if case .ready = item.transitTime {
- buttonOrigin += directionsWidth + directionsSpacing
+ let walkingButtonFrame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: walkingButtonSize)
+ if let walkingButtonView = strongSelf.walkingButton.view {
+ if walkingButtonView.superview == nil {
+ strongSelf.view.addSubview(walkingButtonView)
+ }
+ walkingButtonView.frame = walkingButtonFrame
+ if walkingButtonView.layer.animation(forKey: "opacity") == nil {
+ walkingButtonView.alpha = walkingButtonVisible ? 1.0 : 0.0
+ }
}
-
- strongSelf.walkingButtonNode?.frame = CGRect(origin: CGPoint(x: buttonOrigin, y: subtitleFrame.maxY + 12.0), size: CGSize(width: directionsWidth, height: walkingHeight))
}
})
})
diff --git a/submodules/LocationUI/Sources/LocationLiveLocationContextMenu.swift b/submodules/LocationUI/Sources/LocationLiveLocationContextMenu.swift
new file mode 100644
index 0000000000..d79e5bb5e1
--- /dev/null
+++ b/submodules/LocationUI/Sources/LocationLiveLocationContextMenu.swift
@@ -0,0 +1,68 @@
+import UIKit
+import Display
+import ContextUI
+import TelegramPresentationData
+import TelegramCore
+
+private final class LocationLiveLocationReferenceContentSource: ContextReferenceContentSource {
+ private let sourceView: UIView
+
+ init(sourceView: UIView) {
+ self.sourceView = sourceView
+ }
+
+ func transitionInfo() -> ContextControllerReferenceViewInfo? {
+ return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
+ }
+}
+
+func makeLiveLocationDurationContextController(
+ presentationData: PresentationData,
+ sourceView: UIView,
+ title: String,
+ selectPeriod: @escaping (Int32) -> Void
+) -> ViewController {
+ let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil
+ var items: [ContextMenuItem] = []
+
+ items.append(.action(ContextMenuActionItem(
+ text: title,
+ textLayout: .multiline,
+ textFont: .small,
+ parseMarkdown: true,
+ icon: { _ in
+ return nil
+ },
+ action: noAction
+ )))
+
+ items.append(.separator)
+
+ let periodItems: [(String, Int32)] = [
+ (presentationData.strings.Map_LiveLocationForMinutes(15), 15 * 60),
+ (presentationData.strings.Map_LiveLocationForHours(1), 60 * 60 - 1),
+ (presentationData.strings.Map_LiveLocationForHours(8), 8 * 60 * 60),
+ (presentationData.strings.Map_LiveLocationIndefinite, liveLocationIndefinitePeriod)
+ ]
+
+ for (text, period) in periodItems {
+ items.append(.action(ContextMenuActionItem(
+ text: text,
+ icon: { _ in
+ return nil
+ },
+ action: { _, f in
+ f(.default)
+ selectPeriod(period)
+ }
+ )))
+ }
+
+ let contextController = makeContextController(
+ presentationData: presentationData,
+ source: .reference(LocationLiveLocationReferenceContentSource(sourceView: sourceView)),
+ items: .single(ContextController.Items(content: .list(items))),
+ gesture: nil
+ )
+ return contextController
+}
diff --git a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift
index c3ef2e37d1..7645090d71 100644
--- a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift
+++ b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift
@@ -2,6 +2,9 @@ import Foundation
import UIKit
import AsyncDisplayKit
import Display
+import AccountContext
+import TelegramCore
+import SwiftSignalKit
import TelegramPresentationData
import AppBundle
import CoreLocation
@@ -10,6 +13,8 @@ import GlassBackgroundComponent
import PlainButtonComponent
import BundleIconComponent
import MultilineTextComponent
+import LottieComponent
+import LottieComponentResourceContent
private let panelInset: CGFloat = 4.0
private let panelButtonSize = CGSize(width: 46.0, height: 46.0)
@@ -48,6 +53,7 @@ public final class LocationMapHeaderNode: ASDisplayNode {
private let goToUserLocation: () -> Void
private let showPlacesInThisArea: () -> Void
private let setupProximityNotification: (Bool) -> Void
+ private let weatherPressed: () -> Void
private var displayingPlacesButton = false
private var proximityNotification: Bool?
@@ -67,10 +73,20 @@ public final class LocationMapHeaderNode: ASDisplayNode {
private let placesBackgroundView: GlassBackgroundView?
private let placesBackgroundNode: ASImageNode
private let placesButtonNode: HighlightTrackingButtonNode
- private let shadowNode: ASImageNode
+ private let weatherBackgroundView: GlassBackgroundView?
+ private let weatherIcon = ComponentView()
+ private let weatherEmojiLabel = ComponentView()
+ private let weatherTemperatureLabel = ComponentView()
+ private let weatherButton: HighlightTrackingButton
+ private var weatherEmoji: String?
+ private var weatherTemperature: String?
+ private var weatherEmojiFile: TelegramMediaFile?
+ private weak var weatherContext: AccountContext?
+ private let weatherEmojiLoadDisposable = MetaDisposable()
+
private var validLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat, CGFloat, CGFloat, CGSize)?
-
+
public init(
presentationData: PresentationData,
glass: Bool,
@@ -79,7 +95,8 @@ public final class LocationMapHeaderNode: ASDisplayNode {
updateMapMode: @escaping (LocationMapMode) -> Void,
goToUserLocation: @escaping () -> Void,
setupProximityNotification: @escaping (Bool) -> Void = { _ in },
- showPlacesInThisArea: @escaping () -> Void = {}
+ showPlacesInThisArea: @escaping () -> Void = {},
+ weatherPressed: @escaping () -> Void = {}
) {
self.presentationData = presentationData
self.glass = glass
@@ -88,6 +105,7 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.goToUserLocation = goToUserLocation
self.setupProximityNotification = setupProximityNotification
self.showPlacesInThisArea = showPlacesInThisArea
+ self.weatherPressed = weatherPressed
self.mapNode = LocationMapNode()
@@ -131,29 +149,35 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.placesBackgroundNode.displayWithoutProcessing = true
self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
self.placesBackgroundNode.isUserInteractionEnabled = true
-
+
self.placesButtonNode = HighlightTrackingButtonNode()
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
- self.shadowNode.displaysAsynchronously = false
- self.shadowNode.displayWithoutProcessing = true
- self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false)
-
+
+ self.weatherButton = HighlightTrackingButton()
+
if glass {
self.optionsBackgroundView = GlassContextExtractableContainer()
self.optionsBackgroundNode.image = nil
self.placesBackgroundView = GlassBackgroundView()
self.placesBackgroundNode.image = nil
+
+ self.weatherBackgroundView = GlassBackgroundView()
} else {
self.optionsBackgroundView = nil
self.placesBackgroundView = nil
+ self.weatherBackgroundView = nil
}
-
+
super.init()
-
+
+ self.mapNode.visibleRegionDidChange = { [weak self] in
+ guard let self, self.glass, self.mapNode.mapMode == .satellite else {
+ return
+ }
+ self.requestLayout(transition: .immediate)
+ }
+
self.clipsToBounds = true
self.addSubnode(self.mapNode)
@@ -176,15 +200,28 @@ public final class LocationMapHeaderNode: ASDisplayNode {
self.optionsBackgroundView?.contentView.addSubview(self.notificationButtonNode.view)
}
}
-
+
self.addSubnode(self.placesBackgroundNode)
self.placesBackgroundView?.contentView.addSubview(self.placesButtonNode.view)
- //self.addSubnode(self.shadowNode)
-
+ if let weatherBackgroundView = self.weatherBackgroundView {
+ weatherBackgroundView.isHidden = true
+ self.view.addSubview(weatherBackgroundView)
+ }
+
self.infoButtonNode.addTarget(self, action: #selector(self.infoPressed), forControlEvents: .touchUpInside)
self.locationButtonNode.addTarget(self, action: #selector(self.locationPressed), forControlEvents: .touchUpInside)
self.notificationButtonNode.addTarget(self, action: #selector(self.notificationPressed), forControlEvents: .touchUpInside)
self.placesButtonNode.addTarget(self, action: #selector(self.placesPressed), forControlEvents: .touchUpInside)
+
+ self.weatherButton.addTarget(self, action: #selector(self.weatherButtonPressed), for: .touchUpInside)
+ }
+
+ deinit {
+ self.weatherEmojiLoadDisposable.dispose()
+ }
+
+ @objc private func weatherButtonPressed() {
+ self.weatherPressed()
}
public func updateState(mapMode: LocationMapMode, trackingMode: LocationTrackingMode, displayingMapModeOptions: Bool, displayingPlacesButton: Bool, proximityNotification: Bool?, animated: Bool) {
@@ -214,6 +251,7 @@ public final class LocationMapHeaderNode: ASDisplayNode {
let buttonColor = self.glass ? presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.62) : presentationData.theme.rootController.navigationBar.buttonColor
+ self.mapNode.isDark = presentationData.theme.overallDarkAppearance
self.optionsBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
self.optionsSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.optionsSecondSeparatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
@@ -227,9 +265,45 @@ public final class LocationMapHeaderNode: ASDisplayNode {
if !self.glass {
self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme)
}
- self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false)
}
+ func updateWeatherData(context: AccountContext, emoji: String, temperature: String, animated: Bool) {
+ let emojiFile = context.animatedEmojiStickersValue[emoji]?.first?.file._parse()
+ if self.weatherEmoji == emoji && self.weatherTemperature == temperature && self.weatherEmojiFile?.fileId == emojiFile?.fileId {
+ return
+ }
+
+ if self.weatherEmojiFile?.fileId != emojiFile?.fileId {
+ if let emojiFile {
+ self.weatherEmojiLoadDisposable.set(context.engine.resources.fetch(reference: .standalone(resource: emojiFile.resource), userLocation: .other, userContentType: .sticker).start())
+ } else {
+ self.weatherEmojiLoadDisposable.set(nil)
+ }
+ }
+
+ self.weatherContext = context
+ self.weatherEmoji = emoji
+ self.weatherTemperature = temperature
+ self.weatherEmojiFile = emojiFile
+ self.requestLayout(transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
+ }
+
+ func clearWeatherData(animated: Bool) {
+ if self.weatherEmoji == nil && self.weatherTemperature == nil {
+ return
+ }
+ self.weatherEmoji = nil
+ self.weatherTemperature = nil
+ self.weatherEmojiFile = nil
+ self.weatherContext = nil
+ self.weatherEmojiLoadDisposable.set(nil)
+ if let weatherIconView = self.weatherIcon.view as? LottieComponent.View {
+ weatherIconView.externalShouldPlay = false
+ }
+ self.weatherIcon.view?.removeFromSuperview()
+ self.requestLayout(transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
+ }
+
private func iconForTracking() -> UIImage? {
switch self.trackingMode {
case .none:
@@ -241,7 +315,23 @@ public final class LocationMapHeaderNode: ASDisplayNode {
}
}
- public func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, topPadding: CGFloat, controlsTopPadding: CGFloat, controlsBottomPadding: CGFloat, offset: CGFloat, size: CGSize, transition: ContainedViewLayoutTransition) {
+ func requestLayout(transition: ContainedViewLayoutTransition) {
+ guard let (layout, navigationBarHeight, topPadding, controlsTopPadding, controlsBottomPadding, offset, size) = self.validLayout else {
+ return
+ }
+ self.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: topPadding, controlsTopPadding: controlsTopPadding, controlsBottomPadding: controlsBottomPadding, offset: offset, size: size, transition: transition)
+ }
+
+ 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
@@ -265,8 +355,126 @@ public final class LocationMapHeaderNode: ASDisplayNode {
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))
-
+ if let weatherBackgroundView = self.weatherBackgroundView {
+ let panelHeight: CGFloat = 36.0
+ let horizontalInset: CGFloat = 10.0
+ let labelSpacing: CGFloat = 5.0
+ let iconSize = CGSize(width: floor(panelHeight * 0.71), height: floor(panelHeight * 0.71))
+ let componentTransition = ComponentTransition(transition)
+ let temperatureSize = self.weatherTemperatureLabel.update(
+ transition: componentTransition,
+ component: AnyComponent(Text(
+ text: self.weatherTemperature ?? "",
+ font: Font.semibold(15.0),
+ color: self.presentationData.theme.rootController.navigationBar.primaryTextColor
+ )),
+ environment: {},
+ containerSize: CGSize(width: 72.0, height: panelHeight)
+ )
+ let emojiSize = self.weatherEmojiLabel.update(
+ transition: componentTransition,
+ component: AnyComponent(Text(
+ text: self.weatherEmoji ?? "",
+ font: Font.regular(18.0),
+ color: self.presentationData.theme.rootController.navigationBar.primaryTextColor
+ )),
+ environment: {},
+ containerSize: iconSize
+ )
+ let panelWidth = max(62.0, ceil(horizontalInset * 2.0 + iconSize.width + labelSpacing + temperatureSize.width))
+ let panelFrame = CGRect(
+ x: layout.safeInsets.left + 16.0,
+ y: navigationBarHeight + topPadding + 14.0,
+ width: panelWidth,
+ height: panelHeight
+ )
+ transition.updateFrame(view: weatherBackgroundView, frame: panelFrame)
+
+ let weatherAlpha: CGFloat = self.weatherEmoji != nil && self.weatherTemperature != nil && size.height > 160.0 + navigationBarHeight && !self.forceIsHidden ? 1.0 : 0.0
+ if weatherBackgroundView.isHidden && weatherAlpha > 0.0 {
+ weatherBackgroundView.isHidden = false
+ }
+ weatherBackgroundView.update(size: panelFrame.size, cornerRadius: panelFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, isVisible: weatherAlpha > 0.0, transition: ComponentTransition(transition))
+
+ let iconFrame = CGRect(
+ x: horizontalInset,
+ y: floorToScreenPixels((panelHeight - iconSize.height) / 2.0),
+ width: iconSize.width,
+ height: iconSize.height
+ )
+ let temperatureFrame = CGRect(
+ x: iconFrame.maxX + labelSpacing,
+ y: floorToScreenPixels((panelHeight - temperatureSize.height) / 2.0),
+ width: temperatureSize.width,
+ height: temperatureSize.height
+ )
+ let emojiFrame = CGRect(
+ x: iconFrame.minX + floorToScreenPixels((iconFrame.width - emojiSize.width) / 2.0),
+ y: iconFrame.minY + floorToScreenPixels((iconFrame.height - emojiSize.height) / 2.0),
+ width: emojiSize.width,
+ height: emojiSize.height
+ )
+ if let weatherEmojiView = self.weatherEmojiLabel.view {
+ if weatherEmojiView.superview == nil {
+ weatherBackgroundView.contentView.addSubview(weatherEmojiView)
+ }
+ transition.updateFrame(view: weatherEmojiView, frame: emojiFrame)
+ }
+ if let weatherTemperatureView = self.weatherTemperatureLabel.view {
+ if weatherTemperatureView.superview == nil {
+ weatherBackgroundView.contentView.addSubview(weatherTemperatureView)
+ }
+ transition.updateFrame(view: weatherTemperatureView, frame: temperatureFrame)
+ }
+
+ if let weatherContext = self.weatherContext, let weatherEmojiFile = self.weatherEmojiFile {
+ var weatherIconTransition = transition
+ let _ = self.weatherIcon.update(
+ transition: ComponentTransition(transition),
+ component: AnyComponent(
+ LottieComponent(
+ content: LottieComponent.ResourceContent(context: weatherContext, file: weatherEmojiFile, attemptSynchronously: false, providesPlaceholder: true),
+ placeholderColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor.withAlphaComponent(0.1),
+ renderingScale: 2.0,
+ loop: true
+ )
+ ),
+ environment: {},
+ containerSize: iconSize
+ )
+ if let weatherIconView = self.weatherIcon.view {
+ if weatherIconView.superview == nil {
+ weatherIconTransition = .immediate
+ weatherBackgroundView.contentView.addSubview(weatherIconView)
+ }
+ weatherIconTransition.updateFrame(view: weatherIconView, frame: iconFrame)
+ ComponentTransition(transition).setAlpha(view: weatherIconView, alpha: 1.0)
+ if let weatherIconView = weatherIconView as? LottieComponent.View {
+ weatherIconView.externalShouldPlay = weatherAlpha > 0.0
+ }
+ }
+ if let weatherEmojiView = self.weatherEmojiLabel.view {
+ componentTransition.setAlpha(view: weatherEmojiView, alpha: 0.0)
+ }
+ } else {
+ if let weatherIconView = self.weatherIcon.view {
+ componentTransition.setAlpha(view: weatherIconView, alpha: 0.0)
+ if let weatherIconView = weatherIconView as? LottieComponent.View {
+ weatherIconView.externalShouldPlay = false
+ }
+ }
+ if let weatherEmojiView = self.weatherEmojiLabel.view {
+ componentTransition.setAlpha(view: weatherEmojiView, alpha: 1.0)
+ }
+ }
+ componentTransition.setAlpha(view: weatherBackgroundView, alpha: weatherAlpha)
+
+ if self.weatherButton.superview == nil {
+ weatherBackgroundView.contentView.addSubview(self.weatherButton)
+ }
+ self.weatherButton.frame = CGRect(origin: .zero, size: panelFrame.size)
+ }
+
if let options = self.options {
let optionsSize = options.update(
transition: ComponentTransition(transition),
@@ -275,24 +483,35 @@ public final class LocationMapHeaderNode: ASDisplayNode {
theme: self.presentationData.theme,
strings: self.presentationData.strings,
mapMode: self.mapNode.mapMode,
+ currentCoordinate: self.mapNode.mapCenterCoordinate,
+ trackingMode: self.mapNode.trackingMode,
showMapModes: self.infoButtonNode.isSelected,
+ proximityNotification: self.proximityNotification,
updateMapMode: { [weak self] mode in
guard let self else {
return
}
self.updateMapMode(mode)
+ self.requestLayout(transition: .immediate)
},
goToUserLocation: { [weak self] in
guard let self else {
return
}
self.goToUserLocation()
+ self.requestLayout(transition: .immediate)
},
requestedMapModes: { [weak self] in
guard let self else {
return
}
self.toggleMapModeSelection()
+ },
+ setupProximityNotification: { [weak self] reset in
+ guard let self else {
+ return
+ }
+ self.setupProximityNotification(reset)
}
)
),
@@ -343,12 +562,17 @@ public final class LocationMapHeaderNode: ASDisplayNode {
}
}
}
-
- public func updateHighlight(_ highlighted: Bool) {
- self.shadowNode.image = generateShadowImage(theme: self.presentationData.theme, highlighted: highlighted)
- }
-
+
public func proximityButtonFrame() -> CGRect? {
+ if let options = self.options {
+ guard let optionsView = options.view as? LocationOptionsComponent.View else {
+ return nil
+ }
+ return optionsView.proximityButtonFrame().flatMap { frame in
+ return optionsView.convert(frame, to: self.view)
+ }
+ }
+
if self.notificationButtonNode.alpha > 0.0 {
return self.optionsBackgroundNode.view.convert(self.notificationButtonNode.frame, to: self.view)
} else {
@@ -376,34 +600,46 @@ public final class LocationMapHeaderNode: ASDisplayNode {
}
-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
+final class LocationOptionsComponent: Component {
+ let theme: PresentationTheme
+ let strings: PresentationStrings
+ let mapMode: LocationMapMode
+ let currentCoordinate: CLLocationCoordinate2D?
+ let trackingMode: LocationTrackingMode
+ let showMapModes: Bool
+ let proximityNotification: Bool?
+ let updateMapMode: (LocationMapMode) -> Void
+ let goToUserLocation: () -> Void
+ let requestedMapModes: () -> Void
+ let setupProximityNotification: (Bool) -> Void
- public init(
+ init(
theme: PresentationTheme,
strings: PresentationStrings,
mapMode: LocationMapMode,
+ currentCoordinate: CLLocationCoordinate2D?,
+ trackingMode: LocationTrackingMode,
showMapModes: Bool,
+ proximityNotification: Bool?,
updateMapMode: @escaping (LocationMapMode) -> Void,
goToUserLocation: @escaping () -> Void,
- requestedMapModes: @escaping () -> Void
+ requestedMapModes: @escaping () -> Void,
+ setupProximityNotification: @escaping (Bool) -> Void
) {
self.theme = theme
self.strings = strings
self.mapMode = mapMode
+ self.currentCoordinate = currentCoordinate
+ self.trackingMode = trackingMode
self.showMapModes = showMapModes
+ self.proximityNotification = proximityNotification
self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation
self.requestedMapModes = requestedMapModes
+ self.setupProximityNotification = setupProximityNotification
}
- public static func ==(lhs: LocationOptionsComponent, rhs: LocationOptionsComponent) -> Bool {
+ static func ==(lhs: LocationOptionsComponent, rhs: LocationOptionsComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
@@ -413,13 +649,43 @@ public final class LocationOptionsComponent: Component {
if lhs.mapMode != rhs.mapMode {
return false
}
+ if lhs.mapMode == .satellite && LocationOptionsComponent.satelliteIconName(coordinate: lhs.currentCoordinate) != LocationOptionsComponent.satelliteIconName(coordinate: rhs.currentCoordinate) {
+ return false
+ }
+ if lhs.trackingMode != rhs.trackingMode {
+ return false
+ }
if lhs.showMapModes != rhs.showMapModes {
return false
}
+ if lhs.proximityNotification != rhs.proximityNotification {
+ return false
+ }
return true
}
- public final class View: HighlightTrackingButton {
+ private static func satelliteIconName(coordinate: CLLocationCoordinate2D?) -> String {
+ guard let coordinate else {
+ return "Location/OptionGlobeEurope"
+ }
+
+ var longitude = coordinate.longitude.truncatingRemainder(dividingBy: 360.0)
+ if longitude < -180.0 {
+ longitude += 360.0
+ } else if longitude > 180.0 {
+ longitude -= 360.0
+ }
+
+ if longitude >= -170.0 && longitude < -25.0 {
+ return "Location/OptionGlobeAmerica"
+ } else if longitude >= -25.0 && longitude < 60.0 {
+ return "Location/OptionGlobeEurope"
+ } else {
+ return "Location/OptionGlobeAsia"
+ }
+ }
+
+ final class View: HighlightTrackingButton {
private let containerView: GlassBackgroundContainerView
private let backgroundView: GlassBackgroundView
private let clippingView: UIView
@@ -427,6 +693,7 @@ public final class LocationOptionsComponent: Component {
private let collapsedContainerView = UIView()
private var mapModeButton = ComponentView()
private var trackingButton = ComponentView()
+ private var notificationButton = ComponentView()
private let expandedContainerView = UIView()
private let checkIcon = UIImageView()
@@ -448,7 +715,7 @@ public final class LocationOptionsComponent: Component {
self.addSubview(self.containerView)
self.containerView.contentView.addSubview(self.backgroundView)
- self.addSubview(self.clippingView)
+ self.backgroundView.contentView.addSubview(self.clippingView)
self.clippingView.addSubview(self.collapsedContainerView)
self.clippingView.addSubview(self.expandedContainerView)
}
@@ -566,7 +833,7 @@ public final class LocationOptionsComponent: Component {
transition.setFrame(view: hybridButtonView, frame: hybridButtonFrame)
}
- let normalSize = CGSize(width: 40.0, height: 80.0)
+ let normalSize = CGSize(width: 40.0, height: component.proximityNotification != nil ? 120.0 : 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)
@@ -575,17 +842,26 @@ public final class LocationOptionsComponent: Component {
self.backgroundView.update(size: effectiveBackgroundFrame.size, cornerRadius: cornerRadius, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
transition.setFrame(view: self.backgroundView, frame: effectiveBackgroundFrame)
- transition.setFrame(view: self.clippingView, frame: effectiveBackgroundFrame)
+ transition.setFrame(view: self.clippingView, frame: CGRect(origin: .zero, size: effectiveBackgroundFrame.size))
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))
+ var mapModeIconName: String
+ switch component.mapMode {
+ case .map:
+ mapModeIconName = "Location/OptionMap"
+ case .satellite:
+ mapModeIconName = LocationOptionsComponent.satelliteIconName(coordinate: component.currentCoordinate)
+ case .hybrid:
+ mapModeIconName = "Location/OptionHybrid"
+ }
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))
+ BundleIconComponent(name: mapModeIconName, tintColor: component.theme.chat.inputPanel.panelControlColor)
),
minSize: CGSize(width: 40.0, height: 40.0),
action: { [weak self] in
@@ -608,13 +884,22 @@ public final class LocationOptionsComponent: Component {
}
transition.setFrame(view: mapModeButtonView, frame: mapModeButtonFrame)
}
-
+
+ let trackingModeIconName: String
+ switch component.trackingMode {
+ case .none:
+ trackingModeIconName = "Location/OptionLocate"
+ case .follow:
+ trackingModeIconName = "Location/OptionLocating"
+ case .followWithHeading:
+ trackingModeIconName = "Location/OptionTracking"
+ }
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))
+ BundleIconComponent(name: trackingModeIconName, tintColor: component.theme.chat.inputPanel.panelControlColor)
),
minSize: CGSize(width: 40.0, height: 40.0),
action: { [weak self] in
@@ -638,6 +923,41 @@ public final class LocationOptionsComponent: Component {
transition.setFrame(view: trackingButtonView, frame: trackingButtonFrame)
}
+ if let proximityNotification = component.proximityNotification {
+ let notificationIconName = proximityNotification ? "Chat/Title Panels/MuteIcon" : "Location/NotificationIcon"
+ let notificationButtonSize = self.notificationButton.update(
+ transition: transition,
+ component: AnyComponent(
+ PlainButtonComponent(
+ content: AnyComponent(
+ BundleIconComponent(name: notificationIconName, tintColor: component.theme.chat.inputPanel.panelControlColor)
+ ),
+ minSize: CGSize(width: 40.0, height: 40.0),
+ action: { [weak self] in
+ guard let self, let component = self.component, let proximityNotification = component.proximityNotification else {
+ return
+ }
+ component.setupProximityNotification(proximityNotification)
+ },
+ animateAlpha: true,
+ animateScale: false
+ )
+ ),
+ environment: {},
+ containerSize: CGSize(width: 40.0, height: 40.0)
+ )
+ let notificationButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: 80.0), size: notificationButtonSize)
+ if let notificationButtonView = self.notificationButton.view {
+ if notificationButtonView.superview == nil {
+ self.collapsedContainerView.addSubview(notificationButtonView)
+ }
+ transition.setFrame(view: notificationButtonView, frame: notificationButtonFrame)
+ transition.setAlpha(view: notificationButtonView, alpha: 1.0)
+ }
+ } else if let notificationButtonView = self.notificationButton.view {
+ transition.setAlpha(view: notificationButtonView, alpha: 0.0)
+ }
+
transition.setAlpha(view: self.collapsedContainerView, alpha: component.showMapModes ? 0.0 : 1.0)
transition.setAlpha(view: self.expandedContainerView, alpha: component.showMapModes ? 1.0 : 0.0)
@@ -650,6 +970,16 @@ public final class LocationOptionsComponent: Component {
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return self.backgroundView.frame.contains(point)
}
+
+ func proximityButtonFrame() -> CGRect? {
+ guard let component = self.component, component.proximityNotification != nil, !component.showMapModes else {
+ return nil
+ }
+ guard let notificationButtonView = self.notificationButton.view, notificationButtonView.superview != nil, notificationButtonView.alpha > 0.0 else {
+ return nil
+ }
+ return notificationButtonView.convert(notificationButtonView.bounds, to: self)
+ }
}
public func makeView() -> View {
diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift
index 072865eccd..35caf43aef 100644
--- a/submodules/LocationUI/Sources/LocationMapNode.swift
+++ b/submodules/LocationUI/Sources/LocationMapNode.swift
@@ -15,9 +15,9 @@ public enum LocationMapMode {
var mapType: MKMapType {
switch self {
case .satellite:
- return .satellite
+ return .satelliteFlyover
case .hybrid:
- return .hybrid
+ return .hybridFlyover
default:
return .standard
}
@@ -135,6 +135,7 @@ private func generateProximityDim(size: CGSize) -> UIImage {
}
protocol MKMapViewDelegateTarget: AnyObject {
+ func mapViewDidChangeVisibleRegion(_ mapView: MKMapView)
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool)
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool)
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation)
@@ -155,6 +156,10 @@ private final class MKMapViewDelegateImpl: NSObject, MKMapViewDelegate {
super.init()
}
+ func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
+ self.target?.mapViewDidChangeVisibleRegion(mapView)
+ }
+
func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
self.target?.mapView(mapView, regionWillChangeAnimated: animated)
}
@@ -237,12 +242,41 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
return self.view as? LocationMapView
}
+ private func updateUserHeadingTransform(mapHeading: CGFloat) {
+ if let heading = self.userHeading {
+ self.headingArrowView?.isHidden = false
+ self.headingArrowView?.transform = CGAffineTransform(rotationAngle: (heading - mapHeading) / 180.0 * CGFloat.pi)
+ } else {
+ self.headingArrowView?.isHidden = true
+ self.headingArrowView?.transform = CGAffineTransform.identity
+ }
+ }
+
+ private func updateHeadingTransforms(mapHeading: CGFloat? = nil) {
+ guard let mapView = self.mapView else {
+ return
+ }
+
+ let mapHeading = mapHeading ?? CGFloat(mapView.camera.heading)
+ self.updateUserHeadingTransform(mapHeading: mapHeading)
+
+ for annotation in mapView.annotations {
+ if let annotationView = mapView.view(for: annotation) as? LocationPinAnnotationView {
+ annotationView.updateMapHeading(mapHeading)
+ }
+ }
+
+ self.pickerAnnotationView?.updateMapHeading(mapHeading)
+ self.customUserLocationAnnotationView?.updateMapHeading(mapHeading)
+ }
+
var returnedToUserLocation = true
var ignoreRegionChanges = false
var isDragging = false
var onTouch: (() -> Void)?
var beganInteractiveDragging: (() -> Void)?
var endedInteractiveDragging: ((CLLocationCoordinate2D) -> Void)?
+ var visibleRegionDidChange: (() -> Void)?
var disableHorizontalTransitionGesture = false
var annotationSelected: ((LocationPinAnnotation?) -> Void)?
@@ -369,6 +403,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
self.onTouch?()
}
self.view.addSubview(self.pickerAnnotationContainerView)
+ self.updateHeadingTransforms()
}
var isRotateEnabled: Bool = true {
@@ -383,6 +418,12 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
}
}
+ var isDark: Bool = false {
+ didSet {
+ self.mapView?.overrideUserInterfaceStyle = self.isDark ? .dark : .light
+ }
+ }
+
public var trackingMode: LocationTrackingMode = .none {
didSet {
self.mapView?.userTrackingMode = self.trackingMode.userTrackingMode
@@ -391,6 +432,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
} else if self.trackingMode != .followWithHeading && self.headingArrowView?.image == nil {
self.headingArrowView?.image = generateHeadingArrowImage()
}
+ self.updateHeadingTransforms()
}
}
@@ -460,6 +502,10 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
}
}
+ public func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) {
+ self.updateHeadingTransforms(mapHeading: CGFloat(mapView.camera.heading))
+ }
+
public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
guard !self.ignoreRegionChanges, let scrollView = mapView.subviews.first, let gestureRecognizers = scrollView.gestureRecognizers else {
return
@@ -478,6 +524,9 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
}
public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
+ self.updateHeadingTransforms(mapHeading: CGFloat(mapView.camera.heading))
+ self.visibleRegionDidChange?()
+
let wasDragging = self.isDragging
if self.isDragging {
self.isDragging = false
@@ -543,6 +592,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
container.insertSubview(self.proximityDimView, at: 0)
}
}
+ self.updateHeadingTransforms(mapHeading: CGFloat(mapView.camera.heading))
}
public func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
@@ -658,6 +708,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
pickerAnnotationView.center = CGPoint(x: self.pickerAnnotationContainerView.frame.width / 2.0, y: self.pickerAnnotationContainerView.frame.height / 2.0 + 16.0)
self.pickerAnnotationContainerView.addSubview(pickerAnnotationView)
self.pickerAnnotationView = pickerAnnotationView
+ self.updateHeadingTransforms()
} else {
self.pickerAnnotationView?.removeFromSuperview()
self.pickerAnnotationView = nil
@@ -696,6 +747,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
self.customUserLocationAnnotationView = annotationView
self.pickerAnnotationView?.annotation = annotation
+ self.updateHeadingTransforms()
} else {
self.customUserLocationAnnotationView?.removeFromSuperview()
self.customUserLocationAnnotationView = nil
@@ -705,13 +757,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
public var userHeading: CGFloat? = nil {
didSet {
- if let heading = self.userHeading {
- self.headingArrowView?.isHidden = false
- self.headingArrowView?.transform = CGAffineTransform(rotationAngle: CGFloat(heading / 180.0 * CGFloat.pi))
- } else {
- self.headingArrowView?.isHidden = true
- self.headingArrowView?.transform = CGAffineTransform.identity
- }
+ self.updateHeadingTransforms()
}
}
@@ -869,7 +915,7 @@ public final class LocationMapNode: ASDisplayNode, MKMapViewDelegateTarget {
}
if let compassView = self.compassView {
- transition.updateFrame(view: compassView, frame: CGRect(origin: CGPoint(x: size.width - compassView.frame.width - 11.0, y: inset + 110.0 + topPadding), size: compassView.frame.size))
+ transition.updateFrame(view: compassView, frame: CGRect(origin: CGPoint(x: size.width - compassView.frame.width - 16.0, y: inset + 14.0 + topPadding), size: compassView.frame.size))
}
self.applyPendingSetMapCenter()
diff --git a/submodules/LocationUI/Sources/LocationOptionsNode.swift b/submodules/LocationUI/Sources/LocationOptionsNode.swift
deleted file mode 100644
index 1036a19a41..0000000000
--- a/submodules/LocationUI/Sources/LocationOptionsNode.swift
+++ /dev/null
@@ -1,64 +0,0 @@
-import Foundation
-import UIKit
-import AsyncDisplayKit
-import Display
-import TelegramCore
-import TelegramPresentationData
-import SegmentedControlNode
-
-public final class LocationOptionsNode: ASDisplayNode {
- private var presentationData: PresentationData
-
- private let backgroundNode: NavigationBackgroundNode
- private let separatorNode: ASDisplayNode
- private let segmentedControlNode: SegmentedControlNode
-
- public init(presentationData: PresentationData, hasBackground: Bool = true, updateMapMode: @escaping (LocationMapMode) -> Void) {
- self.presentationData = presentationData
-
- self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor)
- self.separatorNode = ASDisplayNode()
- self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
-
- self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: self.presentationData.theme), items: [SegmentedControlItem(title: self.presentationData.strings.Map_Map), SegmentedControlItem(title: self.presentationData.strings.Map_Satellite), SegmentedControlItem(title: self.presentationData.strings.Map_Hybrid)], selectedIndex: 0)
-
- super.init()
-
- if hasBackground {
- self.addSubnode(self.backgroundNode)
- self.addSubnode(self.separatorNode)
- }
-
- self.addSubnode(self.segmentedControlNode)
-
- self.segmentedControlNode.selectedIndexChanged = { index in
- switch index {
- case 0:
- updateMapMode(.map)
- case 1:
- updateMapMode(.satellite)
- case 2:
- updateMapMode(.hybrid)
- default:
- break
- }
- }
- }
-
- public func updatePresentationData(_ presentationData: PresentationData) {
- self.presentationData = presentationData
- self.backgroundNode.updateColor(color: self.presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
- self.separatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
- self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.presentationData.theme))
- }
-
- public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
- transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
- self.backgroundNode.update(size: size, transition: transition)
-
- transition.updateFrame(node: self.separatorNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel))
-
- let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: size.width - 16.0 - leftInset - rightInset), transition: .immediate)
- self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floor((size.width - controlSize.width) / 2.0), y: 0.0), size: controlSize)
- }
-}
diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift
index 634e4c5d3f..09634f18a5 100644
--- a/submodules/LocationUI/Sources/LocationPickerController.swift
+++ b/submodules/LocationUI/Sources/LocationPickerController.swift
@@ -30,11 +30,10 @@ class LocationPickerInteraction {
let updateSearchQuery: (String) -> Void
let dismissSearch: () -> Void
let dismissInput: () -> Void
- let updateSendActionHighlight: (Bool) -> Void
let openHomeWorkInfo: () -> Void
let showPlacesInThisArea: () -> Void
- init(sendLocation: @escaping (CLLocationCoordinate2D, String?, MapGeoAddress?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap, Int64?, String?) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D, Bool) -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, openHomeWorkInfo: @escaping () -> Void, showPlacesInThisArea: @escaping ()-> Void) {
+ init(sendLocation: @escaping (CLLocationCoordinate2D, String?, MapGeoAddress?) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D) -> Void, sendVenue: @escaping (TelegramMediaMap, Int64?, String?) -> Void, toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D, Bool) -> Void, openSearch: @escaping () -> Void, updateSearchQuery: @escaping (String) -> Void, dismissSearch: @escaping () -> Void, dismissInput: @escaping () -> Void, openHomeWorkInfo: @escaping () -> Void, showPlacesInThisArea: @escaping ()-> Void) {
self.sendLocation = sendLocation
self.sendLiveLocation = sendLiveLocation
self.sendVenue = sendVenue
@@ -46,7 +45,6 @@ class LocationPickerInteraction {
self.updateSearchQuery = updateSearchQuery
self.dismissSearch = dismissSearch
self.dismissInput = dismissInput
- self.updateSendActionHighlight = updateSendActionHighlight
self.openHomeWorkInfo = openHomeWorkInfo
self.showPlacesInThisArea = showPlacesInThisArea
}
@@ -104,7 +102,15 @@ public final class LocationPickerController: ViewController, AttachmentContainab
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) {
+ 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
@@ -119,11 +125,12 @@ public final class LocationPickerController: ViewController, AttachmentContainab
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.navigationPresentation = .modal
+ self._hasGlassStyle = true
- if case .glass = style {
+ if case .share = mode {
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))
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "___close", style: .plain, target: self, action: #selector(self.cancelPressed))
}
self.updateBarButtons()
@@ -179,44 +186,25 @@ public final class LocationPickerController: ViewController, AttachmentContainab
guard let strongSelf = self, authorized else {
return
}
- let controller = ActionSheetController(presentationData: strongSelf.presentationData)
var title = strongSelf.presentationData.strings.Map_LiveLocationGroupNewDescription
if case let .share(peer, _, _) = strongSelf.mode, let peer = peer, case .user = peer {
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(peer.compactDisplayTitle).string
}
- let sendLiveLocationImpl: (Int32) -> Void = { [weak self, weak controller] period in
- controller?.dismissAnimated()
- guard let self, let controller else {
- return
+ let sourceView = strongSelf.controllerNode.liveLocationActionSourceView(extend: false) ?? strongSelf.view
+ let controller = makeLiveLocationDurationContextController(
+ presentationData: strongSelf.presentationData,
+ sourceView: sourceView!,
+ title: title,
+ selectPeriod: { [weak self] period in
+ guard let self else {
+ return
+ }
+ self.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period), nil, nil, nil, nil)
+ self.dismiss()
}
- controller.dismissAnimated()
- self.completion(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period), nil, nil, nil, nil)
- self.dismiss()
- }
-
- controller.setItemGroups([
- ActionSheetItemGroup(items: [
- ActionSheetTextItem(title: title, font: .large, parseMarkdown: true),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: { sendLiveLocationImpl(15 * 60)
- }),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: {
- sendLiveLocationImpl(60 * 60 - 1)
- }),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: {
- sendLiveLocationImpl(8 * 60 * 60)
- }),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: {
- sendLiveLocationImpl(liveLocationIndefinitePeriod)
- })
- ]),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in
- controller?.dismissAnimated()
- })
- ])
- ])
- strongSelf.present(controller, in: .window(.root))
+ )
+ strongSelf.presentInGlobalOverlay(controller)
})
}, sendVenue: { [weak self] venue, queryId, resultId in
guard let strongSelf = self else {
@@ -316,11 +304,6 @@ public final class LocationPickerController: ViewController, AttachmentContainab
}
self.searchNavigationContentNode?.deactivate()
self.controllerNode.deactivateInput()
- }, updateSendActionHighlight: { [weak self] highlighted in
- guard let strongSelf = self else {
- return
- }
- strongSelf.controllerNode.updateSendActionHighlight(highlighted)
}, openHomeWorkInfo: { [weak self] in
guard let strongSelf = self else {
return
diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift
index b9a825cc98..5123a26635 100644
--- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift
+++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift
@@ -171,10 +171,9 @@ private enum LocationPickerEntry: Comparable, Identifiable {
if let coordinate = coordinate {
interaction?.sendLocation(coordinate, name, address?.withUpdated(street: nil))
}
- }, highlighted: { highlighted in
- interaction?.updateSendActionHighlight(highlighted)
+ }, highlighted: { _ in
})
- case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name, address, isTop):
+ case let .location(_, title, subtitle, venue, queryId, resultId, coordinate, name, address, _):
let icon: LocationActionListItemIcon
if let venue = venue {
icon = .venue(venue)
@@ -187,10 +186,7 @@ private enum LocationPickerEntry: Comparable, Identifiable {
} else if let coordinate {
interaction?.sendLocation(coordinate, name, address)
}
- }, highlighted: { highlighted in
- if isTop {
- interaction?.updateSendActionHighlight(highlighted)
- }
+ }, highlighted: { _ in
})
case let .liveLocation(_, title, subtitle, coordinate):
return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: .liveLocation, beginTimeAndTimeout: nil, action: {
@@ -361,7 +357,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
private var sendButton: ComponentView?
- private let optionsNode: LocationOptionsNode
private(set) var searchContainerNode: LocationSearchContainerNode?
private var placeholderBackgroundNode: NavigationBackgroundNode?
@@ -422,9 +417,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
showPlacesInThisArea: interaction.showPlacesInThisArea
)
self.headerNode.mapNode.isRotateEnabled = false
-
- self.optionsNode = LocationOptionsNode(presentationData: presentationData, updateMapMode: interaction.updateMapMode)
-
+
self.shadeNode = ASDisplayNode()
self.shadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.shadeNode.alpha = 0.0
@@ -443,7 +436,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.addSubnode(self.listNode)
self.addSubnode(self.headerNode)
- //self.addSubnode(self.optionsNode)
+
self.listNode.addSubnode(self.emptyResultsTextNode)
self.shadeNode.addSubnode(self.innerShadeNode)
self.addSubnode(self.shadeNode)
@@ -1099,7 +1092,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.listNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.headerNode.updatePresentationData(self.presentationData)
- self.optionsNode.updatePresentationData(self.presentationData)
self.shadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.innerShadeNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.searchContainerNode?.updatePresentationData(self.presentationData)
@@ -1214,6 +1206,16 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
return (self.state.selectedLocation.isCustom || self.state.forceSelection) && !self.state.searchingVenuesAround
}
+ func liveLocationActionSourceView(extend: Bool) -> UIView? {
+ var result: UIView?
+ self.listNode.forEachItemNode { itemNode in
+ if result == nil, let itemNode = itemNode as? LocationActionListItemNode {
+ result = itemNode.liveLocationContextSourceView(extend: extend)
+ }
+ }
+ return result
+ }
+
func requestLayout(transition: ContainedViewLayoutTransition) {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: transition)
@@ -1291,12 +1293,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
}
- let optionsOffset: CGFloat = self.state.displayingMapModeOptions ? navigationHeight : navigationHeight - optionsHeight
- let optionsFrame = CGRect(x: 0.0, y: optionsOffset, width: layout.size.width, height: optionsHeight)
- transition.updateFrame(node: self.optionsNode, frame: optionsFrame)
- self.optionsNode.updateLayout(size: optionsFrame.size, leftInset: insets.left, rightInset: insets.right, transition: transition)
- self.optionsNode.isUserInteractionEnabled = self.state.displayingMapModeOptions
-
if let searchContainerNode = self.searchContainerNode {
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: navigationHeight, transition: transition)
@@ -1655,11 +1651,6 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
}
}
- func updateSendActionHighlight(_ highlighted: Bool) {
- self.headerNode.updateHighlight(highlighted)
- self.shadeNode.backgroundColor = highlighted ? self.presentationData.theme.list.itemHighlightedBackgroundColor : self.presentationData.theme.list.plainBackgroundColor
- }
-
func goToUserLocation() {
guard let controller = self.controller else {
return
diff --git a/submodules/LocationUI/Sources/LocationViewController.swift b/submodules/LocationUI/Sources/LocationViewController.swift
index 637782af41..6b74ecc142 100644
--- a/submodules/LocationUI/Sources/LocationViewController.swift
+++ b/submodules/LocationUI/Sources/LocationViewController.swift
@@ -31,7 +31,7 @@ public class LocationViewParams {
}
}
-class LocationViewInteraction {
+final class LocationViewInteraction {
let toggleMapModeSelection: () -> Void
let updateMapMode: (LocationMapMode) -> Void
let toggleTrackingMode: () -> Void
@@ -39,12 +39,11 @@ class LocationViewInteraction {
let requestDirections: (TelegramMediaMap, String?, OpenInLocationDirections) -> Void
let share: () -> Void
let setupProximityNotification: (Bool, EngineMessage.Id?) -> Void
- let updateSendActionHighlight: (Bool) -> Void
let sendLiveLocation: (Int32?, Bool, EngineMessage.Id?) -> Void
let stopLiveLocation: () -> Void
let present: (ViewController) -> Void
- init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (Int32?, Bool, EngineMessage.Id?) -> Void, stopLiveLocation: @escaping () -> Void, present: @escaping (ViewController) -> Void) {
+ init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, toggleTrackingMode: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping (TelegramMediaMap, String?, OpenInLocationDirections) -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, EngineMessage.Id?) -> Void, sendLiveLocation: @escaping (Int32?, Bool, EngineMessage.Id?) -> Void, stopLiveLocation: @escaping () -> Void, present: @escaping (ViewController) -> Void) {
self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode
self.toggleTrackingMode = toggleTrackingMode
@@ -52,7 +51,6 @@ class LocationViewInteraction {
self.requestDirections = requestDirections
self.share = share
self.setupProximityNotification = setupProximityNotification
- self.updateSendActionHighlight = updateSendActionHighlight
self.sendLiveLocation = sendLiveLocation
self.stopLiveLocation = stopLiveLocation
self.present = present
@@ -65,10 +63,11 @@ public final class LocationViewController: ViewController {
}
private let context: AccountContext
public var subject: EngineMessage
+ private var basePresentationData: PresentationData
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
+ private var currentMapMode: LocationMapMode = .map
private var showAll: Bool
- private let isStoryLocation: Bool
private let isPreview: Bool
private let locationManager = LocationManager()
@@ -77,31 +76,36 @@ public final class LocationViewController: ViewController {
public var dismissed: () -> Void = {}
- public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, subject: EngineMessage, isStoryLocation: Bool = false, isPreview: Bool = false, params: LocationViewParams) {
+ public init(
+ context: AccountContext,
+ updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil,
+ subject: EngineMessage,
+ isPreview: Bool = false,
+ params: LocationViewParams
+ ) {
self.context = context
self.subject = subject
self.showAll = params.showAll
- self.isStoryLocation = isStoryLocation
self.isPreview = isPreview
- self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
+ let initialPresentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
+ self.basePresentationData = initialPresentationData
+ self.presentationData = LocationViewController.effectivePresentationData(initialPresentationData, mapMode: .map)
super.init(navigationBarPresentationData: nil)
self._hasGlassStyle = true
+ self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationPresentation = .modal
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
- guard let strongSelf = self, strongSelf.presentationData.theme !== presentationData.theme else {
+ guard let strongSelf = self, strongSelf.basePresentationData.theme !== presentationData.theme else {
return
}
- strongSelf.presentationData = presentationData
-
- if strongSelf.isNodeLoaded {
- strongSelf.controllerNode.updatePresentationData(presentationData)
- }
+ strongSelf.basePresentationData = presentationData
+ strongSelf.updateEffectivePresentationData(animated: true)
})
self.interaction = LocationViewInteraction(toggleMapModeSelection: { [weak self] in
@@ -117,12 +121,14 @@ public final class LocationViewController: ViewController {
guard let strongSelf = self else {
return
}
+ strongSelf.currentMapMode = mode
strongSelf.controllerNode.updateState { state in
var state = state
state.mapMode = mode
state.displayingMapModeOptions = false
return state
}
+ strongSelf.updateEffectivePresentationData(animated: true)
}, toggleTrackingMode: { [weak self] in
guard let strongSelf = self else {
return
@@ -174,7 +180,7 @@ public final class LocationViewController: ViewController {
}
}
} else {
- strongSelf.present(OpenInActionSheetController(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: directions), additionalAction: nil, openUrl: params.openUrl), in: .window(.root), with: nil)
+ strongSelf.push(OpenInOptionsScreen(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: directions), additionalAction: nil, openUrl: params.openUrl))
}
}, share: { [weak self] in
guard let strongSelf = self else {
@@ -184,7 +190,7 @@ public final class LocationViewController: ViewController {
let shareAction = OpenInControllerAction(title: strongSelf.presentationData.strings.Conversation_ContextMenuShare, action: {
strongSelf.present(context.sharedContext.makeShareController(context: context, params: ShareControllerParams(subject: .mapMedia(location), externalShare: true)), in: .window(.root), with: nil)
})
- strongSelf.present(OpenInActionSheetController(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: nil), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
+ strongSelf.push(OpenInOptionsScreen(context: context, updatedPresentationData: updatedPresentationData, item: .location(location: location, directions: nil), additionalAction: shareAction, openUrl: params.openUrl))
}
}, setupProximityNotification: { [weak self] reset, messageId in
guard let strongSelf = self else {
@@ -328,11 +334,6 @@ public final class LocationViewController: ViewController {
})
})
}
- }, updateSendActionHighlight: { [weak self] highlighted in
- guard let strongSelf = self else {
- return
- }
- strongSelf.controllerNode.updateSendActionHighlight(highlighted)
}, sendLiveLocation: { [weak self] distance, extend, messageId in
guard let strongSelf = self else {
return
@@ -405,7 +406,6 @@ public final class LocationViewController: ViewController {
}
}
|> deliverOnMainQueue).start(next: { peer in
- let controller = ActionSheetController(presentationData: strongSelf.presentationData)
var title: String
if extend {
title = strongSelf.presentationData.strings.Map_LiveLocationExtendDescription
@@ -415,47 +415,32 @@ public final class LocationViewController: ViewController {
title = strongSelf.presentationData.strings.Map_LiveLocationPrivateNewDescription(peer.compactDisplayTitle).string
}
}
-
- let sendLiveLocationImpl: (Int32) -> Void = { [weak controller] period in
- controller?.dismissAnimated()
-
- if extend {
- if let messageId {
- let _ = context.engine.messages.requestEditLiveLocation(messageId: messageId, stop: false, coordinate: nil, heading: nil, proximityNotificationRadius: nil, extendPeriod: period).start()
+
+ let sourceView = strongSelf.controllerNode.liveLocationActionSourceView(extend: extend) ?? strongSelf.view
+ let controller = makeLiveLocationDurationContextController(
+ presentationData: strongSelf.presentationData,
+ sourceView: sourceView!,
+ title: title,
+ selectPeriod: { [weak self] period in
+ guard let strongSelf = self else {
+ return
}
- } else {
- let _ = (strongSelf.controllerNode.coordinate
- |> deliverOnMainQueue).start(next: { coordinate in
- params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period))
- })
- strongSelf.controllerNode.showAll()
+ if extend {
+ if let messageId {
+ let _ = context.engine.messages.requestEditLiveLocation(messageId: messageId, stop: false, coordinate: nil, heading: nil, proximityNotificationRadius: nil, extendPeriod: period).start()
+ }
+ } else {
+ let _ = (strongSelf.controllerNode.coordinate
+ |> deliverOnMainQueue).start(next: { coordinate in
+ params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period))
+ })
+
+ strongSelf.controllerNode.showAll()
+ }
}
- }
-
- controller.setItemGroups([
- ActionSheetItemGroup(items: [
- ActionSheetTextItem(title: title, font: .large, parseMarkdown: true),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForMinutes(15), color: .accent, action: {
- sendLiveLocationImpl(15 * 60)
- }),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(1), color: .accent, action: {
- sendLiveLocationImpl(60 * 60 - 1)
- }),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationForHours(8), color: .accent, action: {
- sendLiveLocationImpl(8 * 60 * 60)
- }),
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationIndefinite, color: .accent, action: {
- sendLiveLocationImpl(liveLocationIndefinitePeriod)
- })
- ]),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in
- controller?.dismissAnimated()
- })
- ])
- ])
- strongSelf.present(controller, in: .window(.root))
+ )
+ strongSelf.presentInGlobalOverlay(controller)
})
}
})
@@ -482,7 +467,40 @@ public final class LocationViewController: ViewController {
deinit {
self.presentationDataDisposable?.dispose()
}
-
+
+ private static func effectivePresentationData(_ presentationData: PresentationData, mapMode: LocationMapMode) -> PresentationData {
+ switch mapMode {
+ case .satellite, .hybrid:
+ if presentationData.theme.overallDarkAppearance {
+ return presentationData
+ }
+ let darkTheme = customizeDefaultDarkPresentationTheme(
+ theme: defaultDarkPresentationTheme,
+ editing: false,
+ title: nil,
+ accentColor: presentationData.theme.list.itemAccentColor,
+ backgroundColors: [],
+ bubbleColors: [],
+ animateBubbleColors: false,
+ wallpaper: nil,
+ baseColor: nil
+ )
+ return presentationData.withUpdated(theme: darkTheme)
+ case .map:
+ return presentationData
+ }
+ }
+
+ private func updateEffectivePresentationData(animated: Bool) {
+ let presentationData = LocationViewController.effectivePresentationData(self.basePresentationData, mapMode: self.currentMapMode)
+ self.presentationData = presentationData
+ self.statusBar.updateStatusBarStyle(presentationData.theme.rootController.statusBarStyle.style, animated: animated)
+
+ if self.isNodeLoaded {
+ self.controllerNode.updatePresentationData(presentationData)
+ }
+ }
+
public func goToUserLocation(visibleRadius: Double? = nil) {
}
@@ -502,7 +520,7 @@ public final class LocationViewController: ViewController {
return
}
- self.displayNode = LocationViewControllerNode(context: self.context, controller: self, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager, isStoryLocation: self.isStoryLocation, isPreview: self.isPreview)
+ self.displayNode = LocationViewControllerNode(context: self.context, controller: self, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager, isPreview: self.isPreview)
self.displayNodeDidLoad()
self.controllerNode.onAnnotationsReady = { [weak self] in
@@ -511,8 +529,6 @@ public final class LocationViewController: ViewController {
}
strongSelf.controllerNode.showAll()
}
-
- self.controllerNode.headerNode.mapNode.disableHorizontalTransitionGesture = self.isStoryLocation
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
@@ -534,4 +550,3 @@ public final class LocationViewController: ViewController {
super.dismiss(completion: completion)
}
}
-
diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift
index 5f8ec123c2..bb3bcafb7d 100644
--- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift
+++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift
@@ -22,6 +22,8 @@ import GlassControls
import BundleIconComponent
import EdgeEffect
import MultilineTextComponent
+import GlassBackgroundComponent
+import Weather
func getLocation(from message: EngineMessage) -> TelegramMediaMap? {
if let poll = message.media.first(where: { $0 is TelegramMediaPoll } ) as? TelegramMediaPoll, let map = poll.attachedMedia as? TelegramMediaMap {
@@ -57,9 +59,9 @@ public enum LocationViewEntryId: Hashable {
}
public enum LocationViewEntry: Comparable, Identifiable {
- case info(PresentationTheme, TelegramMediaMap, String?, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Bool)
+ case info(PresentationTheme, TelegramMediaMap, String?, Double?, ExpectedTravelTime, ExpectedTravelTime, Bool)
case toggleLiveLocation(PresentationTheme, String, String, Double?, Double?, Bool, EngineMessage.Id?)
- case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessage, Double?, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime, Int)
+ case liveLocation(PresentationTheme, PresentationDateTimeFormat, PresentationPersonNameOrder, EngineMessage, Double?, ExpectedTravelTime, ExpectedTravelTime, Int)
public var stableId: LocationViewEntryId {
switch self {
@@ -67,15 +69,15 @@ public enum LocationViewEntry: Comparable, Identifiable {
return .info
case let .toggleLiveLocation(_, _, _, _, _, additional, _):
return .toggleLiveLocation(additional)
- case let .liveLocation(_, _, _, message, _, _, _, _, _):
+ case let .liveLocation(_, _, _, message, _, _, _, _):
return .liveLocation(message.stableId)
}
}
public static func ==(lhs: LocationViewEntry, rhs: LocationViewEntry) -> Bool {
switch lhs {
- case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsHasEta):
- if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsHasEta) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsHasEta == rhsHasEta {
+ case let .info(lhsTheme, lhsLocation, lhsAddress, lhsDistance, lhsDrivingTime, lhsWalkingTime, lhsHasEta):
+ if case let .info(rhsTheme, rhsLocation, rhsAddress, rhsDistance, rhsDrivingTime, rhsWalkingTime, rhsHasEta) = rhs, lhsTheme === rhsTheme, lhsLocation.venue?.id == rhsLocation.venue?.id, lhsAddress == rhsAddress, lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsWalkingTime == rhsWalkingTime, lhsHasEta == rhsHasEta {
return true
} else {
return false
@@ -86,8 +88,8 @@ public enum LocationViewEntry: Comparable, Identifiable {
} else {
return false
}
- case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsDrivingTime, lhsTransitTime, lhsWalkingTime, lhsIndex):
- if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsDrivingTime, rhsTransitTime, rhsWalkingTime, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsTransitTime == rhsTransitTime, lhsWalkingTime == rhsWalkingTime, lhsIndex == rhsIndex {
+ case let .liveLocation(lhsTheme, lhsDateTimeFormat, lhsNameDisplayOrder, lhsMessage, lhsDistance, lhsDrivingTime, lhsWalkingTime, lhsIndex):
+ if case let .liveLocation(rhsTheme, rhsDateTimeFormat, rhsNameDisplayOrder, rhsMessage, rhsDistance, rhsDrivingTime, rhsWalkingTime, rhsIndex) = rhs, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameDisplayOrder == rhsNameDisplayOrder, areMessagesEqual(lhsMessage, rhsMessage), lhsDistance == rhsDistance, lhsDrivingTime == rhsDrivingTime, lhsWalkingTime == rhsWalkingTime, lhsIndex == rhsIndex {
return true
} else {
return false
@@ -113,11 +115,11 @@ public enum LocationViewEntry: Comparable, Identifiable {
case .liveLocation:
return true
}
- case let .liveLocation(_, _, _, _, _, _, _, _, lhsIndex):
+ case let .liveLocation(_, _, _, _, _, _, _, lhsIndex):
switch rhs {
case .info, .toggleLiveLocation:
return false
- case let .liveLocation(_, _, _, _, _, _, _, _, rhsIndex):
+ case let .liveLocation(_, _, _, _, _, _, _, rhsIndex):
return lhsIndex < rhsIndex
}
}
@@ -125,7 +127,7 @@ public enum LocationViewEntry: Comparable, Identifiable {
func item(context: AccountContext, presentationData: PresentationData, interaction: LocationViewInteraction?) -> ListViewItem {
switch self {
- case let .info(_, location, address, distance, drivingTime, transitTime, walkingTime, hasEta):
+ case let .info(_, location, address, distance, drivingTime, walkingTime, hasEta):
let addressString: String?
if let address = address {
addressString = address
@@ -138,12 +140,10 @@ public enum LocationViewEntry: Comparable, Identifiable {
} else {
distanceString = nil
}
- return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, hasEta: hasEta, action: {
+ return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, drivingTime: drivingTime, walkingTime: walkingTime, hasEta: hasEta, action: {
interaction?.goToCoordinate(location.coordinate)
}, drivingAction: {
interaction?.requestDirections(location, nil, .driving)
- }, transitAction: {
- interaction?.requestDirections(location, nil, .transit)
}, walkingAction: {
interaction?.requestDirections(location, nil, .walking)
})
@@ -164,7 +164,7 @@ public enum LocationViewEntry: Comparable, Identifiable {
icon = .liveLocation
}
- return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: !additional ? beginTimeAndTimeout : nil, action: {
+ return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: icon, isOpaque: false, beginTimeAndTimeout: !additional ? beginTimeAndTimeout : nil, action: {
if beginTimeAndTimeout != nil {
if let timeout, Int32(timeout) != liveLocationIndefinitePeriod {
if additional {
@@ -178,15 +178,14 @@ public enum LocationViewEntry: Comparable, Identifiable {
} else {
interaction?.sendLiveLocation(nil, false, nil)
}
- }, highlighted: { highlight in
- interaction?.updateSendActionHighlight(highlight)
+ }, highlighted: { _ in
})
- case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, _):
+ case let .liveLocation(_, dateTimeFormat, nameDisplayOrder, message, distance, drivingTime, walkingTime, _):
var title: String?
if let author = message.author {
title = author.displayTitle(strings: presentationData.strings, displayOrder: nameDisplayOrder)
}
- return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, drivingTime: drivingTime, transitTime: transitTime, walkingTime: walkingTime, action: {
+ return LocationLiveListItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: context, message: message, distance: distance, drivingTime: drivingTime, walkingTime: walkingTime, action: {
if let location = getLocation(from: message) {
interaction?.goToCoordinate(location.coordinate)
}
@@ -194,10 +193,6 @@ public enum LocationViewEntry: Comparable, Identifiable {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .driving)
}
- }, transitAction: {
- if let location = getLocation(from: message) {
- interaction?.requestDirections(location, title, .transit)
- }
}, walkingAction: {
if let location = getLocation(from: message) {
interaction?.requestDirections(location, title, .walking)
@@ -285,7 +280,6 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
private var subject: EngineMessage
private let interaction: LocationViewInteraction
private let locationManager: LocationManager
- private let isStoryLocation: Bool
private let isPreview: Bool
private var rightBarButtonAction: LocationViewRightBarButton = .none
@@ -295,11 +289,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
private let title = ComponentView()
private let listNode: ListView
+ let backgroundView = GlassBackgroundView()
let headerNode: LocationMapHeaderNode
private var enqueuedTransitions: [LocationViewTransaction] = []
private var disposable: Disposable?
+ private let weatherDisposable = MetaDisposable()
private var state: LocationViewState
private let statePromise: Promise
@@ -312,14 +308,14 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
var onAnnotationsReady: (() -> Void)?
private let travelDisposables = DisposableSet()
- private var travelTimes: [EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime)] = [:] {
+ private var travelTimes: [EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime)] = [:] {
didSet {
self.travelTimesPromise.set(.single(self.travelTimes))
}
}
- private let travelTimesPromise = Promise<[EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime)]>([:])
+ private let travelTimesPromise = Promise<[EngineMessage.Id: (Double, ExpectedTravelTime, ExpectedTravelTime)]>([:])
- init(context: AccountContext, controller: LocationViewController, presentationData: PresentationData, subject: EngineMessage, interaction: LocationViewInteraction, locationManager: LocationManager, isStoryLocation: Bool, isPreview: Bool) {
+ init(context: AccountContext, controller: LocationViewController, presentationData: PresentationData, subject: EngineMessage, interaction: LocationViewInteraction, locationManager: LocationManager, isPreview: Bool) {
self.context = context
self.controller = controller
self.presentationData = presentationData
@@ -327,14 +323,14 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
self.subject = subject
self.interaction = interaction
self.locationManager = locationManager
- self.isStoryLocation = isStoryLocation
self.isPreview = isPreview
self.state = LocationViewState()
self.statePromise = Promise(self.state)
self.listNode = ListViewImpl()
- self.listNode.backgroundColor = .clear //self.presentationData.theme.list.plainBackgroundColor
+ self.listNode.backgroundColor = .clear
+ self.listNode.limitHitTestToNodes = true
self.listNode.verticalScrollIndicatorColor = UIColor(white: 0.0, alpha: 0.3)
self.listNode.verticalScrollIndicatorFollowsOverscroll = true
self.listNode.accessibilityPageScrolledString = { row, count in
@@ -342,6 +338,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
}
var setupProximityNotificationImpl: ((Bool) -> Void)?
+ var weatherPressedImpl: (() -> Void)?
self.headerNode = LocationMapHeaderNode(
presentationData: presentationData,
glass: true,
@@ -351,6 +348,9 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
goToUserLocation: interaction.toggleTrackingMode,
setupProximityNotification: { reset in
setupProximityNotificationImpl?(reset)
+ },
+ weatherPressed: {
+ weatherPressedImpl?()
}
)
@@ -358,35 +358,42 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
+ self.addSubnode(self.headerNode)
if !self.isPreview {
self.addSubnode(self.listNode)
}
- self.addSubnode(self.headerNode)
let userLocation: Signal = .single(nil)
|> then(
throttledUserLocation(self.headerNode.mapNode.userLocation)
)
- var eta: Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> = .single((.calculating, .calculating, .calculating))
+ var eta: Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> = .single((.calculating, .calculating))
var address: Signal = .single(nil)
+ let subjectLocation = getLocation(from: subject)
+ let isStaticLocationView: Bool
+ if let subjectLocation {
+ isStaticLocationView = subjectLocation.liveBroadcastingTimeout == nil
+ } else {
+ isStaticLocationView = false
+ }
+
let locale = localeWithStrings(presentationData.strings)
- if let location = getLocation(from: subject), location.liveBroadcastingTimeout == nil {
- eta = .single((.calculating, .calculating, .calculating))
- |> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .transit), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking))
- |> mapToSignal { drivingTime, transitTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> in
+ if let location = subjectLocation, isStaticLocationView {
+ self.headerNode.mapNode.setMapCenter(coordinate: location.coordinate, span: LocationMapNode.viewMapSpan, animated: false)
+
+ eta = .single((.calculating, .calculating))
+ |> then(combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking))
+ |> mapToSignal { drivingTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> in
if case .calculating = drivingTime {
return .complete()
}
- if case .calculating = transitTime {
- return .complete()
- }
if case .calculating = walkingTime {
return .complete()
}
- return .single((drivingTime, transitTime, walkingTime))
+ return .single((drivingTime, walkingTime))
})
if let venue = location.venue, let venueAddress = venue.address, !venueAddress.isEmpty {
@@ -402,13 +409,21 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
}
}
- let liveLocations = context.engine.messages.topPeerActiveLiveLocationMessages(peerId: subject.id.peerId)
+ let actualLiveLocations = context.engine.messages.topPeerActiveLiveLocationMessages(peerId: subject.id.peerId)
|> map { _, messages -> [EngineMessage] in
return messages
}
+ let renderLiveLocations: Signal<[EngineMessage], NoError>
+ if isStaticLocationView {
+ renderLiveLocations = .single([])
+ |> then(actualLiveLocations)
+ } else {
+ renderLiveLocations = actualLiveLocations
+ }
+
setupProximityNotificationImpl = { reset in
- let _ = (liveLocations
+ let _ = (actualLiveLocations
|> take(1)
|> deliverOnMainQueue).start(next: { messages in
var ownMessageId: EngineMessage.Id?
@@ -424,15 +439,36 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
})
}
+ weatherPressedImpl = {
+ if let location = subjectLocation {
+ context.sharedContext.openExternalUrl(
+ context: context,
+ urlContext: .generic,
+ url: "https://weather.apple.com/?lat=\(location.latitude)&long=\(location.longitude)",
+ forceExternal: true,
+ presentationData: presentationData,
+ navigationController: nil,
+ dismissInput: {}
+ )
+ }
+ }
+
let previousState = Atomic(value: nil)
let previousUserAnnotation = Atomic(value: nil)
let previousAnnotations = Atomic<[LocationPinAnnotation]>(value: [])
let previousEntries = Atomic<[LocationViewEntry]?>(value: nil)
let previousHadTravelTimes = Atomic(value: false)
- let selfPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
+ let actualSelfPeer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
+ let renderSelfPeer: Signal
+ if isStaticLocationView {
+ renderSelfPeer = .single(nil)
+ |> then(actualSelfPeer)
+ } else {
+ renderSelfPeer = actualSelfPeer
+ }
- self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), selfPeer, liveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta, self.travelTimesPromise.get())
+ self.disposable = (combineLatest(self.presentationDataPromise.get(), self.statePromise.get(), renderSelfPeer, renderLiveLocations, self.headerNode.mapNode.userLocation, userLocation, address, eta, self.travelTimesPromise.get())
|> deliverOnMainQueue).start(next: { [weak self] presentationData, state, selfPeer, liveLocations, userLocation, distance, address, eta, travelTimes in
if let strongSelf = self, let location = getLocation(from: subject) {
var entries: [LocationViewEntry] = []
@@ -453,7 +489,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let subjectLocation = CLLocation(latitude: location.latitude, longitude: location.longitude)
let distance = userLocation.flatMap { subjectLocation.distance(from: $0) }
- entries.append(.info(presentationData.theme, location, address, distance, eta.0, eta.1, eta.2, !isStoryLocation))
+ entries.append(.info(presentationData.theme, location, address, distance, eta.0, eta.1, true))
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, location: location, queryId: nil, resultId: nil, forcedSelection: true))
} else {
@@ -529,7 +565,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
for message in effectiveLiveLocations {
if let location = getLocation(from: message) {
- if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, message.threadId != nil {
+ if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info, let threadId = message.threadId, threadId != 1 {
continue
}
@@ -555,60 +591,57 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
userAnnotation = LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, isSelf: true, heading: location.heading)
} else {
var drivingTime: ExpectedTravelTime = .unknown
- var transitTime: ExpectedTravelTime = .unknown
var walkingTime: ExpectedTravelTime = .unknown
if !isLocationView && message.author?.id != context.account.peerId {
- let signal = combineLatest(queue: Queue.mainQueue(), getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile), getExpectedTravelTime(coordinate: location.coordinate, transportType: .transit), getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking))
- |> mapToSignal { drivingTime, transitTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime, ExpectedTravelTime), NoError> in
+ let signal = combineLatest(
+ queue: Queue.mainQueue(),
+ getExpectedTravelTime(coordinate: location.coordinate, transportType: .automobile),
+ getExpectedTravelTime(coordinate: location.coordinate, transportType: .walking)
+ )
+ |> mapToSignal { drivingTime, walkingTime -> Signal<(ExpectedTravelTime, ExpectedTravelTime), NoError> in
if case .calculating = drivingTime {
return .complete()
}
- if case .calculating = transitTime {
- return .complete()
- }
if case .calculating = walkingTime {
return .complete()
}
-
- return .single((drivingTime, transitTime, walkingTime))
+ return .single((drivingTime, walkingTime))
}
- if let (previousTimestamp, maybeDrivingTime, maybeTransitTime, maybeWalkingTime) = travelTimes[message.id] {
+ if let (previousTimestamp, maybeDrivingTime, maybeWalkingTime) = travelTimes[message.id] {
drivingTime = maybeDrivingTime
- transitTime = maybeTransitTime
walkingTime = maybeWalkingTime
if timestamp > previousTimestamp + 60.0 {
- strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, transitTime, walkingTime in
+ strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, walkingTime in
guard let strongSelf = self else {
return
}
let timestamp = CACurrentMediaTime()
var travelTimes = strongSelf.travelTimes
- travelTimes[message.id] = (timestamp, drivingTime, transitTime, walkingTime)
+ travelTimes[message.id] = (timestamp, drivingTime, walkingTime)
strongSelf.travelTimes = travelTimes
}))
}
} else {
drivingTime = .calculating
- transitTime = .calculating
walkingTime = .calculating
- strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, transitTime, walkingTime in
+ strongSelf.travelDisposables.add(signal.start(next: { [weak self] drivingTime, walkingTime in
guard let strongSelf = self else {
return
}
let timestamp = CACurrentMediaTime()
var travelTimes = strongSelf.travelTimes
- travelTimes[message.id] = (timestamp, drivingTime, transitTime, walkingTime)
+ travelTimes[message.id] = (timestamp, drivingTime, walkingTime)
strongSelf.travelTimes = travelTimes
}))
}
}
annotations.append(LocationPinAnnotation(context: context, theme: presentationData.theme, message: message, selfPeer: selfPeer, isSelf: message.author?.id == context.account.peerId, heading: location.heading))
- entries.append(.liveLocation(presentationData.theme, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, message, distance, drivingTime, transitTime, walkingTime, index))
+ entries.append(.liveLocation(presentationData.theme, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, message, distance, drivingTime, walkingTime, index))
}
index += 1
}
@@ -738,14 +771,11 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
if !isPreview {
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
- guard let strongSelf = self, let (layout, navigationBarHeight) = strongSelf.validLayout, strongSelf.listNode.scrollEnabled else {
+ guard let self, self.listNode.scrollEnabled else {
return
}
- let overlap: CGFloat = 0.0
- 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, controlsBottomPadding: 0.0, offset: 0.0, size: headerFrame.size, transition: listTransition)
+ self.listOffset = max(0.0, offset)
+ self.updateHeader(transition: listTransition)
}
}
@@ -790,14 +820,25 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
self.locationManager.manager.startUpdatingHeading()
self.locationManager.manager.delegate = self
+
+ if !self.isPreview, let location = getLocation(from: subject) {
+ self.requestWeatherData(coordinate: location.coordinate)
+ }
}
deinit {
self.disposable?.dispose()
+ self.weatherDisposable.dispose()
self.travelDisposables.dispose()
self.locationManager.manager.stopUpdatingHeading()
}
+ override func didLoad() {
+ super.didLoad()
+
+ self.view.insertSubview(self.backgroundView, aboveSubview: self.headerNode.view)
+ }
+
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
if newHeading.headingAccuracy < 0.0 {
self.headerNode.mapNode.userHeading = nil
@@ -814,17 +855,70 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
self.presentationDataPromise.set(.single(presentationData))
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
- self.listNode.backgroundColor = .clear // self.presentationData.theme.list.plainBackgroundColor
+ self.listNode.backgroundColor = .clear
self.headerNode.updatePresentationData(self.presentationData)
+ if let (layout, navigationBarHeight) = self.validLayout {
+ self.containerLayoutUpdated(layout, navigationHeight: navigationBarHeight, transition: .immediate)
+ }
}
func updateState(_ f: (LocationViewState) -> LocationViewState) {
self.state = f(self.state)
self.statePromise.set(.single(self.state))
}
-
- func updateSendActionHighlight(_ highlighted: Bool) {
- self.headerNode.updateHighlight(highlighted)
+
+ private func requestWeatherData(coordinate: CLLocationCoordinate2D) {
+ self.weatherDisposable.set((Weather.requestWeatherData(context: self.context, location: coordinate)
+ |> deliverOnMainQueue).start(next: { [weak self] weatherData in
+ guard let self else {
+ return
+ }
+ if let weatherData {
+ self.headerNode.updateWeatherData(context: self.context, emoji: weatherData.emoji, temperature: stringForTemperature(weatherData.temperature), animated: true)
+ } else {
+ self.headerNode.clearWeatherData(animated: true)
+ }
+ }))
+ }
+
+ func updateHeader(transition: ContainedViewLayoutTransition) {
+ guard let (layout, navigationBarHeight) = self.validLayout else {
+ return
+ }
+ let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height))
+ transition.updateFrame(node: self.headerNode, frame: headerFrame)
+
+ let headerHeight: CGFloat
+ if self.isPreview {
+ headerHeight = layout.size.height
+ } else if let listOffset = self.listOffset {
+ headerHeight = max(0.0, listOffset)
+ } else {
+ headerHeight = headerFrame.height
+ }
+ let headerSize = CGSize(width: headerFrame.width, height: headerHeight)
+ self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationBarHeight, topPadding: 0.0, controlsTopPadding: 0.0, controlsBottomPadding: 6.0, offset: 0.0, size: headerSize, transition: transition)
+
+ let backgroundHeight = layout.size.height - headerHeight
+
+ let glassInset: CGFloat = 6.0
+ let backgroundSize = CGSize(width: layout.size.width - glassInset * 2.0, height: backgroundHeight)
+
+ let bottomCornerRadius = max(24.0, layout.deviceMetrics.screenCornerRadius) - 2.0
+
+ self.backgroundView.update(
+ size: backgroundSize,
+ cornerRadii: .init(
+ topLeft: 38.0,
+ topRight: 38.0,
+ bottomLeft: bottomCornerRadius,
+ bottomRight: bottomCornerRadius
+ ),
+ isDark: self.presentationData.theme.overallDarkAppearance,
+ tintColor: .init(kind: .panel),
+ transition: ComponentTransition(transition)
+ )
+ transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: glassInset, y: layout.size.height - backgroundSize.height - glassInset), size: backgroundSize))
}
private func enqueueTransition(_ transition: LocationViewTransaction) {
@@ -927,6 +1021,16 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
self.headerNode.mapNode.showAll()
}
+ func liveLocationActionSourceView(extend: Bool) -> UIView? {
+ var result: UIView?
+ self.listNode.forEachItemNode { itemNode in
+ if result == nil, let itemNode = itemNode as? LocationActionListItemNode {
+ result = itemNode.liveLocationContextSourceView(extend: extend)
+ }
+ }
+ return result
+ }
+
private func displayProximityAlertTooltip() {
guard let location = self.headerNode.proximityButtonFrame().flatMap({ frame -> CGRect in
return self.headerNode.view.convert(frame, to: nil)
@@ -962,7 +1066,6 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let isFirstLayout = self.validLayout == nil
self.validLayout = (layout, navigationHeight)
- let optionsHeight: CGFloat = 38.0
var actionHeight: CGFloat?
self.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? LocationActionListItemNode {
@@ -974,25 +1077,16 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
let overlap: CGFloat = 0.0
var topInset: CGFloat = layout.size.height - layout.intrinsicInsets.bottom - overlap
- if !self.isStoryLocation {
- topInset -= 100.0
- }
+ topInset -= 100.0
+
if let location = getLocation(from: self.subject), location.liveBroadcastingTimeout != nil {
topInset += 66.0
}
- let headerHeight: CGFloat
- if self.isPreview {
- headerHeight = layout.size.height
- } else if let listOffset = self.listOffset {
- headerHeight = max(0.0, listOffset + overlap)
- } else {
- headerHeight = topInset + overlap
+ if self.listOffset == nil {
+ self.listOffset = topInset
}
- let headerFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height))
- transition.updateFrame(node: self.headerNode, frame: headerFrame)
-
- self.headerNode.updateLayout(layout: layout, navigationBarHeight: navigationHeight, topPadding: self.state.displayingMapModeOptions ? optionsHeight : 0.0, controlsTopPadding: 0.0, controlsBottomPadding: 0.0, offset: 0.0, size: CGSize(width: headerFrame.width, height: headerHeight), transition: transition)
+ self.updateHeader(transition: transition)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
@@ -1005,7 +1099,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
if !self.isPreview {
let topEdgeEffectFrame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: 80.0))
transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
- self.topEdgeEffectView.update(content: self.headerNode.mapNode.mapMode == .map ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
+ self.topEdgeEffectView.update(content: !self.presentationData.theme.overallDarkAppearance ? self.presentationData.theme.list.plainBackgroundColor : .clear, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
if self.topEdgeEffectView.superview == nil {
self.view.addSubview(self.topEdgeEffectView)
}
diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD
index 8088d37de4..eb9d00e7bf 100644
--- a/submodules/MediaPickerUI/BUILD
+++ b/submodules/MediaPickerUI/BUILD
@@ -36,7 +36,6 @@ swift_library(
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
- "//submodules/WebSearchUI:WebSearchUI",
"//submodules/ChatMessageBackground:ChatMessageBackground",
"//submodules/SparseItemGrid:SparseItemGrid",
"//submodules/UndoUI:UndoUI",
@@ -57,6 +56,7 @@ swift_library(
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/GlassControls",
+ "//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/Components/BundleIconComponent",
diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift
index 254707296d..7d939ef00c 100644
--- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift
+++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift
@@ -152,6 +152,15 @@ func presentLegacyMediaPickerGallery(
legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
let paintStickersContext = LegacyPaintStickersContext(context: context)
+ paintStickersContext.presentMediaPickerSendActionMenu = makeLegacyMediaPickerSendActionMenuPresenter(context: context, presentationData: presentationData, presentInGlobalOverlay: { [weak legacyController] controller in
+ if let legacyController {
+ legacyController.presentInGlobalOverlay(controller)
+ } else if let mainWindow = context.sharedContext.mainWindow {
+ mainWindow.presentInGlobalOverlay(controller)
+ } else {
+ context.sharedContext.presentGlobalController(controller, nil)
+ }
+ })
paintStickersContext.captionPanelView = {
return getCaptionPanelView()
}
@@ -165,6 +174,9 @@ func presentLegacyMediaPickerGallery(
livePhotoButton.present = present
return livePhotoButton
}
+ paintStickersContext.photoToolbarView = { backButton, doneButton, solidBackground, hasSendStarsButton in
+ return makeMediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton)
+ }
paintStickersContext.editCover = { dimensions, completion in
editCover(dimensions, completion)
}
@@ -298,7 +310,7 @@ func presentLegacyMediaPickerGallery(
}
}
if !isScheduledMessages && peer != nil {
- model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model] item in
+ model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model, weak paintStickersContext] item, sourceView in
if let legacyController = legacyController, let item = item as? TGMediaPickerGalleryItem, let model = model, let selectionContext = selectionContext {
var effectiveHasSchedule = hasSchedule
@@ -344,37 +356,35 @@ func presentLegacyMediaPickerGallery(
let _ = (sendWhenOnlineAvailable
|> take(1)
|> deliverOnMainQueue).start(next: { sendWhenOnlineAvailable in
- let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
- let sheetController = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSendWhenOnline: sendWhenOnlineAvailable && effectiveHasSchedule, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer)
let dismissImpl = { [weak model] in
model?.dismiss(true, false)
dismissAll()
}
- sheetController.send = {
+ let send = {
completed(item.asset, false, nil, {
dismissImpl()
})
}
- sheetController.sendSilently = { [weak model] in
+ let sendSilently = { [weak model] in
model?.interfaceView.onDismiss()
completed(item.asset, true, nil, {
dismissImpl()
})
}
- sheetController.sendWhenOnline = {
+ let sendWhenOnline = {
completed(item.asset, false, scheduleWhenOnlineTimestamp, {
dismissImpl()
})
}
- sheetController.schedule = {
+ let schedule = {
presentSchedulePicker(true, { time, silentPosting in
completed(item.asset, silentPosting, time, {
dismissImpl()
})
})
}
- sheetController.sendWithTimer = {
+ let sendWithTimer = {
presentTimerPicker { time in
var items = selectionContext.selectedItems() ?? []
items.append(item.asset as Any)
@@ -388,6 +398,20 @@ func presentLegacyMediaPickerGallery(
})
}
}
+
+ if let sourceView, let paintStickersContext, paintStickersContext.presentMediaPickerSendActionMenu?(sourceView, hasSilentPosting, sendWhenOnlineAvailable && effectiveHasSchedule, effectiveHasSchedule, reminder, hasTimer, sendSilently, sendWhenOnline, schedule, sendWithTimer) == true {
+ let hapticFeedback = HapticFeedback()
+ hapticFeedback.impact()
+ return
+ }
+
+ let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
+ let sheetController = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSendWhenOnline: sendWhenOnlineAvailable && effectiveHasSchedule, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer)
+ sheetController.send = send
+ sheetController.sendSilently = sendSilently
+ sheetController.sendWhenOnline = sendWhenOnline
+ sheetController.schedule = schedule
+ sheetController.sendWithTimer = sendWithTimer
sheetController.customDismissBlock = { [weak legacySheetController] in
legacySheetController?.dismiss()
}
diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift b/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift
index e2b5f31a29..8f2d4df733 100644
--- a/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift
+++ b/submodules/MediaPickerUI/Sources/MediaGroupsContextMenuContent.swift
@@ -167,6 +167,7 @@ final class MediaGroupsContextMenuContent: ContextControllerItemsContent {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.scrollNode.clipsToBounds = false
+ self.scrollNode.view.scrollsToTop = false
super.init()
diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift
index 79dbd0c8c9..8171f784fc 100644
--- a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift
+++ b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift
@@ -470,7 +470,7 @@ public final class MediaGroupsScreen: ViewController, AttachmentContainable {
self.updateNavigationStack { current in
var mediaPickerContext: AttachmentMediaPickerContext?
if let first = current.first as? MediaPickerScreenImpl {
- mediaPickerContext = first.webSearchController?.mediaPickerContext ?? first.mediaPickerContext
+ mediaPickerContext = first.mediaPickerContext
}
return (current.filter { $0 !== self }, mediaPickerContext)
}
diff --git a/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift
index c48fc25276..a26d7c686a 100644
--- a/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift
+++ b/submodules/MediaPickerUI/Sources/MediaPickerManageNode.swift
@@ -15,7 +15,7 @@ final class MediaPickerManageNode: ASDisplayNode {
private let measureButtonNode: ImmediateTextNode
private let buttonNode: SolidRoundedButtonNode
- var pressed: () -> Void = {}
+ var pressed: (UIView) -> Void = { _ in }
override init() {
self.textNode = ImmediateTextNode()
@@ -33,7 +33,10 @@ final class MediaPickerManageNode: ASDisplayNode {
self.addSubnode(self.buttonNode)
self.buttonNode.pressed = { [weak self] in
- self?.pressed()
+ guard let self else {
+ return
+ }
+ self.pressed(self.buttonNode.view)
}
}
diff --git a/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift b/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift
new file mode 100644
index 0000000000..b7ae2d96a4
--- /dev/null
+++ b/submodules/MediaPickerUI/Sources/MediaPickerPhotoToolbarView.swift
@@ -0,0 +1,1187 @@
+import Foundation
+import UIKit
+import Display
+import LegacyComponents
+import AccountContext
+import TelegramPresentationData
+import ComponentFlow
+import GlassBackgroundComponent
+import GlassBarButtonComponent
+import PlainButtonComponent
+import BundleIconComponent
+
+private let toolbarButtonSide: CGFloat = 44.0
+private let toolbarSideButtonSide: CGFloat = 44.0
+private let centerButtonSpacing: CGFloat = 10.0
+
+private let toolbarTabOrder: [TGPhotoEditorTab] = [
+ .cropTab,
+ .stickerTab,
+ .paintTab,
+ .eraserTab,
+ .textTab,
+ .toolsTab,
+ .rotateTab,
+ .qualityTab,
+ .timerTab,
+ .mirrorTab,
+ .aspectRatioTab,
+ .tintTab,
+ .blurTab,
+ .curvesTab
+]
+
+private let dontHighlightOnSelectionTabs = Set([
+ TGPhotoEditorTab.rotateTab.rawValue,
+ TGPhotoEditorTab.stickerTab.rawValue,
+ TGPhotoEditorTab.textTab.rawValue,
+ TGPhotoEditorTab.qualityTab.rawValue,
+ TGPhotoEditorTab.timerTab.rawValue,
+ TGPhotoEditorTab.mirrorTab.rawValue,
+ TGPhotoEditorTab.aspectRatioTab.rawValue
+])
+
+
+private final class MediaPickerPhotoToolbarImageCache {
+ private var images: [String: UIImage] = [:]
+
+ func qualityIcon(isPhoto: Bool, highQuality: Bool, preset: Int, color: UIColor) -> UIImage? {
+ let key = "quality-\(isPhoto)-\(highQuality)-\(preset)-\(self.colorKey(color))"
+ if let image = self.images[key] {
+ return image
+ }
+ let image = generateQualityIcon(isPhoto: isPhoto, highQuality: highQuality, preset: preset, color: color)
+ self.images[key] = image
+ return image
+ }
+
+ func timerIcon(value: Int, color: UIColor) -> UIImage? {
+ let key = "timer-\(value)-\(self.colorKey(color))"
+ if let image = self.images[key] {
+ return image
+ }
+ let image = generateTimerIcon(value: value, color: color)
+ self.images[key] = image
+ return image
+ }
+
+ private func colorKey(_ color: UIColor) -> String {
+ var red: CGFloat = 0.0
+ var green: CGFloat = 0.0
+ var blue: CGFloat = 0.0
+ var alpha: CGFloat = 0.0
+ if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
+ return "\(Int(red * 255.0))-\(Int(green * 255.0))-\(Int(blue * 255.0))-\(Int(alpha * 255.0))"
+ }
+ var white: CGFloat = 0.0
+ if color.getWhite(&white, alpha: &alpha) {
+ return "\(Int(white * 255.0))-\(Int(alpha * 255.0))"
+ }
+ return color.description
+ }
+}
+
+private func generateQualityIcon(isPhoto: Bool, highQuality: Bool, preset: Int, color: UIColor) -> UIImage? {
+ let label: String
+ if isPhoto {
+ label = highQuality ? "HD" : "SD"
+ } else {
+ switch preset {
+ case 1:
+ label = "240"
+ case 2:
+ label = "360"
+ case 3:
+ label = "480"
+ case 4:
+ label = "720"
+ case 5:
+ label = "HD"
+ default:
+ label = "480"
+ }
+ }
+
+ let size = CGSize(width: isPhoto ? 24.0 : 28.0, height: 24.0)
+ let lineWidth = 2.0 - UIScreenPixel
+ let rect = CGRect(origin: .zero, size: size).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)
+
+ UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
+ guard let context = UIGraphicsGetCurrentContext() else {
+ UIGraphicsEndImageContext()
+ return nil
+ }
+
+ context.setStrokeColor(color.cgColor)
+ context.setLineWidth(lineWidth)
+ context.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 7.0).cgPath)
+ context.strokePath()
+
+ let font = Font.with(size: 11.0, design: .round, weight: .bold)
+ let attributes: [NSAttributedString.Key: Any] = [
+ .font: font,
+ .foregroundColor: color,
+ .kern: -0.7
+ ]
+ let textSize = label.size(withAttributes: attributes)
+ label.draw(
+ in: CGRect(
+ x: floorToScreenPixels((size.width - textSize.width) / 2.0) + UIScreenPixel,
+ y: 5.0,
+ width: textSize.width,
+ height: textSize.height
+ ),
+ withAttributes: attributes
+ )
+
+ let image = UIGraphicsGetImageFromCurrentImageContext()
+ UIGraphicsEndImageContext()
+ return image
+}
+
+private func generateTimerIcon(value: Int, color: UIColor) -> UIImage? {
+ let size = CGSize(width: 24.0, height: 24.0)
+ let lineWidth = 2.0 - UIScreenPixel
+
+ UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
+ guard let context = UIGraphicsGetCurrentContext() else {
+ UIGraphicsEndImageContext()
+ return nil
+ }
+
+ context.setStrokeColor(color.cgColor)
+ context.setLineWidth(lineWidth)
+ context.setLineCap(.round)
+ context.setLineJoin(.round)
+
+ let bodyRect = CGRect(x: 4.0, y: 5.0, width: 16.0, height: 16.0).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)
+ context.strokeEllipse(in: bodyRect)
+
+ context.move(to: CGPoint(x: 9.0, y: 2.5))
+ context.addLine(to: CGPoint(x: 15.0, y: 2.5))
+ context.strokePath()
+
+ context.move(to: CGPoint(x: 12.0, y: 5.0))
+ context.addLine(to: CGPoint(x: 12.0, y: 8.0))
+ context.strokePath()
+
+ if value == 0 {
+ context.move(to: CGPoint(x: 12.0, y: 13.0))
+ context.addLine(to: CGPoint(x: 12.0, y: 9.0))
+ context.move(to: CGPoint(x: 12.0, y: 13.0))
+ context.addLine(to: CGPoint(x: 15.0, y: 13.0))
+ context.strokePath()
+ } else {
+ let label = "\(value)"
+ let font = Font.with(size: 10.0, design: .round, weight: .semibold)
+ let attributes: [NSAttributedString.Key: Any] = [
+ .font: font,
+ .foregroundColor: color
+ ]
+ let textSize = label.size(withAttributes: attributes)
+ label.draw(
+ in: CGRect(
+ x: floorToScreenPixels((size.width - textSize.width) / 2.0),
+ y: 9.0,
+ width: textSize.width,
+ height: textSize.height
+ ),
+ withAttributes: attributes
+ )
+ }
+
+ let image = UIGraphicsGetImageFromCurrentImageContext()
+ UIGraphicsEndImageContext()
+ return image
+}
+
+private final class MediaPickerPhotoToolbarComponent: Component {
+ let context: AccountContext
+ let solidBackground: Bool
+ let backButtonType: TGPhotoEditorBackButton
+ let doneButtonType: TGPhotoEditorDoneButton
+ let currentTabs: TGPhotoEditorTab
+ let activeTab: TGPhotoEditorTab
+ let highlightedTabs: TGPhotoEditorTab
+ let disabledTabs: TGPhotoEditorTab
+ let qualityIsPhoto: Bool
+ let qualityHighQuality: Bool
+ let qualityPreset: Int
+ let timerValue: Int
+ let hasSendStarsButton: Bool
+ let sendPaidMessageStars: Int64
+ let editButtonsHidden: Bool
+ let editButtonsEnabled: Bool
+ let centerButtonsHidden: Bool
+ let allButtonsHidden: Bool
+ let cancelDoneButtonsHidden: Bool
+ let doneButtonEnabled: Bool
+ let interfaceOrientation: UIInterfaceOrientation
+ let bottomInset: CGFloat
+ let infoString: String?
+ let cancelPressed: (() -> Void)?
+ let donePressed: (() -> Void)?
+ let doneLongPressed: ((Any?) -> Void)?
+ let tabPressed: ((TGPhotoEditorTab) -> Void)?
+
+ init(
+ context: AccountContext,
+ solidBackground: Bool,
+ backButtonType: TGPhotoEditorBackButton,
+ doneButtonType: TGPhotoEditorDoneButton,
+ currentTabs: TGPhotoEditorTab,
+ activeTab: TGPhotoEditorTab,
+ highlightedTabs: TGPhotoEditorTab,
+ disabledTabs: TGPhotoEditorTab,
+ qualityIsPhoto: Bool,
+ qualityHighQuality: Bool,
+ qualityPreset: Int,
+ timerValue: Int,
+ hasSendStarsButton: Bool,
+ sendPaidMessageStars: Int64,
+ editButtonsHidden: Bool,
+ editButtonsEnabled: Bool,
+ centerButtonsHidden: Bool,
+ allButtonsHidden: Bool,
+ cancelDoneButtonsHidden: Bool,
+ doneButtonEnabled: Bool,
+ interfaceOrientation: UIInterfaceOrientation,
+ bottomInset: CGFloat,
+ infoString: String?,
+ cancelPressed: (() -> Void)?,
+ donePressed: (() -> Void)?,
+ doneLongPressed: ((Any?) -> Void)?,
+ tabPressed: ((TGPhotoEditorTab) -> Void)?
+ ) {
+ self.context = context
+ self.solidBackground = solidBackground
+ self.backButtonType = backButtonType
+ self.doneButtonType = doneButtonType
+ self.currentTabs = currentTabs
+ self.activeTab = activeTab
+ self.highlightedTabs = highlightedTabs
+ self.disabledTabs = disabledTabs
+ self.qualityIsPhoto = qualityIsPhoto
+ self.qualityHighQuality = qualityHighQuality
+ self.qualityPreset = qualityPreset
+ self.timerValue = timerValue
+ self.hasSendStarsButton = hasSendStarsButton
+ self.sendPaidMessageStars = sendPaidMessageStars
+ self.editButtonsHidden = editButtonsHidden
+ self.editButtonsEnabled = editButtonsEnabled
+ self.centerButtonsHidden = centerButtonsHidden
+ self.allButtonsHidden = allButtonsHidden
+ self.cancelDoneButtonsHidden = cancelDoneButtonsHidden
+ self.doneButtonEnabled = doneButtonEnabled
+ self.interfaceOrientation = interfaceOrientation
+ self.bottomInset = bottomInset
+ self.infoString = infoString
+ self.cancelPressed = cancelPressed
+ self.donePressed = donePressed
+ self.doneLongPressed = doneLongPressed
+ self.tabPressed = tabPressed
+ }
+
+ static func ==(lhs: MediaPickerPhotoToolbarComponent, rhs: MediaPickerPhotoToolbarComponent) -> Bool {
+ if lhs.context !== rhs.context {
+ return false
+ }
+ if lhs.solidBackground != rhs.solidBackground {
+ return false
+ }
+ if lhs.backButtonType != rhs.backButtonType {
+ return false
+ }
+ if lhs.doneButtonType != rhs.doneButtonType {
+ return false
+ }
+ if lhs.currentTabs != rhs.currentTabs {
+ return false
+ }
+ if lhs.activeTab != rhs.activeTab {
+ return false
+ }
+ if lhs.highlightedTabs != rhs.highlightedTabs {
+ return false
+ }
+ if lhs.disabledTabs != rhs.disabledTabs {
+ return false
+ }
+ if lhs.qualityIsPhoto != rhs.qualityIsPhoto {
+ return false
+ }
+ if lhs.qualityHighQuality != rhs.qualityHighQuality {
+ return false
+ }
+ if lhs.qualityPreset != rhs.qualityPreset {
+ return false
+ }
+ if lhs.timerValue != rhs.timerValue {
+ return false
+ }
+ if lhs.hasSendStarsButton != rhs.hasSendStarsButton {
+ return false
+ }
+ if lhs.sendPaidMessageStars != rhs.sendPaidMessageStars {
+ return false
+ }
+ if lhs.editButtonsHidden != rhs.editButtonsHidden {
+ return false
+ }
+ if lhs.editButtonsEnabled != rhs.editButtonsEnabled {
+ return false
+ }
+ if lhs.centerButtonsHidden != rhs.centerButtonsHidden {
+ return false
+ }
+ if lhs.allButtonsHidden != rhs.allButtonsHidden {
+ return false
+ }
+ if lhs.cancelDoneButtonsHidden != rhs.cancelDoneButtonsHidden {
+ return false
+ }
+ if lhs.doneButtonEnabled != rhs.doneButtonEnabled {
+ return false
+ }
+ if lhs.interfaceOrientation != rhs.interfaceOrientation {
+ return false
+ }
+ if lhs.bottomInset != rhs.bottomInset {
+ return false
+ }
+ if lhs.infoString != rhs.infoString {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView {
+ fileprivate let cancelButton = ComponentView()
+ fileprivate let doneButton = ComponentView()
+ private let doneButtonContextView = ContextControllerSourceView()
+
+ private let buttonsBackgroundView = GlassBackgroundView()
+ private let selectionView = UIView()
+ private let infoLabel = UILabel()
+ private let imageCache = MediaPickerPhotoToolbarImageCache()
+ private var centerButtonViews: [UInt: ComponentView] = [:]
+
+ private var component: MediaPickerPhotoToolbarComponent?
+ private var cancelPressed: (() -> Void)?
+ private var donePressed: (() -> Void)?
+ private var doneLongPressed: ((Any?) -> Void)?
+ private var tabPressed: ((TGPhotoEditorTab) -> Void)?
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+
+ self.clipsToBounds = false
+ self.addSubview(self.buttonsBackgroundView)
+
+ self.selectionView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.15)
+ self.selectionView.isUserInteractionEnabled = false
+ self.selectionView.alpha = 0.0
+
+ self.infoLabel.backgroundColor = .clear
+ self.infoLabel.textAlignment = .center
+ self.infoLabel.textColor = .white
+ self.infoLabel.font = Font.regular(13.0)
+ self.infoLabel.isUserInteractionEnabled = false
+ self.addSubview(self.infoLabel)
+
+ self.doneButtonContextView.beginDelay = 0.4
+ self.doneButtonContextView.activated = { [weak self] gesture, _ in
+ guard let self else {
+ gesture.cancel()
+ return
+ }
+ self.doneLongPressed?(self.doneButtonContextView)
+ }
+ self.doneButtonContextView.shouldBegin = { [weak self] _ in
+ guard let self, let component = self.component else {
+ return false
+ }
+ return self.doneLongPressed != nil && component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ preconditionFailure()
+ }
+
+ fileprivate var cancelButtonFrame: CGRect {
+ return self.cancelButton.view?.frame ?? .zero
+ }
+
+ fileprivate var doneButtonFrame: CGRect {
+ return self.doneButtonContextView.frame
+ }
+
+ fileprivate var doneButtonSourceView: UIView {
+ return self.doneButtonContextView
+ }
+
+ fileprivate func viewForTab(_ tab: TGPhotoEditorTab) -> UIView? {
+ return self.centerButtonViews[tab.rawValue]?.view
+ }
+
+ func update(component: MediaPickerPhotoToolbarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ self.component = component
+ self.cancelPressed = component.cancelPressed
+ self.donePressed = component.donePressed
+ self.doneLongPressed = component.doneLongPressed
+ self.tabPressed = component.tabPressed
+
+ //let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
+ let accentColor = UIColor(rgb: 0xffd300) //presentationData.theme.list.itemAccentColor
+ let selectedIconColor = UIColor.white //(rgb: 0xffd300)
+
+ self.backgroundColor = .clear //component.solidBackground ? UIColor(rgb: 0x000000, alpha: 0.55) : .clear
+
+ let cancelSize = self.cancelButton.update(
+ transition: transition,
+ component: AnyComponent(
+ GlassBarButtonComponent(
+ size: CGSize(width: toolbarButtonSide, height: toolbarButtonSide),
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ isEnabled: !component.cancelDoneButtonsHidden && !component.allButtonsHidden,
+ isVisible: true,
+ component: AnyComponentWithIdentity(
+ id: "close",
+ component: AnyComponent(
+ BundleIconComponent(
+ name: component.backButtonType == TGPhotoEditorBackButtonBack ? "Navigation/Back" : "Navigation/Close",
+ tintColor: .white
+ )
+ )
+ ),
+ action: { [weak self] _ in
+ self?.cancelPressed?()
+ }
+ )
+ ),
+ environment: {},
+ containerSize: CGSize(width: toolbarButtonSide, height: toolbarButtonSide)
+ )
+
+ let doneIconName: String
+ let doneBackgroundColor: UIColor? = UIColor(rgb: 0x0088ff)
+ var doneWidth: CGFloat = toolbarButtonSide
+ switch component.doneButtonType {
+ case TGPhotoEditorDoneButtonSend:
+ doneIconName = "Chat/Input/Text/SendIcon"
+ doneWidth = 46.0
+ case TGPhotoEditorDoneButtonSchedule:
+ doneIconName = "Chat/Input/ScheduleIcon"
+ doneWidth = 46.0
+ case TGPhotoEditorDoneButtonDone:
+ doneIconName = "Navigation/Done"
+ default:
+ doneIconName = "Navigation/Done"
+ }
+
+ let doneSize = self.doneButton.update(
+ transition: transition,
+ component: AnyComponent(
+ GlassBarButtonComponent(
+ size: CGSize(width: doneWidth, height: toolbarButtonSide),
+ backgroundColor: doneBackgroundColor,
+ isDark: true,
+ state: doneBackgroundColor != nil ? .tintedGlass : .glass,
+ isEnabled: component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden,
+ isVisible: true,
+ component: AnyComponentWithIdentity(
+ id: "done",
+ component: AnyComponent(
+ BundleIconComponent(
+ name: doneIconName,
+ tintColor: .white
+ )
+ )
+ ),
+ action: { [weak self] _ in
+ self?.donePressed?()
+ }
+ )
+ ),
+ environment: {},
+ containerSize: CGSize(width: doneWidth, height: toolbarButtonSide)
+ )
+
+// let doneContent: AnyComponentWithIdentity
+// let doneFixedSize: CGSize?
+// let doneAvailableSize: CGSize
+// if component.hasSendStarsButton {
+// let text = "\u{2b50}\u{fe0f} \(component.sendPaidMessageStars)"
+// doneContent = AnyComponentWithIdentity(
+// id: "stars-\(component.sendPaidMessageStars)",
+// component: AnyComponent(Text(text: text, font: Font.with(size: 17.0, design: .round, weight: .semibold, traits: .monospacedNumbers), color: .white))
+// )
+// doneFixedSize = nil
+// doneAvailableSize = CGSize(width: 180.0, height: toolbarSideButtonSide)
+// } else {
+// doneContent = self.sideButtonContent(iconName: self.doneIconName(component.doneButtonType), color: .white, id: "done-\(component.doneButtonType.rawValue)")
+// doneFixedSize = CGSize(width: toolbarSideButtonSide, height: toolbarSideButtonSide)
+// doneAvailableSize = CGSize(width: toolbarSideButtonSide, height: toolbarSideButtonSide)
+// }
+// let doneSize = self.doneButton.update(
+// content: doneContent,
+// fixedSize: doneFixedSize,
+// availableSize: doneAvailableSize,
+// isEnabled: component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden,
+// transition: transition
+// )
+
+ let sideAlpha = component.allButtonsHidden ? 0.0 : 1.0
+ let sideFrames = self.sideButtonFrames(availableSize: availableSize, cancelSize: cancelSize, doneSize: doneSize, component: component)
+ if let cancelButtonView = self.cancelButton.view {
+ if cancelButtonView.superview == nil {
+ self.addSubview(cancelButtonView)
+ }
+ transition.setAlpha(view: cancelButtonView, alpha: sideAlpha)
+ transition.setFrame(view: cancelButtonView, frame: sideFrames.cancel)
+ }
+ if let doneButtonView = self.doneButton.view {
+ if self.doneButtonContextView.superview == nil {
+ self.addSubview(self.doneButtonContextView)
+ }
+ if doneButtonView.superview !== self.doneButtonContextView {
+ self.doneButtonContextView.addSubview(doneButtonView)
+ }
+ self.doneButtonContextView.targetViewForActivationProgress = doneButtonView
+ self.doneButtonContextView.isGestureEnabled = component.doneLongPressed != nil && component.doneButtonEnabled && !component.cancelDoneButtonsHidden && !component.allButtonsHidden
+ transition.setAlpha(view: self.doneButtonContextView, alpha: sideAlpha * (component.doneButtonEnabled ? 1.0 : 0.2))
+ transition.setFrame(view: self.doneButtonContextView, frame: sideFrames.done)
+ transition.setFrame(view: doneButtonView, frame: CGRect(origin: .zero, size: sideFrames.done.size))
+ }
+
+ self.updateCenterButtons(
+ component: component,
+ availableSize: availableSize,
+ doneSize: doneSize,
+ selectedIconColor: selectedIconColor,
+ accentColor: accentColor,
+ transition: transition
+ )
+
+ self.updateInfoLabel(component: component, availableSize: availableSize, transition: transition)
+
+ return availableSize
+ }
+
+ private func doneIconName(_ type: TGPhotoEditorDoneButton) -> String {
+ switch type.rawValue {
+ case 1:
+ return "Editor/Commit"
+ case 2:
+ return "Editor/Commit"
+ case 3:
+ return "PhotoPickerSendIcon"
+ default:
+ return "PhotoPickerSendIcon"
+ }
+ }
+
+ private func sideButtonFrames(availableSize: CGSize, cancelSize: CGSize, doneSize: CGSize, component: MediaPickerPhotoToolbarComponent) -> (cancel: CGRect, done: CGRect) {
+ let sideInset: CGFloat = 26.0
+ if availableSize.width > availableSize.height {
+ let cancelFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: cancelSize)
+ let doneFrame: CGRect
+ if component.hasSendStarsButton {
+ doneFrame = CGRect(x: availableSize.width - doneSize.width - 2.0, y: 0.0, width: doneSize.width, height: doneSize.height)
+ } else {
+ doneFrame = CGRect(x: availableSize.width - doneSize.width - sideInset, y: 0.0, width: doneSize.width, height: doneSize.height)
+ }
+ return (cancelFrame, doneFrame)
+ } else {
+ let offset: CGFloat = component.interfaceOrientation == .landscapeLeft ? availableSize.width - toolbarSideButtonSide : 0.0
+ let cancelFrame = CGRect(x: offset, y: availableSize.height - toolbarSideButtonSide, width: toolbarSideButtonSide, height: toolbarSideButtonSide)
+ let doneFrame = CGRect(x: offset, y: 0.0, width: toolbarSideButtonSide, height: toolbarSideButtonSide)
+ return (cancelFrame, doneFrame)
+ }
+ }
+
+ private func updateCenterButtons(component: MediaPickerPhotoToolbarComponent, availableSize: CGSize, doneSize: CGSize, selectedIconColor: UIColor, accentColor: UIColor, transition: ComponentTransition) {
+ let tabs = toolbarTabOrder.filter { component.currentTabs.contains($0) }
+ let visibleRawValues = Set(tabs.map { $0.rawValue })
+
+ for (rawValue, buttonView) in Array(self.centerButtonViews) {
+ if !visibleRawValues.contains(rawValue) {
+ if let view = buttonView.view {
+ transition.setAlpha(view: view, alpha: 0.0, completion: { _ in
+ view.removeFromSuperview()
+ })
+ }
+ self.centerButtonViews.removeValue(forKey: rawValue)
+ }
+ }
+
+ guard !tabs.isEmpty else {
+ transition.setAlpha(view: self.buttonsBackgroundView, alpha: 0.0)
+ transition.setAlpha(view: self.selectionView, alpha: 0.0)
+ self.buttonsBackgroundView.isUserInteractionEnabled = false
+ return
+ }
+
+ let buttonFrames = self.centerButtonFrames(tabs: tabs, availableSize: availableSize, doneSize: doneSize, component: component)
+ var backgroundFrame = CGRect.null
+ for tab in tabs {
+ if let frame = buttonFrames[tab.rawValue] {
+ backgroundFrame = backgroundFrame.union(frame)
+ }
+ }
+ if backgroundFrame.isNull {
+ transition.setAlpha(view: self.buttonsBackgroundView, alpha: 0.0)
+ transition.setAlpha(view: self.selectionView, alpha: 0.0)
+ self.buttonsBackgroundView.isUserInteractionEnabled = false
+ return
+ }
+ backgroundFrame = CGRect(
+ x: floorToScreenPixels(backgroundFrame.minX),
+ y: floorToScreenPixels(backgroundFrame.minY),
+ width: ceil(backgroundFrame.width),
+ height: ceil(backgroundFrame.height)
+ )
+
+ let centerHidden = component.allButtonsHidden || component.centerButtonsHidden || component.editButtonsHidden
+ let centerAlpha: CGFloat = centerHidden ? 0.0 : (component.editButtonsEnabled ? 1.0 : 0.2)
+ self.buttonsBackgroundView.isUserInteractionEnabled = !centerHidden && component.editButtonsEnabled
+ transition.setAlpha(view: self.buttonsBackgroundView, alpha: centerAlpha)
+ transition.setFrame(view: self.buttonsBackgroundView, frame: backgroundFrame)
+
+ let minSide = min(backgroundFrame.width, backgroundFrame.height)
+ self.buttonsBackgroundView.update(size: backgroundFrame.size, cornerRadius: minSide * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, isVisible: !centerHidden, transition: transition)
+
+ if self.selectionView.superview == nil {
+ self.buttonsBackgroundView.contentView.insertSubview(self.selectionView, at: 0)
+ } else {
+ self.buttonsBackgroundView.contentView.sendSubviewToBack(self.selectionView)
+ }
+
+ var selectionFrame: CGRect?
+
+ for tab in tabs {
+ guard let buttonFrame = buttonFrames[tab.rawValue] else {
+ continue
+ }
+
+ let rawValue = tab.rawValue
+ let buttonView: ComponentView
+ if let current = self.centerButtonViews[rawValue] {
+ buttonView = current
+ } else {
+ buttonView = ComponentView()
+ self.centerButtonViews[rawValue] = buttonView
+ }
+
+ let isDisabled = component.disabledTabs.contains(tab)
+ let isHighlighted = component.highlightedTabs.contains(tab)
+ let selectedVisual = component.activeTab == tab && !dontHighlightOnSelectionTabs.contains(rawValue)
+ let iconColor: UIColor
+ if selectedVisual {
+ iconColor = selectedIconColor
+ } else if isHighlighted && !isDisabled {
+ iconColor = accentColor
+ } else {
+ iconColor = .white
+ }
+
+ let content: AnyComponent
+ switch tab {
+ case .qualityTab:
+ content = AnyComponent(
+ Image(
+ image: self.imageCache.qualityIcon(
+ isPhoto: component.qualityIsPhoto,
+ highQuality: component.qualityHighQuality,
+ preset: component.qualityPreset,
+ color: iconColor
+ ),
+ size: CGSize(width: 28.0, height: 22.0),
+ contentMode: .center
+ )
+ )
+ case .timerTab:
+ content = AnyComponent(
+ Image(
+ image: self.imageCache.timerIcon(value: component.timerValue, color: iconColor),
+ size: CGSize(width: 24.0, height: 24.0),
+ contentMode: .center
+ )
+ )
+ default:
+ content = AnyComponent(
+ BundleIconComponent(
+ name: self.iconName(tab),
+ tintColor: iconColor,
+ maxSize: CGSize(width: 28.0, height: 28.0)
+ )
+ )
+ }
+ let buttonSize = buttonView.update(
+ transition: transition,
+ component: AnyComponent(
+ PlainButtonComponent(
+ content: content,
+ minSize: CGSize(width: toolbarButtonSide, height: toolbarButtonSide),
+ action: { [weak self] in
+ self?.tabPressed?(tab)
+ },
+ isEnabled: component.editButtonsEnabled && !isDisabled,
+ animateAlpha: true,
+ animateScale: false,
+ animateContents: false
+ )
+ ),
+ environment: {},
+ containerSize: CGSize(width: toolbarButtonSide, height: toolbarButtonSide)
+ )
+
+ if let view = buttonView.view {
+ if view.superview == nil {
+ self.buttonsBackgroundView.contentView.addSubview(view)
+ }
+ let localFrame = CGRect(
+ x: buttonFrame.minX - backgroundFrame.minX,
+ y: buttonFrame.minY - backgroundFrame.minY,
+ width: buttonSize.width,
+ height: buttonSize.height
+ )
+ if selectedVisual {
+ let selectionSide = max(0.0, min(backgroundFrame.width, backgroundFrame.height) - 6.0)
+ selectionFrame = CGRect(
+ x: floorToScreenPixels(localFrame.midX - selectionSide / 2.0),
+ y: floorToScreenPixels(localFrame.midY - selectionSide / 2.0),
+ width: selectionSide,
+ height: selectionSide
+ )
+ }
+ transition.setFrame(view: view, frame: localFrame)
+ transition.setAlpha(view: view, alpha: isDisabled ? 0.2 : 1.0)
+ }
+ }
+
+ if let selectionFrame = selectionFrame {
+ self.selectionView.layer.cornerRadius = selectionFrame.width * 0.5
+ transition.setFrame(view: self.selectionView, frame: selectionFrame)
+ transition.setAlpha(view: self.selectionView, alpha: centerHidden ? 0.0 : 1.0)
+ } else {
+ transition.setAlpha(view: self.selectionView, alpha: 0.0)
+ }
+ }
+
+ private func iconName(_ tab: TGPhotoEditorTab) -> String {
+ switch tab {
+ case .cropTab:
+ return "Media Editor/Crop"
+ case .toolsTab:
+ return "Media Editor/Adjustments"
+ case .rotateTab:
+ return "Editor/Rotate"
+ case .paintTab:
+ return "Media Editor/Pencil"
+ case .stickerTab:
+ return "Media Editor/AddSticker"
+ case .textTab:
+ return "Media Editor/AddText"
+ case .eraserTab:
+ return "Editor/Eraser"
+ case .mirrorTab:
+ return "Media Editor/Mirror"
+ case .aspectRatioTab:
+ return "Editor/AspectRatio"
+ case .tintTab:
+ return "Media Editor/Tint"
+ case .blurTab:
+ return "Media Editor/Blur"
+ case .curvesTab:
+ return "Media Editor/Curves"
+ default:
+ return "Media Editor/Crop"
+ }
+ }
+
+ private func centerButtonFrames(tabs: [TGPhotoEditorTab], availableSize: CGSize, doneSize: CGSize, component: MediaPickerPhotoToolbarComponent) -> [UInt: CGRect] {
+ var result: [UInt: CGRect] = [:]
+ let count = tabs.count
+ guard count != 0 else {
+ return result
+ }
+
+ let totalLength = CGFloat(count) * toolbarButtonSide + CGFloat(count - 1) * centerButtonSpacing
+ let step = toolbarButtonSide + centerButtonSpacing
+
+ if availableSize.width > availableSize.height {
+ let leftEdge = toolbarSideButtonSide
+ let rightEdge: CGFloat = component.hasSendStarsButton ? doneSize.width + 2.0 : toolbarSideButtonSide
+ let availableWidth = availableSize.width - leftEdge - rightEdge
+ let startX = floorToScreenPixels(leftEdge + (availableWidth - totalLength) / 2.0)
+
+ for i in 0 ..< count {
+ result[tabs[i].rawValue] = CGRect(
+ x: startX + CGFloat(i) * step,
+ y: 0.0,
+ width: toolbarButtonSide,
+ height: toolbarButtonSide
+ )
+ }
+ } else {
+ let x: CGFloat
+ if component.interfaceOrientation == .landscapeLeft {
+ x = availableSize.width - toolbarButtonSide - 8.0
+ } else {
+ x = 8.0
+ }
+
+ let topInset = toolbarSideButtonSide
+ let bottomInset = toolbarSideButtonSide
+ let availableHeight = availableSize.height - topInset - bottomInset
+ let startY = floorToScreenPixels(topInset + (availableHeight - totalLength) / 2.0)
+
+ for i in 0 ..< count {
+ result[tabs[i].rawValue] = CGRect(
+ x: x,
+ y: startY + CGFloat(i) * step,
+ width: toolbarButtonSide,
+ height: toolbarButtonSide
+ )
+ }
+ }
+
+ return result
+ }
+
+ private func updateInfoLabel(component: MediaPickerPhotoToolbarComponent, availableSize: CGSize, transition: ComponentTransition) {
+ self.infoLabel.text = component.infoString
+ self.infoLabel.isHidden = component.infoString == nil
+ guard component.infoString != nil else {
+ return
+ }
+
+ if availableSize.width > availableSize.height {
+ self.infoLabel.transform = .identity
+ transition.setFrame(view: self.infoLabel, frame: CGRect(x: toolbarSideButtonSide + 10.0, y: 0.0, width: availableSize.width - (toolbarSideButtonSide + 10.0) * 2.0, height: toolbarSideButtonSide))
+ } else {
+ let bounds = CGRect(x: 0.0, y: 0.0, width: availableSize.height - (toolbarSideButtonSide + 10.0) * 2.0, height: availableSize.width)
+ self.infoLabel.bounds = bounds
+ self.infoLabel.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0)
+ if component.interfaceOrientation == .landscapeLeft {
+ self.infoLabel.transform = CGAffineTransform(rotationAngle: .pi / 2.0)
+ } else if component.interfaceOrientation == .landscapeRight {
+ self.infoLabel.transform = CGAffineTransform(rotationAngle: -.pi / 2.0)
+ } else {
+ self.infoLabel.transform = .identity
+ }
+ }
+ }
+ }
+
+ func makeView() -> View {
+ return View(frame: .zero)
+ }
+
+ 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 func makeMediaPickerPhotoToolbarView(context: AccountContext, backButton: TGPhotoEditorBackButton, doneButton: TGPhotoEditorDoneButton, solidBackground: Bool, hasSendStarsButton: Bool) -> (UIView & TGPhotoToolbarViewProtocol)? {
+ return MediaPickerPhotoToolbarView(context: context, backButton: backButton, doneButton: doneButton, solidBackground: solidBackground, hasSendStarsButton: hasSendStarsButton)
+}
+
+final class MediaPickerPhotoToolbarView: UIView, TGPhotoToolbarViewProtocol {
+ private let context: AccountContext
+ private let solidBackground: Bool
+ private let hasSendStarsButton: Bool
+ private let rootView = ComponentView()
+
+ private var transitionedOut = false
+
+ var cancelPressed: (() -> Void)? {
+ didSet {
+ self.update(transition: .immediate)
+ }
+ }
+ var donePressed: (() -> Void)? {
+ didSet {
+ self.update(transition: .immediate)
+ }
+ }
+ var doneLongPressed: ((Any?) -> Void)? {
+ didSet {
+ self.update(transition: .immediate)
+ }
+ }
+ var tabPressed: ((TGPhotoEditorTab) -> Void)? {
+ didSet {
+ self.update(transition: .immediate)
+ }
+ }
+
+ var interfaceOrientation: UIInterfaceOrientation = .portrait {
+ didSet {
+ self.update(transition: .immediate)
+ }
+ }
+
+ var bottomInset: CGFloat = 0.0
+
+ var backButtonType: TGPhotoEditorBackButton {
+ didSet {
+ self.update(transition: .immediate)
+ }
+ }
+
+ var doneButtonType: TGPhotoEditorDoneButton {
+ didSet {
+ self.update(transition: .immediate)
+ }
+ }
+
+ var sendPaidMessageStars: Int64 = 0 {
+ didSet {
+ self.update(transition: ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut)))
+ }
+ }
+
+ private(set) var currentTabs: TGPhotoEditorTab = []
+ private var activeTab: TGPhotoEditorTab = []
+ private var highlightedTabs: TGPhotoEditorTab = []
+ private var disabledTabs: TGPhotoEditorTab = []
+ private var editButtonsHidden = false
+ private var editButtonsEnabled = true
+ private var centerButtonsHidden = false
+ private var allButtonsHidden = false
+ private var cancelDoneButtonsHidden = false
+ private var doneButtonEnabled = true
+ private var qualityIsPhoto = false
+ private var qualityHighQuality = false
+ private var qualityPreset = 3
+ private var timerValue = 0
+ private var infoString: String?
+
+ var doneButton: UIView {
+ return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.doneButtonSourceView ?? UIView()
+ }
+
+ var cancelButtonFrame: CGRect {
+ return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.cancelButtonFrame ?? .zero
+ }
+
+ var doneButtonFrame: CGRect {
+ return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.doneButtonFrame ?? .zero
+ }
+
+ init(context: AccountContext, backButton: TGPhotoEditorBackButton, doneButton: TGPhotoEditorDoneButton, solidBackground: Bool, hasSendStarsButton: Bool) {
+ self.context = context
+ self.backButtonType = backButton
+ self.doneButtonType = doneButton
+ self.solidBackground = solidBackground
+ self.hasSendStarsButton = hasSendStarsButton
+
+ super.init(frame: .zero)
+
+ self.clipsToBounds = false
+ self.backgroundColor = .clear
+ self.update(transition: .immediate)
+ }
+
+ required init?(coder: NSCoder) {
+ preconditionFailure()
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ self.update(transition: .immediate)
+ self.updateRootFrame(transition: .immediate)
+ }
+
+ func transitionIn(animated: Bool) {
+ self.transitionIn(animated: animated, transparent: false)
+ }
+
+ func transitionIn(animated: Bool, transparent: Bool) {
+ self.transitionedOut = false
+ self.isHidden = false
+ if !transparent {
+ self.backgroundColor = UIColor(rgb: 0x000000)
+ }
+
+ let transition = animated ? ComponentTransition(animation: .curve(duration: 0.3, curve: .easeInOut)) : .immediate
+ self.updateRootFrame(transition: transition)
+
+ if !transparent {
+ self.backgroundColor = .clear
+ }
+ }
+
+ func transitionOut(animated: Bool) {
+ self.transitionOut(animated: animated, transparent: false, hideOnCompletion: false)
+ }
+
+ func transitionOut(animated: Bool, transparent: Bool, hideOnCompletion: Bool) {
+ self.transitionedOut = true
+ if !transparent {
+ self.backgroundColor = UIColor(rgb: 0x000000)
+ }
+
+ let duration: Double = animated ? 0.3 : 0.0
+ let transition = animated ? ComponentTransition(animation: .curve(duration: duration, curve: .easeInOut)) : .immediate
+ self.updateRootFrame(transition: transition)
+
+ if hideOnCompletion {
+ DispatchQueue.main.asyncAfter(deadline: .now() + duration) { [weak self] in
+ self?.isHidden = true
+ }
+ }
+ }
+
+ func setDoneButtonEnabled(_ enabled: Bool, animated: Bool) {
+ self.doneButtonEnabled = enabled
+ self.update(transition: self.transition(animated: animated))
+ }
+
+ func setEditButtonsEnabled(_ enabled: Bool, animated: Bool) {
+ self.editButtonsEnabled = enabled
+ self.update(transition: self.transition(animated: animated))
+ }
+
+ func setEditButtonsHidden(_ hidden: Bool, animated: Bool) {
+ self.editButtonsHidden = hidden
+ self.update(transition: self.transition(animated: animated))
+ }
+
+ func setEditButtonsHighlighted(_ buttons: TGPhotoEditorTab) {
+ self.highlightedTabs = buttons
+ self.update(transition: .immediate)
+ }
+
+ func setEditButtonsDisabled(_ buttons: TGPhotoEditorTab) {
+ self.disabledTabs = buttons
+ self.update(transition: .immediate)
+ }
+
+ func setCenterButtonsHidden(_ hidden: Bool, animated: Bool) {
+ self.centerButtonsHidden = hidden
+ self.update(transition: self.transition(animated: animated))
+ }
+
+ func setAllButtonsHidden(_ hidden: Bool, animated: Bool) {
+ self.allButtonsHidden = hidden
+ self.update(transition: self.transition(animated: animated))
+ }
+
+ func setCancelDoneButtonsHidden(_ hidden: Bool, animated: Bool) {
+ self.cancelDoneButtonsHidden = hidden
+ self.update(transition: self.transition(animated: animated))
+ }
+
+ func setToolbarTabs(_ tabs: TGPhotoEditorTab, animated: Bool) {
+ self.currentTabs = tabs
+ self.update(transition: self.transition(animated: animated))
+ }
+
+ func setActiveTab(_ tab: TGPhotoEditorTab) {
+ self.activeTab = tab
+ self.update(transition: .spring(duration: 0.4))
+ }
+
+ func setQualityButtonIsPhoto(_ isPhoto: Bool, highQuality: Bool, videoPreset: Int) {
+ self.qualityIsPhoto = isPhoto
+ self.qualityHighQuality = highQuality
+ self.qualityPreset = videoPreset
+ self.update(transition: .immediate)
+ }
+
+ func setTimerButtonValue(_ value: Int) {
+ self.timerValue = value
+ self.update(transition: .immediate)
+ }
+
+ func setInfoString(_ string: String?) {
+ self.infoString = string
+ self.update(transition: .immediate)
+ }
+
+ @objc(viewForTab:)
+ func view(for tab: TGPhotoEditorTab) -> UIView? {
+ return (self.rootView.view as? MediaPickerPhotoToolbarComponent.View)?.viewForTab(tab)
+ }
+
+ private func transition(animated: Bool) -> ComponentTransition {
+ if animated {
+ return ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))
+ } else {
+ return .immediate
+ }
+ }
+
+ private func update(transition: ComponentTransition) {
+ let size = self.bounds.size
+ let _ = self.rootView.update(
+ transition: transition,
+ component: AnyComponent(
+ MediaPickerPhotoToolbarComponent(
+ context: self.context,
+ solidBackground: self.solidBackground,
+ backButtonType: self.backButtonType,
+ doneButtonType: self.doneButtonType,
+ currentTabs: self.currentTabs,
+ activeTab: self.activeTab,
+ highlightedTabs: self.highlightedTabs,
+ disabledTabs: self.disabledTabs,
+ qualityIsPhoto: self.qualityIsPhoto,
+ qualityHighQuality: self.qualityHighQuality,
+ qualityPreset: self.qualityPreset,
+ timerValue: self.timerValue,
+ hasSendStarsButton: self.hasSendStarsButton,
+ sendPaidMessageStars: self.sendPaidMessageStars,
+ editButtonsHidden: self.editButtonsHidden,
+ editButtonsEnabled: self.editButtonsEnabled,
+ centerButtonsHidden: self.centerButtonsHidden,
+ allButtonsHidden: self.allButtonsHidden,
+ cancelDoneButtonsHidden: self.cancelDoneButtonsHidden,
+ doneButtonEnabled: self.doneButtonEnabled,
+ interfaceOrientation: self.interfaceOrientation,
+ bottomInset: self.bottomInset,
+ infoString: self.infoString,
+ cancelPressed: self.cancelPressed,
+ donePressed: self.donePressed,
+ doneLongPressed: self.doneLongPressed,
+ tabPressed: self.tabPressed
+ )
+ ),
+ environment: {},
+ forceUpdate: true,
+ containerSize: size
+ )
+
+ if let view = self.rootView.view {
+ if view.superview == nil {
+ self.addSubview(view)
+ }
+ self.updateRootFrame(transition: transition)
+ }
+ }
+
+ private func updateRootFrame(transition: ComponentTransition) {
+ guard let view = self.rootView.view else {
+ return
+ }
+
+ var frame = CGRect(origin: .zero, size: self.bounds.size)
+ if self.transitionedOut {
+ if self.bounds.width > self.bounds.height {
+ frame.origin.y = self.bounds.height
+ } else if self.interfaceOrientation == .landscapeLeft {
+ frame.origin.x = -self.bounds.width
+ } else {
+ frame.origin.x = self.bounds.width
+ }
+ }
+ transition.setFrame(view: view, frame: frame)
+ }
+}
diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift
index 142ad3d7dc..a95317f79d 100644
--- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift
+++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift
@@ -15,7 +15,6 @@ import LegacyComponents
import LegacyMediaPickerUI
import AttachmentUI
import ContextUI
-import WebSearchUI
import SparseItemGrid
import UndoUI
import PresentationDataUtils
@@ -192,7 +191,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
case addImage
case cover
case createSticker
- case createAvatar
+ case createAvatar(mode: PeerType)
case poll(mode: PollMode, asFile: Bool)
}
@@ -244,12 +243,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
private let moreButtonNode: MoreButtonNode
private let selectedButtonNode: SelectedButtonNode
- public weak var webSearchController: WebSearchController?
-
public var openCamera: ((Any?) -> Void)?
public var presentSchedulePicker: (Bool, @escaping (Int32, Bool) -> Void) -> Void = { _, _ in }
public var presentTimerPicker: (@escaping (Int32) -> Void) -> Void = { _ in }
- public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in }
public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil }
public var openBoost: () -> Void = { }
@@ -705,8 +701,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
var useModernCamera = false
if case .assets(nil, .default) = controller.subject {
useLegacyCamera = true
- } else if case .assets(nil, let mode) = controller.subject, [.createSticker, .createAvatar].contains(mode) {
- useModernCamera = true
+ } else if case .assets(nil, let mode) = controller.subject {
+ switch mode {
+ case .createSticker, .createAvatar:
+ useModernCamera = true
+ default:
+ break
+ }
}
if useLegacyCamera {
@@ -1517,28 +1518,32 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
}
- private func openLimitedMediaOptions() {
- let presentationData = self.presentationData
- let controller = ActionSheetController(presentationData: self.presentationData)
- let dismissAction: () -> Void = { [weak controller] in
- controller?.dismissAnimated()
+ private func openLimitedMediaOptions(sourceView: UIView) {
+ guard let controller = self.controller else {
+ return
}
- controller.setItemGroups([
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: presentationData.strings.Media_LimitedAccessSelectMore, color: .accent, action: { [weak self] in
- dismissAction()
- if #available(iOS 14.0, *), let strongController = self?.controller {
- PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: strongController)
- }
- }),
- ActionSheetButtonItem(title: presentationData.strings.Media_LimitedAccessChangeSettings, color: .accent, action: { [weak self] in
- dismissAction()
- self?.controller?.context.sharedContext.applicationBindings.openSettings()
- })
- ]),
- ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
- ])
- self.controller?.present(controller, in: .window(.root))
+
+ let presentationData = self.presentationData
+ let items: [ContextMenuItem] = [
+ .action(ContextMenuActionItem(text: presentationData.strings.Media_LimitedAccessSelectMore, icon: { theme in
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Image"), color: theme.contextMenu.primaryColor)
+ }, action: { [weak self] _, f in
+ f(.default)
+
+ if #available(iOS 14.0, *), let controller = self?.controller {
+ PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: controller)
+ }
+ })),
+ .action(ContextMenuActionItem(text: presentationData.strings.Media_LimitedAccessChangeSettings, icon: { theme in
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
+ }, action: { [weak self] _, f in
+ f(.default)
+
+ self?.controller?.context.sharedContext.applicationBindings.openSettings()
+ }))
+ ]
+ let contextController = makeContextController(presentationData: presentationData, source: .reference(MediaPickerContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
+ controller.presentInGlobalOverlay(contextController)
}
private func getItemSnapshot(_ identifier: String) -> UIView? {
@@ -1770,9 +1775,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
manageNode = current
} else {
manageNode = MediaPickerManageNode()
- manageNode.pressed = { [weak self] in
+ manageNode.pressed = { [weak self] sourceView in
if let strongSelf = self {
- strongSelf.openLimitedMediaOptions()
+ strongSelf.openLimitedMediaOptions(sourceView: sourceView)
}
}
self.manageNode = manageNode
@@ -1788,7 +1793,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
manageNode = current
} else {
manageNode = MediaPickerManageNode()
- manageNode.pressed = { [weak self] in
+ manageNode.pressed = { [weak self] _ in
self?.controller?.context.sharedContext.applicationBindings.openSettings()
}
self.manageNode = manageNode
@@ -2070,9 +2075,16 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.titleView.title = presentationData.strings.MediaPicker_Recents
self.titleView.subtitle = presentationData.strings.MediaPicker_CreateSticker
self.titleView.isEnabled = true
- case .createAvatar:
+ case let .createAvatar(avatarMode):
self.titleView.title = presentationData.strings.MediaPicker_Recents
- self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewPhoto
+ switch avatarMode {
+ case .user:
+ self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewPhoto
+ case .group:
+ self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewGroupPhoto
+ case .channel:
+ self.titleView.subtitle = presentationData.strings.MediaPicker_SetNewChannelPhoto
+ }
self.titleView.isEnabled = true
case .story:
self.titleView.title = presentationData.strings.MediaPicker_Recents
@@ -2209,21 +2221,13 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.scrollToTop = { [weak self] in
if let strongSelf = self {
- if let webSearchController = strongSelf.webSearchController {
- webSearchController.scrollToTop?()
- } else {
- strongSelf.controllerNode.scrollToTop(animated: true)
- }
+ strongSelf.controllerNode.scrollToTop(animated: true)
}
}
self.scrollToTopWithTabBar = { [weak self] in
if let strongSelf = self {
- if let webSearchController = strongSelf.webSearchController {
- webSearchController.cancel()
- } else {
- strongSelf.scrollToTop?()
- }
+ strongSelf.scrollToTop?()
}
}
@@ -2822,7 +2826,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.updateNavigationStack { current in
var mediaPickerContext: AttachmentMediaPickerContext?
if let first = current.first as? MediaPickerScreenImpl {
- mediaPickerContext = first.webSearchController?.mediaPickerContext ?? first.mediaPickerContext
+ mediaPickerContext = first.mediaPickerContext
}
return (current.filter { $0 !== self }, mediaPickerContext)
}
@@ -2905,10 +2909,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
public func resetForReuse() {
- if let webSearchController = self.webSearchController {
- self.webSearchController = nil
- webSearchController.dismiss()
- }
self.scrollToTop?()
self.controllerNode.isSuspended = true
@@ -2971,8 +2971,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if case .story = mode {
self.updateNavigationStack({ _ in return ([self, groupsController], self.mediaPickerContext)})
- } else {
- self.presentWebSearch(groupsController, activateOnDisplay)
}
self.groupsController = groupsController
}
@@ -3589,26 +3587,6 @@ public func mediaPickerController(
completion(result)
controller.dismiss(animated: true)
}
- if hasSearch {
- mediaPickerController.presentWebSearch = { [weak mediaPickerController] groups, activateOnDisplay in
- let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.SearchBots())
- |> deliverOnMainQueue).start(next: { configuration in
- let webSearchController = WebSearchController(
- context: context,
- updatedPresentationData: updatedPresentationData,
- peer: nil,
- chatLocation: nil,
- configuration: configuration,
- mode: .editor(completion: { [weak mediaPickerController] image in
- completion(image)
- mediaPickerController?.dismiss(animated: true)
- }),
- activateOnDisplay: activateOnDisplay
- )
- mediaPickerController?.present(webSearchController, in: .current)
- })
- }
- }
present(mediaPickerController, mediaPickerController.mediaPickerContext)
return true
}
@@ -3909,6 +3887,7 @@ public func stickerMediaPickerController(
public func avatarMediaPickerController(
context: AccountContext,
+ peerType: PeerType,
getSourceRect: @escaping () -> CGRect?,
canDelete: Bool,
performDelete: @escaping () -> Void,
@@ -4022,7 +4001,7 @@ public func avatarMediaPickerController(
chatLocation: nil,
bannedSendPhotos: nil,
bannedSendVideos: nil,
- subject: .assets(nil, .createAvatar),
+ subject: .assets(nil, .createAvatar(mode: peerType)),
mainButtonState: mainButtonState,
mainButtonAction: { [weak controller] in
controller?.dismiss(animated: true)
diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift
index 31622e2b02..2f58275139 100644
--- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift
+++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift
@@ -663,6 +663,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
self.scrollNode.view.panGestureRecognizer.cancelsTouchesInView = true
self.scrollNode.view.showsVerticalScrollIndicator = false
+ self.scrollNode.view.scrollsToTop = false
self.view.addGestureRecognizer(ReorderingGestureRecognizer(animateOnTouch: !self.persistentItems, shouldBegin: { [weak self] point in
if let strongSelf = self, !strongSelf.scrollNode.view.isDragging && strongSelf.itemNodes.count > 1 {
diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift
index 680b6eb9ee..656f3c7938 100644
--- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift
+++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift
@@ -405,11 +405,11 @@ public func notificationSoundSelectionController(context: AccountContext, update
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let signal = combineLatest(presentationData, statePromise.get(), context.engine.peers.notificationSoundList())
|> map { presentationData, state, notificationSoundList -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
arguments.cancel()
})
- let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
arguments.complete()
})
diff --git a/submodules/OpenInExternalAppUI/BUILD b/submodules/OpenInExternalAppUI/BUILD
index ea1b5232ae..7d78a66292 100644
--- a/submodules/OpenInExternalAppUI/BUILD
+++ b/submodules/OpenInExternalAppUI/BUILD
@@ -11,14 +11,19 @@ swift_library(
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
- "//submodules/AsyncDisplayKit:AsyncDisplayKit",
+ "//submodules/ComponentFlow:ComponentFlow",
"//submodules/Display:Display",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramPresentationData:TelegramPresentationData",
- "//submodules/PhotoResources:PhotoResources",
"//submodules/UrlEscaping:UrlEscaping",
"//submodules/AppBundle:AppBundle",
+ "//submodules/Components/BundleIconComponent",
+ "//submodules/Components/MultilineTextComponent",
+ "//submodules/Components/SheetComponent",
+ "//submodules/Components/ViewControllerComponent",
+ "//submodules/TelegramUI/Components/ButtonComponent",
+ "//submodules/TelegramUI/Components/GlassBarButtonComponent",
],
visibility = [
"//visibility:public",
diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInActionSheetController.swift b/submodules/OpenInExternalAppUI/Sources/OpenInActionSheetController.swift
deleted file mode 100644
index f6771facb0..0000000000
--- a/submodules/OpenInExternalAppUI/Sources/OpenInActionSheetController.swift
+++ /dev/null
@@ -1,264 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import SwiftSignalKit
-import TelegramCore
-import MapKit
-import TelegramPresentationData
-import AccountContext
-import PhotoResources
-import AppBundle
-
-public struct OpenInControllerAction {
- public let title: String
- public let action: () -> Void
-
- public init(title: String, action: @escaping () -> Void) {
- self.title = title
- self.action = action
- }
-}
-
-public final class OpenInActionSheetController: ActionSheetController {
- private var presentationDisposable: Disposable?
-
- private let _ready = Promise()
- override public var ready: Promise {
- return self._ready
- }
-
- public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, forceTheme: PresentationTheme? = nil, item: OpenInItem, additionalAction: OpenInControllerAction? = nil, openUrl: @escaping (String) -> Void) {
- var presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
- if let forceTheme = forceTheme {
- presentationData = presentationData.withUpdated(theme: forceTheme)
- }
-
- let strings = presentationData.strings
-
- super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
-
- self.presentationDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak self] presentationData in
- if let strongSelf = self {
- var presentationData = presentationData
- if let forceTheme = forceTheme {
- presentationData = presentationData.withUpdated(theme: forceTheme)
- }
- strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
- }
- })
-
- self._ready.set(.single(true))
-
- let invokeActionImpl: (OpenInAction) -> Void = { action in
- switch action {
- case let .openUrl(url):
- openUrl(url)
- case let .openLocation(latitude, longitude, directions):
- let placemark = MKPlacemark(coordinate: CLLocationCoordinate2DMake(latitude, longitude), addressDictionary: [:])
- let mapItem = MKMapItem(placemark: placemark)
-
- if let directions = directions {
- let options = [ MKLaunchOptionsDirectionsModeKey: directions.launchOptions ]
- MKMapItem.openMaps(with: [MKMapItem.forCurrentLocation(), mapItem], launchOptions: options)
- } else {
- mapItem.openInMaps(launchOptions: nil)
- }
- default:
- break
- }
- }
-
- var items: [ActionSheetItem] = []
- items.append(OpenInActionSheetItem(context: context, strings: strings, options: availableOpenInOptions(context: context, item: item), invokeAction: invokeActionImpl))
-
- if let action = additionalAction {
- items.append(ActionSheetButtonItem(title: action.title, action: { [weak self] in
- action.action()
- self?.dismissAnimated()
- }))
- }
-
- self.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
- self?.dismissAnimated()
- })
- ])
- ])
- }
-
- required public init(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- deinit {
- self.presentationDisposable?.dispose()
- }
-}
-
-private final class OpenInActionSheetItem: ActionSheetItem {
- let context: AccountContext
- let strings: PresentationStrings
- let options: [OpenInOption]
- let invokeAction: (OpenInAction) -> Void
-
- init(context: AccountContext, strings: PresentationStrings, options: [OpenInOption], invokeAction: @escaping (OpenInAction) -> Void) {
- self.context = context
- self.strings = strings
- self.options = options
- self.invokeAction = invokeAction
- }
-
- func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- return OpenInActionSheetItemNode(context: self.context, theme: theme, strings: self.strings, options: self.options, invokeAction: self.invokeAction)
- }
-
- func updateNode(_ node: ActionSheetItemNode) {
- }
-}
-
-private final class OpenInActionSheetItemNode: ActionSheetItemNode {
- let theme: ActionSheetControllerTheme
- let strings: PresentationStrings
-
- let titleNode: ASTextNode
- let scrollNode: ASScrollNode
-
- let openInNodes: [OpenInAppNode]
-
- init(context: AccountContext, theme: ActionSheetControllerTheme, strings: PresentationStrings, options: [OpenInOption], invokeAction: @escaping (OpenInAction) -> Void) {
- self.theme = theme
- self.strings = strings
-
- let titleFont = Font.medium(floor(theme.baseFontSize * 20.0 / 17.0))
-
- self.titleNode = ASTextNode()
- self.titleNode.isUserInteractionEnabled = false
- self.titleNode.displaysAsynchronously = true
- self.titleNode.attributedText = NSAttributedString(string: strings.Map_OpenIn, font: titleFont, textColor: theme.primaryTextColor, paragraphAlignment: .center)
-
- self.scrollNode = ASScrollNode()
- self.scrollNode.view.showsVerticalScrollIndicator = false
- self.scrollNode.view.showsHorizontalScrollIndicator = false
- self.scrollNode.view.clipsToBounds = false
- self.scrollNode.view.scrollsToTop = false
- self.scrollNode.view.delaysContentTouches = false
- self.scrollNode.scrollableDirections = [.left, .right]
-
- self.openInNodes = options.map { option in
- let node = OpenInAppNode()
- node.setup(context: context, theme: theme, option: option, invokeAction: invokeAction)
- return node
- }
-
- super.init(theme: theme)
-
- self.addSubnode(self.titleNode)
-
- if !self.openInNodes.isEmpty {
- for openInNode in openInNodes {
- self.scrollNode.addSubnode(openInNode)
- }
- self.addSubnode(self.scrollNode)
- }
- }
-
- public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let size = CGSize(width: constrainedSize.width, height: 148.0)
-
- let titleSize = self.titleNode.measure(size)
- self.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 16.0), size: CGSize(width: size.width, height: titleSize.height))
-
- self.scrollNode.frame = CGRect(origin: CGPoint(x: 0, y: 36.0), size: CGSize(width: size.width, height: size.height - 36.0))
-
- let nodeInset: CGFloat = 2.0
- let nodeSize = CGSize(width: 80.0, height: 112.0)
- var nodeOffset = nodeInset
-
- for node in self.openInNodes {
- node.frame = CGRect(origin: CGPoint(x: nodeOffset, y: 0.0), size: nodeSize)
- nodeOffset += nodeSize.width
- }
-
- if let lastNode = self.openInNodes.last {
- let contentSize = CGSize(width: lastNode.frame.maxX + nodeInset, height: self.scrollNode.frame.height)
- if self.scrollNode.view.contentSize != contentSize {
- self.scrollNode.view.contentSize = contentSize
- }
- }
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-}
-
-private final class OpenInAppNode : ASDisplayNode {
- private let iconNode: TransformImageNode
- private let textNode: ASTextNode
- private var action: (() -> Void)?
-
- override init() {
- self.iconNode = TransformImageNode()
- self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0))
- self.iconNode.isLayerBacked = true
-
- self.textNode = ASTextNode()
- self.textNode.isUserInteractionEnabled = false
- self.textNode.displaysAsynchronously = true
-
- super.init()
-
- self.addSubnode(self.iconNode)
- self.addSubnode(self.textNode)
- }
-
- func setup(context: AccountContext, theme: ActionSheetControllerTheme, option: OpenInOption, invokeAction: @escaping (OpenInAction) -> Void) {
- let textFont = Font.regular(floor(theme.baseFontSize * 11.0 / 17.0))
- self.textNode.attributedText = NSAttributedString(string: option.title, font: textFont, textColor: theme.primaryTextColor, paragraphAlignment: .center)
-
- let iconSize = CGSize(width: 60.0, height: 60.0)
- let makeLayout = self.iconNode.asyncLayout()
- let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()))
- applyLayout()
-
- switch option.application {
- case .safari:
- if let image = UIImage(bundleImageName: "Open In/Safari") {
- self.iconNode.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image)))
- }
- case .maps:
- if let image = UIImage(bundleImageName: "Open In/Maps") {
- self.iconNode.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image)))
- }
- case let .other(_, identifier, _, store):
- self.iconNode.setSignal(openInAppIcon(engine: context.engine, appIcon: .resource(resource: OpenInAppIconResource(appStoreId: identifier, store: store))))
- }
-
- self.action = {
- invokeAction(option.action())
- }
- }
-
- override func didLoad() {
- super.didLoad()
-
- self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
- }
-
- @objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
- if case .ended = recognizer.state {
- self.action?()
- }
- }
-
- override func layout() {
- super.layout()
-
- let bounds = self.bounds
-
- self.iconNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 60.0, height: 60.0))
- self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 14.0 + 60.0 + 4.0), size: CGSize(width: bounds.size.width, height: 16.0))
- }
-}
diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift b/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift
index 9af464ac9d..1fc779008e 100644
--- a/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift
+++ b/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift
@@ -3,6 +3,7 @@ import UIKit
import TelegramCore
import SwiftSignalKit
import Display
+import TelegramPresentationData
public struct OpenInAppIconResourceId {
public let appStoreId: Int64
@@ -131,7 +132,7 @@ private func drawOpenInAppIconBorder(into c: CGContext, arguments: TransformImag
c.strokePath()
}
-public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
+public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon, withChrome: Bool = true) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
switch appIcon {
case let .resource(resource):
return openInAppIconData(engine: engine, appIcon: resource) |> map { data in
@@ -166,14 +167,17 @@ public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon) -> Sig
guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else {
return nil
}
-
+
context.withFlippedContext { c in
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: arguments.drawingSize))
- drawOpenInAppIconBorder(into: c, arguments: arguments)
+ if withChrome {
+ drawOpenInAppIconBorder(into: c, arguments: arguments)
+ }
}
- addCorners(context, arguments: arguments)
-
+ if withChrome {
+ addCorners(context, arguments: arguments)
+ }
return context
})
}
diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift b/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift
index 7721f5bdb1..dc427f26f2 100644
--- a/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift
+++ b/submodules/OpenInExternalAppUI/Sources/OpenInOptions.swift
@@ -90,24 +90,20 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
var options: [OpenInOption] = []
switch item {
case let .url(url):
- var skipSafari = false
if url.contains("youtube.com/") || url.contains("youtu.be/") {
let updatedUrl = url.replacingOccurrences(of: "https://", with: "youtube://").replacingOccurrences(of: "http://", with: "youtube://")
options.append(OpenInOption(identifier: "youtube", application: .other(title: "YouTube", identifier: 544007664, scheme: "youtube", store: nil), action: {
return .openUrl(url: updatedUrl)
}))
- skipSafari = true
}
- if !skipSafari {
- options.append(OpenInOption(identifier: "safari", application: .safari, action: {
- var url = url
- if url.hasPrefix("https://") {
- url = url.replacingOccurrences(of: "https://", with: "x-safari-https://")
- }
- return .openUrl(url: url)
- }))
- }
+ options.append(OpenInOption(identifier: "safari", application: .safari, action: {
+ var url = url
+ if url.hasPrefix("https://") {
+ url = url.replacingOccurrences(of: "https://", with: "x-safari-https://")
+ }
+ return .openUrl(url: url)
+ }))
options.append(OpenInOption(identifier: "chrome", application: .other(title: "Chrome", identifier: 535886823, scheme: "googlechrome", store: nil), action: {
if let url = URL(string: url), var components = URLComponents(url: url, resolvingAgainstBaseURL: true) {
@@ -202,6 +198,10 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
options.append(OpenInOption(identifier: "alook", application: .other(title: "Alook Browser", identifier: 1261944766, scheme: "alook", store: nil), action: {
return .openUrl(url: "alook://\(url)")
}))
+
+ options.append(OpenInOption(identifier: "vivaldi", application: .other(title: "Vivaldi", identifier: 1633234600, scheme: "vivaldi", store: "us"), action: {
+ return .openUrl(url: "vivaldi://\(url)")
+ }))
case let .location(location, directions):
let lat = location.latitude
let lon = location.longitude
@@ -334,6 +334,14 @@ private func allOpenInOptions(context: AccountContext, item: OpenInItem) -> [Ope
return .openUrl(url: url)
}
}))
+
+ options.append(OpenInOption(identifier: "yandexGo", application: .other(title: "Yandex Go", identifier: 472650686, scheme: "yandextaxi", store: nil), action: {
+ return .openUrl(url: "yandextaxi://route?end-lat=\(lat)&end-lon=\(lon)")
+ }))
+
+ options.append(OpenInOption(identifier: "yango", application: .other(title: "Yango", identifier: 1437157286, scheme: "yangoride", store: nil), action: {
+ return .openUrl(url: "yangoride://route?end-lat=\(lat)&end-lon=\(lon)")
+ }))
}
return options
}
diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInOptionsScreen.swift b/submodules/OpenInExternalAppUI/Sources/OpenInOptionsScreen.swift
new file mode 100644
index 0000000000..d740cba975
--- /dev/null
+++ b/submodules/OpenInExternalAppUI/Sources/OpenInOptionsScreen.swift
@@ -0,0 +1,544 @@
+import Foundation
+import UIKit
+import Display
+import ComponentFlow
+import SwiftSignalKit
+import TelegramCore
+import MapKit
+import TelegramPresentationData
+import AccountContext
+import AppBundle
+import ViewControllerComponent
+import SheetComponent
+import ButtonComponent
+import GlassBarButtonComponent
+import BundleIconComponent
+import MultilineTextComponent
+
+public struct OpenInControllerAction {
+ public let title: String
+ public let action: () -> Void
+
+ public init(title: String, action: @escaping () -> Void) {
+ self.title = title
+ self.action = action
+ }
+}
+
+public final class OpenInOptionsScreen: ViewControllerComponentContainer {
+ public init(
+ context: AccountContext,
+ updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil,
+ forceTheme: PresentationTheme? = nil,
+ item: OpenInItem,
+ additionalAction: OpenInControllerAction? = nil,
+ openUrl: @escaping (String) -> Void
+ ) {
+ let invokeAction: (OpenInAction) -> Void = { action in
+ switch action {
+ case let .openUrl(url):
+ openUrl(url)
+ case let .openLocation(latitude, longitude, directions):
+ let placemark = MKPlacemark(coordinate: CLLocationCoordinate2DMake(latitude, longitude), addressDictionary: [:])
+ let mapItem = MKMapItem(placemark: placemark)
+
+ if let directions = directions {
+ let options = [MKLaunchOptionsDirectionsModeKey: directions.launchOptions]
+ MKMapItem.openMaps(with: [MKMapItem.forCurrentLocation(), mapItem], launchOptions: options)
+ } else {
+ mapItem.openInMaps(launchOptions: nil)
+ }
+ default:
+ break
+ }
+ }
+
+ let effectiveUpdatedPresentationData: (initial: PresentationData, signal: Signal)?
+ if let forceTheme {
+ let initial = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
+ let signal = updatedPresentationData?.signal ?? context.sharedContext.presentationData
+ effectiveUpdatedPresentationData = (
+ initial: initial.withUpdated(theme: forceTheme),
+ signal: signal |> map { presentationData in
+ presentationData.withUpdated(theme: forceTheme)
+ }
+ )
+ } else {
+ effectiveUpdatedPresentationData = updatedPresentationData
+ }
+
+ super.init(
+ context: context,
+ component: OpenInOptionsScreenComponent(
+ context: context,
+ options: availableOpenInOptions(context: context, item: item),
+ additionalAction: additionalAction,
+ invokeAction: invokeAction
+ ),
+ navigationBarAppearance: .none,
+ statusBarStyle: .ignore,
+ updatedPresentationData: effectiveUpdatedPresentationData
+ )
+
+ self.blocksBackgroundWhenInOverlay = true
+ self.navigationPresentation = .flatModal
+ }
+
+ required public init(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public func dismissAnimated() {
+ if let view = self.node.hostView.findTaggedView(tag: SheetComponent.View.Tag()) as? SheetComponent.View {
+ view.dismissAnimated()
+ }
+ }
+}
+
+private final class OpenInOptionsScreenComponent: Component {
+ typealias EnvironmentType = ViewControllerComponentContainer.Environment
+
+ let context: AccountContext
+ let options: [OpenInOption]
+ let additionalAction: OpenInControllerAction?
+ let invokeAction: (OpenInAction) -> Void
+
+ init(
+ context: AccountContext,
+ options: [OpenInOption],
+ additionalAction: OpenInControllerAction?,
+ invokeAction: @escaping (OpenInAction) -> Void
+ ) {
+ self.context = context
+ self.options = options
+ self.additionalAction = additionalAction
+ self.invokeAction = invokeAction
+ }
+
+ static func ==(lhs: OpenInOptionsScreenComponent, rhs: OpenInOptionsScreenComponent) -> Bool {
+ if lhs.context !== rhs.context {
+ return false
+ }
+ if lhs.options.map(\.identifier) != rhs.options.map(\.identifier) {
+ return false
+ }
+ if lhs.additionalAction?.title != rhs.additionalAction?.title {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView {
+ private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, SheetComponentEnvironment)>()
+ private let sheetAnimateOut = ActionSlot>()
+
+ private var environment: EnvironmentType?
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func dismiss(animated: Bool) {
+ guard let controller = self.environment?.controller() else {
+ return
+ }
+
+ if animated {
+ self.sheetAnimateOut.invoke(Action { _ in
+ controller.dismiss(completion: nil)
+ })
+ } else {
+ controller.dismiss(animated: false, completion: nil)
+ }
+ }
+
+ func update(component: OpenInOptionsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ let environment = environment[ViewControllerComponentContainer.Environment.self].value
+ self.environment = environment
+
+ let sheetEnvironment = SheetComponentEnvironment(
+ metrics: environment.metrics,
+ deviceMetrics: environment.deviceMetrics,
+ isDisplaying: environment.isVisible,
+ isCentered: environment.metrics.widthClass == .regular,
+ hasInputHeight: !environment.inputHeight.isZero,
+ regularMetricsSize: CGSize(width: 430.0, height: 900.0),
+ dismiss: { [weak self] animated in
+ self?.dismiss(animated: animated)
+ }
+ )
+
+ let _ = self.sheet.update(
+ transition: transition,
+ component: AnyComponent(SheetComponent(
+ content: AnyComponent(OpenInOptionsSheetContentComponent(
+ context: component.context,
+ options: component.options,
+ additionalAction: component.additionalAction,
+ invokeAction: component.invokeAction,
+ dismiss: { [weak self] in
+ self?.dismiss(animated: true)
+ }
+ )),
+ style: .glass,
+ backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
+ followContentSizeChanges: true,
+ clipsContent: true,
+ 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)
+ }
+}
+
+private final class OpenInOptionsSheetContentComponent: Component {
+ typealias EnvironmentType = ViewControllerComponentContainer.Environment
+
+ let context: AccountContext
+ let options: [OpenInOption]
+ let additionalAction: OpenInControllerAction?
+ let invokeAction: (OpenInAction) -> Void
+ let dismiss: () -> Void
+
+ init(
+ context: AccountContext,
+ options: [OpenInOption],
+ additionalAction: OpenInControllerAction?,
+ invokeAction: @escaping (OpenInAction) -> Void,
+ dismiss: @escaping () -> Void
+ ) {
+ self.context = context
+ self.options = options
+ self.additionalAction = additionalAction
+ self.invokeAction = invokeAction
+ self.dismiss = dismiss
+ }
+
+ static func ==(lhs: OpenInOptionsSheetContentComponent, rhs: OpenInOptionsSheetContentComponent) -> Bool {
+ if lhs.context !== rhs.context {
+ return false
+ }
+ if lhs.options.map(\.identifier) != rhs.options.map(\.identifier) {
+ return false
+ }
+ if lhs.additionalAction?.title != rhs.additionalAction?.title {
+ return false
+ }
+ return true
+ }
+
+ final class View: UIView {
+ private let closeButton = ComponentView()
+ private let title = ComponentView()
+ private let scrollView: UIScrollView
+ private var optionViews: [String: OpenInAppView] = [:]
+ private var shareButton: ComponentView?
+
+ override init(frame: CGRect) {
+ self.scrollView = UIScrollView()
+ self.scrollView.showsVerticalScrollIndicator = false
+ self.scrollView.showsHorizontalScrollIndicator = false
+ self.scrollView.clipsToBounds = false
+ self.scrollView.scrollsToTop = false
+ self.scrollView.delaysContentTouches = false
+ self.scrollView.alwaysBounceHorizontal = true
+ if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
+ self.scrollView.contentInsetAdjustmentBehavior = .never
+ }
+
+ super.init(frame: frame)
+
+ self.addSubview(self.scrollView)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func update(component: OpenInOptionsSheetContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
+ let environment = environment[ViewControllerComponentContainer.Environment.self].value
+ let theme = environment.theme
+ let strings = environment.strings
+
+ let closeButtonSize = self.closeButton.update(
+ transition: transition,
+ component: AnyComponent(GlassBarButtonComponent(
+ size: CGSize(width: 44.0, height: 44.0),
+ backgroundColor: nil,
+ isDark: theme.overallDarkAppearance,
+ state: .glass,
+ component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
+ BundleIconComponent(
+ name: "Navigation/Close",
+ tintColor: theme.chat.inputPanel.panelControlColor
+ )
+ )),
+ action: { _ in
+ component.dismiss()
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: 44.0, height: 44.0)
+ )
+ if let closeButtonView = self.closeButton.view {
+ if closeButtonView.superview == nil {
+ self.addSubview(closeButtonView)
+ }
+ transition.setFrame(view: closeButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: closeButtonSize))
+ }
+
+ let titleSize = self.title.update(
+ transition: transition,
+ component: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: strings.Map_OpenIn,
+ font: Font.semibold(17.0),
+ textColor: theme.actionSheet.primaryTextColor,
+ paragraphAlignment: .center
+ )),
+ horizontalAlignment: .center,
+ maximumNumberOfLines: 1
+ )),
+ environment: {},
+ containerSize: CGSize(width: max(1.0, availableSize.width - 32.0 - 60.0), height: CGFloat.greatestFiniteMagnitude)
+ )
+ if let titleView = self.title.view {
+ if titleView.superview == nil {
+ self.addSubview(titleView)
+ }
+ transition.setFrame(view: titleView, frame: CGRect(
+ origin: CGPoint(
+ x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0),
+ y: floorToScreenPixels(38.0 - titleSize.height / 2.0)
+ ),
+ size: titleSize
+ ))
+ }
+
+ let optionInset: CGFloat = 2.0
+ var optionSpacing: CGFloat = 8.0
+ if component.options.count == 3 {
+ optionSpacing = 32.0
+ } else if component.options.count == 2 {
+ optionSpacing = 64.0
+ }
+
+ let optionSize = CGSize(width: 80.0, height: 112.0)
+ let scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: 82.0), size: CGSize(width: availableSize.width, height: optionSize.height))
+ transition.setFrame(view: self.scrollView, frame: scrollFrame)
+
+ var validIds = Set()
+ for (index, option) in component.options.enumerated() {
+ validIds.insert(option.identifier)
+
+ let optionView: OpenInAppView
+ if let current = self.optionViews[option.identifier] {
+ optionView = current
+ } else {
+ optionView = OpenInAppView()
+ self.optionViews[option.identifier] = optionView
+ self.scrollView.addSubview(optionView)
+ }
+
+ optionView.update(context: component.context, theme: theme, option: option, action: {
+ component.invokeAction(option.action())
+ component.dismiss()
+ })
+
+ let optionOriginX: CGFloat
+ if component.options.count < 5 {
+ optionOriginX = floorToScreenPixels(max(0.0, (availableSize.width - optionSize.width * CGFloat(component.options.count) - optionSpacing * CGFloat(component.options.count - 1)) / 2.0)) + CGFloat(index) * (optionSize.width + optionSpacing)
+ } else {
+ optionOriginX = optionInset + CGFloat(index) * (optionSize.width + optionSpacing)
+ }
+
+ transition.setFrame(view: optionView, frame: CGRect(
+ origin: CGPoint(x: optionOriginX, y: 0.0),
+ size: optionSize
+ ))
+ }
+
+ for id in Array(self.optionViews.keys) {
+ if !validIds.contains(id) {
+ let optionView = self.optionViews.removeValue(forKey: id)
+ optionView?.removeFromSuperview()
+ }
+ }
+
+ let optionsContentWidth: CGFloat
+ if component.options.isEmpty {
+ optionsContentWidth = availableSize.width
+ } else {
+ optionsContentWidth = optionInset * 2.0 + CGFloat(component.options.count) * (optionSize.width + optionSpacing) - optionSpacing
+ }
+ self.scrollView.contentSize = CGSize(width: max(availableSize.width, optionsContentWidth), height: optionSize.height)
+
+ var contentHeight = scrollFrame.maxY + 22.0
+ if let additionalAction = component.additionalAction {
+ let shareButton: ComponentView
+ if let current = self.shareButton {
+ shareButton = current
+ } else {
+ shareButton = ComponentView()
+ self.shareButton = shareButton
+ }
+
+ let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
+ let buttonSize = shareButton.update(
+ transition: transition,
+ component: AnyComponent(ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: theme.list.itemCheckColors.fillColor,
+ foreground: theme.list.itemCheckColors.foregroundColor,
+ pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
+ ),
+ content: AnyComponentWithIdentity(
+ id: AnyHashable(additionalAction.title),
+ component: AnyComponent(MultilineTextComponent(
+ text: .plain(NSAttributedString(
+ string: additionalAction.title,
+ font: Font.semibold(17.0),
+ textColor: theme.list.itemCheckColors.foregroundColor,
+ paragraphAlignment: .center
+ )),
+ horizontalAlignment: .center,
+ maximumNumberOfLines: 1
+ ))
+ ),
+ action: {
+ additionalAction.action()
+ component.dismiss()
+ }
+ )),
+ environment: {},
+ containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0)
+ )
+
+ if let shareButtonView = shareButton.view {
+ if shareButtonView.superview == nil {
+ self.addSubview(shareButtonView)
+ }
+ transition.setFrame(view: shareButtonView, frame: CGRect(
+ origin: CGPoint(x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0), y: contentHeight),
+ size: buttonSize
+ ))
+ }
+ contentHeight += buttonSize.height
+ contentHeight += buttonInsets.bottom
+ } else {
+ if let shareButton = self.shareButton {
+ self.shareButton = nil
+ shareButton.view?.removeFromSuperview()
+ }
+ contentHeight += max(20.0, environment.safeInsets.bottom + 12.0)
+ }
+
+ 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 OpenInAppView: UIControl {
+ private let iconView: TransformImageView
+ private let titleLabel: UILabel
+
+ private var currentIdentifier: String?
+ private var action: (() -> Void)?
+
+ override init(frame: CGRect) {
+ self.iconView = TransformImageView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 64.0, height: 64.0)))
+ self.titleLabel = UILabel()
+
+ super.init(frame: frame)
+
+ self.titleLabel.textAlignment = .center
+ self.titleLabel.numberOfLines = 1
+ self.titleLabel.adjustsFontSizeToFitWidth = true
+ self.titleLabel.minimumScaleFactor = 0.75
+
+ self.isAccessibilityElement = true
+
+ self.addSubview(self.iconView)
+ self.addSubview(self.titleLabel)
+
+ self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func update(context: AccountContext, theme: PresentationTheme, option: OpenInOption, action: @escaping () -> Void) {
+ self.action = action
+ self.accessibilityLabel = option.title
+ self.titleLabel.attributedText = NSAttributedString(string: option.title, font: Font.medium(12.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
+
+ let iconSize = CGSize(width: 64.0, height: 64.0)
+ let makeLayout = self.iconView.asyncLayout()
+ let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets()))
+ applyLayout()
+
+ if self.currentIdentifier != option.identifier {
+ self.currentIdentifier = option.identifier
+
+ switch option.application {
+ case .safari:
+ if let image = UIImage(bundleImageName: "Open In/Safari") {
+ self.iconView.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image)))
+ }
+ case .maps:
+ if let image = UIImage(bundleImageName: "Open In/Maps") {
+ self.iconView.setSignal(openInAppIcon(engine: context.engine, appIcon: .image(image: image)))
+ }
+ case let .other(_, identifier, _, store):
+ self.iconView.setSignal(openInAppIcon(engine: context.engine, appIcon: .resource(resource: OpenInAppIconResource(appStoreId: identifier, store: store))))
+ }
+ }
+ }
+
+ @objc private func pressed() {
+ self.action?()
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ self.iconView.frame = CGRect(origin: CGPoint(x: 8.0, y: 12.0), size: CGSize(width: 64.0, height: 64.0))
+ self.titleLabel.frame = CGRect(origin: CGPoint(x: 0.0, y: 80.0), size: CGSize(width: self.bounds.width, height: 16.0))
+ }
+}
diff --git a/submodules/PassportUI/BUILD b/submodules/PassportUI/BUILD
index ab6d2f27cd..28b67dd27e 100644
--- a/submodules/PassportUI/BUILD
+++ b/submodules/PassportUI/BUILD
@@ -24,12 +24,12 @@ swift_library(
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/LegacyUI:LegacyUI",
"//submodules/ImageCompression:ImageCompression",
- "//submodules/DateSelectionUI:DateSelectionUI",
"//submodules/PasswordSetupUI:PasswordSetupUI",
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/Markdown:Markdown",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
+ "//submodules/TelegramUI/Components/ChatTimerScreen",
],
visibility = [
"//visibility:public",
diff --git a/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift b/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift
index cc9c224885..ee691a9d42 100644
--- a/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift
+++ b/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift
@@ -51,6 +51,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode {
self.addSubnode(self.activityIndicator)
self.scrollNode.view.alwaysBounceVertical = true
+ self.scrollNode.view.scrollsToTop = false
self.addSubnode(self.scrollNode)
self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
diff --git a/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift b/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift
index 93db136475..de0a6c4d05 100644
--- a/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift
+++ b/submodules/PassportUI/Sources/SecureIdDocumentFormControllerNode.swift
@@ -9,8 +9,8 @@ import TelegramStringFormatting
import AccountContext
import GalleryUI
import CountrySelectionUI
-import DateSelectionUI
import AppBundle
+import ChatTimerScreen
private enum SecureIdDocumentFormTextField {
case identifier
@@ -2368,49 +2368,76 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
var rightNavigationButton: ItemListNavigationButton?
if state.checking {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} else {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.code.isEmpty, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: !state.code.isEmpty, action: {
var state: ResetPasswordControllerState?
updateState { s in
state = s
diff --git a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift
index cf77e35974..aea80e2cf4 100644
--- a/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift
+++ b/submodules/PasswordSetupUI/Sources/TwoFactorAuthDataInputScreen.swift
@@ -1819,6 +1819,7 @@ private final class TwoFactorDataInputScreenNode: ViewControllerTracingNode, ASS
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
+ self.scrollNode.view.scrollsToTop = false
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
diff --git a/submodules/PeerInfoUI/BUILD b/submodules/PeerInfoUI/BUILD
index 5a25b9755f..0ee49ffed5 100644
--- a/submodules/PeerInfoUI/BUILD
+++ b/submodules/PeerInfoUI/BUILD
@@ -34,7 +34,6 @@ swift_library(
"//submodules/LegacyUI:LegacyUI",
"//submodules/GalleryUI:GalleryUI",
"//submodules/ShareController:ShareController",
- "//submodules/WebSearchUI:WebSearchUI",
"//submodules/SearchBarNode:SearchBarNode",
"//submodules/ContactsPeerItem:ContactsPeerItem",
"//submodules/PasswordSetupUI:PasswordSetupUI",
@@ -80,10 +79,14 @@ swift_library(
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
"//submodules/TelegramUI/Components/PeerInfo/MessagePriceItem",
+ "//submodules/TelegramUI/Components/AlertComponent",
+ "//submodules/TelegramUI/Components/AlertComponent/AlertTransferHeaderComponent",
+ "//submodules/TelegramUI/Components/AvatarComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/CounterControllerTitleView",
+ "//submodules/TelegramUI/Components/ChatTimerScreen",
],
visibility = [
"//visibility:public",
diff --git a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift
index 10bf515648..dc1bce65f1 100644
--- a/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift
+++ b/submodules/PeerInfoUI/CreateExternalMediaStreamScreen/Sources/CreateExternalMediaStreamScreen.swift
@@ -164,7 +164,7 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
let text: String
text = presentationData.strings.Login_UnknownError
- baseController?.present(textAlertController(context: strongSelf.context, updatedPresentationData: nil, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
+ baseController?.present(textAlertController(context: strongSelf.context, forceTheme: defaultDarkPresentationTheme, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}))
}
@@ -191,17 +191,6 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
let credentialsSection = Child(ListSectionComponent.self)
-// let credentialsBackground = Child(RoundedRectangle.self)
-// let credentialsStripe = Child(Rectangle.self)
-// let credentialsURLTitle = Child(MultilineTextComponent.self)
-// let credentialsURLText = Child(MultilineTextComponent.self)
-//
-// let credentialsKeyTitle = Child(MultilineTextComponent.self)
-// let credentialsKeyText = Child(MultilineTextComponent.self)
-//
-// let credentialsCopyURLButton = Child(Button.self)
-// let credentialsCopyKeyButton = Child(Button.self)
-
return { context in
let topInset: CGFloat = 16.0
let sideInset: CGFloat = 16.0
@@ -477,12 +466,13 @@ private final class CreateExternalMediaStreamScreenComponent: CombinedComponent
maximumNumberOfLines: 1
)),
titleAlignment: .center,
+ accessory: .none,
action: { [weak state] _ in
guard let state = state else {
return
}
- let alertController = textAlertController(context: component.context, title: nil, text: environment.strings.CreateExternalStream_Revoke_Text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
- }), TextAlertAction(type: .defaultAction, title: environment.strings.CreateExternalStream_Revoke_Revoke, action: { [weak state] in
+ let alertController = textAlertController(context: component.context, forceTheme: theme, title: nil, text: environment.strings.CreateExternalStream_Revoke_Text, actions: [TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {
+ }), TextAlertAction(type: .defaultDestructiveAction, title: environment.strings.CreateExternalStream_Revoke_Revoke, action: { [weak state] in
state?.getCredentials(revoke: true)
})])
environment.controller()?.present(alertController, in: .window(.root))
diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift
index ab6d4cfe48..4967801ddb 100644
--- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift
+++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift
@@ -16,6 +16,10 @@ import Markdown
import SendInviteLinkScreen
import OwnershipTransferController
import OldChannelsController
+import ComponentFlow
+import AlertComponent
+import AlertTransferHeaderComponent
+import AvatarComponent
private let rankMaxLength: Int32 = 16
@@ -23,6 +27,7 @@ private final class ChannelAdminControllerArguments {
let context: AccountContext
let updateAdminRights: (Bool) -> Void
let toggleRight: (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void
+ let updateProcessJoinRequests: (Bool) -> Void
let toggleRightWhileDisabled: (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void
let transferOwnership: () -> Void
let updateRank: (String, String) -> Void
@@ -33,10 +38,11 @@ private final class ChannelAdminControllerArguments {
let toggleIsOptionExpanded: (RightsItem.Sub) -> Void
let openPeer: () -> Void
- init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void, toggleIsOptionExpanded: @escaping (RightsItem.Sub) -> Void, openPeer: @escaping () -> Void) {
+ init(context: AccountContext, updateAdminRights: @escaping (Bool) -> Void, toggleRight: @escaping (RightsItem, TelegramChatAdminRightsFlags, Bool) -> Void, updateProcessJoinRequests: @escaping (Bool) -> Void, toggleRightWhileDisabled: @escaping (TelegramChatAdminRightsFlags, TelegramChatAdminRightsFlags) -> Void, transferOwnership: @escaping () -> Void, updateRank: @escaping (String, String) -> Void, updateFocusedOnRank: @escaping (Bool) -> Void, dismissAdmin: @escaping () -> Void, dismissInput: @escaping () -> Void, animateError: @escaping () -> Void, toggleIsOptionExpanded: @escaping (RightsItem.Sub) -> Void, openPeer: @escaping () -> Void) {
self.context = context
self.updateAdminRights = updateAdminRights
self.toggleRight = toggleRight
+ self.updateProcessJoinRequests = updateProcessJoinRequests
self.toggleRightWhileDisabled = toggleRightWhileDisabled
self.transferOwnership = transferOwnership
self.updateRank = updateRank
@@ -80,6 +86,8 @@ private enum ChannelAdminEntryStableId: Hashable {
case rightsTitle
case right(RightsItem)
case addAdminsInfo
+ case processJoinRequests
+ case processJoinRequestsInfo
case transfer
case dismiss
}
@@ -123,6 +131,8 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
case rightsTitle(PresentationTheme, String)
case rightItem(PresentationTheme, Int, String, RightsItem, TelegramChatAdminRightsFlags, Bool, Bool, [AdminSubPermission], Bool)
case addAdminsInfo(PresentationTheme, String)
+ case processJoinRequests(PresentationTheme, String, Bool, Bool)
+ case processJoinRequestsInfo(PresentationTheme, String)
case transfer(PresentationTheme, String)
case dismiss(PresentationTheme, String)
@@ -134,7 +144,7 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
return ChannelAdminSection.rank.rawValue
case .adminRights:
return ChannelAdminSection.adminRights.rawValue
- case .rightsTitle, .rightItem, .addAdminsInfo:
+ case .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo:
return ChannelAdminSection.rights.rawValue
case .transfer:
return ChannelAdminSection.transfer.rawValue
@@ -163,6 +173,10 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
return .right(right)
case .addAdminsInfo:
return .addAdminsInfo
+ case .processJoinRequests:
+ return .processJoinRequests
+ case .processJoinRequestsInfo:
+ return .processJoinRequestsInfo
case .transfer:
return .transfer
case .dismiss:
@@ -269,6 +283,18 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
} else {
return false
}
+ case let .processJoinRequests(lhsTheme, lhsText, lhsValue, lhsEnabled):
+ if case let .processJoinRequests(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
+ return true
+ } else {
+ return false
+ }
+ case let .processJoinRequestsInfo(lhsTheme, lhsText):
+ if case let .processJoinRequestsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ return true
+ } else {
+ return false
+ }
case let .transfer(lhsTheme, lhsText):
if case let .transfer(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@@ -323,37 +349,51 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
default:
return true
}
+ case .processJoinRequests:
+ switch rhs {
+ case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests:
+ return false
+ default:
+ return true
+ }
+ case .processJoinRequestsInfo:
+ switch rhs {
+ case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo:
+ return false
+ default:
+ return true
+ }
case .transfer:
switch rhs {
- case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer:
+ case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer:
return false
default:
return true
}
case .rankTitle:
switch rhs {
- case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle:
+ case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle:
return false
default:
return true
}
case .rankPreview:
switch rhs {
- case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview:
+ case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle, .rankPreview:
return false
default:
return true
}
case .rank:
switch rhs {
- case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview, .rank:
+ case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle, .rankPreview, .rank:
return false
default:
return true
}
case .rankInfo:
switch rhs {
- case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .transfer, .rankTitle, .rankPreview, .rank, .rankInfo:
+ case .info, .adminRights, .rightsTitle, .rightItem, .addAdminsInfo, .processJoinRequests, .processJoinRequestsInfo, .transfer, .rankTitle, .rankPreview, .rank, .rankInfo:
return false
default:
return true
@@ -448,6 +488,13 @@ private enum ChannelAdminEntry: ItemListNodeEntry {
}
case let .addAdminsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
+ case let .processJoinRequests(_, text, value, enabled):
+ return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, type: .icon, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
+ arguments.updateProcessJoinRequests(value)
+ }, activatedWhileDisabled: {
+ })
+ case let .processJoinRequestsInfo(_, text):
+ return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .transfer(_, text):
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
arguments.transferOwnership()
@@ -464,14 +511,16 @@ private struct ChannelAdminControllerState: Equatable {
var adminRights: Bool
var updatedFlags: TelegramChatAdminRightsFlags?
var updatedRank: String?
+ var processJoinRequests: Bool?
var updating: Bool
var focusedOnRank: Bool
var expandedPermissions: Set = Set()
- init(adminRights: Bool = true, updatedFlags: TelegramChatAdminRightsFlags? = nil, updatedRank: String? = nil, updating: Bool = false, focusedOnRank: Bool = false, expandedPermissions: Set = Set()) {
+ init(adminRights: Bool = true, updatedFlags: TelegramChatAdminRightsFlags? = nil, updatedRank: String? = nil, processJoinRequests: Bool? = nil, updating: Bool = false, focusedOnRank: Bool = false, expandedPermissions: Set = Set()) {
self.adminRights = adminRights
self.updatedFlags = updatedFlags
self.updatedRank = updatedRank
+ self.processJoinRequests = processJoinRequests
self.updating = updating
self.focusedOnRank = focusedOnRank
self.expandedPermissions = expandedPermissions
@@ -487,6 +536,9 @@ private struct ChannelAdminControllerState: Equatable {
if lhs.updatedRank != rhs.updatedRank {
return false
}
+ if lhs.processJoinRequests != rhs.processJoinRequests {
+ return false
+ }
if lhs.updating != rhs.updating {
return false
}
@@ -500,23 +552,27 @@ private struct ChannelAdminControllerState: Equatable {
}
func withUpdatedAdminRights(_ adminRights: Bool) -> ChannelAdminControllerState {
- return ChannelAdminControllerState(adminRights: adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
+ return ChannelAdminControllerState(adminRights: adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
}
func withUpdatedUpdatedFlags(_ updatedFlags: TelegramChatAdminRightsFlags?) -> ChannelAdminControllerState {
- return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
+ return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
}
-
+
func withUpdatedUpdatedRank(_ updatedRank: String?) -> ChannelAdminControllerState {
- return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: updatedRank, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
+ return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
+ }
+
+ func withUpdatedProcessJoinRequests(_ processJoinRequests: Bool) -> ChannelAdminControllerState {
+ return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: processJoinRequests, updating: self.updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
}
func withUpdatedUpdating(_ updating: Bool) -> ChannelAdminControllerState {
- return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
+ return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: updating, focusedOnRank: self.focusedOnRank, expandedPermissions: self.expandedPermissions)
}
func withUpdatedFocusedOnRank(_ focusedOnRank: Bool) -> ChannelAdminControllerState {
- return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, updating: self.updating, focusedOnRank: focusedOnRank, expandedPermissions: self.expandedPermissions)
+ return ChannelAdminControllerState(adminRights: self.adminRights, updatedFlags: self.updatedFlags, updatedRank: self.updatedRank, processJoinRequests: self.processJoinRequests, updating: self.updating, focusedOnRank: focusedOnRank, expandedPermissions: self.expandedPermissions)
}
}
@@ -624,7 +680,68 @@ private func areAllAdminRightsEnabled(_ flags: TelegramChatAdminRightsFlags, pee
return TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except).intersection(flags) == TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except)
}
-private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: EnginePeer.Id, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] {
+private func guardBotAdminAlertText(_ text: String, presentationData: PresentationData) -> NSAttributedString {
+ return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(
+ body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: presentationData.theme.actionSheet.primaryTextColor),
+ bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: presentationData.theme.actionSheet.primaryTextColor),
+ link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: presentationData.theme.actionSheet.primaryTextColor),
+ linkAttribute: { _ in return nil }
+ ), textAlignment: .center)
+}
+
+private func guardBotReplacementAlertController(context: AccountContext, presentationData: PresentationData, currentBot: EnginePeer, newBot: EnginePeer, commit: @escaping () -> Void) -> AlertScreen {
+ let currentBotName = currentBot.compactDisplayTitle
+ let newBotName = newBot.compactDisplayTitle
+ var content: [AnyComponentWithIdentity] = []
+ content.append(AnyComponentWithIdentity(
+ id: "header",
+ component: AnyComponent(
+ AlertTransferHeaderComponent(
+ fromComponent: AnyComponentWithIdentity(id: "currentBot", component: AnyComponent(
+ AvatarComponent(
+ context: context,
+ theme: presentationData.theme,
+ peer: currentBot
+ )
+ )),
+ toComponent: AnyComponentWithIdentity(id: "newBot", component: AnyComponent(
+ AvatarComponent(
+ context: context,
+ theme: presentationData.theme,
+ peer: newBot
+ )
+ )),
+ type: .transfer
+ )
+ )
+ ))
+ content.append(AnyComponentWithIdentity(
+ id: "title",
+ component: AnyComponent(
+ AlertTitleComponent(title: presentationData.strings.Channel_EditAdmin_GuardBotReplaceTitle)
+ )
+ ))
+ content.append(AnyComponentWithIdentity(
+ id: "text",
+ component: AnyComponent(
+ AlertTextComponent(content: .attributed(guardBotAdminAlertText(presentationData.strings.Channel_EditAdmin_GuardBotReplaceText(currentBotName, newBotName).string, presentationData: presentationData)))
+ )
+ ))
+
+ return AlertScreen(
+ context: context,
+ configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false),
+ content: content,
+ actions: [
+ .init(title: presentationData.strings.Channel_EditAdmin_GuardBotReplaceKeep(currentBotName).string),
+ .init(title: presentationData.strings.Channel_EditAdmin_GuardBotReplaceUse(newBotName).string, type: .default, action: {
+ commit()
+ })
+ ]
+ )
+}
+
+private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: EnginePeer.Id, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, currentGuardBotId: EnginePeer.Id?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] {
var entries: [ChannelAdminEntry] = []
if case let .channel(channel) = channelPeer, let admin = adminPeer {
@@ -634,7 +751,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
if case .broadcast = channel.info {
isChannel = true
}
-
+
var isCreator = false
if let initialParticipant = initialParticipant, case .creator = initialParticipant {
isCreator = true
@@ -642,6 +759,12 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
var canTransfer = false
var canDismiss = false
+ let canEditProcessJoinRequests: Bool
+ if case let .user(user) = admin, user.botInfo?.flags.contains(.isGuardBot) == true {
+ canEditProcessJoinRequests = canEdit && user.id != accountPeerId
+ } else {
+ canEditProcessJoinRequests = false
+ }
let isGroup: Bool
var maskRightsFlags: TelegramChatAdminRightsFlags
@@ -697,7 +820,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
if isCreator {
if isGroup {
entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader))
-
+
let accountUserRightsFlags: TelegramChatAdminRightsFlags
if channel.flags.contains(.isCreator) {
accountUserRightsFlags = maskRightsFlags
@@ -845,7 +968,11 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
index += 1
}
- if accountUserRightsFlags.contains(.canAddAdmins) {
+ if canEditProcessJoinRequests {
+ let processJoinRequests = state.processJoinRequests ?? (currentGuardBotId == admin.id)
+ entries.append(.processJoinRequests(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionProcessJoinRequests, processJoinRequests, !state.updating))
+ entries.append(.processJoinRequestsInfo(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionProcessJoinRequestsInfo))
+ } else if accountUserRightsFlags.contains(.canAddAdmins) {
entries.append(.addAdminsInfo(presentationData.theme, currentRightsFlags.contains(.canAddAdmins) ? presentationData.strings.Channel_EditAdmin_PermissinAddAdminOn : presentationData.strings.Channel_EditAdmin_PermissinAddAdminOff))
}
@@ -912,7 +1039,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
}
}
}
-
+
if !invite || state.adminRights {
if canTransfer {
entries.append(.transfer(presentationData.theme, isGroup ? presentationData.strings.Group_EditAdmin_TransferOwnership : presentationData.strings.Channel_EditAdmin_TransferOwnership))
@@ -1065,6 +1192,9 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
let transferOwnershipDisposable = MetaDisposable()
actionsDisposable.add(transferOwnershipDisposable)
+
+ let guardBotDisposable = MetaDisposable()
+ actionsDisposable.add(guardBotDisposable)
var dismissImpl: (() -> Void)?
var dismissInputImpl: (() -> Void)?
@@ -1105,6 +1235,10 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
}
return current.withUpdatedUpdatedFlags(updated)
}
+ }, updateProcessJoinRequests: { value in
+ updateState { current in
+ return current.withUpdatedProcessJoinRequests(value)
+ }
}, toggleRightWhileDisabled: { right, _ in
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId),
@@ -1248,16 +1382,140 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.ExportedInvitation(id: peerId)
+ ),
+ context.engine.data.subscribe(
+ TelegramEngine.EngineData.Item.Peer.CachedData(id: peerId)
)
)
|> deliverOnMainQueue
- |> map { presentationData, state, peerInfoData, exportedInvitation -> (ItemListControllerState, (ItemListNodeState, Any)) in
+ |> map { presentationData, state, peerInfoData, exportedInvitation, cachedData -> (ItemListControllerState, (ItemListNodeState, Any)) in
let channelPeer = peerInfoData.0.flatMap { $0 }
let adminPeer = peerInfoData.1.flatMap { $0 }
let adminPresence = peerInfoData.2
+ let cachedChannelData = cachedData.flatMap { $0 as? CachedChannelData }
+ let currentGuardBotId = cachedChannelData?.guardBotId
let canEdit = canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: channelPeer!, initialParticipant: initialParticipant)
+ let channelIsGroup: Bool
+ let requestToJoinEnabled: Bool
+ if case let .channel(channel) = channelPeer {
+ if case .group = channel.info {
+ channelIsGroup = true
+ } else {
+ channelIsGroup = false
+ }
+ requestToJoinEnabled = channel.flags.contains(.requestToJoin)
+ } else {
+ channelIsGroup = true
+ requestToJoinEnabled = false
+ }
+ let canUseProcessJoinRequests: Bool
+ if case let .user(user) = adminPeer, user.botInfo?.flags.contains(.isGuardBot) == true, case .channel = channelPeer, canEdit, !invite || state.adminRights {
+ canUseProcessJoinRequests = true
+ } else {
+ canUseProcessJoinRequests = false
+ }
+
+ let finishAfterSaving: (TelegramChatAdminRights?, Bool) -> Void = { adminRights, notifyUpdated in
+ let _ = (statePromise.get()
+ |> take(1)).start(next: { state in
+ let processJoinRequests = state.processJoinRequests ?? (currentGuardBotId == adminId)
+
+ let complete: () -> Void = {
+ if notifyUpdated {
+ updated(adminRights)
+ }
+ dismissImpl?()
+ }
+ guard canUseProcessJoinRequests else {
+ complete()
+ return
+ }
+
+ if processJoinRequests {
+ if currentGuardBotId == adminId {
+ complete()
+ return
+ }
+ if notifyUpdated {
+ updated(adminRights)
+ }
+ updateState { current in
+ return current.withUpdatedUpdating(true)
+ }
+ guardBotDisposable.set((context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: true, guardBotId: adminId, applyToInvites: false, clearGuardBot: false)
+ |> deliverOnMainQueue).start(error: { _ in
+ updateState { current in
+ return current.withUpdatedUpdating(false)
+ }
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
+ }, completed: {
+ dismissImpl?()
+ }))
+ } else if currentGuardBotId == adminId {
+ if notifyUpdated {
+ updated(adminRights)
+ }
+ updateState { current in
+ return current.withUpdatedUpdating(true)
+ }
+ guardBotDisposable.set((context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: requestToJoinEnabled, guardBotId: nil, applyToInvites: false, clearGuardBot: true)
+ |> deliverOnMainQueue).start(error: { _ in
+ updateState { current in
+ return current.withUpdatedUpdating(false)
+ }
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
+ }, completed: {
+ dismissImpl?()
+ }))
+ } else {
+ complete()
+ }
+ })
+ }
+
+ let confirmProcessJoinRequests: (@escaping () -> Void) -> Void = { commit in
+ let _ = (statePromise.get()
+ |> take(1)).start(next: { state in
+ let processJoinRequests = state.processJoinRequests ?? (currentGuardBotId == adminId)
+
+ guard canUseProcessJoinRequests, processJoinRequests else {
+ commit()
+ return
+ }
+ if currentGuardBotId == nil {
+ guard let adminPeer else {
+ commit()
+ return
+ }
+ let title = channelIsGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers
+ let text = channelIsGroup ? presentationData.strings.Channel_EditAdmin_GuardBotEnableMembersText(adminPeer.compactDisplayTitle).string : presentationData.strings.Channel_EditAdmin_GuardBotEnableSubscribersText(adminPeer.compactDisplayTitle).string
+ presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: title, text: text, actions: [
+ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
+ TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_EditAdmin_GuardBotEnable, action: {
+ commit()
+ })
+ ], parseMarkdown: true), nil)
+ } else if currentGuardBotId != adminId, let currentGuardBotId, let adminPeer {
+ let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: currentGuardBotId))
+ |> deliverOnMainQueue).start(next: { currentGuardPeer in
+ guard let currentGuardPeer else {
+ presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
+ return
+ }
+ presentControllerImpl?(guardBotReplacementAlertController(context: context, presentationData: presentationData, currentBot: currentGuardPeer, newBot: adminPeer, commit: {
+ commit()
+ }), nil)
+ })
+ } else {
+ commit()
+ }
+ })
+ }
let rightButtonActionImpl = {
+ confirmProcessJoinRequests {
if invite && !state.adminRights {
updateState { current in
return current.withUpdatedUpdating(true)
@@ -1411,8 +1669,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
}
presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}, completed: {
- updated(TelegramChatAdminRights(rights: updateFlags))
- dismissImpl?()
+ finishAfterSaving(TelegramChatAdminRights(rights: updateFlags), true)
}))
} else if let updateRank = updateRank, let currentFlags = currentFlags {
updateState { current in
@@ -1421,11 +1678,10 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
updateRightsDisposable.set((context.peerChannelMemberCategoriesContextsManager.updateMemberAdminRights(engine: context.engine, peerId: peerId, memberId: adminId, adminRights: TelegramChatAdminRights(rights: currentFlags), rank: updateRank) |> deliverOnMainQueue).start(error: { _ in
}, completed: {
- updated(TelegramChatAdminRights(rights: currentFlags))
- dismissImpl?()
+ finishAfterSaving(TelegramChatAdminRights(rights: currentFlags), true)
}))
} else {
- dismissImpl?()
+ finishAfterSaving(nil, false)
}
} else if canEdit {
var updateFlags: TelegramChatAdminRightsFlags?
@@ -1500,8 +1756,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
}
dismissImpl?()
}, completed: {
- updated(TelegramChatAdminRights(rights: updateFlags))
- dismissImpl?()
+ finishAfterSaving(TelegramChatAdminRights(rights: updateFlags), true)
}))
}
}
@@ -1631,6 +1886,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
dismissImpl?()
}
}
+ }
}
var footerButtonTitle: String = presentationData.strings.Channel_Management_SaveChanges
@@ -1701,7 +1957,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
let rightNavigationButton: ItemListNavigationButton?
if state.focusedOnRank {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
rightButtonActionImpl()
})
footerItem = nil
@@ -1711,7 +1967,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
- let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminControllerEntries(presentationData: presentationData, state: state, accountPeerId: context.account.peerId, channelPeer: channelPeer, adminPeer: adminPeer, adminPresence: adminPresence, initialParticipant: initialParticipant, invite: invite, canEdit: canEdit), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, emptyStateItem: nil, footerItem: footerItem, animateChanges: true)
+ let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminControllerEntries(presentationData: presentationData, state: state, accountPeerId: context.account.peerId, channelPeer: channelPeer, adminPeer: adminPeer, adminPresence: adminPresence, currentGuardBotId: currentGuardBotId, initialParticipant: initialParticipant, invite: invite, canEdit: canEdit), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, emptyStateItem: nil, footerItem: footerItem, animateChanges: true)
return (controllerState, (listState, arguments))
}
diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift
index 3ee97b784d..705c240543 100644
--- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift
+++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift
@@ -973,7 +973,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
var secondaryRightNavigationButton: ItemListNavigationButton?
if let admins = admins, admins.count > 1 {
if state.editing {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
updateState { state in
return state.withUpdatedEditing(false)
}
diff --git a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift
index f23d0903a7..96110577b5 100644
--- a/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift
+++ b/submodules/PeerInfoUI/Sources/ChannelBannedMemberController.swift
@@ -13,6 +13,8 @@ import AlertUI
import PresentationDataUtils
import ItemListAvatarAndNameInfoItem
import OldChannelsController
+import ChatTimerScreen
+import ContextUI
private let rankMaxLength: Int32 = 16
@@ -54,6 +56,7 @@ private enum ChannelBannedMemberSection: Int32 {
private enum ChannelBannedMemberEntryTag: ItemListItemTag {
case rank
+ case timeout
func isEqual(to other: ItemListItemTag) -> Bool {
if let other = other as? ChannelBannedMemberEntryTag, self == other {
@@ -361,7 +364,7 @@ private enum ChannelBannedMemberEntry: ItemListNodeEntry {
case let .timeout(_, text, value):
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openTimeout()
- })
+ }, tag: ChannelBannedMemberEntryTag.timeout)
case let .exceptionInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .delete(_, text):
@@ -407,6 +410,18 @@ private struct ChannelBannedMemberControllerState: Equatable {
var focusedOnRank: Bool
}
+private final class ChannelBannedMemberContextReferenceContentSource: ContextReferenceContentSource {
+ private let sourceView: UIView
+
+ init(sourceView: UIView) {
+ self.sourceView = sourceView
+ }
+
+ func transitionInfo() -> ContextControllerReferenceViewInfo? {
+ return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0))
+ }
+}
+
func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
var result = flags
result.remove(.banReadMessages)
@@ -603,10 +618,12 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
var dismissImpl: (() -> Void)?
var presentControllerImpl: ((ViewController, Any?) -> Void)?
+ var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
var dismissInputImpl: (() -> Void)?
var errorImpl: (() -> Void)?
var scrollToRankImpl: (() -> Void)?
+ var findTimeoutReferenceNode: (() -> ItemListDisclosureItemNode?)?
let peerSignal = Promise()
peerSignal.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
@@ -714,12 +731,20 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
}
}, openTimeout: {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
- let actionSheet = ActionSheetController(presentationData: presentationData)
let intervals: [Int32] = [
1 * 60 * 60 * 24,
7 * 60 * 60 * 24,
30 * 60 * 60 * 24
]
+ let currentTimeout: Int32 = stateValue.with { state in
+ if let updatedTimeout = state.updatedTimeout {
+ return updatedTimeout
+ } else if let initialParticipant = initialParticipant, case let .member(_, _, _, maybeBanInfo, _, _) = initialParticipant, let banInfo = maybeBanInfo {
+ return banInfo.rights.untilDate
+ } else {
+ return Int32.max
+ }
+ }
let applyValue: (Int32?) -> Void = { value in
updateState { state in
var state = state
@@ -727,29 +752,71 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
return state
}
}
- var items: [ActionSheetItem] = []
+ var items: [ContextMenuItem] = []
for interval in intervals {
- items.append(ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: interval), color: .accent, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- applyValue(initialState.referenceTimestamp + interval)
- }))
+ let timeoutValue = initialState.referenceTimestamp + interval
+ items.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: presentationData.strings, value: interval), icon: { theme in
+ if currentTimeout == timeoutValue {
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
+ } else {
+ return UIImage()
+ }
+ }, action: { _, f in
+ f(.default)
+ applyValue(timeoutValue)
+ })))
}
- items.append(ActionSheetButtonItem(title: presentationData.strings.MessageTimer_Forever, color: .accent, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
+ items.append(.action(ContextMenuActionItem(text: presentationData.strings.MessageTimer_Forever, icon: { theme in
+ if currentTimeout == 0 || currentTimeout == Int32.max {
+ return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
+ } else {
+ return UIImage()
+ }
+ }, action: { _, f in
+ f(.default)
applyValue(Int32.max)
- }))
- items.append(ActionSheetButtonItem(title: presentationData.strings.MessageTimer_Custom, color: .accent, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- presentControllerImpl?(PeerBanTimeoutController(context: context, updatedPresentationData: updatedPresentationData, currentValue: Int32(Date().timeIntervalSince1970), applyValue: { value in
- applyValue(value)
- }), nil)
- }))
- actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
- actionSheet?.dismissAnimated()
- })
- ])])
- presentControllerImpl?(actionSheet, nil)
+ })))
+ items.append(.action(ContextMenuActionItem(text: presentationData.strings.MessageTimer_Custom, icon: { _ in
+ return nil
+ }, action: { _, f in
+ f(.default)
+ let controller = ChatTimerScreen(
+ context: context,
+ updatedPresentationData: updatedPresentationData,
+ configuration: ChatTimerScreen.Configuration(
+ style: .default,
+ picker: .date,
+ currentValue: Int32(Date().timeIntervalSince1970),
+ minimumDate: Date(),
+ maximumDate: Date(timeIntervalSince1970: Double(Int32.max - 1)),
+ pickerValueMapping: .roundDateToDaysUTC,
+ primaryActionTitle: { strings, _, _ in
+ strings.Wallpaper_Set
+ }
+ ),
+ completion: { value in
+ guard let value else {
+ return
+ }
+ applyValue(value)
+ }
+ )
+ presentControllerImpl?(controller, nil)
+ })))
+ guard let sourceNode = findTimeoutReferenceNode?() else {
+ return
+ }
+ let contextController = makeContextController(
+ presentationData: presentationData,
+ source: .reference(ChannelBannedMemberContextReferenceContentSource(sourceView: sourceNode.labelNode.view)),
+ items: .single(ContextController.Items(content: .list(items))),
+ gesture: nil
+ )
+ sourceNode.updateHasContextMenu(hasContextMenu: true)
+ contextController.dismissed = { [weak sourceNode] in
+ sourceNode?.updateHasContextMenu(hasContextMenu: false)
+ }
+ presentInGlobalOverlayImpl?(contextController)
}, delete: {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
@@ -1055,7 +1122,7 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
let rightNavigationButton: ItemListNavigationButton?
let footerItem: ItemListControllerFooterItem?
if state.focusedOnRank {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
rightButtonActionImpl()
})
footerItem = nil
@@ -1084,9 +1151,15 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
presentControllerImpl = { [weak controller] value, presentationArguments in
controller?.present(value, in: .window(.root), with: presentationArguments)
}
+ presentInGlobalOverlayImpl = { [weak controller] value in
+ controller?.presentInGlobalOverlay(value, with: nil)
+ }
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
+ findTimeoutReferenceNode = { [weak controller] in
+ return controller?.itemNode(forTag: ChannelBannedMemberEntryTag.timeout) as? ItemListDisclosureItemNode
+ }
let hapticFeedback = HapticFeedback()
errorImpl = { [weak controller] in
diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift
index 563e800c12..b85436332f 100644
--- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift
+++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift
@@ -454,7 +454,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat
var secondaryRightNavigationButton: ItemListNavigationButton?
if let participants = participants, !participants.isEmpty {
if state.editing {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
updateState { state in
return state.withUpdatedEditing(false)
}
diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift
index 0019fb3423..a9311e1547 100644
--- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift
+++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift
@@ -752,7 +752,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
}
if !isEmpty {
if state.editing {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
updateState { state in
return state.withUpdatedEditing(false)
}
diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift
index c9bed2e235..236c5c511b 100644
--- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift
+++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift
@@ -41,11 +41,12 @@ private final class ChannelVisibilityControllerArguments {
let toggleForwarding: (Bool) -> Void
let updateJoinToSend: (CurrentChannelJoinToSend) -> Void
let toggleApproveMembers: (Bool) -> Void
+ let openGuardBot: (EnginePeer.Id) -> Void
let activateLink: (String) -> Void
let deactivateLink: (String) -> Void
let openAuction: (String) -> Void
- init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, revokePeerId: @escaping (EnginePeer.Id) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
+ init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, revokePeerId: @escaping (EnginePeer.Id) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, openGuardBot: @escaping (EnginePeer.Id) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
self.context = context
self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText
@@ -60,6 +61,7 @@ private final class ChannelVisibilityControllerArguments {
self.toggleForwarding = toggleForwarding
self.updateJoinToSend = updateJoinToSend
self.toggleApproveMembers = toggleApproveMembers
+ self.openGuardBot = openGuardBot
self.activateLink = activateLink
self.deactivateLink = deactivateLink
self.openAuction = openAuction
@@ -126,7 +128,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case joinToSendMembers(PresentationTheme, String, Bool)
case approveMembers(PresentationTheme, String, Bool)
- case approveMembersInfo(PresentationTheme, String)
+ case approveMembersInfo(PresentationTheme, String, EnginePeer.Id?)
case forwardingHeader(PresentationTheme, String)
case forwardingDisabled(PresentationTheme, String, Bool)
@@ -386,8 +388,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else {
return false
}
- case let .approveMembersInfo(lhsTheme, lhsText):
- if case let .approveMembersInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ case let .approveMembersInfo(lhsTheme, lhsText, lhsGuardBotId):
+ if case let .approveMembersInfo(rhsTheme, rhsText, rhsGuardBotId) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsGuardBotId == rhsGuardBotId {
return true
} else {
return false
@@ -740,8 +742,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
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):
- return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
+ case let .approveMembersInfo(_, text, guardBotId):
+ return ItemListTextItem(presentationData: presentationData, text: guardBotId == nil ? .plain(text) : .markdown(text), sectionId: self.section, linkAction: { _ in
+ if let guardBotId {
+ arguments.openGuardBot(guardBotId)
+ }
+ })
case let .forwardingHeader(_, title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .forwardingDisabled(_, text, selected):
@@ -780,6 +786,7 @@ private struct ChannelVisibilityControllerState: Equatable {
let forwardingEnabled: Bool?
let joinToSend: CurrentChannelJoinToSend?
let approveMembers: Bool?
+ let approveMembersApplyToInvites: Bool?
init() {
self.selectedType = nil
@@ -792,9 +799,10 @@ private struct ChannelVisibilityControllerState: Equatable {
self.forwardingEnabled = nil
self.joinToSend = nil
self.approveMembers = nil
+ self.approveMembersApplyToInvites = nil
}
- init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: EnginePeer.Id?, revokingPeerId: EnginePeer.Id?, revokingPrivateLink: Bool, forwardingEnabled: Bool?, joinToSend: CurrentChannelJoinToSend?, approveMembers: Bool?) {
+ init(selectedType: CurrentChannelType?, editingPublicLinkText: String?, addressNameValidationStatus: AddressNameValidationStatus?, updatingAddressName: Bool, revealedRevokePeerId: EnginePeer.Id?, revokingPeerId: EnginePeer.Id?, revokingPrivateLink: Bool, forwardingEnabled: Bool?, joinToSend: CurrentChannelJoinToSend?, approveMembers: Bool?, approveMembersApplyToInvites: Bool?) {
self.selectedType = selectedType
self.editingPublicLinkText = editingPublicLinkText
self.addressNameValidationStatus = addressNameValidationStatus
@@ -805,6 +813,7 @@ private struct ChannelVisibilityControllerState: Equatable {
self.forwardingEnabled = forwardingEnabled
self.joinToSend = joinToSend
self.approveMembers = approveMembers
+ self.approveMembersApplyToInvites = approveMembersApplyToInvites
}
static func ==(lhs: ChannelVisibilityControllerState, rhs: ChannelVisibilityControllerState) -> Bool {
@@ -838,47 +847,54 @@ private struct ChannelVisibilityControllerState: Equatable {
if lhs.approveMembers != rhs.approveMembers {
return false
}
+ if lhs.approveMembersApplyToInvites != rhs.approveMembersApplyToInvites {
+ return false
+ }
return true
}
func withUpdatedSelectedType(_ selectedType: CurrentChannelType?) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedEditingPublicLinkText(_ editingPublicLinkText: String?) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedAddressNameValidationStatus(_ addressNameValidationStatus: AddressNameValidationStatus?) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: addressNameValidationStatus, updatingAddressName: self.updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedUpdatingAddressName(_ updatingAddressName: Bool) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedRevokingPeerId(_ revokingPeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedRevokingPrivateLink(_ revokingPrivateLink: Bool) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedForwardingEnabled(_ forwardingEnabled: Bool) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedJoinToSend(_ joinToSend: CurrentChannelJoinToSend?) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: joinToSend, approveMembers: self.approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: self.approveMembersApplyToInvites)
}
func withUpdatedApproveMembers(_ approveMembers: Bool) -> ChannelVisibilityControllerState {
- return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: approveMembers)
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: approveMembers, approveMembersApplyToInvites: nil)
+ }
+
+ func withUpdatedApproveMembersApplyToInvites(_ approveMembersApplyToInvites: Bool) -> ChannelVisibilityControllerState {
+ return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers, approveMembersApplyToInvites: approveMembersApplyToInvites)
}
}
@@ -1154,12 +1170,18 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
entries.append(.joinToSendEveryone(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_Everyone, joinToSend == .everyone))
entries.append(.joinToSendMembers(presentationData.theme, presentationData.strings.Group_Setup_WhoCanSendMessages_OnlyMembers, joinToSend == .members))
}
-
- if !isDiscussion || joinToSend == .members {
- entries.append(.approveMembers(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembers, approveMembers))
- entries.append(.approveMembersInfo(presentationData.theme, presentationData.strings.Group_Setup_ApproveNewMembersInfo))
- }
}
+
+ let approveMembersTitle = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers
+ var approveMembersInfo = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembersInfo : presentationData.strings.Channel_Setup_ApproveNewSubscribersInfo
+ var guardBotId: EnginePeer.Id?
+ if let cachedChannelData = view.cachedData as? CachedChannelData, let currentGuardBotId = cachedChannelData.guardBotId, let guardBotPeer = view.peers[currentGuardBotId].flatMap(EnginePeer.init), let guardBotUsername = guardBotPeer.addressName, !guardBotUsername.isEmpty {
+ guardBotId = currentGuardBotId
+ approveMembersInfo += " " + presentationData.strings.Group_Setup_ApproveNewMembersManagedBy("[@\(guardBotUsername)](guardbot)").string
+ }
+
+ entries.append(.approveMembers(presentationData.theme, approveMembersTitle, approveMembers))
+ entries.append(.approveMembersInfo(presentationData.theme, approveMembersInfo, guardBotId))
entries.append(.forwardingHeader(presentationData.theme, isGroup ? presentationData.strings.Group_Setup_ForwardingGroupTitle.uppercased() : presentationData.strings.Group_Setup_ForwardingChannelTitle.uppercased()))
entries.append(.forwardingDisabled(presentationData.theme, presentationData.strings.Group_Setup_ForwardingDisabled, !forwardingEnabled))
@@ -1458,6 +1480,13 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
actionsDisposable.add(toggleRequestToJoinDisposable)
let temporaryOrder = Promise<[String]?>(nil)
+ let activeInviteLinksCount = Atomic(value: 0)
+ let currentPeerIsGroup = Atomic(value: true)
+ let activeInviteLinksContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: false)
+ actionsDisposable.add((activeInviteLinksContext.state
+ |> deliverOnMainQueue).start(next: { state in
+ let _ = activeInviteLinksCount.swap(state.hasLoadedOnce ? state.count : 0)
+ }))
let arguments = ChannelVisibilityControllerArguments(context: context, updateCurrentType: { type in
if type == .publicChannel {
@@ -1697,9 +1726,34 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
return state.withUpdatedJoinToSend(value)
}
}, toggleApproveMembers: { value in
- updateState { state in
- return state.withUpdatedApproveMembers(value)
+ let updateApproveMembers: (Bool) -> Void = { applyToInvites in
+ updateState { state in
+ return state.withUpdatedApproveMembers(value).withUpdatedApproveMembersApplyToInvites(applyToInvites)
+ }
}
+
+ let inviteLinksCount = activeInviteLinksCount.with { $0 }
+ if inviteLinksCount > 0 {
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ let isGroup = currentPeerIsGroup.with { $0 }
+ let approvalTitle = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembers : presentationData.strings.Channel_Setup_ApproveNewSubscribers
+ let action = value ? presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksEnable : presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksDisable
+ let peerType = isGroup ? presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksPeerGroup : presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksPeerChannel
+ let text = presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksText(action, approvalTitle, "\(inviteLinksCount)", peerType).string
+ presentControllerImpl?(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.Group_Setup_ApproveNewMembersApplyToExistingInviteLinksTitle, text: text, actions: [
+ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {
+ updateApproveMembers(false)
+ }),
+ TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
+ updateApproveMembers(true)
+ })
+ ], parseMarkdown: true), nil)
+ } else {
+ updateApproveMembers(false)
+ }
+ }, openGuardBot: { guardBotId in
+ let controller = channelAdminController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, adminId: guardBotId, initialParticipant: nil, updated: { _ in }, upgradedToSupergroup: upgradedToSupergroup, transferedOwnership: { _ in })
+ pushControllerImpl?(controller)
}, activateLink: { name in
dismissInputImpl?()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
@@ -1836,6 +1890,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
} else if let _ = peer as? TelegramGroup {
isGroup = true
}
+ let _ = currentPeerIsGroup.swap(isGroup)
var rightNavigationButton: ItemListNavigationButton?
if case .revokeNames = mode {
@@ -1879,7 +1934,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
isInitialSetup = false
}
- rightNavigationButton = ItemListNavigationButton(content: .text(isInitialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
+ rightNavigationButton = ItemListNavigationButton(content: isInitialSetup ? .text(presentationData.strings.Common_Next) : .icon(.done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
var updatedAddressNameValue: String?
updateState { state in
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .channel(peer), cachedData: view.cachedData)
@@ -1899,7 +1954,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
}
if let updatedApproveMembers = state.approveMembers {
- toggleRequestToJoinDisposable.set(context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: updatedApproveMembers).start())
+ toggleRequestToJoinDisposable.set(context.engine.peers.toggleChannelJoinRequest(peerId: peerId, enabled: updatedApproveMembers, guardBotId: nil, applyToInvites: state.approveMembersApplyToInvites ?? false).start())
}
if let updatedAddressNameValue = updatedAddressNameValue {
@@ -1975,7 +2030,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
}
}
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: {
var updatedAddressNameValue: String?
updateState { state in
updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .legacyGroup(peer), cachedData: nil)
@@ -2063,7 +2118,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
case .initialSetup:
leftNavigationButton = nil
case .generic, .privateLink, .revokeNames:
- leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
}
diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift
index aa571d51df..c7c27627a6 100644
--- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift
+++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift
@@ -1089,7 +1089,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext,
case .vcard:
break
case .filter, .create:
- leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?(true)
cancelled?()
})
@@ -1131,7 +1131,7 @@ public func deviceContactInfoController(context: ShareControllerAccountContext,
composedContactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: filteredPhoneNumbers), middleName: filteredData.middleName, prefix: filteredData.prefix, suffix: filteredData.suffix, organization: filteredData.organization, jobTitle: filteredData.jobTitle, department: filteredData.department, emailAddresses: filteredData.emailAddresses, urls: urls, addresses: filteredData.addresses, birthdayDate: filteredData.birthdayDate, socialProfiles: filteredData.socialProfiles, instantMessagingProfiles: filteredData.instantMessagingProfiles, note: filteredData.note)
}
- rightNavigationButton = ItemListNavigationButton(content: .text(isShare ? presentationData.strings.Common_Done : presentationData.strings.Compose_Create), style: .bold, enabled: (isShare || !filteredPhoneNumbers.isEmpty) && composedContactData != nil, action: {
+ rightNavigationButton = ItemListNavigationButton(content: isShare ? .icon(.done) : .text(presentationData.strings.Compose_Create), style: .bold, enabled: (isShare || !filteredPhoneNumbers.isEmpty) && composedContactData != nil, action: {
if let composedContactData = composedContactData {
guard let context = (context as? ShareControllerAppAccountContext)?.context else {
return
diff --git a/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift b/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift
index c9cecdc0bc..b7c3ca891f 100644
--- a/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift
+++ b/submodules/PeerInfoUI/Sources/GroupPreHistorySetupController.swift
@@ -144,14 +144,14 @@ public func groupPreHistorySetupController(context: AccountContext, updatedPrese
|> deliverOnMainQueue
|> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState, Any)) in
let defaultValue: Bool = (view.cachedData as? CachedChannelData)?.flags.contains(.preHistoryEnabled) ?? false
- let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
var rightNavigationButton: ItemListNavigationButton?
if state.applyingSetting {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} else {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
var value: Bool?
updateState { state in
var state = state
diff --git a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift
index 64133463f7..3683dcf37b 100644
--- a/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift
+++ b/submodules/PeerInfoUI/Sources/ItemListReactionItem.swift
@@ -383,7 +383,7 @@ public class ItemListReactionItemNode: ListViewItemNode, ItemListItemNode {
imageNode.update(size: imageFitSize)
}
- strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.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)
if let switchView = strongSelf.switchNode.view as? UISwitch {
if strongSelf.switchNode.bounds.size.width.isZero {
switchView.sizeToFit()
diff --git a/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift b/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift
index ec009e1670..6e4983cbcf 100644
--- a/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift
+++ b/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift
@@ -187,14 +187,14 @@ public func peerAutoremoveSetupScreen(context: AccountContext, updatedPresentati
let peer = view.peers[view.peerId]
- let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
var rightNavigationButton: ItemListNavigationButton?
if state.applyingSetting {
rightNavigationButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
} else {
- rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
var changedValue: Int32?
updateState { state in
var state = state
diff --git a/submodules/PeerInfoUI/Sources/PeerBanTimeoutController.swift b/submodules/PeerInfoUI/Sources/PeerBanTimeoutController.swift
deleted file mode 100644
index 396e10a4d6..0000000000
--- a/submodules/PeerInfoUI/Sources/PeerBanTimeoutController.swift
+++ /dev/null
@@ -1,127 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import UIKit
-import SwiftSignalKit
-import TelegramCore
-import TelegramPresentationData
-import TelegramStringFormatting
-import AccountContext
-import UIKitRuntimeUtils
-
-final class PeerBanTimeoutController: ActionSheetController {
- private var presentationDisposable: Disposable?
-
- private let _ready = Promise()
- override var ready: Promise {
- return self._ready
- }
-
- init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, currentValue: Int32, applyValue: @escaping (Int32?) -> Void) {
- let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
- let strings = presentationData.strings
-
- super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
-
- self._ready.set(.single(true))
-
- self.presentationDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak self] presentationData in
- if let strongSelf = self {
- strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
- }
- })
-
- var updatedValue = currentValue
- var items: [ActionSheetItem] = []
- items.append(PeerBanTimeoutActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in
- updatedValue = value
- }))
- items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in
- self?.dismissAnimated()
- applyValue(updatedValue)
- }))
- self.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
- self?.dismissAnimated()
- }),
- ])
- ])
- }
-
- required init(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- deinit {
- self.presentationDisposable?.dispose()
- }
-}
-
-private final class PeerBanTimeoutActionSheetItem: ActionSheetItem {
- let strings: PresentationStrings
-
- let currentValue: Int32
- let valueChanged: (Int32) -> Void
-
- init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
- self.strings = strings
- self.currentValue = roundDateToDays(currentValue)
- self.valueChanged = valueChanged
- }
-
- func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- return PeerBanTimeoutActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged)
- }
-
- func updateNode(_ node: ActionSheetItemNode) {
- }
-}
-
-private final class PeerBanTimeoutActionSheetItemNode: ActionSheetItemNode {
- private let theme: ActionSheetControllerTheme
- private let strings: PresentationStrings
-
- private let valueChanged: (Int32) -> Void
- private let pickerView: UIDatePicker
-
- init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
- self.theme = theme
- self.strings = strings
- self.valueChanged = valueChanged
-
- UILabel.setDateLabel(theme.primaryTextColor)
-
- self.pickerView = UIDatePicker()
- self.pickerView.datePickerMode = .countDownTimer
- self.pickerView.datePickerMode = .date
- self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
- self.pickerView.locale = localeWithStrings(strings)
- self.pickerView.minimumDate = Date()
- self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
- if #available(iOS 13.4, *) {
- self.pickerView.preferredDatePickerStyle = .wheels
- }
- self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
-
- super.init(theme: theme)
-
- self.view.addSubview(self.pickerView)
- self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
- }
-
- public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let size = CGSize(width: constrainedSize.width, height: 216.0)
-
- self.pickerView.frame = CGRect(origin: CGPoint(), size: size)
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-
- @objc private func datePickerUpdated() {
- self.valueChanged(roundDateToDays(Int32(self.pickerView.date.timeIntervalSince1970)))
- }
-}
diff --git a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift
index 5bb4a86ac6..ce2b21fb4a 100644
--- a/submodules/PeerInfoUI/Sources/PhoneLabelController.swift
+++ b/submodules/PeerInfoUI/Sources/PhoneLabelController.swift
@@ -110,11 +110,11 @@ public func phoneLabelController(context: AccountContext, currentLabel: String,
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get())
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
arguments.cancel()
})
- let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
arguments.complete()
})
diff --git a/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift b/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift
index 985a4bf850..7f75d032ee 100644
--- a/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift
+++ b/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift
@@ -73,6 +73,8 @@ final class SecretChatKeyControllerNode: ViewControllerTracingNode {
override func didLoad() {
super.didLoad()
+ self.scrollNode.view.scrollsToTop = false
+
self.infoNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.infoTap(_:))))
}
diff --git a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift
index c3ff6f4c80..33b4bb07be 100644
--- a/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift
+++ b/submodules/PeerInfoUI/Sources/UserInfoEditingPhoneItem.swift
@@ -284,7 +284,7 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor)]))
}
})
}
diff --git a/submodules/PeersNearbyIconNode/BUILD b/submodules/PeersNearbyIconNode/BUILD
deleted file mode 100644
index bcb7f6d667..0000000000
--- a/submodules/PeersNearbyIconNode/BUILD
+++ /dev/null
@@ -1,21 +0,0 @@
-load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
-
-swift_library(
- name = "PeersNearbyIconNode",
- module_name = "PeersNearbyIconNode",
- srcs = glob([
- "Sources/**/*.swift",
- ]),
- copts = [
- "-warnings-as-errors",
- ],
- deps = [
- "//submodules/AsyncDisplayKit:AsyncDisplayKit",
- "//submodules/Display:Display",
- "//submodules/TelegramPresentationData:TelegramPresentationData",
- "//submodules/LegacyComponents:LegacyComponents",
- ],
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/submodules/PeersNearbyIconNode/Sources/PeersNearbyIconNode.swift b/submodules/PeersNearbyIconNode/Sources/PeersNearbyIconNode.swift
deleted file mode 100644
index 22b2a8f99b..0000000000
--- a/submodules/PeersNearbyIconNode/Sources/PeersNearbyIconNode.swift
+++ /dev/null
@@ -1,209 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import TelegramPresentationData
-import LegacyComponents
-
-private final class PeersNearbyIconWavesNodeParams: NSObject {
- let color: UIColor
- let progress: CGFloat
-
- init(color: UIColor, progress: CGFloat) {
- self.color = color
- self.progress = progress
-
- super.init()
- }
-}
-
-private func degToRad(_ degrees: CGFloat) -> CGFloat {
- return degrees * CGFloat.pi / 180.0
-}
-
-public final class PeersNearbyIconWavesNode: ASDisplayNode {
- public var color: UIColor {
- didSet {
- self.setNeedsDisplay()
- }
- }
-
- private var effectiveProgress: CGFloat = 0.0 {
- didSet {
- self.setNeedsDisplay()
- }
- }
-
- public init(color: UIColor) {
- self.color = color
-
- super.init()
-
- self.isLayerBacked = true
- self.isOpaque = false
- }
-
- override public func willEnterHierarchy() {
- super.willEnterHierarchy()
-
- self.pop_removeAnimation(forKey: "indefiniteProgress")
-
- let animation = POPBasicAnimation()
- animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
- property?.readBlock = { node, values in
- values?.pointee = (node as! PeersNearbyIconWavesNode).effectiveProgress
- }
- property?.writeBlock = { node, values in
- (node as! PeersNearbyIconWavesNode).effectiveProgress = values!.pointee
- }
- property?.threshold = 0.01
- }) as! POPAnimatableProperty)
- animation.fromValue = CGFloat(0.0) as NSNumber
- animation.toValue = CGFloat(1.0) as NSNumber
- animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
- animation.duration = 3.5
- animation.repeatForever = true
- self.pop_add(animation, forKey: "indefiniteProgress")
- }
-
- override public func didExitHierarchy() {
- super.didExitHierarchy()
-
- self.pop_removeAnimation(forKey: "indefiniteProgress")
- }
-
- override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
- let t = CACurrentMediaTime()
- let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
- return PeersNearbyIconWavesNodeParams(color: self.color, progress: value)
- }
-
- @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
- let context = UIGraphicsGetCurrentContext()!
-
- if !isRasterizing {
- context.setBlendMode(.copy)
- context.setFillColor(UIColor.clear.cgColor)
- context.fill(bounds)
- }
-
- if let parameters = parameters as? PeersNearbyIconWavesNodeParams {
- let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
- let radius: CGFloat = bounds.width * 0.3333
- let range: CGFloat = (bounds.width - radius * 2.0) / 2.0
-
- context.setFillColor(parameters.color.cgColor)
-
- let draw: (CGContext, CGFloat) -> Void = { context, pos in
- let path = CGMutablePath()
-
- let pathRadius: CGFloat = bounds.width * 0.3333 + range * pos
- path.addEllipse(in: CGRect(x: center.x - pathRadius, y: center.y - pathRadius, width: pathRadius * 2.0, height: pathRadius * 2.0))
-
- let strokedPath = path.copy(strokingWithWidth: 1.0, lineCap: .round, lineJoin: .miter, miterLimit: 10.0)
- context.addPath(strokedPath)
- context.fillPath()
- }
-
- let position = parameters.progress
- var alpha = position / 0.5
- if alpha > 1.0 {
- alpha = 2.0 - alpha
- }
- context.setAlpha(alpha * 0.7)
-
- draw(context, position)
-
- var progress = parameters.progress + 0.3333
- if progress > 1.0 {
- progress = progress - 1.0
- }
-
- var largerPos = progress
- var largerAlpha = largerPos / 0.5
- if largerAlpha > 1.0 {
- largerAlpha = 2.0 - largerAlpha
- }
- context.setAlpha(largerAlpha * 0.7)
-
- draw(context, largerPos)
-
- progress = parameters.progress + 0.6666
- if progress > 1.0 {
- progress = progress - 1.0
- }
-
- largerPos = progress
- largerAlpha = largerPos / 0.5
- if largerAlpha > 1.0 {
- largerAlpha = 2.0 - largerAlpha
- }
- context.setAlpha(largerAlpha * 0.7)
-
- draw(context, largerPos)
- }
- }
-}
-
-private func generateIcon(size: CGSize, color: UIColor, contentColor: UIColor) -> UIImage {
- return generateImage(size, rotatedContext: { size, context in
- let bounds = CGRect(origin: CGPoint(), size: size)
- context.clear(bounds)
-
- context.setFillColor(color.cgColor)
- context.fillEllipse(in: bounds)
-
- context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
- context.scaleBy(x: size.width / 120.0, y: size.height / 120.0)
- context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
- context.translateBy(x: 0.0, y: 6.0)
- context.setFillColor(contentColor.cgColor)
-
- if size.width == 120.0 {
- context.translateBy(x: 30.0, y: 30.0)
- }
-
- let _ = try? drawSvgPath(context, path: "M27.8628211,52.2347452 L27.8628211,27.1373017 L2.76505663,27.1373017 C1.55217431,27.1373017 0.568938916,26.1540663 0.568938916,24.941184 C0.568938916,24.0832172 1.06857435,23.3038117 1.84819149,22.9456161 L51.2643819,0.241311309 C52.586928,-0.366333451 54.1516568,0.213208572 54.7593016,1.53575465 C55.0801868,2.23416513 55.080181,3.03785964 54.7592857,3.7362655 L32.0544935,53.1516391 C31.548107,54.2537536 30.2441593,54.7366865 29.1420449,54.2302999 C28.3624433,53.8720978 27.8628211,53.0927006 27.8628211,52.2347452 Z ")
- })!
-}
-
-public final class PeersNearbyIconNode: ASDisplayNode {
- private var theme: PresentationTheme
-
- private var iconNode: ASImageNode
- private var wavesNode: PeersNearbyIconWavesNode
-
- public init(theme: PresentationTheme) {
- self.theme = theme
-
- self.iconNode = ASImageNode()
- self.iconNode.isOpaque = false
- self.wavesNode = PeersNearbyIconWavesNode(color: theme.list.itemAccentColor)
-
- super.init()
-
- self.addSubnode(self.iconNode)
- self.addSubnode(self.wavesNode)
- }
-
- public func updateTheme(_ theme: PresentationTheme) {
- guard self.theme !== theme else {
- return
- }
- self.theme = theme
-
- self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor)
- self.wavesNode.color = theme.list.itemAccentColor
- }
-
- override public func layout() {
- super.layout()
-
- if let image = self.iconNode.image, image.size.width == self.bounds.width {
- } else {
- self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor)
- }
- self.iconNode.frame = self.bounds
- self.wavesNode.frame = self.bounds.insetBy(dx: -self.bounds.width * 0.3, dy: -self.bounds.height * 0.3)
- }
-}
diff --git a/submodules/PeersNearbyUI/BUILD b/submodules/PeersNearbyUI/BUILD
deleted file mode 100644
index 4ebbdcdbb5..0000000000
--- a/submodules/PeersNearbyUI/BUILD
+++ /dev/null
@@ -1,39 +0,0 @@
-load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
-
-swift_library(
- name = "PeersNearbyUI",
- module_name = "PeersNearbyUI",
- srcs = glob([
- "Sources/**/*.swift",
- ]),
- copts = [
- "-warnings-as-errors",
- ],
- deps = [
- "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
- "//submodules/AsyncDisplayKit:AsyncDisplayKit",
- "//submodules/Display:Display",
- "//submodules/TelegramCore:TelegramCore",
- "//submodules/TelegramPresentationData:TelegramPresentationData",
- "//submodules/AccountContext:AccountContext",
- "//submodules/TelegramUIPreferences:TelegramUIPreferences",
- "//submodules/ItemListUI:ItemListUI",
- "//submodules/OverlayStatusController:OverlayStatusController",
- "//submodules/DeviceLocationManager:DeviceLocationManager",
- "//submodules/AlertUI:AlertUI",
- "//submodules/PresentationDataUtils:PresentationDataUtils",
- "//submodules/ItemListPeerItem:ItemListPeerItem",
- "//submodules/TelegramPermissionsUI:TelegramPermissionsUI",
- "//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
- "//submodules/PeersNearbyIconNode:PeersNearbyIconNode",
- "//submodules/Geocoding:Geocoding",
- "//submodules/AppBundle:AppBundle",
- "//submodules/AnimatedStickerNode:AnimatedStickerNode",
- "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
- "//submodules/TelegramStringFormatting:TelegramStringFormatting",
- "//submodules/TelegramNotices:TelegramNotices",
- ],
- visibility = [
- "//visibility:public",
- ],
-)
diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift
deleted file mode 100644
index 8dae8cc38d..0000000000
--- a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift
+++ /dev/null
@@ -1,659 +0,0 @@
-import Foundation
-import UIKit
-import AsyncDisplayKit
-import Display
-import SwiftSignalKit
-import TelegramCore
-import MapKit
-import TelegramPresentationData
-import TelegramUIPreferences
-import ItemListUI
-import PresentationDataUtils
-import OverlayStatusController
-import DeviceLocationManager
-import AccountContext
-import AlertUI
-import PresentationDataUtils
-import ItemListPeerItem
-import TelegramPermissionsUI
-import ItemListPeerActionItem
-import Geocoding
-import AppBundle
-import ContextUI
-import TelegramNotices
-import TelegramStringFormatting
-
-private let maxUsersDisplayedLimit: Int32 = 5
-
-private struct PeerNearbyEntry {
- let peer: EnginePeer
- let memberCount: Int32?
- let expires: Int32
- let distance: Int32
-}
-
-private func arePeersNearbyEqual(_ lhs: PeerNearbyEntry?, _ rhs: PeerNearbyEntry?) -> Bool {
- if let lhs = lhs, let rhs = rhs {
- return lhs.peer == rhs.peer && lhs.expires == rhs.expires && lhs.distance == rhs.distance
- } else {
- return (lhs != nil) == (rhs != nil)
- }
-}
-
-private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNearbyEntry]) -> Bool {
- if lhs.count != rhs.count {
- return false
- }
- for i in 0 ..< lhs.count {
- if lhs[i].peer != rhs[i].peer || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance {
- return false
- }
- }
- return true
-}
-
-private final class PeersNearbyControllerArguments {
- let context: AccountContext
- let toggleVisibility: (Bool) -> Void
- let openProfile: (EnginePeer, Int32) -> Void
- let openChat: (EnginePeer) -> Void
- let openCreateGroup: (Double, Double, String?) -> Void
- let contextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void
- let expandUsers: () -> Void
-
- init(context: AccountContext, toggleVisibility: @escaping (Bool) -> Void, openProfile: @escaping (EnginePeer, Int32) -> Void, openChat: @escaping (EnginePeer) -> Void, openCreateGroup: @escaping (Double, Double, String?) -> Void, contextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, expandUsers: @escaping () -> Void) {
- self.context = context
- self.toggleVisibility = toggleVisibility
- self.openProfile = openProfile
- self.openChat = openChat
- self.openCreateGroup = openCreateGroup
- self.contextAction = contextAction
- self.expandUsers = expandUsers
- }
-}
-
-private enum PeersNearbySection: Int32 {
- case header
- case users
- case groups
- case channels
-}
-
-private enum PeersNearbyEntry: ItemListNodeEntry {
- case header(PresentationTheme, String)
-
- case usersHeader(PresentationTheme, String, Bool)
- case empty(PresentationTheme, String)
- case visibility(PresentationTheme, String, Bool)
- case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
- case expand(PresentationTheme, String)
-
- case groupsHeader(PresentationTheme, String, Bool)
- case createGroup(PresentationTheme, String, Double?, Double?, String?)
- case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry, Bool)
-
- case channelsHeader(PresentationTheme, String)
- case channel(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry, Bool)
-
- var section: ItemListSectionId {
- switch self {
- case .header:
- return PeersNearbySection.header.rawValue
- case .usersHeader, .empty, .visibility, .user, .expand:
- return PeersNearbySection.users.rawValue
- case .groupsHeader, .createGroup, .group:
- return PeersNearbySection.groups.rawValue
- case .channelsHeader, .channel:
- return PeersNearbySection.channels.rawValue
- }
- }
-
- var stableId: Int32 {
- switch self {
- case .header:
- return 0
- case .usersHeader:
- return 1
- case .empty:
- return 2
- case .visibility:
- return 3
- case let .user(index, _, _, _, _, _):
- return 4 + index
- case .expand:
- return 1000
- case .groupsHeader:
- return 1001
- case .createGroup:
- return 1002
- case let .group(index, _, _, _, _, _, _):
- return 1003 + index
- case .channelsHeader:
- return 2000
- case let .channel(index, _, _, _, _, _, _):
- return 2001 + index
- }
- }
-
- static func ==(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool {
- switch lhs {
- case let .header(lhsTheme, lhsText):
- if case let .header(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
- return true
- } else {
- return false
- }
- case let .usersHeader(lhsTheme, lhsText, lhsLoading):
- if case let .usersHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading {
- return true
- } else {
- return false
- }
- case let .empty(lhsTheme, lhsText):
- if case let .empty(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
- return true
- } else {
- return false
- }
- case let .visibility(lhsTheme, lhsText, lhsStop):
- if case let .visibility(rhsTheme, rhsText, rhsStop) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStop == rhsStop {
- return true
- } else {
- return false
- }
-
- case let .user(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer):
- if case let .user(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer) {
- return true
- } else {
- return false
- }
- case let .expand(lhsTheme, lhsText):
- if case let .expand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
- return true
- } else {
- return false
- }
- case let .groupsHeader(lhsTheme, lhsText, lhsLoading):
- if case let .groupsHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading {
- return true
- } else {
- return false
- }
- case let .createGroup(lhsTheme, lhsText, lhsLatitude, lhsLongitude, lhsAddress):
- if case let .createGroup(rhsTheme, rhsText, rhsLatitude, rhsLongitude, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLatitude == rhsLatitude && lhsLongitude == rhsLongitude && lhsAddress == rhsAddress {
- return true
- } else {
- return false
- }
- case let .group(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer, lhsHighlighted):
- if case let .group(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer, rhsHighlighted) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer), lhsHighlighted == rhsHighlighted {
- return true
- } else {
- return false
- }
- case let .channelsHeader(lhsTheme, lhsText):
- if case let .channelsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
- return true
- } else {
- return false
- }
- case let .channel(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer, lhsHighlighted):
- if case let .channel(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer, rhsHighlighted) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer), lhsHighlighted == rhsHighlighted {
- return true
- } else {
- return false
- }
- }
- }
-
- static func <(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool {
- return lhs.stableId < rhs.stableId
- }
-
- func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
- let arguments = arguments as! PeersNearbyControllerArguments
- switch self {
- case let .header(theme, text):
- return PeersNearbyHeaderItem(context: arguments.context, theme: theme, text: text, sectionId: self.section)
- case let .usersHeader(_, text, loading):
- return ItemListSectionHeaderItem(presentationData: presentationData, text: text, activityIndicator: loading ? .left : .none, sectionId: self.section)
- case let .empty(theme, text):
- return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks)
- case let .visibility(theme, title, stop):
- return ItemListPeerActionItem(presentationData: presentationData, icon: stop ? PresentationResourcesItemList.makeInvisibleIcon(theme) : PresentationResourcesItemList.makeVisibleIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, color: stop ? .destructive : .accent, editing: false, action: {
- arguments.toggleVisibility(!stop)
- })
- case let .user(_, _, strings, dateTimeFormat, nameDisplayOrder, peer):
- var text = strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string
- let isSelfPeer = peer.peer.id == arguments.context.account.peerId
- if isSelfPeer {
- text = strings.PeopleNearby_VisibleUntil(humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: peer.expires).string).string
- }
- return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: !isSelfPeer, sectionId: self.section, action: {
- if !isSelfPeer {
- arguments.openProfile(peer.peer, peer.distance)
- }
- }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil, hasTopGroupInset: false, tag: nil)
- case let .expand(theme, title):
- return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
- arguments.expandUsers()
- })
- case let .groupsHeader(_, text, loading):
- return ItemListSectionHeaderItem(presentationData: presentationData, text: text, activityIndicator: loading ? .left : .none, sectionId: self.section)
- case let .createGroup(theme, title, latitude, longitude, address):
- return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: {
- if let latitude = latitude, let longitude = longitude {
- arguments.openCreateGroup(latitude, longitude, address)
- }
- })
- case let .group(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted):
- var text: ItemListPeerItemText
- if let memberCount = peer.memberCount {
- text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(memberCount > 0 ? strings.Conversation_StatusMembers(memberCount) : strings.PeopleNearby_NoMembers)", .secondary)
- } else {
- text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary)
- }
- return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
- arguments.openChat(peer.peer)
- }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in
- arguments.contextAction(peer.peer, node, gesture)
- }, hasTopGroupInset: false, tag: nil)
- case let .channelsHeader(_, text):
- return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
- case let .channel(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted):
- var text: ItemListPeerItemText
- if let memberCount = peer.memberCount {
- text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(strings.Conversation_StatusSubscribers(memberCount))", .secondary)
- } else {
- text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary)
- }
- return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
- arguments.openChat(peer.peer)
- }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in
- arguments.contextAction(peer.peer, node, gesture)
- }, hasTopGroupInset: false, tag: nil)
- }
- }
-}
-
-private struct PeersNearbyData: Equatable {
- let latitude: Double
- let longitude: Double
- let address: String?
- let visible: Bool
- let accountPeerId: EnginePeer.Id
- let users: [PeerNearbyEntry]
- let groups: [PeerNearbyEntry]
- let channels: [PeerNearbyEntry]
-
- init(latitude: Double, longitude: Double, address: String?, visible: Bool, accountPeerId: EnginePeer.Id, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
- self.latitude = latitude
- self.longitude = longitude
- self.address = address
- self.visible = visible
- self.accountPeerId = accountPeerId
- self.users = users
- self.groups = groups
- self.channels = channels
- }
-
- static func ==(lhs: PeersNearbyData, rhs: PeersNearbyData) -> Bool {
- return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && lhs.address == rhs.address && lhs.visible == rhs.visible && lhs.accountPeerId == rhs.accountPeerId && arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
- }
-}
-
-private func peersNearbyControllerEntries(data: PeersNearbyData?, state: PeersNearbyState, presentationData: PresentationData, displayLoading: Bool, expanded: Bool, chatLocation: ChatLocation?) -> [PeersNearbyEntry] {
- var entries: [PeersNearbyEntry] = []
-
- entries.append(.header(presentationData.theme, presentationData.strings.PeopleNearby_DiscoverDescription))
- entries.append(.usersHeader(presentationData.theme, presentationData.strings.PeopleNearby_Users.uppercased(), displayLoading && data == nil))
-
- let visible = state.visibilityExpires != nil
- entries.append(.visibility(presentationData.theme, visible ? presentationData.strings.PeopleNearby_MakeInvisible : presentationData.strings.PeopleNearby_MakeVisible, visible))
-
- if let data = data, !data.users.isEmpty {
- var index: Int32 = 0
- var users = data.users.filter { $0.peer.id != data.accountPeerId }
- var effectiveExpanded = expanded
- if users.count > maxUsersDisplayedLimit && !expanded {
- users = Array(users.prefix(Int(maxUsersDisplayedLimit)))
- } else {
- effectiveExpanded = true
- }
-
- for user in users {
- entries.append(.user(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, user))
- index += 1
- }
-
- if !effectiveExpanded {
- entries.append(.expand(presentationData.theme, presentationData.strings.PeopleNearby_ShowMorePeople(Int32(data.users.count) - maxUsersDisplayedLimit)))
- }
- }
-
- var highlightedPeerId: EnginePeer.Id?
- if let chatLocation = chatLocation, case let .peer(peerId) = chatLocation {
- highlightedPeerId = peerId
- }
-
- entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased(), displayLoading && data == nil))
- entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup, data?.latitude, data?.longitude, data?.address))
- if let data = data, !data.groups.isEmpty {
- var i: Int32 = 0
- for group in data.groups {
- entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group, highlightedPeerId == group.peer.id))
- i += 1
- }
- }
-
- if let data = data, !data.channels.isEmpty {
- var i: Int32 = 0
- for channel in data.channels {
- entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel, highlightedPeerId == channel.peer.id))
- i += 1
- }
- }
-
- return entries
-}
-
-private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
- let controller: ViewController
- weak var sourceNode: ASDisplayNode?
-
- let navigationController: NavigationController? = nil
-
- let passthroughTouches: Bool = true
-
- init(controller: ViewController, sourceNode: ASDisplayNode?) {
- self.controller = controller
- self.sourceNode = sourceNode
- }
-
- func transitionInfo() -> ContextControllerTakeControllerInfo? {
- let sourceNode = self.sourceNode
- return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
- if let sourceNode = sourceNode {
- return (sourceNode.view, sourceNode.bounds)
- } else {
- return nil
- }
- })
- }
-
- func animatedIn() {
- }
-}
-
-private func peerNearbyContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, present: @escaping (ViewController) -> Void) -> Signal<[ContextMenuItem], NoError> {
- return .single([])
-}
-
-private class PeersNearbyControllerImpl: ItemListController {
- fileprivate let chatLocation = Promise(nil)
-
- public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
- if self.isNodeLoaded {
- self.chatLocation.set(.single(data as? ChatLocation))
- }
- }
-}
-
-public func peersNearbyController(context: AccountContext) -> ViewController {
- var pushControllerImpl: ((ViewController) -> Void)?
- var replaceTopControllerImpl: ((ViewController) -> Void)?
- var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
- var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
- var navigateToProfileImpl: ((EnginePeer, Int32) -> Void)?
- var navigateToChatImpl: ((EnginePeer) -> Void)?
-
- let actionsDisposable = DisposableSet()
- let checkCreationAvailabilityDisposable = MetaDisposable()
- actionsDisposable.add(checkCreationAvailabilityDisposable)
-
- let dataPromise = Promise(nil)
- let addressPromise = Promise(nil)
- let expandedPromise = ValuePromise(false)
-
- let chatLocationPromise = Promise(nil)
-
- let coordinatePromise = Promise(nil)
- coordinatePromise.set(.single(nil) |> then(currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)))
-
- let arguments = PeersNearbyControllerArguments(context: context, toggleVisibility: { visible in
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
-
- if visible {
- presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.PeopleNearby_MakeVisibleTitle, text: presentationData.strings.PeopleNearby_MakeVisibleDescription, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
- let _ = (coordinatePromise.get()
- |> deliverOnMainQueue).start(next: { coordinate in
- if let coordinate = coordinate {
- let _ = context.engine.peersNearby.updatePeersNearbyVisibility(update: .visible(latitude: coordinate.latitude, longitude: coordinate.longitude), background: false).start()
- }
- })
- })]), nil)
-
-
- } else {
- let _ = context.engine.peersNearby.updatePeersNearbyVisibility(update: .invisible, background: false).start()
- }
- }, openProfile: { peer, distance in
- navigateToProfileImpl?(peer, distance)
- }, openChat: { peer in
- navigateToChatImpl?(peer)
- }, openCreateGroup: { latitude, longitude, address in
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
-
- var cancelImpl: (() -> Void)?
- let progressSignal = Signal { subscriber in
- let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
- cancelImpl?()
- }))
- presentControllerImpl?(controller, nil)
- return ActionDisposable { [weak controller] in
- Queue.mainQueue().async() {
- controller?.dismiss()
- }
- }
- }
- |> runOn(Queue.mainQueue())
- |> delay(0.5, queue: Queue.mainQueue())
- let progressDisposable = progressSignal.start()
- cancelImpl = {
- checkCreationAvailabilityDisposable.set(nil)
- }
- checkCreationAvailabilityDisposable.set((context.engine.peers.checkPublicChannelCreationAvailability(location: true)
- |> afterDisposed {
- Queue.mainQueue().async {
- progressDisposable.dispose()
- }
- }
- |> deliverOnMainQueue).start(next: { available in
- if available {
- let controller = PermissionController(context: context, splashScreen: true)
- controller.navigationPresentation = .modalInLargeLayout
- controller.setState(.custom(icon: .icon(PermissionControllerCustomIcon(light: UIImage(bundleImageName: "Location/LocalGroupLightIcon"), dark: UIImage(bundleImageName: "Location/LocalGroupDarkIcon"))), title: presentationData.strings.LocalGroup_Title, subtitle: address, text: presentationData.strings.LocalGroup_Text, buttonTitle: presentationData.strings.LocalGroup_ButtonTitle, secondaryButtonTitle: nil, footerText: presentationData.strings.LocalGroup_IrrelevantWarning), animated: false)
- controller.proceed = { result in
- let controller = context.sharedContext.makeCreateGroupController(context: context, peerIds: [], initialTitle: nil, mode: .locatedGroup(latitude: latitude, longitude: longitude, address: address), completion: nil)
- controller.navigationPresentation = .modalInLargeLayout
- replaceTopControllerImpl?(controller)
- }
- pushControllerImpl?(controller)
- } else {
- presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.CreateGroup_ErrorLocatedGroupsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
- }
- }))
- }, contextAction: { peer, node, gesture in
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil)
- chatController.canReadHistory.set(false)
- let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: peerNearbyContextMenuItems(context: context, peerId: peer.id, present: { c in
- presentControllerImpl?(c, nil)
- }) |> map { ContextController.Items(content: .list($0), animationCache: nil) }, gesture: gesture)
- presentInGlobalOverlayImpl?(contextController)
- }, expandUsers: {
- expandedPromise.set(true)
- })
-
- let dataSignal: Signal = coordinatePromise.get()
- |> distinctUntilChanged(isEqual: { lhs, rhs in
- return lhs?.latitude == rhs?.latitude && lhs?.longitude == rhs?.longitude
- })
- |> mapToSignal { coordinate -> Signal in
- guard let coordinate = coordinate else {
- return .single(nil)
- /*let peersNearbyContext = PeersNearbyContext(network: context.account.network, stateManager: context.account.stateManager, coordinate: nil)
- return peersNearbyContext.get()
- |> map { peersNearby -> PeersNearbyData in
- var isVisible = false
- if let peersNearby {
- for peer in peersNearby {
- if case .selfPeer = peer {
- isVisible = true
- break
- }
- }
- }
- return PeersNearbyData(latitude: 0.0, longitude: 0.0, address: nil, visible: isVisible, accountPeerId: context.account.peerId, users: [], groups: [], channels: [])
- }*/
- }
-
- return Signal { subscriber in
- let peersNearbyContext = PeersNearbyContext(network: context.account.network, stateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
-
- let peersNearby: Signal = combineLatest(peersNearbyContext.get(), addressPromise.get())
- |> mapToSignal { peersNearby, address -> Signal<([PeerNearby]?, String?), NoError> in
- if let address = address {
- return .single((peersNearby, address))
- } else {
- return reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
- |> map { placemark in
- return (peersNearby, placemark?.fullAddress)
- }
- }
- }
- |> mapToSignal { peersNearby, address -> Signal in
- guard let peersNearby = peersNearby else {
- return .single(nil)
- }
- let peerIds = peersNearby.map { entry -> EnginePeer.Id in
- switch entry {
- case let .peer(id, _, _):
- return id
- case .selfPeer:
- return context.account.peerId
- }
- }
- return context.engine.data.get(
- EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)),
- EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init))
- )
- |> map { peerMap, participantCountMap -> PeersNearbyData? in
- var users: [PeerNearbyEntry] = []
- var groups: [PeerNearbyEntry] = []
- var visible = false
- for peerNearby in peersNearby {
- switch peerNearby {
- case let .peer(id, expires, distance):
- if let maybePeer = peerMap[id], let peer = maybePeer {
- if id.namespace == Namespaces.Peer.CloudUser {
- users.append(PeerNearbyEntry(peer: peer, memberCount: nil, expires: expires, distance: distance))
- } else {
- var participantCount: Int32?
- if let maybeParticipantCount = participantCountMap[id] {
- participantCount = maybeParticipantCount.flatMap(Int32.init)
- }
- groups.append(PeerNearbyEntry(peer: peer, memberCount: participantCount, expires: expires, distance: distance))
- }
- }
- case let .selfPeer(expires):
- visible = true
- if let maybePeer = peerMap[context.account.peerId], let peer = maybePeer {
- users.append(PeerNearbyEntry(peer: peer, memberCount: nil, expires: expires, distance: 0))
- }
- }
- }
- return PeersNearbyData(latitude: coordinate.latitude, longitude: coordinate.longitude, address: address, visible: visible, accountPeerId: context.account.peerId, users: users, groups: groups, channels: [])
- }
- }
-
- let disposable = peersNearby.start(next: { data in
- subscriber.putNext(data)
- })
-
- return ActionDisposable {
- disposable.dispose()
- let _ = peersNearbyContext.get()
- }
- }
- }
- dataPromise.set(.single(nil) |> then(dataSignal))
-
- let previousData = Atomic(value: nil)
- let displayLoading: Signal = .single(false)
- |> then(
- .single(true)
- |> delay(1.0, queue: Queue.mainQueue())
- )
-
- let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), chatLocationPromise.get(), displayLoading, expandedPromise.get(), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.peersNearby)))
- |> deliverOnMainQueue
- |> map { presentationData, data, chatLocation, displayLoading, expanded, view -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let previous = previousData.swap(data)
- let state = view?.get(PeersNearbyState.self) ?? .default
-
- var crossfade = false
- if (data?.users.isEmpty ?? true) != (previous?.users.isEmpty ?? true) {
- crossfade = true
- }
- if (data?.groups.isEmpty ?? true) != (previous?.groups.isEmpty ?? true) {
- crossfade = true
- }
-
- let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PeopleNearby_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
- let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: peersNearbyControllerEntries(data: data, state: state, presentationData: presentationData, displayLoading: displayLoading, expanded: expanded, chatLocation: chatLocation), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: !crossfade)
-
- return (controllerState, (listState, arguments))
- }
- |> afterDisposed {
- actionsDisposable.dispose()
- }
-
- let controller = PeersNearbyControllerImpl(context: context, state: signal)
- chatLocationPromise.set(controller.chatLocation.get())
- controller.didDisappear = { [weak controller] _ in
- controller?.clearItemNodesHighlight(animated: true)
- }
- navigateToProfileImpl = { [weak controller] peer, distance in
- if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
- navigationController.pushViewController(controller)
- }
- }
- navigateToChatImpl = { [weak controller] peer in
- if let navigationController = controller?.navigationController as? NavigationController {
- context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil))
- }
- }
- pushControllerImpl = { [weak controller] c in
- if let controller = controller {
- (controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
- }
- }
- replaceTopControllerImpl = { [weak controller] c in
- if let controller = controller {
- (controller.navigationController as? NavigationController)?.replaceTopController(c, animated: true)
- }
- }
- presentControllerImpl = { [weak controller] c, p in
- if let controller = controller {
- controller.present(c, in: .window(.root), with: p)
- }
- }
- presentInGlobalOverlayImpl = { [weak controller] c in
- if let controller = controller {
- controller.presentInGlobalOverlay(c)
- }
- }
- return controller
-}
diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift
deleted file mode 100644
index e1b70cb30c..0000000000
--- a/submodules/PeersNearbyUI/Sources/PeersNearbyHeaderItem.swift
+++ /dev/null
@@ -1,127 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import SwiftSignalKit
-import TelegramPresentationData
-import ItemListUI
-import PresentationDataUtils
-import AnimatedStickerNode
-import TelegramAnimatedStickerNode
-import AccountContext
-
-class PeersNearbyHeaderItem: ListViewItem, ItemListItem {
- let context: AccountContext
- let theme: PresentationTheme
- let text: String
- let sectionId: ItemListSectionId
-
- init(context: AccountContext, theme: PresentationTheme, text: String, sectionId: ItemListSectionId) {
- self.context = context
- self.theme = theme
- self.text = text
- self.sectionId = sectionId
- }
-
- 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 = PeersNearbyHeaderItemNode()
- 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 {
- guard let nodeValue = node() as? PeersNearbyHeaderItemNode else {
- assertionFailure()
- return
- }
-
- 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()
- })
- }
- }
- }
- }
-}
-
-private let titleFont = Font.regular(13.0)
-
-class PeersNearbyHeaderItemNode: ListViewItemNode {
- private let titleNode: TextNode
- private var animationNode: AnimatedStickerNode
-
- private var item: PeersNearbyHeaderItem?
-
- init() {
- self.titleNode = TextNode()
- self.titleNode.isUserInteractionEnabled = false
- self.titleNode.contentMode = .left
- self.titleNode.contentsScale = UIScreen.main.scale
-
- self.animationNode = DefaultAnimatedStickerNodeImpl()
-
- super.init(layerBacked: false)
-
- self.addSubnode(self.titleNode)
- self.addSubnode(self.animationNode)
- }
-
- func asyncLayout() -> (_ item: PeersNearbyHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
- let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
-
- return { item, params, neighbors in
- let leftInset: CGFloat = 32.0 + params.leftInset
- let topInset: CGFloat = 92.0
-
- let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor)
- let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
-
- let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height)
- let insets = itemListNeighborsGroupedInsets(neighbors, params)
-
- let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
-
- return (layout, { [weak self] in
- if let strongSelf = self {
- if strongSelf.item == nil {
- strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Compass"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
- strongSelf.animationNode.visibility = true
- }
- strongSelf.item = item
- strongSelf.accessibilityLabel = attributedText.string
-
- let iconSize = CGSize(width: 96.0, height: 96.0)
- strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
- strongSelf.animationNode.updateLayout(size: iconSize)
-
- let _ = titleApply()
- strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 8.0), size: titleLayout.size)
- }
- })
- }
- }
-
- override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
- self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
- }
-
- override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
- self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
- }
-}
diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift
index 9ffa3fb2b2..cd063c8afd 100644
--- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift
@@ -1438,7 +1438,7 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
)
)
},
- backgroundColor: .color(theme.list.modalBlocksBackgroundColor),
+ backgroundColor: .color(theme.list.modalPlainBackgroundColor),
animateOut: animateOut
),
environment: {
@@ -1472,306 +1472,6 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
}
}
-//private final class BoostLevelsContainerComponent: CombinedComponent {
-// class ExternalState {
-// var isGroup: Bool = false
-// var contentHeight: CGFloat = 0.0
-// }
-//
-// let context: AccountContext
-// let theme: PresentationTheme
-// let strings: PresentationStrings
-// let externalState: ExternalState
-// let peerId: EnginePeer.Id
-// let mode: PremiumBoostLevelsScreen.Mode
-// let status: ChannelBoostStatus?
-// let boostState: InternalBoostState.DisplayData?
-// let boost: () -> Void
-// let copyLink: (String) -> Void
-// let dismiss: () -> Void
-// let openStats: (() -> Void)?
-// let openGift: (() -> Void)?
-// let openPeer: ((EnginePeer) -> Void)?
-// let updated: () -> Void
-//
-// init(
-// context: AccountContext,
-// theme: PresentationTheme,
-// strings: PresentationStrings,
-// externalState: ExternalState,
-// peerId: EnginePeer.Id,
-// mode: PremiumBoostLevelsScreen.Mode,
-// status: ChannelBoostStatus?,
-// boostState: InternalBoostState.DisplayData?,
-// boost: @escaping () -> Void,
-// copyLink: @escaping (String) -> Void,
-// dismiss: @escaping () -> Void,
-// openStats: (() -> Void)?,
-// openGift: (() -> Void)?,
-// openPeer: ((EnginePeer) -> Void)?,
-// updated: @escaping () -> Void
-// ) {
-// self.context = context
-// self.theme = theme
-// self.strings = strings
-// self.externalState = externalState
-// self.peerId = peerId
-// self.mode = mode
-// self.status = status
-// self.boostState = boostState
-// self.boost = boost
-// self.copyLink = copyLink
-// self.dismiss = dismiss
-// self.openStats = openStats
-// self.openGift = openGift
-// self.openPeer = openPeer
-// self.updated = updated
-// }
-//
-// static func ==(lhs: BoostLevelsContainerComponent, rhs: BoostLevelsContainerComponent) -> Bool {
-// if lhs.context !== rhs.context {
-// return false
-// }
-// if lhs.theme !== rhs.theme {
-// return false
-// }
-// if lhs.peerId != rhs.peerId {
-// return false
-// }
-// if lhs.mode != rhs.mode {
-// return false
-// }
-// if lhs.status != rhs.status {
-// return false
-// }
-// if lhs.boostState != rhs.boostState {
-// return false
-// }
-// return true
-// }
-//
-// final class State: ComponentState {
-// var topContentOffset: CGFloat = 0.0
-// var cachedStatsImage: (UIImage, PresentationTheme)?
-// var cachedCloseImage: (UIImage, PresentationTheme)?
-//
-// private var disposable: Disposable?
-// private(set) var peer: EnginePeer?
-//
-// init(context: AccountContext, peerId: EnginePeer.Id, updated: @escaping () -> Void) {
-// super.init()
-//
-// self.disposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
-// |> deliverOnMainQueue).startStrict(next: { [weak self] peer in
-// guard let self else {
-// return
-// }
-// self.peer = peer
-// updated()
-// })
-// }
-//
-// deinit {
-// self.disposable?.dispose()
-// }
-// }
-//
-// func makeState() -> State {
-// return State(context: self.context, peerId: self.peerId, updated: self.updated)
-// }
-//
-// static var body: Body {
-// let background = Child(Rectangle.self)
-// let scroll = Child(ScrollComponent.self)
-// let topPanel = Child(BlurredBackgroundComponent.self)
-// let topSeparator = Child(Rectangle.self)
-// let title = Child(MultilineTextComponent.self)
-// let statsButton = Child(Button.self)
-// let closeButton = Child(Button.self)
-//
-// let externalScrollState = ScrollComponent.ExternalState()
-//
-// return { context in
-// let state = context.state
-//
-// let theme = context.component.theme
-// let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings
-//
-// let topInset: CGFloat = 56.0
-//
-// let component = context.component
-//
-// var isGroup: Bool?
-// if let peer = state.peer {
-// if case let .channel(channel) = peer, case .group = channel.info {
-// isGroup = true
-// } else {
-// isGroup = false
-// }
-// }
-//
-// if let isGroup {
-// component.externalState.isGroup = isGroup
-// let updated = component.updated
-// let scroll = scroll.update(
-// component: ScrollComponent(
-// content: AnyComponent(
-// SheetContent(
-// context: component.context,
-// theme: component.theme,
-// strings: component.strings,
-// insets: .zero,
-// peerId: component.peerId,
-// isGroup: isGroup,
-// mode: component.mode,
-// status: component.status,
-// boostState: component.boostState,
-// boost: component.boost,
-// copyLink: component.copyLink,
-// dismiss: component.dismiss,
-// openStats: component.openStats,
-// openGift: component.openGift,
-// openPeer: component.openPeer,
-// updated: { [weak state] in
-// updated()
-// }
-// )
-// ),
-// externalState: externalScrollState,
-// contentInsets: UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0),
-// contentOffsetUpdated: { [weak state] topContentOffset, _ in
-// state?.topContentOffset = topContentOffset
-// Queue.mainQueue().justDispatch {
-// state?.updated(transition: .immediate)
-// }
-// },
-// contentOffsetWillCommit: { _ in }
-// ),
-// availableSize: context.availableSize,
-// transition: context.transition
-// )
-// component.externalState.contentHeight = externalScrollState.contentHeight
-//
-// let background = background.update(
-// component: Rectangle(color: theme.overallDarkAppearance ? theme.list.blocksBackgroundColor : theme.list.plainBackgroundColor),
-// availableSize: scroll.size,
-// transition: context.transition
-// )
-// context.add(background
-// .position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
-// )
-//
-// context.add(scroll
-// .position(CGPoint(x: context.availableSize.width / 2.0, y: scroll.size.height / 2.0))
-// )
-// }
-//
-// let titleString: String
-// var titleFont = Font.semibold(17.0)
-//
-// switch component.mode {
-// case let .owner(subject):
-// if let status = component.status, let _ = status.nextLevelBoosts {
-// if let subject {
-// switch subject {
-// case .stories:
-// if status.level == 0 {
-// titleString = strings.ChannelBoost_EnableStories
-// } else {
-// titleString = strings.ChannelBoost_IncreaseLimit
-// }
-// case .nameColors:
-// titleString = strings.ChannelBoost_NameColor
-// case .nameIcon:
-// titleString = strings.ChannelBoost_NameIcon
-// case .profileColors:
-// titleString = strings.ChannelBoost_ProfileColor
-// case .profileIcon:
-// titleString = strings.ChannelBoost_ProfileIcon
-// case .channelReactions:
-// titleString = strings.ChannelBoost_CustomReactions
-// case .emojiStatus:
-// titleString = strings.ChannelBoost_EmojiStatus
-// case .wallpaper:
-// titleString = strings.ChannelBoost_Wallpaper
-// case .customWallpaper:
-// titleString = strings.ChannelBoost_CustomWallpaper
-// case .audioTranscription:
-// titleString = strings.GroupBoost_AudioTranscription
-// case .emojiPack:
-// titleString = strings.GroupBoost_EmojiPack
-// case .noAds:
-// titleString = strings.ChannelBoost_NoAds
-// case .wearGift:
-// titleString = strings.ChannelBoost_WearGift
-// case .autoTranslate:
-// titleString = strings.ChannelBoost_AutoTranslate
-// }
-// } else {
-// titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current
-// }
-// } else {
-// titleString = strings.ChannelBoost_MaxLevelReached
-// }
-// case let .user(mode):
-// var remaining: Int?
-// if let status = component.status, let nextLevelBoosts = status.nextLevelBoosts {
-// remaining = nextLevelBoosts - status.boosts
-// }
-//
-// if let _ = remaining {
-// if case .current = mode {
-// titleString = isGroup == true ? strings.GroupBoost_Title_Current : strings.ChannelBoost_Title_Current
-// } else {
-// titleString = isGroup == true ? strings.GroupBoost_Title_Other : strings.ChannelBoost_Title_Other
-// }
-// } else {
-// titleString = strings.ChannelBoost_MaxLevelReached
-// }
-// case .features:
-// titleString = strings.GroupBoost_AdditionalFeatures
-// titleFont = Font.semibold(20.0)
-// }
-//
-// let title = title.update(
-// component: MultilineTextComponent(
-// text: .plain(NSAttributedString(string: titleString, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)),
-// horizontalAlignment: .center,
-// truncationType: .end,
-// maximumNumberOfLines: 1
-// ),
-// availableSize: context.availableSize,
-// transition: context.transition
-// )
-//
-// let topPanelAlpha: CGFloat
-// let titleOriginY: CGFloat
-// let titleScale: CGFloat
-// if case .features = component.mode {
-// if state.topContentOffset > 78.0 {
-// topPanelAlpha = min(30.0, state.topContentOffset - 78.0) / 30.0
-// } else {
-// topPanelAlpha = 0.0
-// }
-//
-// let titleTopOriginY = topPanel.size.height / 2.0
-// let titleBottomOriginY: CGFloat = 146.0
-// let titleOriginDelta = titleTopOriginY - titleBottomOriginY
-//
-// let fraction = min(1.0, state.topContentOffset / abs(titleOriginDelta))
-// titleOriginY = titleBottomOriginY + fraction * titleOriginDelta
-// titleScale = 1.0 - max(0.0, fraction * 0.2)
-// } else {
-// topPanelAlpha = min(30.0, state.topContentOffset) / 30.0
-// titleOriginY = topPanel.size.height / 2.0
-// titleScale = 1.0
-// }
-//
-
-// }
-// }
-//}
-
public class PremiumBoostLevelsScreen: ViewControllerComponentContainer {
public enum Mode: Equatable {
public enum UserMode: Equatable {
@@ -1844,1067 +1544,6 @@ public class PremiumBoostLevelsScreen: ViewControllerComponentContainer {
}
}
-//public class PremiumBoostLevelsScreen: ViewController {
-// public enum Mode: Equatable {
-// public enum UserMode: Equatable {
-// case external
-// case current
-// case groupPeer(EnginePeer.Id, Int)
-// case unrestrict(Int)
-// }
-// case user(mode: UserMode)
-// case owner(subject: BoostSubject?)
-// case features
-// }
-//
-// final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
-// private var presentationData: PresentationData
-// private weak var controller: PremiumBoostLevelsScreen?
-//
-// let dim: ASDisplayNode
-// let wrappingView: UIView
-// let containerView: UIView
-//
-// let contentView: ComponentHostView
-// let footerContainerView: UIView
-// let footerView: ComponentHostView
-//
-// private let containerExternalState = BoostLevelsContainerComponent.ExternalState()
-//
-// private(set) var isExpanded = false
-// private var panGestureRecognizer: UIPanGestureRecognizer?
-// private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?)?
-//
-// private let hapticFeedback = HapticFeedback()
-//
-// private var currentIsVisible: Bool = false
-// private var currentLayout: ContainerViewLayout?
-//
-// init(context: AccountContext, controller: PremiumBoostLevelsScreen) {
-// self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
-// if controller.forceDark {
-// self.presentationData = self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
-// }
-// self.presentationData = self.presentationData.withUpdated(theme: self.presentationData.theme.withModalBlocksBackground())
-//
-// self.controller = controller
-//
-// self.dim = ASDisplayNode()
-// self.dim.alpha = 0.0
-// self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
-//
-// self.wrappingView = UIView()
-// self.containerView = UIView()
-// self.contentView = ComponentHostView()
-//
-// self.footerContainerView = UIView()
-// self.footerView = ComponentHostView()
-//
-// super.init()
-//
-// self.containerView.clipsToBounds = true
-// self.containerView.backgroundColor = self.presentationData.theme.overallDarkAppearance ? self.presentationData.theme.list.blocksBackgroundColor : self.presentationData.theme.list.plainBackgroundColor
-//
-// self.addSubnode(self.dim)
-//
-// self.view.addSubview(self.wrappingView)
-// self.wrappingView.addSubview(self.containerView)
-// self.containerView.addSubview(self.contentView)
-//
-// if case .user = controller.mode {
-// self.containerView.addSubview(self.footerContainerView)
-// self.footerContainerView.addSubview(self.footerView)
-// }
-//
-// if let status = controller.status, let myBoostStatus = controller.myBoostStatus {
-// var myBoostCount: Int32 = 0
-// var currentMyBoostCount: Int32 = 0
-// var availableBoosts: [MyBoostStatus.Boost] = []
-// var occupiedBoosts: [MyBoostStatus.Boost] = []
-//
-// for boost in myBoostStatus.boosts {
-// if let boostPeer = boost.peer {
-// if boostPeer.id == controller.peerId {
-// myBoostCount += 1
-// } else {
-// occupiedBoosts.append(boost)
-// }
-// } else {
-// availableBoosts.append(boost)
-// }
-// }
-//
-// let boosts = max(Int32(status.boosts), myBoostCount)
-// let initialState = InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts)
-// self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 0, replacedBoosts: controller.replacedBoosts?.0)
-//
-// self.updatedState.set(.single(InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: boosts + 1)))
-//
-// if let (replacedBoosts, sourcePeers) = controller.replacedBoosts {
-// currentMyBoostCount += 1
-//
-// self.boostState = initialState.displayData(myBoostCount: myBoostCount, currentMyBoostCount: 1)
-// Queue.mainQueue().justDispatch {
-// self.updated(transition: .easeInOut(duration: 0.2))
-// }
-//
-// Queue.mainQueue().after(0.3) {
-// let presentationData = context.sharedContext.currentPresentationData.with { $0 }
-//
-// var groupCount: Int32 = 0
-// var channelCount: Int32 = 0
-// for peer in sourcePeers {
-// if case let .channel(channel) = peer {
-// switch channel.info {
-// case .broadcast:
-// channelCount += 1
-// case .group:
-// groupCount += 1
-// }
-// }
-// }
-// let otherText: String
-// if channelCount > 0 && groupCount == 0 {
-// otherText = presentationData.strings.ReassignBoost_OtherChannels(channelCount)
-// } else if groupCount > 0 && channelCount == 0 {
-// otherText = presentationData.strings.ReassignBoost_OtherGroups(groupCount)
-// } else {
-// otherText = presentationData.strings.ReassignBoost_OtherGroupsAndChannels(Int32(sourcePeers.count))
-// }
-// let text = presentationData.strings.ReassignBoost_Success(presentationData.strings.ReassignBoost_Boosts(replacedBoosts), otherText).string
-// let undoController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "BoostReplace", scale: 0.066, colors: [:], title: nil, text: text, customUndoText: nil, timeout: 4.0), elevatedLayout: false, position: .top, action: { _ in return true })
-// controller.present(undoController, in: .current)
-// }
-// }
-//
-// self.availableBoosts = availableBoosts
-// self.occupiedBoosts = occupiedBoosts
-// self.myBoostCount = myBoostCount
-// self.currentMyBoostCount = currentMyBoostCount
-// }
-// }
-//
-// override func didLoad() {
-// super.didLoad()
-//
-// let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
-// panRecognizer.delegate = self.wrappedGestureRecognizerDelegate
-// panRecognizer.delaysTouchesBegan = false
-// panRecognizer.cancelsTouchesInView = true
-// self.panGestureRecognizer = panRecognizer
-// self.wrappingView.addGestureRecognizer(panRecognizer)
-//
-// self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
-// self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
-// }
-//
-// @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
-// if case .ended = recognizer.state {
-// self.controller?.dismiss(animated: true)
-// }
-// }
-//
-// override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
-// if let layout = self.currentLayout {
-// if case .regular = layout.metrics.widthClass {
-// return false
-// }
-// }
-// return true
-// }
-//
-// func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
-// if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer {
-// if let scrollView = otherGestureRecognizer.view as? UIScrollView {
-// if scrollView.contentSize.width > scrollView.contentSize.height {
-// return false
-// }
-// }
-// return true
-// }
-// return false
-// }
-//
-// private var isDismissing = false
-// func animateIn() {
-// ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
-//
-// let targetPosition = self.containerView.center
-// let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height)
-//
-// self.containerView.center = startPosition
-// let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
-// transition.animateView(allowUserInteraction: true, {
-// self.containerView.center = targetPosition
-// }, completion: { _ in
-// })
-// }
-//
-// func animateOut(completion: @escaping () -> Void = {}) {
-// self.isDismissing = true
-//
-// let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
-// positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in
-// self?.controller?.dismiss(animated: false, completion: completion)
-// })
-// let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
-// alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
-//
-// self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
-// }
-//
-// func requestLayout(transition: ComponentTransition) {
-// guard let layout = self.currentLayout else {
-// return
-// }
-// self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition)
-// }
-//
-// private var dismissOffset: CGFloat?
-// func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, transition: ComponentTransition) {
-// guard !self.isDismissing else {
-// return
-// }
-// self.currentLayout = layout
-//
-// self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0))
-//
-// let isLandscape = layout.orientation == .landscape
-//
-// var containerTopInset: CGFloat = 0.0
-// let clipFrame: CGRect
-// if layout.metrics.widthClass == .compact {
-// self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25)
-// if isLandscape {
-// self.containerView.layer.cornerRadius = 0.0
-// } else {
-// self.containerView.layer.cornerRadius = 10.0
-// }
-//
-// if #available(iOS 11.0, *) {
-// if layout.safeInsets.bottom.isZero {
-// self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
-// } else {
-// self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
-// }
-// }
-//
-// if isLandscape {
-// clipFrame = CGRect(origin: CGPoint(), size: layout.size)
-// } else {
-// let coveredByModalTransition: CGFloat = 0.0
-// containerTopInset = 10.0
-// if let statusBarHeight = layout.statusBarHeight {
-// containerTopInset += statusBarHeight
-// }
-//
-// let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset))
-// let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width
-// let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition
-// let maxScaledTopInset: CGFloat = containerTopInset - 10.0
-// let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition
-// let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0))
-//
-// clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height)
-// }
-// } else {
-// self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4)
-// self.containerView.layer.cornerRadius = 10.0
-//
-// let verticalInset: CGFloat = 44.0
-//
-// let maxSide = max(layout.size.width, layout.size.height)
-// let minSide = min(layout.size.width, layout.size.height)
-// let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0)
-// clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize)
-// }
-//
-// transition.setFrame(view: self.containerView, frame: clipFrame)
-//
-// var effectiveExpanded = self.isExpanded
-// if case .regular = layout.metrics.widthClass {
-// effectiveExpanded = true
-// }
-//
-// self.updated(transition: transition, forceUpdate: forceUpdate)
-//
-// let contentHeight = self.containerExternalState.contentHeight
-// if contentHeight > 0.0 && contentHeight < 400.0, let view = self.footerView.componentView as? FooterComponent.View {
-// view.backgroundView.alpha = 0.0
-// view.separator.opacity = 0.0
-// }
-// let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset
-//
-// let topInset: CGFloat
-// if let (panInitialTopInset, panOffset, _) = self.panGestureArguments {
-// if effectiveExpanded {
-// topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset))
-// } else {
-// topInset = max(0.0, panInitialTopInset + min(0.0, panOffset))
-// }
-// } else if let dismissOffset = self.dismissOffset, !dismissOffset.isZero {
-// topInset = edgeTopInset * dismissOffset
-// } else {
-// topInset = effectiveExpanded ? 0.0 : edgeTopInset
-// }
-// transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size), completion: nil)
-//
-// let modalProgress = isLandscape ? 0.0 : (1.0 - topInset / self.defaultTopInset)
-// self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition)
-//
-// let footerHeight = self.footerHeight
-// let convertedFooterFrame = self.view.convert(CGRect(origin: CGPoint(x: clipFrame.minX, y: clipFrame.maxY - footerHeight), size: CGSize(width: clipFrame.width, height: footerHeight)), to: self.containerView)
-// transition.setFrame(view: self.footerContainerView, frame: convertedFooterFrame)
-// }
-//
-// private var boostState: InternalBoostState.DisplayData?
-// func updated(transition: ComponentTransition, forceUpdate: Bool = false) {
-// guard let controller = self.controller else {
-// return
-// }
-// let contentSize = self.contentView.update(
-// transition: transition,
-// component: AnyComponent(
-// BoostLevelsContainerComponent(
-// context: controller.context,
-// theme: self.presentationData.theme,
-// strings: self.presentationData.strings,
-// externalState: self.containerExternalState,
-// peerId: controller.peerId,
-// mode: controller.mode,
-// status: controller.status,
-// boostState: self.boostState,
-// boost: { [weak controller] in
-// guard let controller else {
-// return
-// }
-// controller.node.updateBoostState()
-// },
-// copyLink: { [weak self, weak controller] link in
-// guard let self else {
-// return
-// }
-// UIPasteboard.general.string = link
-//
-// if let previousController = controller?.navigationController?.viewControllers.reversed().first(where: { $0 !== controller }) as? ViewController {
-// previousController.present(UndoOverlayController(presentationData: self.presentationData, content: .linkCopied(title: nil, text: self.presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: true, position: .top, animateInAsReplacement: false, action: { _ in return false }), in: .current)
-// }
-// },
-// dismiss: { [weak controller] in
-// controller?.dismiss(animated: true)
-// },
-// openStats: controller.openStats,
-// openGift: controller.openGift,
-// openPeer: controller.openPeer,
-// updated: { [weak self] in
-// self?.requestLayout(transition: .immediate)
-// }
-// )
-// ),
-// environment: {},
-// forceUpdate: forceUpdate,
-// containerSize: self.containerView.bounds.size
-// )
-// self.contentView.frame = CGRect(origin: .zero, size: contentSize)
-//
-// let footerHeight = self.footerHeight
-//
-// let actionTitle: String
-// if self.currentMyBoostCount > 0 {
-// actionTitle = self.presentationData.strings.ChannelBoost_BoostAgain
-// } else {
-// actionTitle = self.containerExternalState.isGroup ? self.presentationData.strings.GroupBoost_BoostGroup : self.presentationData.strings.ChannelBoost_BoostChannel
-// }
-//
-// let footerSize = self.footerView.update(
-// transition: .immediate,
-// component: AnyComponent(
-// FooterComponent(
-// context: controller.context,
-// theme: self.presentationData.theme,
-// title: actionTitle,
-// action: { [weak self] in
-// guard let self else {
-// return
-// }
-// self.buttonPressed()
-// }
-// )
-// ),
-// environment: {},
-// containerSize: CGSize(width: self.containerView.bounds.width, height: footerHeight)
-// )
-// self.footerView.frame = CGRect(origin: .zero, size: footerSize)
-// }
-//
-// private var didPlayAppearAnimation = false
-// func updateIsVisible(isVisible: Bool) {
-// if self.currentIsVisible == isVisible {
-// return
-// }
-// self.currentIsVisible = isVisible
-//
-// guard let layout = self.currentLayout else {
-// return
-// }
-// self.containerLayoutUpdated(layout: layout, transition: .immediate)
-//
-// if !self.didPlayAppearAnimation {
-// self.didPlayAppearAnimation = true
-// self.animateIn()
-// }
-// }
-//
-// private var footerHeight: CGFloat {
-// if let mode = self.controller?.mode, case .owner = mode {
-// return 0.0
-// }
-//
-// guard let layout = self.currentLayout else {
-// return 58.0
-// }
-//
-// var footerHeight: CGFloat = 8.0 + 50.0
-// footerHeight += layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : 8.0
-// return footerHeight
-// }
-//
-// private var defaultTopInset: CGFloat {
-// guard let layout = self.currentLayout else {
-// return 210.0
-// }
-// if case .compact = layout.metrics.widthClass {
-// let bottomPanelPadding: CGFloat = 12.0
-// let bottomInset: CGFloat = layout.intrinsicInsets.bottom > 0.0 ? layout.intrinsicInsets.bottom + 5.0 : bottomPanelPadding
-// let panelHeight: CGFloat = bottomPanelPadding + 50.0 + bottomInset + 28.0
-//
-// var defaultTopInset = layout.size.height - layout.size.width - 128.0 - panelHeight
-//
-// let containerTopInset = 10.0 + (layout.statusBarHeight ?? 0.0)
-// let contentHeight = self.containerExternalState.contentHeight
-// let footerHeight = self.footerHeight
-// if contentHeight > 0.0 {
-// let delta = (layout.size.height - defaultTopInset - containerTopInset) - contentHeight - footerHeight - 16.0
-// if delta > 0.0 {
-// defaultTopInset += delta
-// }
-// }
-// return defaultTopInset
-// } else {
-// return 210.0
-// }
-// }
-//
-// private func findVerticalScrollView(view: UIView?) -> UIScrollView? {
-// if let view = view {
-// if let view = view as? UIScrollView, view.contentSize.height > view.contentSize.width {
-// return view
-// }
-// return findVerticalScrollView(view: view.superview)
-// } else {
-// return nil
-// }
-// }
-//
-// @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
-// guard let layout = self.currentLayout else {
-// return
-// }
-//
-// let isLandscape = layout.orientation == .landscape
-// let edgeTopInset = isLandscape ? 0.0 : defaultTopInset
-//
-// switch recognizer.state {
-// case .began:
-// let point = recognizer.location(in: self.view)
-// let currentHitView = self.hitTest(point, with: nil)
-//
-// var scrollView = self.findVerticalScrollView(view: currentHitView)
-// if scrollView?.frame.height == self.frame.width {
-// scrollView = nil
-// }
-// if scrollView?.isDescendant(of: self.view) == false {
-// scrollView = nil
-// }
-//
-// let topInset: CGFloat
-// if self.isExpanded {
-// topInset = 0.0
-// } else {
-// topInset = edgeTopInset
-// }
-//
-// self.panGestureArguments = (topInset, 0.0, scrollView)
-// case .changed:
-// guard let (topInset, panOffset, scrollView) = self.panGestureArguments else {
-// return
-// }
-// let contentOffset = scrollView?.contentOffset.y ?? 0.0
-//
-// var translation = recognizer.translation(in: self.view).y
-//
-// var currentOffset = topInset + translation
-//
-// let epsilon = 1.0
-// if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon {
-// scrollView.bounces = false
-// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
-// } else if let scrollView = scrollView {
-// translation = panOffset
-// currentOffset = topInset + translation
-// if self.isExpanded {
-// recognizer.setTranslation(CGPoint(), in: self.view)
-// } else if currentOffset > 0.0 {
-// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
-// }
-// }
-//
-// if scrollView == nil {
-// translation = max(0.0, translation)
-// }
-//
-// self.panGestureArguments = (topInset, translation, scrollView)
-//
-// if !self.isExpanded {
-// if currentOffset > 0.0, let scrollView = scrollView {
-// scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView)
-// }
-// }
-//
-// var bounds = self.bounds
-// if self.isExpanded {
-// bounds.origin.y = -max(0.0, translation - edgeTopInset)
-// } else {
-// bounds.origin.y = -translation
-// }
-// bounds.origin.y = min(0.0, bounds.origin.y)
-// self.bounds = bounds
-//
-// self.containerLayoutUpdated(layout: layout, transition: .immediate)
-// case .ended:
-// guard let (currentTopInset, panOffset, scrollView) = self.panGestureArguments else {
-// return
-// }
-// self.panGestureArguments = nil
-//
-// let contentOffset = scrollView?.contentOffset.y ?? 0.0
-//
-// let translation = recognizer.translation(in: self.view).y
-// var velocity = recognizer.velocity(in: self.view)
-//
-// if self.isExpanded {
-// if contentOffset > 0.1 {
-// velocity = CGPoint()
-// }
-// }
-//
-// var bounds = self.bounds
-// if self.isExpanded {
-// bounds.origin.y = -max(0.0, translation - edgeTopInset)
-// } else {
-// bounds.origin.y = -translation
-// }
-// bounds.origin.y = min(0.0, bounds.origin.y)
-//
-// scrollView?.bounces = true
-//
-// let offset = currentTopInset + panOffset
-// let topInset: CGFloat = edgeTopInset
-//
-// var dismissing = false
-// if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 1800.0) {
-// self.controller?.dismiss(animated: true, completion: nil)
-// dismissing = true
-// } else if self.isExpanded {
-// if velocity.y > 300.0 || offset > topInset / 2.0 {
-// self.isExpanded = false
-// if let scrollView = scrollView {
-// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
-// }
-//
-// let distance = topInset - offset
-// let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance)
-// let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
-//
-// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition))
-// } else {
-// self.isExpanded = true
-//
-// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut)))
-// }
-// } else if scrollView != nil, (velocity.y < -300.0 || offset < topInset / 2.0) {
-// let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset)
-// let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity))
-// self.isExpanded = true
-//
-// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition))
-// } else {
-// if let scrollView = scrollView {
-// scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false)
-// }
-//
-// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut)))
-// }
-//
-// if !dismissing {
-// var bounds = self.bounds
-// let previousBounds = bounds
-// bounds.origin.y = 0.0
-// self.bounds = bounds
-// self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue)
-// }
-// case .cancelled:
-// self.panGestureArguments = nil
-//
-// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(.animated(duration: 0.3, curve: .easeInOut)))
-// default:
-// break
-// }
-// }
-//
-// func updateDismissOffset(_ offset: CGFloat) {
-// guard self.isExpanded, let layout = self.currentLayout else {
-// return
-// }
-//
-// self.dismissOffset = offset
-// self.containerLayoutUpdated(layout: layout, transition: .immediate)
-// }
-//
-// func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) {
-// guard isExpanded != self.isExpanded else {
-// return
-// }
-// self.dismissOffset = nil
-// self.isExpanded = isExpanded
-//
-// guard let layout = self.currentLayout else {
-// return
-// }
-// self.containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition))
-// }
-//
-// private var currentMyBoostCount: Int32 = 0
-// private var myBoostCount: Int32 = 0
-// private var availableBoosts: [MyBoostStatus.Boost] = []
-// private var occupiedBoosts: [MyBoostStatus.Boost] = []
-// private let updatedState = Promise()
-//
-// private func updateBoostState() {
-// guard let controller = self.controller else {
-// return
-// }
-// let context = controller.context
-// let peerId = controller.peerId
-// let mode = controller.mode
-// let status = controller.status
-// let isPremium = controller.context.isPremium
-// let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with({ $0 }))
-// let canBoostAgain = premiumConfiguration.boostsPerGiftCount > 0
-// let presentationData = self.presentationData
-// let forceDark = controller.forceDark
-// let boostStatusUpdated = controller.boostStatusUpdated
-//
-// if let _ = status?.nextLevelBoosts {
-// if let availableBoost = self.availableBoosts.first {
-// self.currentMyBoostCount += 1
-// self.myBoostCount += 1
-//
-// let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot])
-// |> deliverOnMainQueue).startStandalone(next: { [weak self] myBoostStatus in
-// self?.updatedState.set(context.engine.peers.getChannelBoostStatus(peerId: peerId)
-// |> beforeNext { [weak self] boostStatus in
-// if let self, let boostStatus, let myBoostStatus {
-// Queue.mainQueue().async {
-// self.controller?.boostStatusUpdated(boostStatus, myBoostStatus)
-// }
-// }
-// }
-// |> map { status in
-// if let status {
-// return InternalBoostState(level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), boosts: Int32(status.boosts + 1))
-// } else {
-// return nil
-// }
-// })
-// })
-//
-// let _ = (self.updatedState.get()
-// |> take(1)
-// |> deliverOnMainQueue).startStandalone(next: { [weak self] state in
-// guard let self, let state else {
-// return
-// }
-// self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount)
-// self.updated(transition: .easeInOut(duration: 0.2))
-//
-// self.animateSuccess()
-// })
-//
-// self.availableBoosts.removeFirst()
-// } else if !self.occupiedBoosts.isEmpty, let myBoostStatus = controller.myBoostStatus {
-// if canBoostAgain {
-// let navigationController = controller.navigationController
-// let openPeer = controller.openPeer
-//
-// var dismissReplaceImpl: (() -> Void)?
-// let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in
-// var sourcePeerIds = Set()
-// var sourcePeers: [EnginePeer] = []
-// for boost in myBoostStatus.boosts {
-// if slots.contains(boost.slot) {
-// if let peer = boost.peer {
-// if !sourcePeerIds.contains(peer.id) {
-// sourcePeerIds.insert(peer.id)
-// sourcePeers.append(peer)
-// }
-// }
-// }
-// }
-//
-// let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone(completed: {
-// let _ = combineLatest(
-// queue: Queue.mainQueue(),
-// context.engine.peers.getChannelBoostStatus(peerId: peerId),
-// context.engine.peers.getMyBoostStatus()
-// ).startStandalone(next: { boostStatus, myBoostStatus in
-// dismissReplaceImpl?()
-//
-// if let boostStatus, let myBoostStatus {
-// boostStatusUpdated(boostStatus, myBoostStatus)
-// }
-//
-// let levelsController = PremiumBoostLevelsScreen(
-// context: context,
-// peerId: peerId,
-// mode: mode,
-// status: boostStatus,
-// myBoostStatus: myBoostStatus,
-// replacedBoosts: (Int32(slots.count), sourcePeers),
-// openStats: nil,
-// openGift: nil,
-// openPeer: openPeer,
-// forceDark: forceDark
-// )
-// levelsController.boostStatusUpdated = boostStatusUpdated
-// if let navigationController {
-// navigationController.pushViewController(levelsController, animated: true)
-// }
-// })
-// })
-// })
-//
-// if let navigationController = controller.navigationController {
-// controller.dismiss(animated: true)
-// navigationController.pushViewController(replaceController, animated: true)
-// }
-//
-// dismissReplaceImpl = { [weak replaceController] in
-// replaceController?.dismiss(animated: true)
-// }
-// } else if let boost = self.occupiedBoosts.first, let occupiedPeer = boost.peer {
-// if let cooldown = boost.cooldownUntil {
-// let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
-// let timeout = cooldown - currentTime
-// let valueText = timeIntervalString(strings: presentationData.strings, value: timeout, usage: .afterTime, preferLowerValue: false)
-// let alertController = textAlertController(
-// sharedContext: context.sharedContext,
-// updatedPresentationData: nil,
-// title: presentationData.strings.ChannelBoost_Error_BoostTooOftenTitle,
-// text: self.containerExternalState.isGroup ? presentationData.strings.GroupBoost_Error_BoostTooOftenText(valueText).string : presentationData.strings.ChannelBoost_Error_BoostTooOftenText(valueText).string,
-// actions: [
-// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
-// ],
-// parseMarkdown: true
-// )
-// controller.present(alertController, in: .window(.root))
-// } else {
-// let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
-// |> deliverOnMainQueue).start(next: { [weak controller] peer in
-// guard let peer, let controller else {
-// return
-// }
-// let replaceController = replaceBoostConfirmationController(context: context, fromPeers: [occupiedPeer], toPeer: peer, commit: { [weak self] in
-// self?.currentMyBoostCount += 1
-// self?.myBoostCount += 1
-// let _ = (context.engine.peers.applyChannelBoost(peerId: peerId, slots: [boost.slot])
-// |> deliverOnMainQueue).startStandalone(completed: { [weak self] in
-// guard let self else {
-// return
-// }
-// let _ = (self.updatedState.get()
-// |> take(1)
-// |> deliverOnMainQueue).startStandalone(next: { [weak self] state in
-// guard let self, let state else {
-// return
-// }
-// self.boostState = state.displayData(myBoostCount: self.myBoostCount, currentMyBoostCount: self.currentMyBoostCount)
-// self.updated(transition: .easeInOut(duration: 0.2))
-//
-// self.animateSuccess()
-// })
-// })
-// })
-// controller.present(replaceController, in: .window(.root))
-// })
-// }
-// } else {
-// controller.dismiss(animated: true, completion: nil)
-// }
-// } else {
-// if isPremium {
-// if !canBoostAgain {
-// controller.dismiss(animated: true, completion: nil)
-// } else {
-// let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
-// |> deliverOnMainQueue).start(next: { [weak controller] peer in
-// guard let peer, let controller else {
-// return
-// }
-// let alertController = textAlertController(
-// sharedContext: context.sharedContext,
-// updatedPresentationData: nil,
-// title: presentationData.strings.ChannelBoost_MoreBoosts_Title,
-// text: presentationData.strings.ChannelBoost_MoreBoosts_Text(peer.compactDisplayTitle, "\(premiumConfiguration.boostsPerGiftCount)").string,
-// actions: [
-// TextAlertAction(type: .defaultAction, title: presentationData.strings.ChannelBoost_MoreBoosts_Gift, action: { [weak controller] in
-// if let navigationController = controller?.navigationController {
-// controller?.dismiss(animated: true, completion: nil)
-//
-// Queue.mainQueue().after(0.4) {
-// let giftController = context.sharedContext.makePremiumGiftController(context: context, source: .channelBoost, completion: nil)
-// navigationController.pushViewController(giftController, animated: true)
-// }
-// }
-// }),
-// TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Close, action: {})
-// ],
-// actionLayout: .vertical,
-// parseMarkdown: true
-// )
-// controller.present(alertController, in: .window(.root))
-// })
-// }
-// } else {
-// let alertController = textAlertController(
-// sharedContext: context.sharedContext,
-// updatedPresentationData: nil,
-// title: presentationData.strings.ChannelBoost_Error_PremiumNeededTitle,
-// text: self.containerExternalState.isGroup ? presentationData.strings.GroupBoost_Error_PremiumNeededText : presentationData.strings.ChannelBoost_Error_PremiumNeededText,
-// actions: [
-// TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
-// TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: { [weak controller] in
-// if let navigationController = controller?.navigationController {
-// controller?.dismiss(animated: true)
-//
-// let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: forceDark, dismissed: nil)
-// navigationController.pushViewController(premiumController, animated: true)
-// }
-// })
-// ],
-// parseMarkdown: true
-// )
-// controller.present(alertController, in: .window(.root))
-// }
-// }
-// } else {
-// controller.dismiss(animated: true)
-// }
-// }
-//
-// func buttonPressed() {
-// self.updateBoostState()
-// }
-//
-// private func animateSuccess() {
-// self.hapticFeedback.impact()
-// self.view.addSubview(ConfettiView(frame: self.view.bounds))
-//
-// if self.isExpanded {
-// self.update(isExpanded: false, transition: .animated(duration: 0.4, curve: .spring))
-// }
-// }
-// }
-//
-// var node: Node {
-// return self.displayNode as! Node
-// }
-//
-// private let context: AccountContext
-// private let peerId: EnginePeer.Id
-// private let mode: Mode
-// private let status: ChannelBoostStatus?
-// private let myBoostStatus: MyBoostStatus?
-// private let replacedBoosts: (Int32, [EnginePeer])?
-// private let openStats: (() -> Void)?
-// private let openGift: (() -> Void)?
-// private let openPeer: ((EnginePeer) -> Void)?
-// private let forceDark: Bool
-//
-// private var currentLayout: ContainerViewLayout?
-//
-// public var boostStatusUpdated: (ChannelBoostStatus, MyBoostStatus) -> Void = { _, _ in }
-// public var disposed: () -> Void = {}
-//
-// public init(
-// context: AccountContext,
-// peerId: EnginePeer.Id,
-// mode: Mode,
-// status: ChannelBoostStatus?,
-// myBoostStatus: MyBoostStatus? = nil,
-// replacedBoosts: (Int32, [EnginePeer])? = nil,
-// openStats: (() -> Void)? = nil,
-// openGift: (() -> Void)? = nil,
-// openPeer: ((EnginePeer) -> Void)? = nil,
-// forceDark: Bool = false
-// ) {
-// self.context = context
-// self.peerId = peerId
-// self.mode = mode
-// self.status = status
-// self.myBoostStatus = myBoostStatus
-// self.replacedBoosts = replacedBoosts
-// self.openStats = openStats
-// self.openGift = openGift
-// self.openPeer = openPeer
-// self.forceDark = forceDark
-//
-// super.init(navigationBarPresentationData: nil)
-//
-// self.navigationPresentation = .flatModal
-// self.statusBar.statusBarStyle = .Ignore
-//
-// self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
-// }
-//
-// required public init(coder aDecoder: NSCoder) {
-// fatalError("init(coder:) has not been implemented")
-// }
-//
-// deinit {
-// self.disposed()
-// }
-//
-//}
-
-//private final class FooterComponent: Component {
-// let context: AccountContext
-// let theme: PresentationTheme
-// let title: String
-// let action: () -> Void
-//
-// init(context: AccountContext, theme: PresentationTheme, title: String, action: @escaping () -> Void) {
-// self.context = context
-// self.theme = theme
-// self.title = title
-// self.action = action
-// }
-//
-// static func ==(lhs: FooterComponent, rhs: FooterComponent) -> Bool {
-// if lhs.context !== rhs.context {
-// return false
-// }
-// if lhs.theme !== rhs.theme {
-// return false
-// }
-// if lhs.title != rhs.title {
-// return false
-// }
-// return true
-// }
-//
-// final class View: UIView {
-// let backgroundView: BlurredBackgroundView
-// let separator = SimpleLayer()
-//
-// private let button = ComponentView()
-//
-// private var component: FooterComponent?
-// private weak var state: EmptyComponentState?
-//
-// override init(frame: CGRect) {
-// self.backgroundView = BlurredBackgroundView(color: nil)
-//
-// super.init(frame: frame)
-//
-// self.backgroundView.clipsToBounds = true
-//
-// self.addSubview(self.backgroundView)
-// self.layer.addSublayer(self.separator)
-// }
-//
-// required init?(coder: NSCoder) {
-// fatalError("init(coder:) has not been implemented")
-// }
-//
-// func update(component: FooterComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
-// self.component = component
-// self.state = state
-//
-// let bounds = CGRect(origin: .zero, size: availableSize)
-//
-// self.backgroundView.updateColor(color: component.theme.rootController.tabBar.backgroundColor, transition: transition.containedViewLayoutTransition)
-// self.backgroundView.update(size: bounds.size, transition: transition.containedViewLayoutTransition)
-// transition.setFrame(view: self.backgroundView, frame: bounds)
-//
-// self.separator.backgroundColor = component.theme.rootController.tabBar.separatorColor.cgColor
-// transition.setFrame(layer: self.separator, frame: CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: UIScreenPixel)))
-//
-// let gradientColors = [
-// UIColor(rgb: 0x0077ff),
-// UIColor(rgb: 0x6b93ff),
-// UIColor(rgb: 0x8878ff),
-// UIColor(rgb: 0xe46ace)
-// ]
-//
-// let buttonSize = self.button.update(
-// transition: .immediate,
-// component: AnyComponent(
-// SolidRoundedButtonComponent(
-// title: component.title,
-// theme: SolidRoundedButtonComponent.Theme(
-// backgroundColor: .black,
-// backgroundColors: gradientColors,
-// foregroundColor: .white
-// ),
-// font: .bold,
-// fontSize: 17.0,
-// height: 50.0,
-// cornerRadius: 10.0,
-// gloss: true,
-// iconName: "Premium/BoostChannel",
-// animationName: nil,
-// iconPosition: .left,
-// action: {
-// component.action()
-// }
-// )
-// ),
-// environment: {},
-// containerSize: CGSize(width: availableSize.width - 32.0, height: availableSize.height)
-// )
-//
-// if let view = self.button.view {
-// if view.superview == nil {
-// self.addSubview(view)
-// }
-// let buttonFrame = CGRect(origin: CGPoint(x: 16.0, y: 8.0), size: buttonSize)
-// view.frame = buttonFrame
-// }
-//
-// 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)
-// }
-//}
-
private struct InternalBoostState: Equatable {
let level: Int32
let currentLevelBoosts: Int32
diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift
index 0217b4dd9c..e9fe7c4fd2 100644
--- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift
@@ -12,11 +12,12 @@ import ViewControllerComponent
import SheetComponent
import MultilineTextComponent
import BundleIconComponent
-import SolidRoundedButtonComponent
+import ButtonComponent
import BlurredBackgroundComponent
import Markdown
import TelegramUIPreferences
import GlassBarButtonComponent
+import LottieComponent
public final class PremiumGradientBackgroundComponent: Component {
public let colors: [UIColor]
@@ -671,7 +672,7 @@ private final class DemoSheetContent: CombinedComponent {
let closeButton = Child(GlassBarButtonComponent.self)
let background = Child(PremiumGradientBackgroundComponent.self)
let pager = Child(DemoPagerComponent.self)
- let button = Child(SolidRoundedButtonComponent.self)
+ let button = Child(ButtonComponent.self)
let measureText = Child(MultilineTextComponent.self)
return { context in
@@ -1326,28 +1327,46 @@ private final class DemoSheetContent: CombinedComponent {
}
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
+ let premiumGradientColors = [
+ UIColor(rgb: 0x0077ff),
+ UIColor(rgb: 0x6b93ff),
+ UIColor(rgb: 0x8878ff),
+ UIColor(rgb: 0xe46ace)
+ ]
+ var buttonTitle: [AnyComponentWithIdentity] = []
+ buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ButtonTextContentComponent(
+ text: buttonText,
+ badge: 0,
+ textColor: .white,
+ badgeBackground: .white,
+ badgeForeground: premiumGradientColors[0]
+ ))))
+ if isStandalone, let buttonAnimationName {
+ buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(LottieComponent(
+ content: LottieComponent.AppBundleContent(name: buttonAnimationName),
+ color: .white,
+ startingPosition: .begin,
+ size: CGSize(width: 30.0, height: 30.0),
+ loop: true
+ ))))
+ }
let button = button.update(
- component: SolidRoundedButtonComponent(
- title: buttonText,
- theme: SolidRoundedButtonComponent.Theme(
- backgroundColor: .black,
- backgroundColors: [
- UIColor(rgb: 0x0077ff),
- UIColor(rgb: 0x6b93ff),
- UIColor(rgb: 0x8878ff),
- UIColor(rgb: 0xe46ace)
- ],
- foregroundColor: .white
+ component: ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: premiumGradientColors[0],
+ foreground: .white,
+ pressedColor: premiumGradientColors[0],
+ isShimmering: state.isPremium != true,
+ gradient: ButtonComponent.Background.Gradient(
+ colors: premiumGradientColors,
+ animation: .horizontalShift(duration: 4.5)
+ )
+ ),
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("\(buttonText)-\(isStandalone ? buttonAnimationName ?? "" : "")"),
+ component: AnyComponent(HStack(buttonTitle, spacing: 4.0))
),
- font: .bold,
- fontSize: 17.0,
- height: 52.0,
- cornerRadius: 26.0,
- gloss: state.isPremium != true,
- glass: true,
- animationName: isStandalone ? buttonAnimationName : nil,
- iconPosition: .right,
- iconSpacing: 4.0,
action: { [weak component, weak state] in
guard let component = component else {
return
diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
index c2d9c19c5b..7d95e05186 100644
--- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
@@ -8,10 +8,9 @@ import TelegramPresentationData
import PresentationDataUtils
import ViewControllerComponent
import AccountContext
-import SolidRoundedButtonComponent
import MultilineTextComponent
import BundleIconComponent
-import SolidRoundedButtonComponent
+import ButtonComponent
import BlurredBackgroundComponent
import Markdown
import InAppPurchaseManager
@@ -1015,7 +1014,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
let secondaryTitle = Child(MultilineTextComponent.self)
let bottomPanel = Child(BlurredBackgroundComponent.self)
let bottomSeparator = Child(Rectangle.self)
- let button = Child(SolidRoundedButtonComponent.self)
+ let button = Child(ButtonComponent.self)
return { context in
let environment = context.environment[EnvironmentType.self].value
@@ -1233,28 +1232,38 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
buttonText = environment.strings.Premium_Gift_GiftSubscription(price ?? "—").string
}
+ let buttonGradientColors = [
+ UIColor(rgb: 0x0077ff),
+ UIColor(rgb: 0x6b93ff),
+ UIColor(rgb: 0x8878ff),
+ UIColor(rgb: 0xe46ace)
+ ]
let button = button.update(
- component: SolidRoundedButtonComponent(
- title: buttonText,
- theme: SolidRoundedButtonComponent.Theme(
- backgroundColor: UIColor(rgb: 0x8878ff),
- backgroundColors: [
- UIColor(rgb: 0x0077ff),
- UIColor(rgb: 0x6b93ff),
- UIColor(rgb: 0x8878ff),
- UIColor(rgb: 0xe46ace)
- ],
- foregroundColor: .white
+ component: ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: buttonGradientColors[0],
+ foreground: .white,
+ pressedColor: buttonGradientColors[0],
+ isShimmering: gloss,
+ gradient: ButtonComponent.Background.Gradient(colors: buttonGradientColors)
),
- height: 50.0,
- cornerRadius: 11.0,
- gloss: gloss,
- isLoading: state.inProgress,
+ content: AnyComponentWithIdentity(
+ id: AnyHashable(buttonText),
+ component: AnyComponent(ButtonTextContentComponent(
+ text: buttonText,
+ badge: 0,
+ textColor: .white,
+ badgeBackground: .white,
+ badgeForeground: buttonGradientColors[0]
+ ))
+ ),
+ displaysProgress: state.inProgress,
action: {
state.buy()
}
),
- availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 50.0),
+ availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - environment.safeInsets.left - environment.safeInsets.right, height: 52.0),
transition: context.transition)
let bottomPanel = bottomPanel.update(
diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift
index c7c0c12e3e..e825015ca7 100644
--- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift
@@ -10,7 +10,6 @@ import TelegramPresentationData
import PresentationDataUtils
import ViewControllerComponent
import AccountContext
-import SolidRoundedButtonComponent
import ButtonComponent
import MultilineTextComponent
import MultilineTextWithEntitiesComponent
@@ -3416,7 +3415,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let title = Child(MultilineTextComponent.self)
let secondaryTitle = Child(MultilineTextWithEntitiesComponent.self)
let bottomEdgeEffect = Child(EdgeEffectComponent.self)
- let button = Child(SolidRoundedButtonComponent.self)
+ let button = Child(ButtonComponent.self)
var updatedInstalled: Bool?
@@ -3645,8 +3644,16 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let controller = context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedPack.flatMap { [$0] } ?? [], actionTitle: nil, isEditing: false, expandIfNeeded: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in
return false
- }, actionPerformed: { added in
- updatedInstalled = added
+ }, actionPerformed: { actions in
+ guard let action = actions.first?.action else {
+ return
+ }
+ switch action {
+ case .add:
+ updatedInstalled = true
+ case .remove:
+ updatedInstalled = false
+ }
})
presentController(controller)
break
@@ -3817,25 +3824,51 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
}
let controller = environment.controller
+ let buttonGradientColors = [
+ UIColor(rgb: 0x0077ff),
+ UIColor(rgb: 0x6b93ff),
+ UIColor(rgb: 0x8878ff),
+ UIColor(rgb: 0xe46ace)
+ ]
+ let buttonContent: AnyComponent
+ if let buttonSubtitle {
+ buttonContent = AnyComponent(VStack([
+ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(Text(
+ text: buttonTitle,
+ font: Font.semibold(17.0),
+ color: .white
+ ))),
+ AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(Text(
+ text: buttonSubtitle,
+ font: Font.medium(11.0),
+ color: UIColor.white.withAlphaComponent(0.7)
+ )))
+ ], spacing: 1.0))
+ } else {
+ buttonContent = AnyComponent(ButtonTextContentComponent(
+ text: buttonTitle,
+ badge: 0,
+ textColor: .white,
+ badgeBackground: .white,
+ badgeForeground: buttonGradientColors[0]
+ ))
+ }
let button = button.update(
- component: SolidRoundedButtonComponent(
- title: buttonTitle,
- subtitle: buttonSubtitle,
- theme: SolidRoundedButtonComponent.Theme(
- backgroundColor: UIColor(rgb: 0x8878ff),
- backgroundColors: [
- UIColor(rgb: 0x0077ff),
- UIColor(rgb: 0x6b93ff),
- UIColor(rgb: 0x8878ff),
- UIColor(rgb: 0xe46ace)
- ],
- foregroundColor: .white
+ component: ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: UIColor(rgb: 0x8878ff),
+ foreground: .white,
+ pressedColor: UIColor(rgb: 0x8878ff).withMultipliedAlpha(0.8),
+ cornerRadius: 26.0,
+ isShimmering: true,
+ gradient: ButtonComponent.Background.Gradient(colors: buttonGradientColors)
),
- height: 52.0,
- cornerRadius: 26.0,
- gloss: true,
- glass: true,
- isLoading: state.inProgress,
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("\(buttonTitle)-\(buttonSubtitle ?? "")"),
+ component: buttonContent
+ ),
+ displaysProgress: state.inProgress,
action: {
if let controller = controller() as? PremiumIntroScreen, let customProceed = controller.customProceed {
controller.dismiss()
diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift
index b487f1ea42..d60ecf31bd 100644
--- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift
@@ -13,6 +13,8 @@ import SheetComponent
import MultilineTextComponent
import BundleIconComponent
import SolidRoundedButtonComponent
+import ButtonComponent
+import LottieComponent
import Markdown
import BalancedTextComponent
import ConfettiEffect
@@ -832,7 +834,7 @@ private final class LimitSheetContent: CombinedComponent {
let alternateText = Child(List.self)
let limit = Child(PremiumLimitDisplayComponent.self)
let linkButton = Child(SolidRoundedButtonComponent.self)
- let button = Child(SolidRoundedButtonComponent.self)
+ let button = Child(ButtonComponent.self)
let peerShortcut = Child(Button.self)
let statsButton = Child(Button.self)
@@ -1456,23 +1458,44 @@ private final class LimitSheetContent: CombinedComponent {
let isIncreaseButton = !reachedMaximumLimit && !isPremiumDisabled
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
+ let buttonTitle = actionButtonText ?? (isIncreaseButton ? strings.Premium_IncreaseLimit : strings.Common_OK)
+ var buttonContentItems: [AnyComponentWithIdentity] = []
+ if let buttonIconName {
+ buttonContentItems.append(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(
+ name: buttonIconName,
+ tintColor: .white
+ ))))
+ }
+ buttonContentItems.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(ButtonTextContentComponent(
+ text: buttonTitle,
+ badge: 0,
+ textColor: .white,
+ badgeBackground: .white,
+ badgeForeground: buttonGradientColors[0]
+ ))))
+ if buttonIconName == nil, isIncreaseButton, let buttonAnimationName {
+ buttonContentItems.append(AnyComponentWithIdentity(id: "animation", component: AnyComponent(LottieComponent(
+ content: LottieComponent.AppBundleContent(name: buttonAnimationName),
+ color: .white,
+ startingPosition: .begin,
+ size: CGSize(width: 30.0, height: 30.0),
+ loop: true
+ ))))
+ }
let button = button.update(
- component: SolidRoundedButtonComponent(
- title: actionButtonText ?? (isIncreaseButton ? strings.Premium_IncreaseLimit : strings.Common_OK),
- theme: SolidRoundedButtonComponent.Theme(
- backgroundColor: .black,
- backgroundColors: buttonGradientColors,
- foregroundColor: .white
+ component: ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: buttonGradientColors[0],
+ foreground: .white,
+ pressedColor: buttonGradientColors[0],
+ isShimmering: isIncreaseButton && actionButtonHasGloss,
+ gradient: ButtonComponent.Background.Gradient(colors: buttonGradientColors)
+ ),
+ content: AnyComponentWithIdentity(
+ id: AnyHashable("\(buttonTitle)-\(buttonIconName ?? "")-\(isIncreaseButton ? buttonAnimationName ?? "" : "")"),
+ component: AnyComponent(HStack(buttonContentItems, spacing: 4.0))
),
- font: .bold,
- fontSize: 17.0,
- height: 52.0,
- cornerRadius: 26.0,
- gloss: isIncreaseButton && actionButtonHasGloss,
- glass: true,
- iconName: buttonIconName,
- animationName: isIncreaseButton ? buttonAnimationName : nil,
- iconPosition: buttonIconName != nil ? .left : .right,
action: {
if isIncreaseButton {
if component.action() {
diff --git a/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift b/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift
index 7fc6496107..af9c412f2b 100644
--- a/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumPrivacyScreen.swift
@@ -13,6 +13,7 @@ import BundleIconComponent
import BalancedTextComponent
import MultilineTextComponent
import SolidRoundedButtonComponent
+import ButtonComponent
import LottieComponent
import AccountContext
import GlassBarButtonComponent
@@ -110,7 +111,7 @@ private final class SheetContent: CombinedComponent {
let premiumTitle = Child(BalancedTextComponent.self)
let premiumText = Child(BalancedTextComponent.self)
- let premiumButton = Child(SolidRoundedButtonComponent.self)
+ let premiumButton = Child(ButtonComponent.self)
return { context in
let environment = context.environment[EnvironmentType.self]
@@ -342,28 +343,32 @@ private final class SheetContent: CombinedComponent {
contentSize.height += premiumText.size.height
contentSize.height += spacing + 5.0
+ let premiumGradientColors = [
+ UIColor(rgb: 0x0077ff),
+ UIColor(rgb: 0x6b93ff),
+ UIColor(rgb: 0x8878ff),
+ UIColor(rgb: 0xe46ace)
+ ]
let premiumButton = premiumButton.update(
- component: SolidRoundedButtonComponent(
- title: premiumButtonTitle,
- theme: SolidRoundedButtonComponent.Theme(
- backgroundColor: .black,
- backgroundColors: [
- UIColor(rgb: 0x0077ff),
- UIColor(rgb: 0x6b93ff),
- UIColor(rgb: 0x8878ff),
- UIColor(rgb: 0xe46ace)
- ],
- foregroundColor: .white
+ component: ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: premiumGradientColors[0],
+ foreground: .white,
+ pressedColor: premiumGradientColors[0],
+ isShimmering: false,
+ gradient: ButtonComponent.Background.Gradient(colors: premiumGradientColors)
+ ),
+ content: AnyComponentWithIdentity(
+ id: AnyHashable(premiumButtonTitle),
+ component: AnyComponent(ButtonTextContentComponent(
+ text: premiumButtonTitle,
+ badge: 0,
+ textColor: .white,
+ badgeBackground: .white,
+ badgeForeground: premiumGradientColors[0]
+ ))
),
- font: .bold,
- fontSize: 17.0,
- height: 52.0,
- cornerRadius: 26.0,
- gloss: false,
- glass: true,
- iconName: nil,
- animationName: nil,
- iconPosition: .left,
action: {
component.openPremiumIntro()
component.dismiss()
diff --git a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift
index 5a6b093777..bd014a9bdb 100644
--- a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift
+++ b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift
@@ -335,6 +335,7 @@ private class StickersCarouselNode: ASDisplayNode, ASScrollViewDelegate {
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.showsVerticalScrollIndicator = false
self.scrollNode.view.canCancelContentTouches = true
+ self.scrollNode.view.scrollsToTop = false
self.tapNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.stickerTapped(_:))))
}
diff --git a/submodules/PresentationDataUtils/BUILD b/submodules/PresentationDataUtils/BUILD
index 2b33e0a691..69e2a085c5 100644
--- a/submodules/PresentationDataUtils/BUILD
+++ b/submodules/PresentationDataUtils/BUILD
@@ -19,9 +19,7 @@ swift_library(
"//submodules/ItemListUI:ItemListUI",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/OverlayStatusController:OverlayStatusController",
- "//submodules/UrlWhitelist:UrlWhitelist",
"//submodules/TelegramUI/Components/AlertComponent",
- "//submodules/UrlHandling",
],
visibility = [
"//visibility:public",
diff --git a/submodules/QrCodeUI/BUILD b/submodules/QrCodeUI/BUILD
index e7eb7e221b..6bb6fe0349 100644
--- a/submodules/QrCodeUI/BUILD
+++ b/submodules/QrCodeUI/BUILD
@@ -11,6 +11,7 @@ swift_library(
],
deps = [
"//submodules/TelegramCore:TelegramCore",
+ "//submodules/MtProtoKit:MtProtoKit",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
@@ -22,7 +23,6 @@ swift_library(
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/PresentationDataUtils:PresentationDataUtils",
- "//submodules/GlassButtonNode:GlassButtonNode",
"//submodules/TextFormat:TextFormat",
"//submodules/Markdown:Markdown",
"//submodules/UndoUI:UndoUI",
@@ -34,12 +34,15 @@ swift_library(
"//submodules/ComponentFlow",
"//submodules/Components/SheetComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
+ "//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
+ "//submodules/TelegramUI/Components/SegmentControlComponent",
+ "//submodules/UrlEscaping:UrlEscaping",
],
visibility = [
"//visibility:public",
diff --git a/submodules/GlassButtonNode/Sources/GlassButtonNode.swift b/submodules/QrCodeUI/Sources/GlassButtonNode.swift
similarity index 72%
rename from submodules/GlassButtonNode/Sources/GlassButtonNode.swift
rename to submodules/QrCodeUI/Sources/GlassButtonNode.swift
index d1f39743df..9bdb0d776d 100644
--- a/submodules/GlassButtonNode/Sources/GlassButtonNode.swift
+++ b/submodules/QrCodeUI/Sources/GlassButtonNode.swift
@@ -3,6 +3,8 @@ import Display
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
+import ComponentFlow
+import GlassBackgroundComponent
private let largeButtonSize = CGSize(width: 72.0, height: 72.0)
private let smallButtonSize = CGSize(width: 60.0, height: 60.0)
@@ -29,15 +31,15 @@ private func generateEmptyButtonImage(icon: UIImage?, strokeColor: UIColor?, fil
}
let imageSize = icon.size
let imageRect = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.width - imageSize.height) / 2.0)), size: imageSize)
+
+ context.setBlendMode(.copy)
+ context.clip(to: imageRect, mask: icon.cgImage!)
if knockout {
- context.setBlendMode(.copy)
- context.clip(to: imageRect, mask: icon.cgImage!)
context.setFillColor(UIColor.clear.cgColor)
- context.fill(imageRect)
} else {
- context.setBlendMode(.normal)
- context.draw(icon.cgImage!, in: imageRect)
+ context.setFillColor(UIColor.white.cgColor)
}
+ context.fill(imageRect)
}
})
}
@@ -60,45 +62,44 @@ private func generateFilledButtonImage(color: UIColor, icon: UIImage?, angle: CG
})
}
-private let emptyHighlightedFill = UIColor(white: 1.0, alpha: 0.3)
-private let invertedFill = UIColor(white: 1.0, alpha: 1.0)
-
private let largeLabelFont = Font.regular(14.5)
private let smallLabelFont = Font.regular(11.5)
-public final class GlassButtonNode: HighlightTrackingButtonNode {
+final class GlassButtonNode: ASDisplayNode {
private var regularImage: UIImage?
- private var highlightedImage: UIImage?
private var filledImage: UIImage?
- private let blurView: UIVisualEffectView
+ private let backgroundView: GlassBackgroundView
private let iconNode: ASImageNode
private var labelNode: ImmediateTextNode?
- public init(icon: UIImage, label: String?) {
- let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
- blurView.clipsToBounds = true
- blurView.isUserInteractionEnabled = false
- self.blurView = blurView
+ private let button: HighlightTrackingButton
+
+ var pressed: () -> Void = {}
+
+ init(icon: UIImage, label: String?) {
+ self.backgroundView = GlassBackgroundView()
+
+ self.button = HighlightTrackingButton()
self.iconNode = ASImageNode()
- self.iconNode.isLayerBacked = true
self.iconNode.displayWithoutProcessing = false
self.iconNode.displaysAsynchronously = false
+ self.iconNode.isUserInteractionEnabled = false
self.regularImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: .clear, buttonSize: largeButtonSize)
- self.highlightedImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: emptyHighlightedFill, buttonSize: largeButtonSize)
- self.filledImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: invertedFill, knockout: true, buttonSize: largeButtonSize)
+ self.filledImage = generateEmptyButtonImage(icon: icon, strokeColor: nil, fillColor: .white, knockout: true, buttonSize: largeButtonSize)
if let label = label {
let labelNode = ImmediateTextNode()
let labelFont: UIFont
- if let image = regularImage, image.size.width < 70.0 {
+ if let image = self.regularImage, image.size.width < 70.0 {
labelFont = smallLabelFont
} else {
labelFont = largeLabelFont
}
labelNode.attributedText = NSAttributedString(string: label, font: labelFont, textColor: .white)
+ labelNode.isUserInteractionEnabled = false
self.labelNode = labelNode
} else {
self.labelNode = nil
@@ -106,38 +107,34 @@ public final class GlassButtonNode: HighlightTrackingButtonNode {
super.init()
- self.view.addSubview(blurView)
- self.addSubnode(self.iconNode)
+ self.view.addSubview(self.backgroundView)
+ self.backgroundView.contentView.addSubview(self.button)
+ self.backgroundView.contentView.addSubview(self.iconNode.view)
if let labelNode = self.labelNode {
- self.addSubnode(labelNode)
+ self.backgroundView.contentView.addSubview(labelNode.view)
}
- self.iconNode.image = regularImage
- self.currentImage = regularImage
+ self.iconNode.image = self.regularImage
+ self.currentImage = self.regularImage
- self.highligthedChanged = { [weak self] highlighted in
- if let strongSelf = self {
- strongSelf.internalHighlighted = highlighted
- strongSelf.updateState(highlighted: highlighted, selected: strongSelf.isSelected)
- }
- }
+ self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
- private var internalHighlighted = false
+ @objc private func buttonPressed() {
+ self.pressed()
+ }
- override public var isSelected: Bool {
+ var isSelected: Bool = false {
didSet {
- self.updateState(highlighted: self.internalHighlighted, selected: self.isSelected)
+ self.updateState(selected: self.isSelected)
}
}
private var currentImage: UIImage?
- private func updateState(highlighted: Bool, selected: Bool) {
+ private func updateState(selected: Bool) {
let image: UIImage?
if selected {
image = self.filledImage
- } else if highlighted {
- image = self.highlightedImage
} else {
image = self.regularImage
}
@@ -160,8 +157,10 @@ public final class GlassButtonNode: HighlightTrackingButtonNode {
let size = self.bounds.size
- self.blurView.layer.cornerRadius = size.width / 2.0
- blurView.frame = self.bounds
+ self.button.frame = self.bounds
+
+ self.backgroundView.frame = self.bounds
+ self.backgroundView.update(size: size, cornerRadius: size.width / 2.0, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, transition: .immediate)
self.iconNode.frame = self.bounds
diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift
index 3a54062f87..bee53f957d 100644
--- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift
+++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift
@@ -3,9 +3,9 @@ import UIKit
import AccountContext
import AsyncDisplayKit
import Display
+import ComponentFlow
import SwiftSignalKit
import Camera
-import GlassButtonNode
import CoreImage
import AlertUI
import TelegramPresentationData
@@ -18,6 +18,8 @@ import LegacyComponents
import LegacyMediaPickerUI
import ImageContentAnalysis
import PresentationDataUtils
+import BundleIconComponent
+import GlassBarButtonComponent
private func parseAuthTransferUrl(_ url: URL) -> Data? {
var tokenString: String?
@@ -77,18 +79,14 @@ public final class QrCodeScanScreen: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
- let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: .white, accentDisabledButtonColor: .white, accentForegroundColor: .black)
-
- super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
+ super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .White
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
-
- self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
-
+
self.inForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground
|> deliverOnMainQueue).start(next: { [weak self] inForeground in
guard let strongSelf = self else {
@@ -96,14 +94,6 @@ public final class QrCodeScanScreen: ViewController {
}
(strongSelf.displayNode as! QrCodeScanScreenNode).updateInForeground(inForeground)
})
-
- if case .custom = subject {
- self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
- } else {
- #if DEBUG
- self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Test", style: .plain, target: self, action: #selector(self.testPressed))
- #endif
- }
}
required init(coder aDecoder: NSCoder) {
@@ -116,19 +106,11 @@ public final class QrCodeScanScreen: ViewController {
self.approveDisposable.dispose()
}
- @objc private func cancelPressed() {
+ @objc fileprivate func cancelPressed() {
self.completion(nil)
self.dismissAnimated()
}
- @objc private func myCodePressed() {
- self.showMyCode()
- }
-
- @objc private func testPressed() {
- self.dismissWithSession(session: nil)
- }
-
private var animatedIn = false
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
@@ -370,6 +352,7 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let errorTextNode: ImmediateTextNode
+ private let topNavigationButton = ComponentView()
private let camera: Camera
private let codeDisposable = MetaDisposable()
@@ -510,10 +493,15 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.errorTextNode)
-
- self.galleryButtonNode.addTarget(self, action: #selector(self.galleryPressed), forControlEvents: .touchUpInside)
- self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside)
-
+
+ self.galleryButtonNode.pressed = { [weak self] in
+ self?.galleryPressed()
+ }
+
+ self.torchButtonNode.pressed = { [weak self] in
+ self?.torchPressed()
+ }
+
self.previewView.resetPlaceholder(front: false)
if #available(iOS 13.0, *) {
let _ = (self.previewView.isPreviewing
@@ -666,6 +654,56 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie
transition.updateFrame(view: self.previewView, frame: bounds)
transition.updateFrame(node: self.fadeNode, frame: bounds)
+ let topNavigationIconName: String
+ if case .custom = self.subject {
+ topNavigationIconName = "Navigation/Close"
+ } else {
+ topNavigationIconName = "Navigation/Back"
+ }
+ let topNavigationButtonSide = CGSize(width: 44.0, height: 44.0)
+ let topNavigationButtonSize = self.topNavigationButton.update(
+ transition: ComponentTransition(transition),
+ component: AnyComponent(GlassBarButtonComponent(
+ size: topNavigationButtonSide,
+ backgroundColor: nil,
+ isDark: true,
+ state: .glass,
+ component: AnyComponentWithIdentity(id: topNavigationIconName, component: AnyComponent(
+ BundleIconComponent(
+ name: topNavigationIconName,
+ tintColor: .white
+ )
+ )),
+ action: { [weak self] _ in
+ guard let self else {
+ return
+ }
+ if case .custom = self.subject {
+ self.controller?.cancelPressed()
+ } else {
+ self.controller?.dismiss()
+ }
+ }
+ )),
+ environment: {},
+ containerSize: topNavigationButtonSide
+ )
+ if let topNavigationButtonView = self.topNavigationButton.view {
+ if topNavigationButtonView.superview == nil {
+ self.view.addSubview(topNavigationButtonView)
+ }
+ transition.updateFrame(
+ view: topNavigationButtonView,
+ frame: CGRect(
+ origin: CGPoint(
+ x: 16.0 + layout.safeInsets.left,
+ y: max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top) + 5.0
+ ),
+ size: topNavigationButtonSize
+ )
+ )
+ }
+
let frameSide = max(240.0, layout.size.width - sideInset * 2.0)
let animateInScale: CGFloat = 0.4
var effectiveFrameSide = frameSide
@@ -897,4 +935,3 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, ASScrollVie
return true
}
}
-
diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift
index 0323caf18b..0cbbbe41ea 100644
--- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift
+++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift
@@ -18,47 +18,56 @@ import Markdown
import TextFormat
import QrCode
import LottieComponent
+import MtProtoKit
+import SegmentControlComponent
+import UrlEscaping
-private func shareQrCode(context: AccountContext, link: String, ecl: String, view: UIView) {
- let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")), ecl: ecl)
- |> map { _, generator -> UIImage? in
- let imageSize = CGSize(width: 768.0, height: 768.0)
- let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
- return context?.generateImage()
- }
- |> deliverOnMainQueue).start(next: { image in
- guard let image = image else {
- return
- }
-
- let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil)
+private func shareQrCode(sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject, asImage: Bool, view: UIView) {
+ let shareImpl: (Any) -> Void = { item in
+ let activityController = UIActivityViewController(activityItems: [item], applicationActivities: nil)
if let window = view.window {
activityController.popoverPresentationController?.sourceView = window
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
}
- context.sharedContext.applicationBindings.presentNativeController(activityController)
- })
+ sharedContext.applicationBindings.presentNativeController(activityController)
+ }
+ if asImage {
+ let _ = (qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: subject.icon, ecl: subject.ecl)
+ |> map { _, generator -> UIImage? in
+ let imageSize = CGSize(width: 768.0, height: 768.0)
+ let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
+ return context?.generateImage()
+ }
+ |> deliverOnMainQueue).start(next: { image in
+ guard let image else {
+ return
+ }
+ shareImpl(image)
+ })
+ } else {
+ shareImpl(subject.link)
+ }
}
private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
- let context: AccountContext
+ let sharedContext: SharedAccountContext
let subject: QrCodeScreen.Subject
let dismiss: () -> Void
init(
- context: AccountContext,
+ sharedContext: SharedAccountContext,
subject: QrCodeScreen.Subject,
dismiss: @escaping () -> Void
) {
- self.context = context
+ self.sharedContext = sharedContext
self.subject = subject
self.dismiss = dismiss
}
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
- if lhs.context !== rhs.context {
+ if lhs.sharedContext !== rhs.sharedContext {
return false
}
return true
@@ -70,11 +79,19 @@ private final class SheetContent: CombinedComponent {
private var initialBrightness: CGFloat?
private var brightnessArguments: (Double, Double, CGFloat, CGFloat)?
private var animator: ConstantDisplayLinkAnimator?
-
- init(context: AccountContext) {
+
+ var selectedProxyExternalLink: Bool
+
+ init(sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject) {
+ if case let .proxy(_, externalLink) = subject {
+ self.selectedProxyExternalLink = externalLink
+ } else {
+ self.selectedProxyExternalLink = false
+ }
+
super.init()
- self.idleTimerExtensionDisposable.set(context.sharedContext.applicationBindings.pushIdleTimerExtension())
+ self.idleTimerExtensionDisposable.set(sharedContext.applicationBindings.pushIdleTimerExtension())
self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateBrightness()
@@ -116,16 +133,18 @@ private final class SheetContent: CombinedComponent {
}
func makeState() -> State {
- return State(context: self.context)
+ return State(sharedContext: self.sharedContext, subject: self.subject)
}
static var body: Body {
let qrCode = Child(PlainButtonComponent.self)
let closeButton = Child(GlassBarButtonComponent.self)
let title = Child(Text.self)
+ let segmentControl = Child(SegmentControlComponent.self)
let text = Child(BalancedTextComponent.self)
let button = Child(ButtonComponent.self)
+ let secondaryButton = Child(ButtonComponent.self)
return { context in
let environment = context.environment[EnvironmentType.self]
@@ -134,10 +153,15 @@ private final class SheetContent: CombinedComponent {
let theme = environment.theme
let strings = environment.strings
-
- let link = component.subject.link
- let ecl = component.subject.ecl
-
+ let state = context.state
+
+ let effectiveSubject: QrCodeScreen.Subject
+ if case let .proxy(server, _) = component.subject {
+ effectiveSubject = .proxy(server: server, externalLink: state.selectedProxyExternalLink)
+ } else {
+ effectiveSubject = component.subject
+ }
+
let titleString: String
let textString: String
switch component.subject {
@@ -154,6 +178,9 @@ private final class SheetContent: CombinedComponent {
case .chatFolder:
titleString = strings.InviteLink_QRCodeFolder_Title
textString = strings.InviteLink_QRCodeFolder_Text
+ case .proxy:
+ titleString = ""
+ textString = strings.SocksProxySetup_ShareQRCodeInfo
default:
titleString = ""
textString = ""
@@ -185,24 +212,67 @@ private final class SheetContent: CombinedComponent {
)
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
-
- let title = title.update(
- component: Text(text: titleString, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor),
- availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
- transition: .immediate
- )
- context.add(title
- .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height))
- )
- contentSize.height += title.size.height
+
+ if case .proxy = component.subject {
+ let constrainedSegmentWidth = min(constrainedTitleWidth, max(200.0, context.availableSize.width - 144.0))
+
+ let theme = SegmentControlComponent.Theme(
+ backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor,
+ legacyBackgroundColor: theme.overallDarkAppearance ? theme.list.itemBlocksBackgroundColor : theme.rootController.navigationBar.segmentedBackgroundColor,
+ foregroundColor: theme.actionSheet.opaqueItemBackgroundColor,
+ textColor: theme.rootController.navigationBar.segmentedTextColor,
+ dividerColor: theme.rootController.navigationBar.segmentedDividerColor
+ )
+
+ //TODO:localize
+ let segmentControl = segmentControl.update(
+ component: SegmentControlComponent(
+ theme: theme,
+ items: [
+ SegmentControlComponent.Item(id: AnyHashable(false), title: "tg:// link"),
+ SegmentControlComponent.Item(id: AnyHashable(true), title: "t.me link")
+ ],
+ selectedId: AnyHashable(state.selectedProxyExternalLink),
+ action: { id in
+ guard let externalLink = id.base as? Bool else {
+ return
+ }
+ if state.selectedProxyExternalLink != externalLink {
+ state.selectedProxyExternalLink = externalLink
+ state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)))
+ }
+ }
+ ),
+ availableSize: CGSize(width: constrainedSegmentWidth, height: 36.0),
+ transition: .immediate
+ )
+ context.add(segmentControl
+ .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height))
+ )
+ contentSize.height += segmentControl.size.height
+ } else {
+ let title = title.update(
+ component: Text(text: titleString, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor),
+ availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
+ transition: .immediate
+ )
+ context.add(title
+ .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height))
+ )
+ contentSize.height += title.size.height
+ }
contentSize.height += 13.0
let qrCode = qrCode.update(
component: PlainButtonComponent(
- content: AnyComponent(QrCodeComponent(context: component.context, link: link, ecl: ecl)),
+ content: AnyComponent(
+ QrCodeComponent(
+ subject: effectiveSubject
+ )
+ ),
action: { [weak controller] in
if let view = controller?.view {
- shareQrCode(context: component.context, link: link, ecl: ecl, view: view)
+ shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: true, view: view)
}
},
animateScale: false
@@ -250,8 +320,7 @@ private final class SheetContent: CombinedComponent {
style: .glass,
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
- pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
- cornerRadius: 10.0,
+ pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
@@ -261,7 +330,7 @@ private final class SheetContent: CombinedComponent {
displaysProgress: false,
action: { [weak controller] in
if let view = controller?.view {
- shareQrCode(context: component.context, link: link, ecl: ecl, view: view)
+ shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: true, view: view)
}
}
),
@@ -272,8 +341,42 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
)
contentSize.height += button.size.height
+
+ if case .proxy = component.subject {
+ contentSize.height += 8.0
+
+ let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
+ let secondaryButton = secondaryButton.update(
+ component: ButtonComponent(
+ background: ButtonComponent.Background(
+ style: .glass,
+ color: theme.list.itemAccentColor.withMultipliedAlpha(0.1),
+ foreground: theme.list.itemAccentColor,
+ pressedColor: theme.list.itemAccentColor.withMultipliedAlpha(0.8)
+ ),
+ content: AnyComponentWithIdentity(
+ id: AnyHashable(0),
+ component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.SocksProxySetup_ShareLink, font: Font.semibold(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .center))))
+ ),
+ isEnabled: true,
+ displaysProgress: false,
+ action: { [weak controller] in
+ if let view = controller?.view {
+ shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: false, view: view)
+ }
+ }
+ ),
+ availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0),
+ transition: .immediate
+ )
+ context.add(secondaryButton
+ .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + secondaryButton.size.height / 2.0))
+ )
+ contentSize.height += secondaryButton.size.height
+ }
+
contentSize.height += buttonInsets.bottom
-
+
return contentSize
}
}
@@ -282,19 +385,19 @@ private final class SheetContent: CombinedComponent {
private final class QrCodeSheetComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
- private let context: AccountContext
+ private let sharedContext: SharedAccountContext
private let subject: QrCodeScreen.Subject
init(
- context: AccountContext,
+ sharedContext: SharedAccountContext,
subject: QrCodeScreen.Subject
) {
- self.context = context
+ self.sharedContext = sharedContext
self.subject = subject
}
static func ==(lhs: QrCodeSheetComponent, rhs: QrCodeSheetComponent) -> Bool {
- if lhs.context !== rhs.context {
+ if lhs.sharedContext !== rhs.sharedContext {
return false
}
return true
@@ -312,7 +415,7 @@ private final class QrCodeSheetComponent: CombinedComponent {
let sheet = sheet.update(
component: SheetComponent(
content: AnyComponent(SheetContent(
- context: context.component.context,
+ sharedContext: context.component.sharedContext,
subject: context.component.subject,
dismiss: {
animateOut.invoke(Action { _ in
@@ -372,10 +475,11 @@ public final class QrCodeScreen: ViewControllerComponentContainer {
case groupCall
}
- public enum Subject {
+ public enum Subject: Equatable {
case peer(peer: EnginePeer)
case invite(invite: ExportedInvitation, type: SubjectType)
case chatFolder(slug: String)
+ case proxy(server: ProxyServerSettings, externalLink: Bool)
var link: String {
switch self {
@@ -389,39 +493,79 @@ public final class QrCodeScreen: ViewControllerComponentContainer {
} else {
return "https://t.me/addlist/\(slug)"
}
+ case let .proxy(server, externalLink):
+ var link: String
+ let serverHost = server.host.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? ""
+ switch server.connection {
+ case let .mtp(secret):
+ let secret = MTProxySecret.parseData(secret)?.serializeToString() ?? ""
+ link = "\(externalLink ? "https://t.me/proxy" : "tg://proxy")?server=\(serverHost)&port=\(server.port)"
+ link += "&secret=\(secret.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
+ case let .socks5(username, password):
+ link = "\(externalLink ? "https://t.me/socks" : "tg://socks")?server=\(serverHost)&port=\(server.port)"
+ if let username, !username.isEmpty {
+ link += "&user=\(username.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
+ }
+ if let password, !password.isEmpty {
+ link += "&pass=\(password.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
+ }
+ }
+ return link
}
}
var ecl: String {
switch self {
- case .peer:
- return "Q"
- case .invite:
- return "Q"
- case .chatFolder:
+ case .peer, .invite, .chatFolder, .proxy:
return "Q"
}
}
+
+ var icon: QrCodeIcon {
+ switch self {
+ case .peer, .invite, .chatFolder:
+ return .custom(UIImage(bundleImageName: "Chat/Links/QrLogo"))
+ case .proxy:
+ return .proxy
+ }
+ }
}
-
- private let context: AccountContext
-
+
public init(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil,
subject: QrCodeScreen.Subject
) {
- self.context = context
-
super.init(
context: context,
component: QrCodeSheetComponent(
- context: context,
+ sharedContext: context.sharedContext,
subject: subject
),
navigationBarAppearance: .none,
statusBarStyle: .ignore,
- theme: .default //
+ theme: .default,
+ updatedPresentationData: updatedPresentationData
+ )
+
+ self.navigationPresentation = .flatModal
+ }
+
+ public init(
+ sharedContext: SharedAccountContext,
+ updatedPresentationData: (initial: PresentationData, signal: Signal),
+ subject: QrCodeScreen.Subject
+ ) {
+ super.init(
+ component: QrCodeSheetComponent(
+ sharedContext: sharedContext,
+ subject: subject
+ ),
+ navigationBarAppearance: .none,
+ statusBarStyle: .ignore,
+ presentationMode: .default,
+ theme: .default,
+ updatedPresentationData: updatedPresentationData
)
self.navigationPresentation = .flatModal
@@ -439,28 +583,16 @@ public final class QrCodeScreen: ViewControllerComponentContainer {
}
private final class QrCodeComponent: Component {
- let context: AccountContext
- let link: String
- let ecl: String
+ let subject: QrCodeScreen.Subject
init(
- context: AccountContext,
- link: String,
- ecl: String
+ subject: QrCodeScreen.Subject
) {
- self.context = context
- self.link = link
- self.ecl = ecl
+ self.subject = subject
}
static func ==(lhs: QrCodeComponent, rhs: QrCodeComponent) -> Bool {
- if lhs.context !== rhs.context {
- return false
- }
- if lhs.link != rhs.link {
- return false
- }
- if lhs.ecl != rhs.ecl {
+ if lhs.subject != rhs.subject {
return false
}
return true
@@ -502,9 +634,14 @@ private final class QrCodeComponent: Component {
let previousComponent = self.component
self.component = component
self.state = state
-
- if previousComponent?.link != component.link {
- self.imageNode.setSignal(qrCode(string: component.link, color: .black, backgroundColor: .white, icon: .cutout, ecl: component.ecl) |> beforeNext { [weak self] size, _ in
+
+ var isProxy = false
+ if case .proxy = component.subject {
+ isProxy = true
+ }
+
+ if previousComponent?.subject != component.subject {
+ self.imageNode.setSignal(qrCode(string: component.subject.link, color: .black, backgroundColor: .white, icon: isProxy ? .proxy : .cutout, ecl: component.subject.ecl) |> beforeNext { [weak self] size, _ in
guard let self else {
return
}
@@ -524,7 +661,7 @@ private final class QrCodeComponent: Component {
let imageFrame = CGRect(origin: CGPoint(x: (size.width - imageSize.width) / 2.0, y: (size.height - imageSize.height) / 2.0), size: imageSize)
self.imageNode.frame = imageFrame
- if let qrCodeSize = self.qrCodeSize {
+ if !isProxy, let qrCodeSize = self.qrCodeSize {
let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil)
let _ = self.icon.update(
diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift
index 89d3a1cee3..bcb2e7d26d 100644
--- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift
+++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift
@@ -533,6 +533,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
self.scrollNode.view.scrollsToTop = false
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.canCancelContentTouches = true
+ self.scrollNode.view.scrollsToTop = false
self.scrollNode.clipsToBounds = false
if #available(iOS 11.0, *) {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD
index b0ecc4368e..b8a0540a12 100644
--- a/submodules/SettingsUI/BUILD
+++ b/submodules/SettingsUI/BUILD
@@ -54,6 +54,7 @@ swift_library(
"//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI",
"//submodules/PhoneInputNode:PhoneInputNode",
"//submodules/PhotoResources:PhotoResources",
+ "//submodules/StickerResources:StickerResources",
"//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/SearchBarNode:SearchBarNode",
@@ -67,7 +68,6 @@ swift_library(
"//submodules/TextFormat:TextFormat",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/UrlEscaping:UrlEscaping",
- "//submodules/WebSearchUI:WebSearchUI",
"//submodules/UrlHandling:UrlHandling",
"//submodules/HexColor:HexColor",
"//submodules/QrCode:QrCode",
@@ -128,6 +128,7 @@ swift_library(
"//submodules/TelegramUI/Components/Settings/PasskeysScreen",
"//submodules/TelegramUI/Components/FaceScanScreen",
"//submodules/ComponentFlow",
+ "//submodules/Components/ResizableSheetComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
@@ -137,6 +138,7 @@ swift_library(
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/AvatarEditorScreen",
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
+ "//submodules/TelegramUI/Components/Settings/ChatbotSetupScreen",
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/Utils/DeviceModel",
diff --git a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift
index 8b2ab482a5..e02fd7938c 100644
--- a/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift
+++ b/submodules/SettingsUI/Sources/BubbleSettings/BubbleSettingsController.swift
@@ -133,6 +133,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, ASScrollViewDel
self.scrollNode.view.isPagingEnabled = true
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
self.scrollNode.view.alwaysBounceHorizontal = false
+ self.scrollNode.view.scrollsToTop = false
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
diff --git a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift
index bb8871dfd9..699e3c24de 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/DataAndStorageSettingsController.swift
@@ -9,7 +9,6 @@ import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
-import OpenInExternalAppUI
import ItemListPeerActionItem
import StorageUsageScreen
import PresentationDataUtils
@@ -33,7 +32,6 @@ private final class DataAndStorageControllerArguments {
let togglePauseMusicOnRecording: (Bool) -> Void
let toggleRaiseToListen: (Bool) -> Void
let toggleDownloadInBackground: (Bool) -> Void
- let openBrowserSelection: () -> Void
let openIntents: () -> Void
let toggleSensitiveContent: (Bool) -> Void
@@ -49,7 +47,6 @@ private final class DataAndStorageControllerArguments {
togglePauseMusicOnRecording: @escaping (Bool) -> Void,
toggleRaiseToListen: @escaping (Bool) -> Void,
toggleDownloadInBackground: @escaping (Bool) -> Void,
- openBrowserSelection: @escaping () -> Void,
openIntents: @escaping () -> Void,
toggleSensitiveContent: @escaping (Bool) -> Void
) {
@@ -64,7 +61,6 @@ private final class DataAndStorageControllerArguments {
self.togglePauseMusicOnRecording = togglePauseMusicOnRecording
self.toggleRaiseToListen = toggleRaiseToListen
self.toggleDownloadInBackground = toggleDownloadInBackground
- self.openBrowserSelection = openBrowserSelection
self.openIntents = openIntents
self.toggleSensitiveContent = toggleSensitiveContent
}
@@ -118,7 +114,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
case useLessVoiceData(PresentationTheme, String, Bool)
case useLessVoiceDataInfo(PresentationTheme, String)
case otherHeader(PresentationTheme, String)
- case openLinksIn(PresentationTheme, String, String)
case shareSheet(PresentationTheme, String)
case saveEditedPhotos(PresentationTheme, String, Bool)
case pauseMusicOnRecording(PresentationTheme, String, Bool)
@@ -143,7 +138,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return DataAndStorageSection.backgroundDownload.rawValue
case .useLessVoiceData, .useLessVoiceDataInfo:
return DataAndStorageSection.voiceCalls.rawValue
- case .otherHeader, .openLinksIn, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo:
+ case .otherHeader, .shareSheet, .saveEditedPhotos, .pauseMusicOnRecording, .raiseToListen, .raiseToListenInfo:
return DataAndStorageSection.other.rawValue
case .sensitiveContent, .sensitiveContentInfo:
return DataAndStorageSection.sensitiveContent.rawValue
@@ -182,8 +177,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return 24
case .otherHeader:
return 29
- case .openLinksIn:
- return 30
case .shareSheet:
return 31
case .saveEditedPhotos:
@@ -279,12 +272,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
} else {
return false
}
- case let .openLinksIn(lhsTheme, lhsText, lhsValue):
- if case let .openLinksIn(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
- return true
- } else {
- return false
- }
case let .shareSheet(lhsTheme, lhsText):
if case let .shareSheet(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@@ -414,10 +401,6 @@ private enum DataAndStorageEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
- case let .openLinksIn(_, text, value):
- 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, systemStyle: .glass, title: text, label: "", sectionId: self.section, style: .blocks, action: {
arguments.openIntents()
@@ -618,7 +601,7 @@ private func autosaveLabelAndValue(presentationData: PresentationData, settings:
return (label, value)
}
-private func dataAndStorageControllerEntries(context: AccountContext, state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, defaultWebBrowser: String, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings, showSensitiveContentSetting: Bool) -> [DataAndStorageEntry] {
+private func dataAndStorageControllerEntries(context: AccountContext, state: DataAndStorageControllerState, data: DataAndStorageData, presentationData: PresentationData, contentSettingsConfiguration: ContentSettingsConfiguration?, networkUsage: Int64, storageUsage: Int64, mediaAutoSaveSettings: MediaAutoSaveSettings, autosaveExceptionPeers: [EnginePeer.Id: EnginePeer?], mediaSettings: MediaDisplaySettings, showSensitiveContentSetting: Bool) -> [DataAndStorageEntry] {
var entries: [DataAndStorageEntry] = []
entries.append(.storageUsage(presentationData.theme, presentationData.strings.ChatSettings_Cache, dataSizeString(storageUsage, formatting: DataSizeStringFormatting(presentationData: presentationData))))
@@ -648,7 +631,6 @@ private func dataAndStorageControllerEntries(context: AccountContext, state: Dat
entries.append(.useLessVoiceDataInfo(presentationData.theme, presentationData.strings.CallSettings_UseLessDataLongDescription))
entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other))
- entries.append(.openLinksIn(presentationData.theme, presentationData.strings.ChatSettings_OpenLinksIn, defaultWebBrowser))
if #available(iOSApplicationExtension 13.2, iOS 13.2, *) {
entries.append(.shareSheet(presentationData.theme, presentationData.strings.ChatSettings_IntentsSettings))
}
@@ -896,9 +878,6 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
settings.downloadInBackground = value
return settings
}).start()
- }, openBrowserSelection: {
- let controller = webBrowserSettingsController(context: context)
- pushControllerImpl?(controller)
}, openIntents: {
let controller = intentsSettingsController(context: context)
pushControllerImpl?(controller)
@@ -952,26 +931,15 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
context.sharedContext.presentationData,
statePromise.get(),
dataAndStorageDataPromise.get(),
- context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]),
+ context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.mediaDisplaySettings]),
contentSettingsConfiguration.get(),
preferences,
usageSignal,
autosaveExceptionPeers
)
|> map { presentationData, state, dataAndStorageData, sharedData, contentSettingsConfiguration, mediaAutoSaveSettings, usageSignal, autosaveExceptionPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let webBrowserSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings
let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
- let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org"))
- let defaultWebBrowser: String
- if let option = options.first(where: { $0.identifier == webBrowserSettings.defaultWebBrowser }) {
- defaultWebBrowser = option.title
- } else if webBrowserSettings.defaultWebBrowser == "inApp" {
- defaultWebBrowser = presentationData.strings.WebBrowser_InAppSafari
- } else {
- defaultWebBrowser = presentationData.strings.WebBrowser_Telegram
- }
-
let previousSensitiveContent = sensitiveContent.swap(contentSettingsConfiguration?.sensitiveContentEnabled)
var animateChanges = false
if previousSensitiveContent != contentSettingsConfiguration?.sensitiveContentEnabled {
@@ -984,7 +952,7 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
let showSensitiveContentSetting = canAdjustSensitiveContent.with { $0 } ?? false
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
- let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(context: context, state: state, data: dataAndStorageData, presentationData: presentationData, defaultWebBrowser: defaultWebBrowser, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings, showSensitiveContentSetting: showSensitiveContentSetting), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges)
+ let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: dataAndStorageControllerEntries(context: context, state: state, data: dataAndStorageData, presentationData: presentationData, contentSettingsConfiguration: contentSettingsConfiguration, networkUsage: usageSignal.network, storageUsage: usageSignal.storage, mediaAutoSaveSettings: mediaAutoSaveSettings, autosaveExceptionPeers: autosaveExceptionPeers, mediaSettings: mediaSettings, showSensitiveContentSetting: showSensitiveContentSetting), style: .blocks, ensureVisibleItemTag: focusOnItemTag, emptyStateItem: nil, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
} |> afterDisposed {
diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift
index 18bbed17c7..1d6821467c 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift
@@ -428,9 +428,13 @@ public func proxySettingsController(accountManager: AccountManager map { presentationData, state, proxySettings, statuses, connectionStatus -> (ItemListControllerState, (ItemListNodeState, Any)) in
+ var presentationData = presentationData
+ let updatedTheme = presentationData.theme.withModalBlocksBackground()
+ presentationData = presentationData.withUpdated(theme: updatedTheme)
+
var leftNavigationButton: ItemListNavigationButton?
if case .modal = mode {
- leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
}
@@ -439,7 +443,7 @@ public func proxySettingsController(accountManager: AccountManager()
- override public var ready: Promise {
- return self._ready
- }
-
- private var isDismissed: Bool = false
-
- convenience public init(context: AccountContext, server: ProxyServerSettings) {
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- self.init(sharedContext: context.sharedContext, presentationData: presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: server, updatedPresentationData: context.sharedContext.presentationData)
- }
-
- public init(sharedContext: SharedAccountContext, presentationData: PresentationData, accountManager: AccountManager, postbox: Postbox, network: Network, server: ProxyServerSettings, updatedPresentationData: Signal?) {
- self.sharedContext = sharedContext
- let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData)
- super.init(theme: sheetTheme)
-
- self._ready.set(.single(true))
-
- var items: [ActionSheetItem] = []
- if case .mtp = server.connection {
- items.append(ActionSheetTextItem(title: presentationData.strings.SocksProxySetup_AdNoticeHelp))
- }
- items.append(ProxyServerInfoItem(strings: presentationData.strings, network: network, server: server))
- items.append(ProxyServerActionItem(sharedContext: sharedContext, accountManager:accountManager, postbox: postbox, network: network, presentationData: presentationData, server: server, dismiss: { [weak self] success in
- guard let strongSelf = self, !strongSelf.isDismissed else {
- return
- }
- strongSelf.isDismissed = true
- if success {
- strongSelf.present(OverlayStatusController(theme: presentationData.theme, type: .shieldSuccess(presentationData.strings.SocksProxySetup_ProxyEnabled, false)), in: .window(.root))
- }
- strongSelf.dismissAnimated()
- }, present: { [weak self] c, a in
- self?.present(c, in: .window(.root), with: a)
- }))
- self.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak self] in
- self?.dismissAnimated()
- })
- ])
- ])
-
- if let updatedPresentationData = updatedPresentationData {
- self.presentationDisposable = updatedPresentationData.start(next: { [weak self] presentationData in
- if let strongSelf = self {
- strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
- }
- })
- }
- }
-
- required public init(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- deinit {
- self.presentationDisposable?.dispose()
- }
-}
-
-private final class ProxyServerInfoItem: ActionSheetItem {
- private let strings: PresentationStrings
- private let network: Network
- private let server: ProxyServerSettings
-
- init(strings: PresentationStrings, network: Network, server: ProxyServerSettings) {
- self.strings = strings
- self.network = network
- self.server = server
- }
-
- func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- return ProxyServerInfoItemNode(theme: theme, strings: self.strings, network: self.network, server: self.server)
- }
-
- func updateNode(_ node: ActionSheetItemNode) {
- }
-}
-
-private enum ProxyServerInfoStatusType {
- case generic(String)
- case failed(String)
-}
-
-private final class ProxyServerInfoItemNode: ActionSheetItemNode {
- private let theme: ActionSheetControllerTheme
- private let strings: PresentationStrings
- private let textFont: UIFont
-
- private let network: Network
- private let server: ProxyServerSettings
-
- private let fieldNodes: [(ImmediateTextNode, ImmediateTextNode)]
- private let statusTextNode: ImmediateTextNode
-
- private let statusDisposable = MetaDisposable()
-
- init(theme: ActionSheetControllerTheme, strings: PresentationStrings, network: Network, server: ProxyServerSettings) {
- self.theme = theme
- self.strings = strings
- self.network = network
- self.server = server
-
- self.textFont = Font.regular(floor(theme.baseFontSize * 16.0 / 17.0))
-
- var fieldNodes: [(ImmediateTextNode, ImmediateTextNode)] = []
- let serverTitleNode = ImmediateTextNode()
- serverTitleNode.isUserInteractionEnabled = false
- serverTitleNode.displaysAsynchronously = false
- serverTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Hostname, font: textFont, textColor: theme.secondaryTextColor)
- let serverTextNode = ImmediateTextNode()
- serverTextNode.isUserInteractionEnabled = false
- serverTextNode.displaysAsynchronously = false
- serverTextNode.attributedText = NSAttributedString(string: urlEncodedStringFromString(server.host), font: textFont, textColor: theme.primaryTextColor)
- fieldNodes.append((serverTitleNode, serverTextNode))
-
- let portTitleNode = ImmediateTextNode()
- portTitleNode.isUserInteractionEnabled = false
- portTitleNode.displaysAsynchronously = false
- portTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Port, font: textFont, textColor: theme.secondaryTextColor)
- let portTextNode = ImmediateTextNode()
- portTextNode.isUserInteractionEnabled = false
- portTextNode.displaysAsynchronously = false
- portTextNode.attributedText = NSAttributedString(string: "\(server.port)", font: textFont, textColor: theme.primaryTextColor)
- fieldNodes.append((portTitleNode, portTextNode))
-
- switch server.connection {
- case let .socks5(username, password):
- if let username = username {
- let usernameTitleNode = ImmediateTextNode()
- usernameTitleNode.isUserInteractionEnabled = false
- usernameTitleNode.displaysAsynchronously = false
- usernameTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Username, font: textFont, textColor: theme.secondaryTextColor)
- let usernameTextNode = ImmediateTextNode()
- usernameTextNode.isUserInteractionEnabled = false
- usernameTextNode.displaysAsynchronously = false
- usernameTextNode.attributedText = NSAttributedString(string: username, font: textFont, textColor: theme.primaryTextColor)
- fieldNodes.append((usernameTitleNode, usernameTextNode))
- }
-
- if let password = password {
- let passwordTitleNode = ImmediateTextNode()
- passwordTitleNode.isUserInteractionEnabled = false
- passwordTitleNode.displaysAsynchronously = false
- passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Password, font: textFont, textColor: theme.secondaryTextColor)
- let passwordTextNode = ImmediateTextNode()
- passwordTextNode.isUserInteractionEnabled = false
- passwordTextNode.displaysAsynchronously = false
- passwordTextNode.attributedText = NSAttributedString(string: password, font: textFont, textColor: theme.primaryTextColor)
- fieldNodes.append((passwordTitleNode, passwordTextNode))
- }
- case .mtp:
- let passwordTitleNode = ImmediateTextNode()
- passwordTitleNode.isUserInteractionEnabled = false
- passwordTitleNode.displaysAsynchronously = false
- passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Secret, font: textFont, textColor: theme.secondaryTextColor)
- let passwordTextNode = ImmediateTextNode()
- passwordTextNode.isUserInteractionEnabled = false
- passwordTextNode.displaysAsynchronously = false
- passwordTextNode.attributedText = NSAttributedString(string: "•••••", font: textFont, textColor: theme.primaryTextColor)
- fieldNodes.append((passwordTitleNode, passwordTextNode))
- }
-
- let statusTitleNode = ImmediateTextNode()
- statusTitleNode.isUserInteractionEnabled = false
- statusTitleNode.displaysAsynchronously = false
- statusTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Status, font: textFont, textColor: theme.secondaryTextColor)
- let statusTextNode = ImmediateTextNode()
- statusTextNode.isUserInteractionEnabled = false
- statusTextNode.displaysAsynchronously = false
- statusTextNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_ProxyStatusChecking, font: textFont, textColor: theme.primaryTextColor)
- fieldNodes.append((statusTitleNode, statusTextNode))
-
- self.fieldNodes = fieldNodes
- self.statusTextNode = statusTextNode
-
- super.init(theme: theme)
-
- for (lhs, rhs) in fieldNodes {
- self.addSubnode(lhs)
- self.addSubnode(rhs)
- }
- }
-
- deinit {
- self.statusDisposable.dispose()
- }
-
- override func didLoad() {
- super.didLoad()
-
- let statusesContext = ProxyServersStatuses(network: network, servers: .single([self.server]))
- self.statusDisposable.set((statusesContext.statuses()
- |> map { return $0.first?.value }
- |> distinctUntilChanged
- |> deliverOnMainQueue).start(next: { [weak self] status in
- if let strongSelf = self, let status = status {
- let statusType: ProxyServerInfoStatusType
- switch status {
- case .checking:
- statusType = .generic(strongSelf.strings.SocksProxySetup_ProxyStatusChecking)
- case let .available(rtt):
- let pingTime = Int(rtt * 1000.0)
- statusType = .generic(strongSelf.strings.SocksProxySetup_ProxyStatusPing("\(pingTime)").string)
- case .notAvailable:
- statusType = .failed(strongSelf.strings.SocksProxySetup_ProxyStatusUnavailable)
- }
- strongSelf.setStatus(statusType)
- }
- }))
- }
-
- func setStatus(_ status: ProxyServerInfoStatusType) {
- let attributedString: NSAttributedString
- switch status {
- case let .generic(text):
- attributedString = NSAttributedString(string: text, font: textFont, textColor: theme.primaryTextColor)
- case let .failed(text):
- attributedString = NSAttributedString(string: text, font: textFont, textColor: theme.destructiveActionTextColor)
- }
- self.statusTextNode.attributedText = attributedString
- self.requestLayoutUpdate()
- }
-
- public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let size = CGSize(width: constrainedSize.width, height: 36.0 * CGFloat(self.fieldNodes.count) + 12.0)
-
- var offset: CGFloat = 15.0
- for (lhs, rhs) in self.fieldNodes {
- let lhsSize = lhs.updateLayout(CGSize(width: size.width - 18.0 * 2.0, height: CGFloat.greatestFiniteMagnitude))
- lhs.frame = CGRect(origin: CGPoint(x: 18, y: offset), size: lhsSize)
-
- let rhsSize = rhs.updateLayout(CGSize(width: max(1.0, size.width - 18 * 2.0 - lhsSize.width - 4.0), height: CGFloat.greatestFiniteMagnitude))
- rhs.frame = CGRect(origin: CGPoint(x: size.width - 18 - rhsSize.width, y: offset), size: rhsSize)
-
- offset += 36.0
- }
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-}
-
-private final class ProxyServerActionItem: ActionSheetItem {
- private let sharedContext: SharedAccountContext
- private let accountManager: AccountManager
- private let postbox: Postbox
- private let network: Network
- private let presentationData: PresentationData
- private let server: ProxyServerSettings
- private let dismiss: (Bool) -> Void
- private let present: (ViewController, Any?) -> Void
-
- init(sharedContext: SharedAccountContext, accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
- self.sharedContext = sharedContext
- self.accountManager = accountManager
- self.postbox = postbox
- self.network = network
- self.presentationData = presentationData
- self.server = server
- self.dismiss = dismiss
- self.present = present
- }
-
- func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- return ProxyServerActionItemNode(sharedContext: self.sharedContext, accountManager: self.accountManager, postbox: self.postbox, network: self.network, presentationData: self.presentationData, theme: theme, server: self.server, dismiss: self.dismiss, present: self.present)
- }
-
- func updateNode(_ node: ActionSheetItemNode) {
- }
-}
-
-private final class ProxyServerActionItemNode: ActionSheetItemNode {
- private let sharedContext: SharedAccountContext
- private let accountManager: AccountManager
- private let postbox: Postbox
- private let network: Network
- private let presentationData: PresentationData
- private let theme: ActionSheetControllerTheme
- private let server: ProxyServerSettings
- private let dismiss: (Bool) -> Void
- private let present: (ViewController, Any?) -> Void
-
- private let buttonNode: HighlightableButtonNode
- private let titleNode: ImmediateTextNode
- private let activityIndicator: ActivityIndicator
-
- private let disposable = MetaDisposable()
- private var revertSettings: ProxySettings?
-
- init(sharedContext: SharedAccountContext, accountManager: AccountManager, postbox: Postbox, network: Network, presentationData: PresentationData, theme: ActionSheetControllerTheme, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
- self.sharedContext = sharedContext
- self.accountManager = accountManager
- self.postbox = postbox
- self.network = network
- self.theme = theme
- self.presentationData = presentationData
- self.server = server
- self.dismiss = dismiss
- self.present = present
-
- let titleFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
-
- self.titleNode = ImmediateTextNode()
- self.titleNode.isUserInteractionEnabled = false
- self.titleNode.displaysAsynchronously = false
- self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.SocksProxySetup_ConnectAndSave, font: titleFont, textColor: theme.controlAccentColor)
-
- self.activityIndicator = ActivityIndicator(type: .custom(theme.controlAccentColor, 22.0, 1.5, false))
- self.activityIndicator.isHidden = true
-
- self.buttonNode = HighlightableButtonNode()
-
- super.init(theme: theme)
-
- self.addSubnode(self.titleNode)
- self.addSubnode(self.activityIndicator)
- self.addSubnode(self.buttonNode)
-
- self.buttonNode.highligthedChanged = { [weak self] highlighted in
- if let strongSelf = self {
- if highlighted {
- strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor
- } else {
- UIView.animate(withDuration: 0.3, animations: {
- strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor
- })
- }
- }
- }
-
- self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
- }
-
- deinit {
- self.disposable.dispose()
- if let revertSettings = self.revertSettings {
- let _ = updateProxySettingsInteractively(accountManager: self.accountManager, { _ in
- return revertSettings
- })
- }
- }
-
- public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let size = CGSize(width: constrainedSize.width, height: 57.0)
-
- self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
-
- let labelSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - 10.0), height: size.height))
- let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
- let activitySize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
- self.titleNode.frame = titleFrame
- self.activityIndicator.frame = CGRect(origin: CGPoint(x: 14.0, y: titleFrame.minY - 0.0), size: activitySize)
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-
- @objc private func buttonPressed() {
- let proxyServerSettings = self.server
- let _ = (self.accountManager.transaction { transaction -> ProxySettings in
- var currentSettings: ProxySettings?
- let _ = updateProxySettingsInteractively(transaction: transaction, { settings in
- currentSettings = settings
- var settings = settings
- if let index = settings.servers.firstIndex(of: proxyServerSettings) {
- settings.servers[index] = proxyServerSettings
- settings.activeServer = proxyServerSettings
- } else {
- settings.servers.insert(proxyServerSettings, at: 0)
- settings.activeServer = proxyServerSettings
- }
- settings.enabled = true
- return settings
- })
- return currentSettings ?? ProxySettings.defaultSettings
- } |> deliverOnMainQueue).start(next: { [weak self] previousSettings in
- if let strongSelf = self {
- strongSelf.revertSettings = previousSettings
- strongSelf.buttonNode.isUserInteractionEnabled = false
- strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.SocksProxySetup_Connecting, font: Font.regular(20.0), textColor: strongSelf.theme.primaryTextColor)
- strongSelf.activityIndicator.isHidden = false
- strongSelf.requestLayoutUpdate()
-
- let signal = strongSelf.network.connectionStatus
- |> filter { status in
- switch status {
- case let .online(proxyAddress):
- if proxyAddress == proxyServerSettings.host {
- return true
- } else {
- return false
- }
- default:
- return false
- }
- }
- |> map { _ -> Bool in
- return true
- }
- |> timeout(15.0, queue: Queue.mainQueue(), alternate: .single(false))
- |> deliverOnMainQueue
- strongSelf.disposable.set(signal.start(next: { value in
- if let strongSelf = self {
- strongSelf.activityIndicator.isHidden = true
- strongSelf.revertSettings = nil
- if value {
- strongSelf.dismiss(true)
- } else {
- let _ = updateProxySettingsInteractively(accountManager: strongSelf.accountManager, { _ in
- return previousSettings
- })
- strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.SocksProxySetup_ConnectAndSave, font: Font.regular(20.0), textColor: strongSelf.theme.controlAccentColor)
- strongSelf.buttonNode.isUserInteractionEnabled = true
- strongSelf.requestLayoutUpdate()
-
- strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
- }
- }
- }))
- }
- })
- }
-}
diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift
index 560bd3fce2..985e7d3fde 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyServerSettingsController.swift
@@ -10,20 +10,7 @@ import PresentationDataUtils
import AccountContext
import UrlEscaping
import UrlHandling
-
-private func shareLink(for server: ProxyServerSettings) -> String {
- var link: String
- switch server.connection {
- case let .mtp(secret):
- let secret = MTProxySecret.parseData(secret)?.serializeToString() ?? ""
- link = "tg://proxy?server=\(server.host)&port=\(server.port)"
- link += "&secret=\(secret.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
- case let .socks5(username, password):
- link = "https://t.me/socks?server=\(server.host)&port=\(server.port)"
- link += "&user=\(username?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")&pass=\(password?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
- }
- return link
-}
+import QrCodeUI
private final class ProxyServerSettingsControllerArguments {
let updateState: ((ProxyServerSettingsControllerState) -> ProxyServerSettingsControllerState) -> Void
@@ -300,7 +287,7 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
statePromise.set(stateValue.modify { f($0) })
}
- var presentControllerImpl: ((ViewController, Any?) -> Void)?
+ var pushControllerImpl: ((ViewController) -> Void)?
var dismissImpl: (() -> Void)?
var shareImpl: (() -> Void)?
@@ -332,10 +319,14 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
let signal = combineLatest(updatedPresentationData, statePromise.get())
|> deliverOnMainQueue
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let leftNavigationButton = ItemListNavigationButton(content: .text("___close"), style: .regular, enabled: true, action: {
+ var presentationData = presentationData
+ let updatedTheme = presentationData.theme.withModalBlocksBackground()
+ presentationData = presentationData.withUpdated(theme: updatedTheme)
+
+ let leftNavigationButton = ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
})
- let rightNavigationButton = ItemListNavigationButton(content: .text("___done"), style: .bold, enabled: state.isComplete, action: {
+ let rightNavigationButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: state.isComplete, action: {
if let proxyServerSettings = proxyServerSettings(with: state) {
let _ = (updateProxySettingsInteractively(accountManager: accountManager, { settings in
var settings = settings
@@ -370,8 +361,8 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
let controller = ItemListController(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: updatedPresentationData |> map(ItemListPresentationData.init(_:)), state: signal, tabBarItem: nil)
controller.navigationPresentation = .modal
- presentControllerImpl = { [weak controller] c, d in
- controller?.present(c, in: .window(.root), with: d)
+ pushControllerImpl = { [weak controller] c in
+ controller?.push(c)
}
dismissImpl = { [weak controller] in
let _ = controller?.dismiss()
@@ -381,12 +372,14 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
guard let server = proxyServerSettings(with: state) else {
return
}
-
- let link = shareLink(for: server)
controller?.view.endEditing(true)
- let controller = ShareProxyServerActionSheetController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, link: link)
- presentControllerImpl?(controller, nil)
+ let controller = QrCodeScreen(
+ sharedContext: sharedContext,
+ updatedPresentationData: (presentationData, updatedPresentationData),
+ subject: .proxy(server: server, externalLink: false)
+ )
+ pushControllerImpl?(controller)
}
return controller
diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift
index 3108d64985..a9dd996e54 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift
@@ -18,7 +18,7 @@ struct ProxySettingsServerItemEditing: Equatable {
let revealed: Bool
}
-final class ProxySettingsServerItem: ListViewItem, ItemListItem {
+final class ProxySettingsServerItem: ListViewItem, ItemListItem, ItemListRevealOptionsStatefulItem {
let theme: PresentationTheme
let strings: PresentationStrings
let systemStyle: ItemListSystemStyle
@@ -34,6 +34,10 @@ final class ProxySettingsServerItem: ListViewItem, ItemListItem {
let infoAction: () -> Void
let setServerWithRevealedOptions: (ProxyServerSettings?, ProxyServerSettings?) -> Void
let removeServer: (ProxyServerSettings) -> Void
+
+ var hasActiveRevealOptions: Bool {
+ return self.editing.revealed
+ }
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
@@ -123,6 +127,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
private var item: ProxySettingsServerItem?
private var layoutParams: ListViewItemLayoutParams?
+ private var isHighlighted = false
override var canBeSelected: Bool {
if self.editableControlNode != nil {
@@ -232,7 +237,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
let peerRevealOptions: [ItemListRevealOption]
if item.editing.editable {
- peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]
+ peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.theme.list.itemSecondaryTextColor)]
} else {
peerRevealOptions = []
}
@@ -383,26 +388,29 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
let hasCorners = itemListHasRoundedBlockLayout(params)
var hasTopCorners = false
var hasBottomCorners = false
+ let topStripeIsHidden: Bool
switch neighbors.top {
case .sameSection(false):
- strongSelf.topStripeNode.isHidden = true
+ topStripeIsHidden = true
default:
hasTopCorners = true
- strongSelf.topStripeNode.isHidden = hasCorners
+ topStripeIsHidden = hasCorners
}
let bottomStripeInset: CGFloat
let bottomStripeOffset: CGFloat
+ let bottomStripeIsHidden: Bool
switch neighbors.bottom {
case .sameSection(false):
bottomStripeInset = leftInset + editingOffset
bottomStripeOffset = -separatorHeight
- strongSelf.bottomStripeNode.isHidden = false
+ bottomStripeIsHidden = false
default:
bottomStripeInset = 0.0
bottomStripeOffset = 0.0
hasBottomCorners = true
- strongSelf.bottomStripeNode.isHidden = hasCorners
+ bottomStripeIsHidden = hasCorners
}
+ strongSelf.updateRevealOptionsSeparatorNodes(top: strongSelf.topStripeNode, bottom: strongSelf.bottomStripeNode, topIsHidden: topStripeIsHidden, bottomIsHidden: bottomStripeIsHidden, topHiddenByPreviousRevealOptions: neighbors.topHasActiveRevealOptions, bottomHiddenByNextRevealOptions: neighbors.bottomHasActiveRevealOptions)
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
@@ -432,7 +440,7 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
strongSelf.infoButtonNode.isUserInteractionEnabled = revealOffset.isZero && !item.editing.editing
strongSelf.infoButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 55.0, y: 0.0), size: CGSize(width: 55.0, height: layout.contentSize.height))
- strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
+ strongSelf.updateRevealOptionsHighlightedBackgroundFrame(strongSelf.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel)), transition: transition)
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
@@ -446,39 +454,8 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
- if highlighted {
- self.highlightedBackgroundNode.alpha = 1.0
- if self.highlightedBackgroundNode.supernode == nil {
- var anchorNode: ASDisplayNode?
- if self.bottomStripeNode.supernode != nil {
- anchorNode = self.bottomStripeNode
- } else if self.topStripeNode.supernode != nil {
- anchorNode = self.topStripeNode
- } else if self.backgroundNode.supernode != nil {
- anchorNode = self.backgroundNode
- }
- if let anchorNode = anchorNode {
- self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
- } else {
- self.addSubnode(self.highlightedBackgroundNode)
- }
- }
- } else {
- if self.highlightedBackgroundNode.supernode != nil {
- if animated {
- self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
- if let strongSelf = self {
- if completed {
- strongSelf.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- })
- self.highlightedBackgroundNode.alpha = 0.0
- } else {
- self.highlightedBackgroundNode.removeFromSupernode()
- }
- }
- }
+ self.isHighlighted = highlighted
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: (animated && !highlighted) ? .animated(duration: 0.4, curve: .easeInOut) : .immediate, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
}
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
@@ -522,10 +499,16 @@ private final class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode {
var infoIconFrame = self.infoIconNode.frame
infoIconFrame.origin.x = offset + params.width - params.rightInset - 55.0 + floor((55.0 - infoIconFrame.width) / 2.0)
transition.updateFrame(node: self.infoIconNode, frame: infoIconFrame)
-
+
self.infoButtonNode.isUserInteractionEnabled = offset.isZero
}
-
+
+ override func revealOptionsActiveStateUpdated(isActive: Bool, transition: ContainedViewLayoutTransition) {
+ super.revealOptionsActiveStateUpdated(isActive: isActive, transition: transition)
+
+ self.updateRevealOptionsHighlightedBackgroundNode(self.highlightedBackgroundNode, isHighlighted: self.isHighlighted || self.isRevealOptionsActive, transition: transition, aboveNodes: [self.bottomStripeNode, self.topStripeNode, self.backgroundNode])
+ }
+
override func revealOptionsInteractivelyOpened() {
if let item = self.item {
item.setServerWithRevealedOptions(item.server, nil)
diff --git a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift
index 4ab5be12f8..6a7d098788 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/SaveIncomingMediaController.swift
@@ -660,7 +660,7 @@ public func saveIncomingMediaController(context: AccountContext, scope: SaveInco
switch scope {
case .peer, .addPeer:
- rightButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
+ rightButton = ItemListNavigationButton(content: .icon(.done), style: .bold, enabled: true, action: {
switch scope {
case let .addPeer(_, completion):
let configuration = stateValue.with({ $0 }).pendingConfiguration
diff --git a/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift b/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift
deleted file mode 100644
index 1ac427d213..0000000000
--- a/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift
+++ /dev/null
@@ -1,184 +0,0 @@
-import Foundation
-import UIKit
-import Display
-import TelegramCore
-import AsyncDisplayKit
-import UIKit
-import SwiftSignalKit
-import TelegramPresentationData
-import QrCode
-
-public final class ShareProxyServerActionSheetController: ActionSheetController {
- private var presentationDisposable: Disposable?
-
- private let _ready = Promise()
- override public var ready: Promise {
- return self._ready
- }
-
- private var isDismissed: Bool = false
-
- public init(presentationData: PresentationData, updatedPresentationData: Signal, link: String) {
- let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData)
- super.init(theme: sheetTheme)
-
- let presentActivityController: (Any) -> Void = { [weak self] item in
- let activityController = UIActivityViewController(activityItems: [item], applicationActivities: nil)
- if let window = self?.view.window, let rootViewController = window.rootViewController {
- activityController.popoverPresentationController?.sourceView = window
- activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
- rootViewController.present(activityController, animated: true, completion: nil)
- }
- }
-
- var items: [ActionSheetItem] = []
- items.append(ProxyServerQRCodeItem(strings: presentationData.strings, link: link, ready: { [weak self] in
- self?._ready.set(.single(true))
- }))
- items.append(ActionSheetButtonItem(title: presentationData.strings.SocksProxySetup_ShareQRCode, action: { [weak self] in
- self?.dismissAnimated()
- let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy)
- |> map { _, generator -> UIImage? in
- let imageSize = CGSize(width: 768.0, height: 768.0)
- let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
- return context?.generateImage()
- }
- |> deliverOnMainQueue).start(next: { image in
- if let image = image {
- presentActivityController(image)
- }
- })
- }))
- items.append(ActionSheetButtonItem(title: presentationData.strings.SocksProxySetup_ShareLink, action: { [weak self] in
- self?.dismissAnimated()
- presentActivityController(link)
- }))
- self.setItemGroups([
- ActionSheetItemGroup(items: items),
- ActionSheetItemGroup(items: [
- ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak self] in
- self?.dismissAnimated()
- })
- ])
- ])
-
- self.presentationDisposable = updatedPresentationData.start(next: { [weak self] presentationData in
- if let strongSelf = self {
- strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
- }
- })
- }
-
- required public init(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- deinit {
- self.presentationDisposable?.dispose()
- }
-}
-
-private final class ProxyServerQRCodeItem: ActionSheetItem {
- private let strings: PresentationStrings
- private let link: String
- private let ready: () -> Void
-
- init(strings: PresentationStrings, link: String, ready: @escaping () -> Void = {}) {
- self.strings = strings
- self.link = link
- self.ready = ready
- }
-
- func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
- return ProxyServerQRCodeItemNode(theme: theme, strings: self.strings, link: self.link, ready: self.ready)
- }
-
- func updateNode(_ node: ActionSheetItemNode) {
- }
-}
-
-private final class ProxyServerQRCodeItemNode: ActionSheetItemNode {
- private let theme: ActionSheetControllerTheme
- private let strings: PresentationStrings
- private let link: String
-
- private let label: ASTextNode
- private let imageNode: TransformImageNode
-
- private let ready: () -> Void
-
- private var cachedHasLabel = true
- private var cachedHasImage = true
-
- init(theme: ActionSheetControllerTheme, strings: PresentationStrings, link: String, ready: @escaping () -> Void = {}) {
- self.theme = theme
- self.strings = strings
- self.link = link
- self.ready = ready
-
- let textFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
-
- self.label = ASTextNode()
- self.label.isUserInteractionEnabled = false
- self.label.maximumNumberOfLines = 0
- self.label.displaysAsynchronously = false
- self.label.truncationMode = .byTruncatingTail
- self.label.isUserInteractionEnabled = false
- self.label.attributedText = NSAttributedString(string: strings.SocksProxySetup_ShareQRCodeInfo, font: textFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center)
-
- self.imageNode = TransformImageNode()
- self.imageNode.clipsToBounds = true
- self.imageNode.setSignal(qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy) |> map { $0.1 }, attemptSynchronously: true)
- self.imageNode.cornerRadius = 14.0
-
- super.init(theme: theme)
-
- self.addSubnode(self.label)
- self.addSubnode(self.imageNode)
- }
-
- override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
- let imageInset: CGFloat = 44.0
- let side = constrainedSize.width - imageInset * 2.0
- var imageSize = CGSize(width: side, height: side)
-
- let makeLayout = self.imageNode.asyncLayout()
- let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
- apply()
-
- var labelSize = self.label.measure(CGSize(width: max(1.0, constrainedSize.width - 64.0), height: constrainedSize.height))
-
- self.cachedHasImage = constrainedSize.width < constrainedSize.height
- if !self.cachedHasImage {
- imageSize = CGSize()
- }
-
- self.ready()
-
- self.cachedHasLabel = constrainedSize.height > 480 || !self.cachedHasImage
- if !self.cachedHasLabel {
- labelSize = CGSize()
- }
- let size = CGSize(width: constrainedSize.width, height: 14.0 + (labelSize.height > 0.0 ? labelSize.height + 14.0 : 0.0) + (imageSize.height > 0.0 ? imageSize.height + 14.0 : 8.0))
-
- let inset: CGFloat = 32.0
- let spacing: CGFloat = 18.0
- if self.cachedHasLabel {
- labelSize = self.label.measure(CGSize(width: max(1.0, size.width - inset * 2.0), height: size.height))
- self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: spacing), size: labelSize)
- } else {
- labelSize = CGSize()
- }
-
- if !self.cachedHasImage {
- imageSize = CGSize()
- } else {
- imageSize = CGSize(width: size.width - imageInset * 2.0, height: size.width - imageInset * 2.0)
- }
- let imageOrigin = CGPoint(x: imageInset, y: self.label.frame.maxY + spacing - 4.0)
- self.imageNode.frame = CGRect(origin: imageOrigin, size: imageSize)
-
- self.updateInternalLayout(size, constrainedSize: constrainedSize)
- return size
- }
-}
diff --git a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift
index 643a4b2550..1460732cce 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/StorageUsageExceptionsScreen.swift
@@ -149,7 +149,7 @@ private enum StorageUsageExceptionsEntry: ItemListNodeEntry {
switch self {
case let .addException(text):
let icon: UIImage? = PresentationResourcesItemList.createGroupIcon(presentationData.theme)
- return ItemListPeerActionItem(presentationData: presentationData, icon: icon, title: text, alwaysPlain: false, sectionId: self.section, editing: false, action: {
+ return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: icon, title: text, alwaysPlain: false, sectionId: self.section, editing: false, action: {
arguments.openAddException()
})
case let .exceptionsHeader(text):
@@ -173,7 +173,7 @@ private enum StorageUsageExceptionsEntry: ItemListNodeEntry {
title = peer.peer.displayTitle(strings: presentationData.strings, displayOrder: .firstLast)
}
- return ItemListDisclosureItem(presentationData: presentationData, icon: nil, context: arguments.context, iconPeer: peer.peer, title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
+ return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: nil, context: arguments.context, iconPeer: peer.peer, title: title, enabled: true, titleFont: .bold, label: optionText, labelStyle: .text, additionalDetailLabel: additionalDetailLabel, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
arguments.openPeerMenu(peer.peer.id, value)
}, tag: StorageUsageExceptionsEntryTag.peer(peer.peer.id))
}
@@ -289,15 +289,9 @@ public func storageUsageExceptionsScreen(
}
}
- var presentControllerImpl: ((ViewController, PresentationContextType, Any?) -> Void)?
- let _ = presentControllerImpl
var pushControllerImpl: ((ViewController) -> Void)?
-
var findPeerReferenceNode: ((EnginePeer.Id) -> ItemListDisclosureItemNode?)?
- let _ = findPeerReferenceNode
-
var presentInGlobalOverlay: ((ViewController) -> Void)?
- let _ = presentInGlobalOverlay
let actionDisposables = DisposableSet()
@@ -440,7 +434,7 @@ public func storageUsageExceptionsScreen(
)
|> deliverOnMainQueue
|> map { presentationData, peerExceptions, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let leftNavigationButton = isModal ? ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
+ let leftNavigationButton = isModal ? ItemListNavigationButton(content: .icon(.close), style: .regular, enabled: true, action: {
dismissImpl?()
}) : nil
@@ -458,9 +452,6 @@ public func storageUsageExceptionsScreen(
controller.navigationPresentation = .modal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
}
- presentControllerImpl = { [weak controller] c, contextType, a in
- controller?.present(c, in: contextType, with: a)
- }
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift
index 29197201c1..14d3d23236 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserDomainExceptionItem.swift
@@ -7,7 +7,7 @@ import TelegramPresentationData
import TelegramCore
import AccountContext
import ItemListUI
-import PhotoResources
+import StickerResources
private enum RevealOptionKey: Int32 {
case delete
@@ -19,7 +19,7 @@ final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem {
let context: AccountContext
let title: String
let label: String
- let icon: TelegramMediaImage?
+ let favicon: Int64?
let sectionId: ItemListSectionId
let style: ItemListStyle
let deleted: (() -> Void)?
@@ -30,7 +30,7 @@ final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem {
context: AccountContext,
title: String,
label: String,
- icon: TelegramMediaImage?,
+ favicon: Int64?,
sectionId: ItemListSectionId,
style: ItemListStyle,
deleted: (() -> Void)?
@@ -40,7 +40,7 @@ final class WebBrowserDomainExceptionItem: ListViewItem, ItemListItem {
self.context = context
self.title = title
self.label = label
- self.icon = icon
+ self.favicon = favicon
self.sectionId = sectionId
self.style = style
self.deleted = deleted
@@ -96,9 +96,11 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It
let labelNode: TextNode
private let activateArea: AccessibilityAreaNode
+ private let iconDisposable = MetaDisposable()
private var item: WebBrowserDomainExceptionItem?
private var layoutParams: ListViewItemLayoutParams?
+ private var currentIconFile: TelegramMediaFile?
override public var canBeSelected: Bool {
return false
@@ -140,6 +142,10 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It
self.addSubnode(self.activateArea)
}
+
+ deinit {
+ self.iconDisposable.dispose()
+ }
func asyncLayout() -> (_ item: WebBrowserDomainExceptionItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
@@ -216,12 +222,33 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
}
- let iconSize = CGSize(width: 40.0, height: 40.0)
- var imageSize = iconSize
- if currentItem?.icon?.id != item.icon?.id, let icon = item.icon {
- strongSelf.iconNode.setSignal(chatMessagePhoto(mediaBox: item.context.sharedContext.accountManager.mediaBox, userLocation: .other, photoReference: .standalone(media: icon)))
+ let iconSize = CGSize(width: 30.0, height: 30.0)
+ if currentItem?.favicon != item.favicon {
+ strongSelf.currentIconFile = nil
+ strongSelf.iconDisposable.set(nil)
+
+ if let favicon = item.favicon {
+ strongSelf.iconDisposable.set((item.context.engine.stickers.resolveInlineStickers(fileIds: [favicon])
+ |> deliverOnMainQueue).start(next: { [weak strongSelf] files in
+ guard let strongSelf, strongSelf.item?.favicon == favicon, let file = files[favicon] else {
+ return
+ }
+ strongSelf.currentIconFile = file
+ var resolvedImageSize = iconSize
+ if let dimensions = file.dimensions?.cgSize {
+ resolvedImageSize = dimensions.aspectFilled(resolvedImageSize)
+ }
+ if file.isAnimatedSticker || file.isVideoSticker {
+ strongSelf.iconNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, userLocation: .other, file: file, small: false, size: resolvedImageSize, fetched: true))
+ } else {
+ strongSelf.iconNode.setSignal(chatMessageSticker(account: item.context.account, userLocation: .other, file: file, small: false, fetched: true))
+ }
+ strongSelf.iconNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: resolvedImageSize, boundingSize: iconSize, intrinsicInsets: .zero))()
+ }))
+ }
}
- if let icon = item.icon, let dimensions = largestImageRepresentation(icon.representations)?.dimensions.cgSize {
+ var imageSize = iconSize
+ if strongSelf.currentIconFile?.fileId.id == item.favicon, let dimensions = strongSelf.currentIconFile?.dimensions?.cgSize {
imageSize = dimensions.aspectFilled(imageSize)
}
@@ -296,7 +323,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It
let labelFrame = CGRect(origin: CGPoint(x: leftInset + strongSelf.revealOffset, y: titleFrame.maxY + titleSpacing), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
- let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + 11.0 + strongSelf.revealOffset, y: floorToScreenPixels((contentSize.height - iconSize.height) / 2.0)), size: iconSize)
+ let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + 16.0 + strongSelf.revealOffset, y: floorToScreenPixels((contentSize.height - iconSize.height) / 2.0)), size: iconSize)
strongSelf.iconNode.frame = iconFrame
strongSelf.iconNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 7.0), imageSize: imageSize, boundingSize: iconSize, intrinsicInsets: .zero))()
@@ -304,7 +331,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
var revealOptions: [ItemListRevealOption] = []
- revealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor))
+ revealOptions.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, iconColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor, textColor: item.presentationData.theme.list.itemSecondaryTextColor))
strongSelf.setRevealOptions((left: [], right: revealOptions))
}
})
@@ -330,7 +357,7 @@ final class WebBrowserDomainExceptionItemNode: ItemListRevealOptionsItemNode, It
let leftInset: CGFloat = 16.0 + params.leftInset + 46.0
var iconFrame = self.iconNode.frame
- iconFrame.origin.x = params.leftInset + 11.0 + offset
+ iconFrame.origin.x = params.leftInset + 16.0 + offset
transition.updateFrame(node: self.iconNode, frame: iconFrame)
var titleFrame = self.titleNode.frame
diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift
index ecaa103547..44f4a7c967 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserItem.swift
@@ -11,22 +11,24 @@ import OpenInExternalAppUI
import AccountContext
import AppBundle
-class WebBrowserItem: ListViewItem, ItemListItem {
+final class WebBrowserItem: ListViewItem, ItemListItem {
let context: AccountContext
let presentationData: ItemListPresentationData
let systemStyle: ItemListSystemStyle
let title: String
let application: OpenInApplication?
+ let identifier: String?
let checked: Bool
public let sectionId: ItemListSectionId
let action: () -> Void
- public init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, title: String, application: OpenInApplication?, checked: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) {
+ public init(context: AccountContext, presentationData: ItemListPresentationData, systemStyle: ItemListSystemStyle, title: String, application: OpenInApplication?, identifier: String?, checked: Bool, sectionId: ItemListSectionId, action: @escaping () -> Void) {
self.context = context
self.presentationData = presentationData
self.systemStyle = systemStyle
self.title = title
self.application = application
+ self.identifier = identifier
self.checked = checked
self.sectionId = sectionId
self.action = action
@@ -148,11 +150,16 @@ private final class WebBrowserItemNode: ListViewItemNode {
if currentItem == nil {
switch item.application {
case .none:
- let icons = item.context.sharedContext.applicationBindings.getAvailableAlternateIcons()
- let current = item.context.sharedContext.applicationBindings.getAlternateIconName()
- let currentIcon = icons.first(where: { $0.name == current })?.imageName ?? "BlueIcon"
- if let image = UIImage(named: currentIcon, in: getAppBundle(), compatibleWith: nil) {
- updatedIconSignal = openInAppIcon(engine: item.context.engine, appIcon: .image(image: image))
+ if item.identifier == "default" {
+ let image = renderSettingsIcon(name: "Chat/Context Menu/Globe", backgroundColors: [UIColor(rgb: 0x8E8E93)])!
+ updatedIconSignal = openInAppIcon(engine: item.context.engine, appIcon: .image(image: image), withChrome: false)
+ } else {
+ let icons = item.context.sharedContext.applicationBindings.getAvailableAlternateIcons()
+ let current = item.context.sharedContext.applicationBindings.getAlternateIconName()
+ let currentIcon = icons.first(where: { $0.name == current })?.imageName ?? "BlueIcon"
+ if let image = UIImage(named: currentIcon, in: getAppBundle(), compatibleWith: nil) {
+ updatedIconSignal = openInAppIcon(engine: item.context.engine, appIcon: .image(image: image))
+ }
}
case .safari:
if let image = UIImage(bundleImageName: "Open In/Safari") {
diff --git a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift
index 018f3d97b8..7eafa1200a 100644
--- a/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift
+++ b/submodules/SettingsUI/Sources/Data and Storage/WebBrowserSettingsController.swift
@@ -12,10 +12,7 @@ import OpenInExternalAppUI
import ItemListPeerActionItem
import UndoUI
import WebKit
-import LinkPresentation
-import CoreServices
import PersistentStringHash
-import UrlHandling
private final class WebBrowserSettingsControllerArguments {
let context: AccountContext
@@ -23,7 +20,7 @@ private final class WebBrowserSettingsControllerArguments {
let clearCookies: () -> Void
let clearCache: () -> Void
let addException: () -> Void
- let removeException: (String) -> Void
+ let removeException: (AccountWebBrowserException) -> Void
let clearExceptions: () -> Void
init(
@@ -32,7 +29,7 @@ private final class WebBrowserSettingsControllerArguments {
clearCookies: @escaping () -> Void,
clearCache: @escaping () -> Void,
addException: @escaping () -> Void,
- removeException: @escaping (String) -> Void,
+ removeException: @escaping (AccountWebBrowserException) -> Void,
clearExceptions: @escaping () -> Void
) {
self.context = context
@@ -48,31 +45,44 @@ private final class WebBrowserSettingsControllerArguments {
private enum WebBrowserSettingsSection: Int32 {
case browsers
case clearCookies
- case exceptions
+ case neverExceptions
+ case alwaysExceptions
+ case clear
}
private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
case browserHeader(PresentationTheme, String)
case browser(PresentationTheme, String, OpenInApplication?, String?, Bool, Int32)
+ case browserInfo(PresentationTheme, String)
case clearCookies(PresentationTheme, String)
case clearCache(PresentationTheme, String)
case clearCookiesInfo(PresentationTheme, String)
- case exceptionsHeader(PresentationTheme, String)
- case exceptionsAdd(PresentationTheme, String)
- case exception(Int32, PresentationTheme, WebBrowserException)
- case exceptionsClear(PresentationTheme, String)
- case exceptionsInfo(PresentationTheme, String)
+ case neverHeader(PresentationTheme, String)
+ case neverAdd(PresentationTheme, String)
+ case neverException(Int32, PresentationTheme, AccountWebBrowserException)
+ case neverExceptionsInfo(PresentationTheme, String)
+ case neverExceptionsClear(PresentationTheme, String)
+
+ case alwaysHeader(PresentationTheme, String)
+ case alwaysAdd(PresentationTheme, String)
+ case alwaysException(Int32, PresentationTheme, AccountWebBrowserException)
+ case alwaysExceptionsInfo(PresentationTheme, String)
+ case alwaysExceptionsClear(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
- case .browserHeader, .browser:
+ case .browserHeader, .browser, .browserInfo:
return WebBrowserSettingsSection.browsers.rawValue
case .clearCookies, .clearCache, .clearCookiesInfo:
return WebBrowserSettingsSection.clearCookies.rawValue
- case .exceptionsHeader, .exceptionsAdd, .exception, .exceptionsClear, .exceptionsInfo:
- return WebBrowserSettingsSection.exceptions.rawValue
+ case .neverHeader, .neverAdd, .neverException, .neverExceptionsInfo:
+ return WebBrowserSettingsSection.neverExceptions.rawValue
+ case .alwaysHeader, .alwaysAdd, .alwaysException, .alwaysExceptionsInfo:
+ return WebBrowserSettingsSection.alwaysExceptions.rawValue
+ case .neverExceptionsClear, .alwaysExceptionsClear:
+ return WebBrowserSettingsSection.clear.rawValue
}
}
@@ -82,22 +92,34 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
return 0
case let .browser(_, _, _, _, _, index):
return UInt64(1 + index)
+ case .browserInfo:
+ return 101
case .clearCookies:
return 102
case .clearCache:
return 103
case .clearCookiesInfo:
return 104
- case .exceptionsHeader:
+ case .neverHeader:
return 105
- case .exceptionsAdd:
+ case .neverAdd:
return 106
- case let .exception(_, _, exception):
- return 2000 + exception.domain.persistentHashValue
- case .exceptionsClear:
- return 1000
- case .exceptionsInfo:
+ case let .neverException(_, _, exception):
+ return 107 + exception.domain.persistentHashValue
+ case .neverExceptionsInfo:
return 1001
+ case .neverExceptionsClear:
+ return 1002
+ case .alwaysHeader:
+ return 1003
+ case .alwaysAdd:
+ return 1004
+ case let .alwaysException(_, _, exception):
+ return 1005 + exception.domain.persistentHashValue
+ case .alwaysExceptionsInfo:
+ return 2000
+ case .alwaysExceptionsClear:
+ return 3000
}
}
@@ -107,22 +129,34 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
return 0
case let .browser(_, _, _, _, _, index):
return 1 + index
+ case .browserInfo:
+ return 101
case .clearCookies:
return 102
case .clearCache:
return 103
case .clearCookiesInfo:
return 104
- case .exceptionsHeader:
+ case .neverHeader:
return 105
- case .exceptionsAdd:
+ case .neverAdd:
return 106
- case let .exception(index, _, _):
+ case let .neverException(index, _, _):
return 107 + index
- case .exceptionsClear:
- return 1000
- case .exceptionsInfo:
+ case .neverExceptionsInfo:
return 1001
+ case .neverExceptionsClear:
+ return 1002
+ case .alwaysHeader:
+ return 1003
+ case .alwaysAdd:
+ return 1004
+ case let .alwaysException(index, _, _):
+ return 1005 + index
+ case .alwaysExceptionsInfo:
+ return 2000
+ case .alwaysExceptionsClear:
+ return 3000
}
}
@@ -140,6 +174,12 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
+ case let .browserInfo(lhsTheme, lhsText):
+ if case let .browserInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ return true
+ } else {
+ return false
+ }
case let .clearCookies(lhsTheme, lhsText):
if case let .clearCookies(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@@ -158,32 +198,62 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
- case let .exceptionsHeader(lhsTheme, lhsText):
- if case let .exceptionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ case let .neverHeader(lhsTheme, lhsText):
+ if case let .neverHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
- case let .exception(lhsIndex, lhsTheme, lhsException):
- if case let .exception(rhsIndex, rhsTheme, rhsException) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsException == rhsException {
+ case let .neverException(lhsIndex, lhsTheme, lhsException):
+ if case let .neverException(rhsIndex, rhsTheme, rhsException) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsException == rhsException {
return true
} else {
return false
}
- case let .exceptionsAdd(lhsTheme, lhsText):
- if case let .exceptionsAdd(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ case let .neverAdd(lhsTheme, lhsText):
+ if case let .neverAdd(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
- case let .exceptionsClear(lhsTheme, lhsText):
- if case let .exceptionsClear(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ case let .neverExceptionsInfo(lhsTheme, lhsText):
+ if case let .neverExceptionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
- case let .exceptionsInfo(lhsTheme, lhsText):
- if case let .exceptionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ case let .neverExceptionsClear(lhsTheme, lhsText):
+ if case let .neverExceptionsClear(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ return true
+ } else {
+ return false
+ }
+ case let .alwaysHeader(lhsTheme, lhsText):
+ if case let .alwaysHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ return true
+ } else {
+ return false
+ }
+ case let .alwaysException(lhsIndex, lhsTheme, lhsException):
+ if case let .alwaysException(rhsIndex, rhsTheme, rhsException) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsException == rhsException {
+ return true
+ } else {
+ return false
+ }
+ case let .alwaysAdd(lhsTheme, lhsText):
+ if case let .alwaysAdd(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ return true
+ } else {
+ return false
+ }
+ case let .alwaysExceptionsInfo(lhsTheme, lhsText):
+ if case let .alwaysExceptionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ return true
+ } else {
+ return false
+ }
+ case let .alwaysExceptionsClear(lhsTheme, lhsText):
+ if case let .alwaysExceptionsClear(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
@@ -201,11 +271,13 @@ 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, systemStyle: .glass, title: title, application: application, checked: selected, sectionId: self.section) {
+ return WebBrowserItem(context: arguments.context, presentationData: presentationData, systemStyle: .glass, title: title, application: application, identifier: identifier, checked: selected, sectionId: self.section) {
arguments.updateDefaultBrowser(identifier)
}
+ case let .browserInfo(_, text):
+ return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .clearCookies(_, text):
- return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.accentDeleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
+ return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .generic, alignment: .center, sectionId: self.section, style: .blocks, action: {
arguments.clearCookies()
})
case let .clearCache(_, text):
@@ -214,61 +286,93 @@ private enum WebBrowserSettingsControllerEntry: ItemListNodeEntry {
})
case let .clearCookiesInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
- case let .exceptionsHeader(_, text):
+ case let .neverHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
- case let .exception(_, _, exception):
- 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 .neverException(_, _, exception):
+ return WebBrowserDomainExceptionItem(presentationData: presentationData, systemStyle: .glass, context: arguments.context, title: exception.title, label: exception.domain, favicon: exception.favicon, sectionId: self.section, style: .blocks, deleted: {
+ arguments.removeException(exception)
})
- case let .exceptionsAdd(_, text):
+ case let .neverAdd(_, text):
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, systemStyle: .glass, icon: PresentationResourcesItemList.deleteIconImage(presentationData.theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
+ case let .neverExceptionsInfo(_, text):
+ return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
+ case let .neverExceptionsClear(_, text):
+ return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
arguments.clearExceptions()
})
- case let .exceptionsInfo(_, text):
+ case let .alwaysHeader(_, text):
+ return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
+ case let .alwaysException(_, _, exception):
+ return WebBrowserDomainExceptionItem(presentationData: presentationData, systemStyle: .glass, context: arguments.context, title: exception.title, label: exception.domain, favicon: exception.favicon, sectionId: self.section, style: .blocks, deleted: {
+ arguments.removeException(exception)
+ })
+ case let .alwaysAdd(_, text):
+ 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 .alwaysExceptionsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
+ case let .alwaysExceptionsClear(_, text):
+ return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: {
+ arguments.clearExceptions()
+ })
}
}
}
-private func webBrowserSettingsControllerEntries(context: AccountContext, presentationData: PresentationData, settings: WebBrowserSettings) -> [WebBrowserSettingsControllerEntry] {
+private func webBrowserSettingsControllerEntries(context: AccountContext, presentationData: PresentationData, localSettings: WebBrowserSettings, accountSettings: AccountWebBrowserSettings) -> [WebBrowserSettingsControllerEntry] {
var entries: [WebBrowserSettingsControllerEntry] = []
- let options = availableOpenInOptions(context: context, item: .url(url: "http://telegram.org"))
+ let options = availableOpenInOptions(context: context, item: .url(url: "https://telegram.org"))
+ let defaultExternalBrowser = localSettings.defaultWebBrowser ?? "default"
entries.append(.browserHeader(presentationData.theme, presentationData.strings.WebBrowser_OpenLinksIn_Title))
- entries.append(.browser(presentationData.theme, presentationData.strings.WebBrowser_Telegram, nil, nil, settings.defaultWebBrowser == nil, 0))
-
+ entries.append(.browser(presentationData.theme, presentationData.strings.WebBrowser_Telegram, nil, nil, !accountSettings.openExternalBrowser, 0))
+
var index: Int32 = 1
for option in options {
- entries.append(.browser(presentationData.theme, option.title, option.application, option.identifier, option.identifier == settings.defaultWebBrowser, index))
+ entries.append(.browser(presentationData.theme, option.title, option.application, option.identifier, accountSettings.openExternalBrowser && option.identifier == defaultExternalBrowser, index))
index += 1
}
- if settings.defaultWebBrowser == nil {
- entries.append(.clearCookies(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies))
-// entries.append(.clearCache(presentationData.theme, presentationData.strings.WebBrowser_ClearCache))
- entries.append(.clearCookiesInfo(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies_Info))
-
- entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Title))
- entries.append(.exceptionsAdd(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_AddException))
+ entries.append(.browserInfo(presentationData.theme, "Open links inside Telegram instead of your default browser for more privacy."))
+
+ entries.append(.clearCookies(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies))
+ entries.append(.clearCookiesInfo(presentationData.theme, presentationData.strings.WebBrowser_ClearCookies_Info))
+
+ //TODO:localize
+ if accountSettings.openExternalBrowser {
+ entries.append(.neverHeader(presentationData.theme, "OPEN IN-APP"))
+ entries.append(.neverAdd(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_AddException))
var exceptionIndex: Int32 = 0
- for exception in settings.exceptions.reversed() {
- entries.append(.exception(exceptionIndex, presentationData.theme, exception))
+ for exception in accountSettings.inAppExceptions.reversed() {
+ entries.append(.neverException(exceptionIndex, presentationData.theme, exception))
exceptionIndex += 1
}
+ entries.append(.neverExceptionsInfo(presentationData.theme, "These sites will still be opened in-app."))
- if !settings.exceptions.isEmpty {
- entries.append(.exceptionsClear(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Clear))
+ if !accountSettings.inAppExceptions.isEmpty {
+ entries.append(.neverExceptionsClear(presentationData.theme, "Delete All Exceptions"))
}
+ } else {
+ entries.append(.alwaysHeader(presentationData.theme, "DON'T OPEN IN-APP"))
+ entries.append(.alwaysAdd(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_AddException))
- entries.append(.exceptionsInfo(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Info))
+ var exceptionIndex: Int32 = 0
+ for exception in accountSettings.externalExceptions.reversed() {
+ entries.append(.alwaysException(exceptionIndex, presentationData.theme, exception))
+ exceptionIndex += 1
+ }
+ entries.append(.alwaysExceptionsInfo(presentationData.theme, presentationData.strings.WebBrowser_Exceptions_Info))
+
+ if !accountSettings.externalExceptions.isEmpty {
+ entries.append(.alwaysExceptionsClear(presentationData.theme, "Delete All Exceptions"))
+ }
}
-
+
return entries
}
@@ -276,15 +380,21 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
var clearCookiesImpl: (() -> Void)?
var clearCacheImpl: (() -> Void)?
var addExceptionImpl: (() -> Void)?
- var removeExceptionImpl: ((String) -> Void)?
+ var removeExceptionImpl: ((AccountWebBrowserException) -> Void)?
var clearExceptionsImpl: (() -> Void)?
let arguments = WebBrowserSettingsControllerArguments(
context: context,
updateDefaultBrowser: { identifier in
- let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, {
- $0.withUpdatedDefaultWebBrowser(identifier)
- }).start()
+ let openExternalBrowser = identifier != nil
+ if let identifier {
+ let _ = (updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, {
+ $0.withUpdatedDefaultWebBrowser(identifier)
+ })
+ |> then(updateRemoteWebBrowserSettings(postbox: context.account.postbox, network: context.account.network, openExternalBrowser: openExternalBrowser))).start()
+ } else {
+ let _ = updateRemoteWebBrowserSettings(postbox: context.account.postbox, network: context.account.network, openExternalBrowser: openExternalBrowser).start()
+ }
},
clearCookies: {
clearCookiesImpl?()
@@ -295,37 +405,39 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
addException: {
addExceptionImpl?()
},
- removeException: { domain in
- removeExceptionImpl?(domain)
+ removeException: { exception in
+ removeExceptionImpl?(exception)
},
clearExceptions: {
clearExceptionsImpl?()
}
)
- let previousSettings = Atomic(value: nil)
+ let previousSettings = Atomic<(WebBrowserSettings, AccountWebBrowserSettings)?>(value: nil)
let signal = combineLatest(
context.sharedContext.presentationData,
- context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings])
+ context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.webBrowserSettings]),
+ context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.webBrowserSettings))
)
|> deliverOnMainQueue
- |> map { presentationData, sharedData -> (ItemListControllerState, (ItemListNodeState, Any)) in
- let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings
- let previousSettings = previousSettings.swap(settings)
+ |> map { presentationData, sharedData, accountSettingsEntry -> (ItemListControllerState, (ItemListNodeState, Any)) in
+ let localSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.webBrowserSettings]?.get(WebBrowserSettings.self) ?? WebBrowserSettings.defaultSettings
+ let accountSettings = accountSettingsEntry?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings
+ let previousSettings = previousSettings.swap((localSettings, accountSettings))
var animateChanges = false
if let previousSettings {
- if previousSettings.defaultWebBrowser != settings.defaultWebBrowser {
+ if previousSettings.0.defaultWebBrowser != localSettings.defaultWebBrowser || previousSettings.1.openExternalBrowser != accountSettings.openExternalBrowser {
animateChanges = true
}
- if previousSettings.exceptions.count != settings.exceptions.count {
+ if previousSettings.1.externalExceptions.count != accountSettings.externalExceptions.count || previousSettings.1.inAppExceptions.count != accountSettings.inAppExceptions.count {
animateChanges = true
}
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.WebBrowser_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
- let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: webBrowserSettingsControllerEntries(context: context, presentationData: presentationData, settings: settings), style: .blocks, animateChanges: animateChanges)
+ let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: webBrowserSettingsControllerEntries(context: context, presentationData: presentationData, localSettings: localSettings, accountSettings: accountSettings), style: .blocks, animateChanges: animateChanges)
return (controllerState, (listState, arguments))
}
@@ -400,18 +512,13 @@ public func webBrowserSettingsController(context: AccountContext) -> ViewControl
var dismissImpl: (() -> Void)?
let linkController = webBrowserDomainController(context: context, apply: { url in
if let url {
- let _ = (fetchDomainExceptionInfo(context: context, url: url)
- |> deliverOnMainQueue).startStandalone(next: { newException in
- let _ = updateWebBrowserSettingsInteractively(accountManager: context.sharedContext.accountManager, { currentSettings in
- var currentExceptions = currentSettings.exceptions
- for exception in currentExceptions {
- if exception.domain == newException.domain {
- return currentSettings
- }
- }
- currentExceptions.append(newException)
- return currentSettings.withUpdatedExceptions(currentExceptions)
- }).start()
+ let _ = (context.account.postbox.transaction { transaction -> AccountWebBrowserSettings in
+ return transaction.getPreferencesEntry(key: PreferencesKeys.webBrowserSettings)?.get(AccountWebBrowserSettings.self) ?? AccountWebBrowserSettings.defaultSettings
+ }
+ |> mapToSignal { settings -> Signal