diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4cfb44a885..608ef84246 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11092,3 +11092,8 @@ Sorry for the inconvenience."; "Notification.Boost.SingleYou" = "You boosted the group"; "Notification.Boost.MultipleYou" = "You boosted the group %1$@"; + +"Contacts.SelectedContacts_1" = "%@ Selected"; +"Contacts.SelectedContacts_any" = "%@ Selected"; + +"Emoji.GroupEmoji" = "GROUP EMOJI"; diff --git a/submodules/Camera/Sources/Camera.swift b/submodules/Camera/Sources/Camera.swift index 1014fca2d9..9e693c2574 100644 --- a/submodules/Camera/Sources/Camera.swift +++ b/submodules/Camera/Sources/Camera.swift @@ -1067,7 +1067,7 @@ public final class Camera { } } } -e + public final class CameraHolder { public let camera: Camera public let previewView: CameraPreviewView diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 37c902668d..5f95d9bf26 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -118,6 +118,7 @@ public class ContactsController: ViewController { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? private var authorizationDisposable: Disposable? + private var selectionDisposable: Disposable? private var actionDisposable = MetaDisposable() private let sortOrderPromise = Promise() private let isInVoiceOver = ValuePromise(false) @@ -234,6 +235,7 @@ public class ContactsController: ViewController { self.presentationDataDisposable?.dispose() self.authorizationDisposable?.dispose() self.actionDisposable.dispose() + self.selectionDisposable?.dispose() } private func updateThemeAndStrings() { @@ -343,8 +345,21 @@ public class ContactsController: ViewController { self?.activateSearch() } - self.contactsNode.contactListNode.openPeer = { peer, _ in - openPeer(peer, false) + self.contactsNode.contactListNode.openPeer = { [weak self] peer, _ in + guard let self else { + return + } + if let _ = self.contactsNode.contactListNode.selectionState { + self.contactsNode.contactListNode.updateSelectionState({ current in + if let updatedState = current?.withToggledPeerId(peer.id), !updatedState.selectedPeerIndices.isEmpty { + return updatedState + } else { + return nil + } + }) + } else { + openPeer(peer, false) + } } self.contactsNode.requestAddContact = { [weak self] phoneNumber in @@ -471,9 +486,46 @@ public class ContactsController: ViewController { self?.presentSortMenu(sourceView: sourceNode.view, gesture: gesture) } + let previousToolbarValue = Atomic(value: nil) + self.selectionDisposable = (self.contactsNode.contactListNode.selectionStateSignal + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self, let layout = self.validLayout else { + return + } + + let toolbar: Toolbar? + if let state, state.selectedPeerIndices.count > 0 { + toolbar = Toolbar(leftAction: nil, rightAction: nil, middleAction: ToolbarAction(title: self.presentationData.strings.ContactList_DeleteConfirmation(Int32(state.selectedPeerIndices.count)), isEnabled: true, color: .custom(self.presentationData.theme.actionSheet.destructiveActionTextColor))) + } else { + toolbar = nil + } + + let _ = self.contactsNode.updateNavigationBar(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + + var transition: ContainedViewLayoutTransition = .immediate + let previousToolbar = previousToolbarValue.swap(toolbar) + if (previousToolbar == nil) != (toolbar == nil) { + transition = .animated(duration: 0.4, curve: .spring) + } + self.setToolbar(toolbar, transition: transition) + }) + self.displayNodeDidLoad() } + override public func toolbarActionSelected(action: ToolbarActionOption) { + guard case .middle = action, let selectionState = self.contactsNode.contactListNode.selectionState else { + return + } + var peerIds: [EnginePeer.Id] = [] + for contactPeerId in selectionState.selectedPeerIndices.keys { + if case let .peer(peerId) = contactPeerId { + peerIds.append(peerId) + } + } + self.requestDeleteContacts(peerIds: peerIds) + } + override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) @@ -587,6 +639,9 @@ public class ContactsController: ViewController { } public func requestDeleteContacts(peerIds: [EnginePeer.Id]) { + guard !peerIds.isEmpty else { + return + } let actionSheet = ActionSheetController(presentationData: self.presentationData) var items: [ActionSheetItem] = [] @@ -604,6 +659,10 @@ public class ContactsController: ViewController { return } + self.contactsNode.contactListNode.updateSelectionState { _ in + return nil + } + self.contactsNode.contactListNode.updatePendingRemovalPeerIds { state in var state = state for peerId in peerIds { diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 02bc45b06e..6105a64ba9 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -297,16 +297,31 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { return false } - private func updateNavigationBar(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { + func updateNavigationBar(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> (navigationHeight: CGFloat, storiesInset: CGFloat) { let tabsNode: ASDisplayNode? = nil let tabsNodeIsSearch = false - let primaryContent = ChatListHeaderComponent.Content( - title: self.presentationData.strings.Contacts_Title, - navigationBackTitle: nil, - titleComponent: nil, - chatListTitle: NetworkStatusTitle(text: self.presentationData.strings.Contacts_Title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil), - leftButton: AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent( + let title: String + let leftButton: AnyComponentWithIdentity? + let rightButtons: [AnyComponentWithIdentity] + + if let selectionState = self.contactListNode.selectionState { + title = self.presentationData.strings.Contacts_SelectedContacts(Int32(selectionState.selectedPeerIndices.count)) + leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] sourceView in + guard let self else { + return + } + self.contactListNode.updateSelectionState { _ in + return nil + } + } + ))) + rightButtons = [] + } else { + title = self.presentationData.strings.Contacts_Title + leftButton = AnyComponentWithIdentity(id: "sort", component: AnyComponent(NavigationButtonComponent( content: .text(title: self.presentationData.strings.Contacts_Sort, isBold: false), pressed: { [weak self] sourceView in guard let self else { @@ -315,8 +330,8 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.controller?.presentSortMenu(sourceView: sourceView, gesture: nil) } - ))), - rightButtons: [AnyComponentWithIdentity(id: "add", component: AnyComponent(NavigationButtonComponent( + ))) + rightButtons = [AnyComponentWithIdentity(id: "add", component: AnyComponent(NavigationButtonComponent( content: .icon(imageName: "Chat List/AddIcon"), pressed: { [weak self] _ in guard let self else { @@ -324,7 +339,16 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.controller?.addPressed() } - )))], + )))] + } + + let primaryContent = ChatListHeaderComponent.Content( + title: self.presentationData.strings.Contacts_Title, + navigationBackTitle: nil, + titleComponent: nil, + chatListTitle: NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil), + leftButton: leftButton, + rightButtons: rightButtons, backTitle: nil, backPressed: nil ) diff --git a/submodules/DrawingUI/Sources/DrawingPenTool.swift b/submodules/DrawingUI/Sources/DrawingPenTool.swift index 5fabb02c1c..ee6880cbd2 100644 --- a/submodules/DrawingUI/Sources/DrawingPenTool.swift +++ b/submodules/DrawingUI/Sources/DrawingPenTool.swift @@ -672,6 +672,7 @@ final class PenTool: DrawingElement { return self.start.location.distance(to: self.end.location) } } + private func smoothPoints(_ input: SmootherInput) -> [Point] { let segmentDistance: CGFloat = 6.0 let distance = input.distance diff --git a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift index cb4a3e086b..9ebfae3d48 100644 --- a/submodules/TelegramAudio/Sources/ManagedAudioSession.swift +++ b/submodules/TelegramAudio/Sources/ManagedAudioSession.swift @@ -963,7 +963,7 @@ public final class ManagedAudioSession: NSObject { if case let .record(_, video, _) = type, video, let input = AVAudioSession.sharedInstance().availableInputs?.first { if let dataSources = input.dataSources { for source in dataSources { - if source.dataSourceName.contains("Front") { + if source.dataSourceName.contains("Bottom") { try? input.setPreferredDataSource(source) break } diff --git a/submodules/TelegramCore/Sources/State/ChannelBoost.swift b/submodules/TelegramCore/Sources/State/ChannelBoost.swift index 704fbf7d7d..2e84bd5978 100644 --- a/submodules/TelegramCore/Sources/State/ChannelBoost.swift +++ b/submodules/TelegramCore/Sources/State/ChannelBoost.swift @@ -129,7 +129,26 @@ func _internal_applyChannelBoost(account: Account, peerId: PeerId, slots: [Int32 |> mapToSignal { result -> Signal in if let result = result { return account.postbox.transaction { transaction -> MyBoostStatus? in - return MyBoostStatus(apiMyBoostStatus: result, accountPeerId: account.peerId, transaction: transaction) + let myBoostStatus = MyBoostStatus(apiMyBoostStatus: result, accountPeerId: account.peerId, transaction: transaction) + + var appliedBoosts: Int32 = 0 + for boost in myBoostStatus.boosts { + if boost.peer?.id == peerId { + appliedBoosts += 1 + } + } + + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + var cachedData: CachedChannelData + if let current = current as? CachedChannelData { + cachedData = current + } else { + cachedData = CachedChannelData() + } + return cachedData.withUpdatedAppliedBoosts(appliedBoosts) + }) + + return myBoostStatus } } else { return .single(nil) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index f62e9d2921..cfef517df3 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -533,7 +533,7 @@ public final class CachedChannelData: CachedPeerData { return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: boostsToUnrestrict, appliedBoosts: self.appliedBoosts, emojiPack: self.emojiPack) } - public func withUpdateAppliedBoosts(_ appliedBoosts: Int32?) -> CachedChannelData { + public func withUpdatedAppliedBoosts(_ appliedBoosts: Int32?) -> CachedChannelData { return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, invitedOn: self.invitedOn, photo: self.photo, activeCall: self.activeCall, callJoinPeerId: self.callJoinPeerId, autoremoveTimeout: self.autoremoveTimeout, pendingSuggestions: pendingSuggestions, themeEmoticon: self.themeEmoticon, inviteRequestsPending: self.inviteRequestsPending, sendAsPeerId: self.sendAsPeerId, allowedReactions: self.allowedReactions, membersHidden: self.membersHidden, viewForumAsMessages: self.viewForumAsMessages, wallpaper: self.wallpaper, boostsToUnrestrict: self.boostsToUnrestrict, appliedBoosts: appliedBoosts, emojiPack: self.emojiPack) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index aa0b8b220f..81af3adfb1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -675,7 +675,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedViewForumAsMessages(.known(forumViewAsMessages)) .withUpdatedWallpaper(wallpaper) .withUpdatedBoostsToUnrestrict(boostsUnrestrict) - .withUpdateAppliedBoosts(appliedBoosts) + .withUpdatedAppliedBoosts(appliedBoosts) .withUpdatedEmojiPack(emojiPack) }) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 67873fb57e..b897145cae 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -278,7 +278,7 @@ public struct PresentationResourcesItemList { public static func addBoostsIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.itemListAddBoostsIcon.rawValue, { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Premium/AddBoosts"), color: theme.list.itemAccentColor) + return generateTintedImage(image: UIImage(bundleImageName: "Premium/Gift"), color: theme.list.itemAccentColor) }) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index b6668cda80..8a266fced1 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1257,8 +1257,7 @@ public extension EmojiPagerContentComponent { itemGroups[groupIndex].items.append(resultItem) } else { itemGroupIndexById[groupId] = itemGroups.count - //TODO:localize - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.info.title, subtitle: nil, badge: "GROUP EMOJI", isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) + itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: peerSpecificPack.info.title, subtitle: nil, badge: strings.Emoji_GroupEmoji, isPremiumLocked: false, isFeatured: false, collapsedLineCount: nil, isClearable: false, headerItem: nil, items: [resultItem])) } } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index fd938c10b1..3abfa3e7f2 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -571,7 +571,7 @@ public final class EntityKeyboardComponent: Component { var topEmojiItems: [EntityKeyboardTopPanelComponent.Item] = [] for itemGroup in emojiContent.panelItemGroups { if !itemGroup.items.isEmpty { - if let id = itemGroup.groupId.base as? String { + if let id = itemGroup.groupId.base as? String, id != "peerSpecific" { if id == "recent" || id == "liked" { let iconMapping: [String: EntityKeyboardIconTopPanelComponent.Icon] = [ "recent": .recent, diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/Contents.json new file mode 100644 index 0000000000..f1ac78866d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "gift_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/gift_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/gift_30.pdf new file mode 100644 index 0000000000..53c86d4359 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Gift.imageset/gift_30.pdf @@ -0,0 +1,443 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 6.250000 3.669434 cm +0.000000 0.000000 0.000000 scn +0.665000 11.330566 m +0.665000 11.697836 0.367269 11.995566 0.000000 11.995566 c +-0.367269 11.995566 -0.665000 11.697836 -0.665000 11.330566 c +0.665000 11.330566 l +h +18.165001 11.330566 m +18.165001 11.697836 17.867270 11.995566 17.500000 11.995566 c +17.132730 11.995566 16.834999 11.697836 16.834999 11.330566 c +18.165001 11.330566 l +h +1.365024 1.603050 m +1.666927 2.195569 l +1.365024 1.603050 l +h +0.272484 2.695590 m +0.865003 2.997494 l +0.272484 2.695590 l +h +16.134975 1.603050 m +16.436880 1.010530 l +16.134975 1.603050 l +h +17.227516 2.695590 m +17.820036 2.393686 l +17.227516 2.695590 l +h +13.500000 1.995566 m +4.000000 1.995566 l +4.000000 0.665566 l +13.500000 0.665566 l +13.500000 1.995566 l +h +0.665000 5.330566 m +0.665000 11.330566 l +-0.665000 11.330566 l +-0.665000 5.330566 l +0.665000 5.330566 l +h +16.834999 11.330566 m +16.834999 5.330566 l +18.165001 5.330566 l +18.165001 11.330566 l +16.834999 11.330566 l +h +4.000000 1.995566 m +3.288961 1.995566 2.795676 1.996083 2.412157 2.027418 c +2.036401 2.058119 1.824946 2.115055 1.666927 2.195569 c +1.063120 1.010530 l +1.439881 0.818562 1.845848 0.739256 2.303853 0.701836 c +2.754094 0.665050 3.310907 0.665566 4.000000 0.665566 c +4.000000 1.995566 l +h +-0.665000 5.330566 m +-0.665000 4.641474 -0.665517 4.084661 -0.628731 3.634419 c +-0.591311 3.176414 -0.512005 2.770447 -0.320036 2.393686 c +0.865003 2.997494 l +0.784489 3.155513 0.727552 3.366968 0.696852 3.742723 c +0.665517 4.126243 0.665000 4.619528 0.665000 5.330566 c +-0.665000 5.330566 l +h +1.666927 2.195569 m +1.321650 2.371497 1.040931 2.652216 0.865003 2.997494 c +-0.320036 2.393686 l +-0.016596 1.798154 0.467587 1.313970 1.063120 1.010530 c +1.666927 2.195569 l +h +13.500000 0.665566 m +14.189093 0.665566 14.745906 0.665050 15.196147 0.701836 c +15.654152 0.739256 16.060120 0.818562 16.436880 1.010530 c +15.833073 2.195569 l +15.675054 2.115055 15.463599 2.058119 15.087843 2.027418 c +14.704324 1.996083 14.211039 1.995566 13.500000 1.995566 c +13.500000 0.665566 l +h +16.834999 5.330566 m +16.834999 4.619527 16.834482 4.126243 16.803148 3.742723 c +16.772448 3.366968 16.715511 3.155513 16.634996 2.997494 c +17.820036 2.393686 l +18.012005 2.770447 18.091311 3.176414 18.128731 3.634419 c +18.165518 4.084660 18.165001 4.641474 18.165001 5.330566 c +16.834999 5.330566 l +h +16.436880 1.010530 m +17.032413 1.313970 17.516596 1.798154 17.820036 2.393686 c +16.634996 2.997494 l +16.459070 2.652216 16.178350 2.371497 15.833073 2.195569 c +16.436880 1.010530 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 5.000000 14.293945 cm +0.000000 0.000000 0.000000 scn +2.500000 0.666055 m +2.867269 0.666055 3.165000 0.963785 3.165000 1.331055 c +3.165000 1.698324 2.867269 1.996055 2.500000 1.996055 c +2.500000 0.666055 l +h +17.500000 1.996055 m +17.132730 1.996055 16.834999 1.698324 16.834999 1.331055 c +16.834999 0.963785 17.132730 0.666055 17.500000 0.666055 c +17.500000 1.996055 l +h +0.144016 2.189722 m +-0.457137 1.905398 l +0.144016 2.189722 l +h +0.858667 1.475071 m +1.142991 2.076224 l +0.858667 1.475071 l +h +19.855984 2.189722 m +20.457136 1.905398 l +19.855984 2.189722 l +h +19.141333 1.475071 m +18.857008 2.076224 l +19.141333 1.475071 l +h +18.568888 7.341028 m +18.284563 6.739875 l +18.568888 7.341028 l +h +19.759974 6.149942 m +19.158821 5.865618 l +19.759974 6.149942 l +h +3.906250 6.916055 m +16.093750 6.916055 l +16.093750 8.246055 l +3.906250 8.246055 l +3.906250 6.916055 l +h +2.343750 0.666055 m +2.500000 0.666055 l +2.500000 1.996055 l +2.343750 1.996055 l +2.343750 0.666055 l +h +17.656250 1.996055 m +17.500000 1.996055 l +17.500000 0.666055 l +17.656250 0.666055 l +17.656250 1.996055 l +h +-0.665000 3.674805 m +-0.665000 3.291543 -0.665455 2.966613 -0.645052 2.700472 c +-0.624109 2.427277 -0.578354 2.161689 -0.457137 1.905398 c +0.745169 2.474046 l +0.722370 2.522251 0.696117 2.605676 0.681057 2.802133 c +0.665455 3.005646 0.665000 3.271009 0.665000 3.674805 c +-0.665000 3.674805 l +h +2.343750 1.996055 m +1.939954 1.996055 1.674591 1.996510 1.471079 2.012111 c +1.274621 2.027172 1.191196 2.053424 1.142991 2.076224 c +0.574343 0.873918 l +0.830635 0.752701 1.096222 0.706945 1.369418 0.686002 c +1.635558 0.665600 1.960488 0.666055 2.343750 0.666055 c +2.343750 1.996055 l +h +-0.457137 1.905398 m +-0.242981 1.452604 0.121549 1.088073 0.574343 0.873918 c +1.142991 2.076224 l +0.968357 2.158820 0.827765 2.299412 0.745169 2.474046 c +-0.457137 1.905398 l +h +19.334999 3.674805 m +19.334999 3.271009 19.334545 3.005646 19.318943 2.802133 c +19.303883 2.605676 19.277630 2.522251 19.254831 2.474046 c +20.457136 1.905398 l +20.578354 2.161689 20.624109 2.427277 20.645052 2.700472 c +20.665455 2.966613 20.665001 3.291543 20.665001 3.674805 c +19.334999 3.674805 l +h +17.656250 0.666055 m +18.039513 0.666055 18.364443 0.665600 18.630583 0.686002 c +18.903778 0.706945 19.169365 0.752701 19.425657 0.873918 c +18.857008 2.076224 l +18.808804 2.053424 18.725378 2.027172 18.528921 2.012111 c +18.325409 1.996510 18.060045 1.996055 17.656250 1.996055 c +17.656250 0.666055 l +h +19.254831 2.474046 m +19.172235 2.299412 19.031643 2.158820 18.857008 2.076224 c +19.425657 0.873918 l +19.878450 1.088073 20.242981 1.452604 20.457136 1.905398 c +19.254831 2.474046 l +h +16.093750 6.916055 m +16.759899 6.916055 17.222025 6.915600 17.582088 6.887997 c +17.935097 6.860935 18.134859 6.810679 18.284563 6.739875 c +18.853212 7.942181 l +18.495422 8.111403 18.113497 8.181161 17.683750 8.214106 c +17.261059 8.246510 16.739365 8.246055 16.093750 8.246055 c +16.093750 6.916055 l +h +20.665001 3.674805 m +20.665001 4.320419 20.665455 4.842113 20.633051 5.264805 c +20.600107 5.694551 20.530348 6.076477 20.361126 6.434267 c +19.158821 5.865618 l +19.229626 5.715915 19.279881 5.516152 19.306942 5.163144 c +19.334545 4.803081 19.334999 4.340953 19.334999 3.674805 c +20.665001 3.674805 l +h +18.284563 6.739875 m +18.668341 6.558362 18.977308 6.249395 19.158821 5.865618 c +20.361126 6.434267 l +20.048054 7.096203 19.515148 7.629108 18.853212 7.942181 c +18.284563 6.739875 l +h +3.906250 8.246055 m +3.260636 8.246055 2.738941 8.246510 2.316250 8.214106 c +1.886503 8.181161 1.504578 8.111403 1.146788 7.942181 c +1.715436 6.739875 l +1.865140 6.810679 2.064903 6.860935 2.417911 6.887997 c +2.777974 6.915600 3.240102 6.916055 3.906250 6.916055 c +3.906250 8.246055 l +h +0.665000 3.674805 m +0.665000 4.340953 0.665455 4.803081 0.693058 5.163144 c +0.720120 5.516152 0.770375 5.715915 0.841180 5.865618 c +-0.361126 6.434267 l +-0.530348 6.076477 -0.600106 5.694551 -0.633051 5.264805 c +-0.665455 4.842113 -0.665000 4.320419 -0.665000 3.674805 c +0.665000 3.674805 l +h +1.146788 7.942181 m +0.484851 7.629108 -0.048053 7.096203 -0.361126 6.434267 c +0.841180 5.865618 l +1.022693 6.249395 1.331660 6.558362 1.715436 6.739875 c +1.146788 7.942181 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 10.000000 20.544922 cm +0.000000 0.000000 0.000000 scn +5.000000 1.330078 m +5.000000 0.665078 l +5.665000 0.665078 l +5.665000 1.330078 l +5.000000 1.330078 l +h +4.335000 3.830078 m +4.335000 1.330078 l +5.665000 1.330078 l +5.665000 3.830078 l +4.335000 3.830078 l +h +5.000000 1.995078 m +2.500000 1.995078 l +2.500000 0.665078 l +5.000000 0.665078 l +5.000000 1.995078 l +h +2.500000 1.995078 m +1.486557 1.995078 0.665000 2.816636 0.665000 3.830078 c +-0.665000 3.830078 l +-0.665000 2.082097 0.752019 0.665078 2.500000 0.665078 c +2.500000 1.995078 l +h +2.500000 5.665078 m +3.513443 5.665078 4.335000 4.843521 4.335000 3.830078 c +5.665000 3.830078 l +5.665000 5.578059 4.247981 6.995078 2.500000 6.995078 c +2.500000 5.665078 l +h +2.500000 6.995078 m +0.752019 6.995078 -0.665000 5.578059 -0.665000 3.830078 c +0.665000 3.830078 l +0.665000 4.843521 1.486557 5.665078 2.500000 5.665078 c +2.500000 6.995078 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 15.000000 20.544922 cm +0.000000 0.000000 0.000000 scn +0.000000 1.330078 m +-0.665000 1.330078 l +-0.665000 0.665078 l +0.000000 0.665078 l +0.000000 1.330078 l +h +2.500000 1.995078 m +0.000000 1.995078 l +0.000000 0.665078 l +2.500000 0.665078 l +2.500000 1.995078 l +h +0.665000 1.330078 m +0.665000 3.830078 l +-0.665000 3.830078 l +-0.665000 1.330078 l +0.665000 1.330078 l +h +4.335000 3.830078 m +4.335000 2.816636 3.513443 1.995078 2.500000 1.995078 c +2.500000 0.665078 l +4.247981 0.665078 5.665000 2.082097 5.665000 3.830078 c +4.335000 3.830078 l +h +2.500000 5.665078 m +3.513443 5.665078 4.335000 4.843521 4.335000 3.830078 c +5.665000 3.830078 l +5.665000 5.578059 4.247981 6.995078 2.500000 6.995078 c +2.500000 5.665078 l +h +2.500000 6.995078 m +0.752019 6.995078 -0.665000 5.578059 -0.665000 3.830078 c +0.665000 3.830078 l +0.665000 4.843521 1.486557 5.665078 2.500000 5.665078 c +2.500000 6.995078 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 15.000000 4.294922 cm +0.000000 0.000000 0.000000 scn +0.665000 3.830078 m +0.665000 4.197348 0.367269 4.495078 0.000000 4.495078 c +-0.367269 4.495078 -0.665000 4.197348 -0.665000 3.830078 c +0.665000 3.830078 l +h +-0.665000 1.330078 m +-0.665000 0.962809 -0.367269 0.665078 0.000000 0.665078 c +0.367269 0.665078 0.665000 0.962809 0.665000 1.330078 c +-0.665000 1.330078 l +h +-0.665000 3.830078 m +-0.665000 1.330078 l +0.665000 1.330078 l +0.665000 3.830078 l +-0.665000 3.830078 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 9.990234 9.851074 cm +0.000000 0.000000 0.000000 scn +4.751247 1.595008 m +2.492245 0.211133 l +2.257351 0.067236 1.950281 0.141003 1.806384 0.375896 c +1.736104 0.490619 1.715151 0.628868 1.748279 0.759263 c +2.097972 2.135665 l +2.224205 2.632523 2.564205 3.047855 3.026343 3.269736 c +5.490801 4.452966 l +5.605695 4.508129 5.654118 4.645987 5.598955 4.760881 c +5.554282 4.853927 5.453256 4.905994 5.351554 4.888387 c +2.608295 4.413459 l +2.050655 4.316917 1.478800 4.470905 1.045039 4.834409 c +0.178421 5.560659 l +-0.032710 5.737593 -0.060432 6.052180 0.116501 6.263311 c +0.202556 6.365997 0.326303 6.429747 0.459872 6.440200 c +3.107648 6.647414 l +3.294706 6.662054 3.457723 6.780433 3.529533 6.953778 c +4.550995 9.419525 l +4.656422 9.674018 4.948194 9.794860 5.202687 9.689434 c +5.324886 9.638812 5.421974 9.541724 5.472596 9.419525 c +6.494058 6.953778 l +6.565868 6.780433 6.728885 6.662054 6.915944 6.647414 c +9.578268 6.439061 l +9.852894 6.417569 10.058100 6.177518 10.036608 5.902892 c +10.026269 5.770781 9.963782 5.648214 9.862940 5.562243 c +7.832497 3.831244 l +7.689560 3.709387 7.627202 3.517563 7.671160 3.334950 c +8.295380 0.741805 l +8.359848 0.473989 8.195003 0.204619 7.927186 0.140151 c +7.798499 0.109174 7.662776 0.130618 7.549908 0.199762 c +5.272344 1.595008 l +5.112454 1.692957 4.911138 1.692957 4.751247 1.595008 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 10525 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000010615 00000 n +0000010639 00000 n +0000010812 00000 n +0000010886 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +10945 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift index e748ee429d..eb3d77fd2f 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputActionButtonsNode.swift @@ -164,38 +164,38 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode { self.backdropNode.update(rect: rect, within: containerSize) } - var isScheduledMessages = false - if case .scheduledMessages = interfaceState.subject { - isScheduledMessages = true - } - - if let slowmodeState = interfaceState.slowmodeState, !isScheduledMessages && interfaceState.editMessageState == nil { - let sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode - if let current = self.sendButtonRadialStatusNode { - sendButtonRadialStatusNode = current - } else { - sendButtonRadialStatusNode = ChatSendButtonRadialStatusNode(color: interfaceState.theme.chat.inputPanel.panelControlAccentColor) - sendButtonRadialStatusNode.alpha = self.sendContainerNode.alpha - self.sendButtonRadialStatusNode = sendButtonRadialStatusNode - self.addSubnode(sendButtonRadialStatusNode) - } - - transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 0.7575, y: 0.7575)) - - let defaultSendButtonSize: CGFloat = 25.0 - let defaultOriginX = floorToScreenPixels((self.sendButton.bounds.width - defaultSendButtonSize) / 2.0) - let defaultOriginY = floorToScreenPixels((self.sendButton.bounds.height - defaultSendButtonSize) / 2.0) - - let radialStatusFrame = CGRect(origin: CGPoint(x: defaultOriginX - 4.0, y: defaultOriginY - 4.0), size: CGSize(width: 33.0, height: 33.0)) - sendButtonRadialStatusNode.frame = radialStatusFrame - sendButtonRadialStatusNode.slowmodeState = slowmodeState - } else { - if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode { - self.sendButtonRadialStatusNode = nil - sendButtonRadialStatusNode.removeFromSupernode() - } - transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 1.0, y: 1.0)) - } +// var isScheduledMessages = false +// if case .scheduledMessages = interfaceState.subject { +// isScheduledMessages = true +// } +// +// if let slowmodeState = interfaceState.slowmodeState, !isScheduledMessages && interfaceState.editMessageState == nil { +// let sendButtonRadialStatusNode: ChatSendButtonRadialStatusNode +// if let current = self.sendButtonRadialStatusNode { +// sendButtonRadialStatusNode = current +// } else { +// sendButtonRadialStatusNode = ChatSendButtonRadialStatusNode(color: interfaceState.theme.chat.inputPanel.panelControlAccentColor) +// sendButtonRadialStatusNode.alpha = self.sendContainerNode.alpha +// self.sendButtonRadialStatusNode = sendButtonRadialStatusNode +// self.addSubnode(sendButtonRadialStatusNode) +// } +// +// transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 0.7575, y: 0.7575)) +// +// let defaultSendButtonSize: CGFloat = 25.0 +// let defaultOriginX = floorToScreenPixels((self.sendButton.bounds.width - defaultSendButtonSize) / 2.0) +// let defaultOriginY = floorToScreenPixels((self.sendButton.bounds.height - defaultSendButtonSize) / 2.0) +// +// let radialStatusFrame = CGRect(origin: CGPoint(x: defaultOriginX - 4.0, y: defaultOriginY - 4.0), size: CGSize(width: 33.0, height: 33.0)) +// sendButtonRadialStatusNode.frame = radialStatusFrame +// sendButtonRadialStatusNode.slowmodeState = slowmodeState +// } else { +// if let sendButtonRadialStatusNode = self.sendButtonRadialStatusNode { +// self.sendButtonRadialStatusNode = nil +// sendButtonRadialStatusNode.removeFromSupernode() +// } +// transition.updateSublayerTransformScale(layer: self.sendContainerNode.layer, scale: CGPoint(x: 1.0, y: 1.0)) +// } transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size)) let expanded = isMediaInputExpanded diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index b652be8b28..09bf35e28a 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -41,6 +41,8 @@ import ChatContextQuery import ChatInputTextNode import ChatInputPanelNode import TelegramNotices +import AnimatedCountLabelNode +import TelegramStringFormatting private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -535,6 +537,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch let textInputBackgroundNode: ASImageNode private var transparentTextInputBackgroundImage: UIImage? let actionButtons: ChatTextInputActionButtonsNode + private let slowModeButton: BoostSlowModeButton var mediaRecordingAccessibilityArea: AccessibilityAreaNode? private let counterTextNode: ImmediateTextNode @@ -570,6 +573,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool, Bool)? private var leftMenuInset: CGFloat = 0.0 + private var rightSlowModeInset: CGFloat = 0.0 var displayAttachmentMenu: () -> Void = { } var sendMessage: () -> Void = { } @@ -855,10 +859,18 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.counterTextNode = ImmediateTextNode() self.counterTextNode.textAlignment = .center + self.slowModeButton = BoostSlowModeButton() + self.slowModeButton.alpha = 0.0 + self.viewOnceButton = ChatRecordingViewOnceButtonNode(icon: .viewOnce) super.init() + self.slowModeButton.requestUpdate = { [weak self] in + self?.requestLayout(transition: .animated(duration: 0.2, curve: .easeInOut)) + } + self.slowModeButton.addTarget(self, action: #selector(self.slowModeButtonPressed), forControlEvents: .touchUpInside) + self.viewForOverlayContent = ChatTextViewForOverlayContent( ignoreHit: { [weak self] view, point in guard let strongSelf = self else { @@ -1045,6 +1057,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.clippingNode.addSubnode(self.actionButtons) self.clippingNode.addSubnode(self.counterTextNode) + self.clippingNode.addSubnode(self.slowModeButton) + self.clippingNode.view.addSubview(self.searchLayoutClearButton) self.textInputBackgroundNode.clipsToBounds = true @@ -1416,11 +1430,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } - func requestLayout() { + func requestLayout(transition: ContainedViewLayoutTransition = .immediate) { guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else { return } - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { @@ -1933,11 +1947,21 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } self.leftMenuInset = leftMenuInset + var rightSlowModeInset: CGFloat = 0.0 + var slowModeButtonSize: CGSize = .zero + if let presentationInterfaceState = self.presentationInterfaceState { + slowModeButtonSize = self.slowModeButton.update(size: CGSize(width: width, height: 44.0), interfaceState: presentationInterfaceState) + if inputHasText { + rightSlowModeInset = slowModeButtonSize.width - 33.0 + } + } + self.rightSlowModeInset = rightSlowModeInset + if buttonTitleUpdated && !transition.isAnimated { transition = .animated(duration: 0.3, curve: .easeInOut) } - let baseWidth = width - leftInset - leftMenuInset - rightInset + let baseWidth = width - leftInset - leftMenuInset - rightInset - rightSlowModeInset let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: maxHeight, metrics: metrics) var panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) if displayBotStartButton { @@ -2332,6 +2356,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, transition: transition, interfaceState: presentationInterfaceState) } + let slowModeButtonFrame = CGRect(origin: CGPoint(x: hideOffset.x + width - rightInset - 5.0 - slowModeButtonSize.width + composeButtonsOffset, y: hideOffset.y + panelHeight - minimalHeight + 6.0), size: slowModeButtonSize) + transition.updateFrame(node: self.slowModeButton, frame: slowModeButtonFrame) + if let _ = interfaceState.inputTextPanelState.mediaRecordingState { let text: String = interfaceState.strings.VoiceOver_MessageContextSend let mediaRecordingAccessibilityArea: AccessibilityAreaNode @@ -2463,7 +2490,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.slowmodePlaceholderNode?.isHidden = true } - var nextButtonTopRight = CGPoint(x: hideOffset.x + width - rightInset - textFieldInsets.right - accessoryButtonInset, y: hideOffset.y + panelHeight - textFieldInsets.bottom - minimalInputHeight) + var nextButtonTopRight = CGPoint(x: hideOffset.x + width - rightInset - textFieldInsets.right - accessoryButtonInset - rightSlowModeInset, y: hideOffset.y + panelHeight - textFieldInsets.bottom - minimalInputHeight) for (item, button) in self.accessoryItemButtons.reversed() { let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight) button.updateLayout(item: item, size: buttonSize) @@ -2736,6 +2763,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch return panelHeight } + @objc private func slowModeButtonPressed() { + self.interfaceInteraction?.openBoostToUnrestrict() + } + @objc private func viewOncePressed() { guard let context = self.context, let interfaceState = self.presentationInterfaceState else { return @@ -3239,7 +3270,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch composeButtonsOffset = 44.0 } - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - self.leftMenuInset, maxHeight: maxHeight, metrics: metrics) + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - self.leftMenuInset - self.rightSlowModeInset, maxHeight: maxHeight, metrics: metrics) let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) var textFieldMinHeight: CGFloat = 33.0 if let presentationInterfaceState = self.presentationInterfaceState { @@ -3544,7 +3575,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } - self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: animated) + let _ = hideMicButton +// self.updateActionButtons(hasText: inputHasText, hideMicButton: hideMicButton, animated: animated) self.updateTextHeight(animated: animated) } @@ -3605,7 +3637,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if (hasText || self.keepSendButtonEnabled && !mediaInputIsActive) { hideMicButton = true - if self.actionButtons.sendContainerNode.alpha.isZero { + + if self.actionButtons.sendContainerNode.alpha.isZero && self.rightSlowModeInset.isZero { self.actionButtons.sendContainerNode.alpha = 1.0 self.actionButtons.sendButtonRadialStatusNode?.alpha = 1.0 self.actionButtons.updateAccessibility() @@ -3621,6 +3654,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } } } + if self.slowModeButton.alpha.isZero && self.rightSlowModeInset > 0.0 { + self.slowModeButton.alpha = 1.0 + if animated { + self.slowModeButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + if animateWithBounce { + self.slowModeButton.layer.animateSpring(from: NSNumber(value: Float(0.1)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.6) + } else { + self.slowModeButton.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25) + } + } + } } else { if !self.actionButtons.sendContainerNode.alpha.isZero { self.actionButtons.sendContainerNode.alpha = 0.0 @@ -3637,6 +3681,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.actionButtons.sendButtonRadialStatusNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } } + if !self.slowModeButton.alpha.isZero { + self.slowModeButton.alpha = 0.0 + if animated { + self.slowModeButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } } } @@ -3694,7 +3744,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch private func updateTextHeight(animated: Bool) { if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, metrics, _, _) = self.validLayout { - let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset, maxHeight: maxHeight, metrics: metrics) + let (_, textFieldHeight) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset - self.rightSlowModeInset, maxHeight: maxHeight, metrics: metrics) let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) if !self.bounds.size.height.isEqual(to: panelHeight) { self.updateHeight(animated) @@ -4620,3 +4670,97 @@ private final class MenuIconNode: ManagedAnimationNode { } } } + +private func generateClearImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 17.0, height: 17.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + context.setStrokeColor(UIColor.clear.cgColor) + context.setLineCap(.round) + context.setLineWidth(1.66) + context.move(to: CGPoint(x: 6.0, y: 6.0)) + context.addLine(to: CGPoint(x: 11.0, y: 11.0)) + context.strokePath() + context.move(to: CGPoint(x: size.width - 6.0, y: 6.0)) + context.addLine(to: CGPoint(x: size.width - 11.0, y: 11.0)) + context.strokePath() + }) +} + + +private final class BoostSlowModeButton: HighlightTrackingButtonNode { + let backgroundNode: ASImageNode + let textNode: ImmediateAnimatedCountLabelNode + let iconNode: ASImageNode + + private var updateTimer: SwiftSignalKit.Timer? + + var requestUpdate: () -> Void = {} + + override init(pointerStyle: PointerStyle? = nil) { + self.backgroundNode = ASImageNode() + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.clipsToBounds = true + self.backgroundNode.image = generateGradientImage(size: CGSize(width: 100.0, height: 2.0), scale: 1.0, colors: [UIColor(rgb: 0x9076ff), UIColor(rgb: 0xbc6de8)], locations: [0.0, 1.0], direction: .horizontal) + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.image = generateClearImage(color: .white) + + self.textNode = ImmediateAnimatedCountLabelNode() + self.textNode.isUserInteractionEnabled = false + + super.init(pointerStyle: pointerStyle) + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.textNode) + } + + func update(size: CGSize, interfaceState: ChatPresentationInterfaceState) -> CGSize { + var text = "" + if let slowmodeState = interfaceState.slowmodeState, case let .timestamp(validUntilTimestamp) = slowmodeState.variant { + let timestamp = CGFloat(Date().timeIntervalSince1970) + let relativeTimestamp = CGFloat(validUntilTimestamp) - timestamp + text = stringForDuration(Int32(relativeTimestamp)) + + self.updateTimer?.invalidate() + self.updateTimer = SwiftSignalKit.Timer(timeout: 1.0 / 60.0, repeat: false, completion: { [weak self] in + self?.requestUpdate() + }, queue: .mainQueue()) + self.updateTimer?.start() + } else { + self.updateTimer?.invalidate() + self.updateTimer = nil + } + + let font = Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]) + let textColor = UIColor.white + + var segments: [AnimatedCountLabelNode.Segment] = [] + var textCount = 0 + + for char in text { + if let intValue = Int(String(char)) { + segments.append(.number(intValue, NSAttributedString(string: String(char), font: font, textColor: textColor))) + } else { + segments.append(.text(textCount, NSAttributedString(string: String(char), font: font, textColor: textColor))) + textCount += 1 + } + } + self.textNode.segments = segments + + let textSize = self.textNode.updateLayout(size: CGSize(width: 200.0, height: 100.0), animated: true) + let totalSize = CGSize(width: textSize.width > 0.0 ? textSize.width + 38.0 : 33.0, height: 33.0) + + self.backgroundNode.frame = CGRect(origin: .zero, size: totalSize) + self.backgroundNode.cornerRadius = totalSize.height / 2.0 + self.textNode.frame = CGRect(origin: CGPoint(x: 9.0, y: floorToScreenPixels((totalSize.height - textSize.height) / 2.0)), size: textSize) + if let icon = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: totalSize.width - icon.size.width - 7.0, y: floorToScreenPixels((totalSize.height - icon.size.height) / 2.0)), size: icon.size) + } + return totalSize + } +}