From ee38ee55d4969320b0b7ceafe249a635eb07bddc Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 5 May 2025 18:42:51 +0400 Subject: [PATCH] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 5 + .../Sources/CallListController.swift | 17 +- .../Sources/ChatListControllerNode.swift | 2 +- .../Sources/Node/ChatListNode.swift | 4 +- .../Sources/ReactionButtonListComponent.swift | 2 +- .../ContextUI/Sources/ContextController.swift | 7 +- .../DrawingUI/Sources/ColorPickerScreen.swift | 2 +- .../TelegramNotices/Sources/Notices.swift | 32 ++ .../CameraScreen/Sources/CameraScreen.swift | 21 +- .../Sources/GiftOptionsScreen.swift | 13 +- .../Sources/FilterSelectorComponent.swift | 9 + .../Sources/GiftStoreScreen.swift | 68 ++-- .../Sources/LoadingShimmerComponent.swift | 13 +- .../Sources/GiftViewScreen.swift | 4 +- .../Components/MediaEditorScreen/BUILD | 1 + .../Sources/MediaEditorScreen.swift | 34 +- .../Sources/PeerInfoScreenAvatarSetup.swift | 2 +- .../WebUI/Sources/WebAppController.swift | 320 +++++++++++------- 18 files changed, 389 insertions(+), 167 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c7ef0e4901..e4cd94bb7e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14315,3 +14315,8 @@ Sorry for the inconvenience."; "Gift.Buy.Confirm.Text.Stars_any" = "**%@** Stars"; "Gift.Buy.Confirm.BuyFor_1" = "Buy for %@ Star"; "Gift.Buy.Confirm.BuyFor_any" = "Buy for %@ Stars"; + +"Calls.HideCallsTab" = "Hide Calls Tab"; + +"Story.Editor.TooltipSelection_1" = "Tap here to view your %@ story"; +"Story.Editor.TooltipSelection_any" = "Tap here to view your %@ stories"; diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 9475b141c7..407693bec4 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -16,6 +16,7 @@ import TelegramBaseController import InviteLinksUI import UndoUI import TelegramCallsUI +import TelegramUIPreferences public enum CallListControllerMode { case tab @@ -734,10 +735,22 @@ public final class CallListController: TelegramBaseController { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in c?.dismiss(completion: { [weak self] in - guard let strongSelf = self else { + guard let self else { return } - strongSelf.callPressed() + self.callPressed() + }) + }))) + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_HideCallsTab, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/HideIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, f in + c?.dismiss(completion: { [weak self] in + guard let self else { + return + } + let _ = updateCallListSettingsInteractively(accountManager: self.context.sharedContext.accountManager, { + $0.withUpdatedShowTab(false) + }).start() }) }))) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 4d8534e741..357cf6e848 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1015,7 +1015,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele } } - itemNode.listNode.isMainTab.set(self.availableFilters.firstIndex(where: { $0.id == id }) == 0 ? true : false) + itemNode.listNode.isMainTab.set(self.availableFilters.firstIndex(where: { $0.id == id }) == 0) itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: itemInlineNavigationTransitionFraction, storiesInset: storiesInset, transition: nodeTransition) if let scrollingOffset = self.scrollingOffset { itemNode.updateScrollingOffset(navigationHeight: scrollingOffset.navigationHeight, offset: scrollingOffset.offset, transition: nodeTransition) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 302bb3a8cf..5f679acdc0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -2092,8 +2092,6 @@ public final class ChatListNode: ListView { return .single(.setupPhoto(accountPeer)) } else if suggestions.contains(.gracePremium) { return .single(.premiumGrace) - } else if suggestions.contains(.setupBirthday) && birthday == nil { - return .single(.setupBirthday) } else if suggestions.contains(.xmasPremiumGift) { return .single(.xmasPremiumGift) } else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager { @@ -2149,6 +2147,8 @@ public final class ChatListNode: ListView { } return .birthdayPremiumGift(peers: todayBirthdayPeers, birthdays: birthdays) } + } else if suggestions.contains(.setupBirthday) && birthday == nil { + return .single(.setupBirthday) } else if case let .link(id, url, title, subtitle) = suggestions.first(where: { if case .link = $0 { return true } else { return false} }) { return .single(.link(id: id, url: url, title: title, subtitle: subtitle)) } else { diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index ce867a96d1..a74dda7990 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -501,7 +501,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) animationFraction = animationState.curve.solve(at: animationFraction) if animationState.fromExtracted != isExtracted { - fixedTransitionDirection = isExtracted ? true : false + fixedTransitionDirection = isExtracted } } else { animationFraction = 1.0 diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 6842c65076..771e502072 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -527,7 +527,12 @@ final class ContextControllerNode: ViewControllerTracingNode, ASScrollViewDelega guard let strongSelf = self, let _ = gesture else { return } - let localPoint = strongSelf.view.convert(point, from: view) + let localPoint: CGPoint + if let layout = strongSelf.validLayout, layout.metrics.isTablet, layout.size.width > layout.size.height, let view { + localPoint = view.convert(point, to: nil) + } else { + localPoint = strongSelf.view.convert(point, from: view) + } let initialPoint: CGPoint if let current = strongSelf.initialContinueGesturePoint { initialPoint = current diff --git a/submodules/DrawingUI/Sources/ColorPickerScreen.swift b/submodules/DrawingUI/Sources/ColorPickerScreen.swift index d7eb09b353..8b0d1fc502 100644 --- a/submodules/DrawingUI/Sources/ColorPickerScreen.swift +++ b/submodules/DrawingUI/Sources/ColorPickerScreen.swift @@ -697,7 +697,7 @@ final class ColorGridComponent: Component { bottomRightRadius = largeCornerRadius } - let isLight = (selectedColor?.toUIColor().lightness ?? 1.0) < 0.5 ? true : false + let isLight = (selectedColor?.toUIColor().lightness ?? 1.0) < 0.5 var selectionKnobImage = ColorSelectionImage(size: CGSize(width: squareSize, height: squareSize), topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius, isLight: isLight) if selectionKnobImage != self.selectionKnobImage { diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index aebfa04f96..29a3e4862b 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -203,6 +203,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case channelSendGiftTooltip = 76 case starGiftWearTips = 77 case channelSuggestTooltip = 78 + case multipleStoriesTooltip = 79 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -564,6 +565,10 @@ private struct ApplicationSpecificNoticeKeys { static func channelSuggestTooltip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.channelSuggestTooltip.key) } + + static func multipleStoriesTooltip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.multipleStoriesTooltip.key) + } } public struct ApplicationSpecificNotice { @@ -2426,4 +2431,31 @@ public struct ApplicationSpecificNotice { return Int(previousValue) } } + + public static func getMultipleStoriesTooltip(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Int32 in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.multipleStoriesTooltip())?.get(ApplicationSpecificCounterNotice.self) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementMultipleStoriesTooltip(accountManager: AccountManager, count: Int = 1) -> Signal { + return accountManager.transaction { transaction -> Int in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.multipleStoriesTooltip())?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + let previousValue = currentValue + currentValue += Int32(count) + + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.multipleStoriesTooltip(), entry) + } + + return Int(previousValue) + } + } } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index bda8dd68ee..5b57038105 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -3630,7 +3630,13 @@ public class CameraScreenImpl: ViewController, CameraScreen { self.node.resumeCameraCapture(fromGallery: true) } - var dismissControllerImpl: (() -> Void)? + class DismissArgs { + var resumeOnDismiss = true + } + + var dismissControllerImpl: ((Bool) -> Void)? + let dismissArgs = DismissArgs() + let controller: ViewController if let current = self.galleryController { controller = current @@ -3686,7 +3692,7 @@ public class CameraScreenImpl: ViewController, CameraScreen { } } - dismissControllerImpl?() + dismissControllerImpl?(true) } else { stopCameraCapture() @@ -3759,17 +3765,19 @@ public class CameraScreenImpl: ViewController, CameraScreen { self.node.collage?.addResults(signals: results) } } else { + self.node.animateOutToEditor() if let assets = results as? [PHAsset] { self.completion(.single(.assets(assets)), nil, self.remainingStoryCount, { - }) } } self.galleryController = nil - dismissControllerImpl?() + dismissControllerImpl?(false) }, dismissed: { [weak self] in - resumeCameraCapture() + if dismissArgs.resumeOnDismiss { + resumeCameraCapture() + } if let self { self.node.hasGallery = false self.node.requestUpdateLayout(transition: .immediate) @@ -3780,7 +3788,8 @@ public class CameraScreenImpl: ViewController, CameraScreen { ) self.galleryController = controller - dismissControllerImpl = { [weak controller] in + dismissControllerImpl = { [weak controller] resume in + dismissArgs.resumeOnDismiss = resume controller?.dismiss(animated: true) } } diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index e28b5d6219..5b79de4903 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -235,6 +235,8 @@ final class GiftOptionsScreenComponent: Component { private var chevronImage: (UIImage, PresentationTheme)? + private var resaleConfiguration: StarsSubscriptionConfiguration? + override init(frame: CGRect) { self.scrollView = ScrollView() self.scrollView.showsVerticalScrollIndicator = true @@ -408,9 +410,14 @@ final class GiftOptionsScreenComponent: Component { switch gift { case let .generic(gift): if let availability = gift.availability, availability.remains == 0, let minResaleStars = availability.minResaleStars { - subject = .starGift(gift: gift, price: "⭐️ \(minResaleStars)+") + let priceString = presentationStringsFormattedNumber(Int32(minResaleStars), environment.dateTimeFormat.groupingSeparator) + if let resaleConfiguration = self.resaleConfiguration, minResaleStars == resaleConfiguration.starGiftResaleMaxAmount || availability.resale == 1 { + subject = .starGift(gift: gift, price: "⭐️ \(priceString)") + } else { + subject = .starGift(gift: gift, price: "⭐️ \(priceString)+") + } } else { - subject = .starGift(gift: gift, price: "⭐️ \(gift.price)") + subject = .starGift(gift: gift, price: "⭐️ \(presentationStringsFormattedNumber(Int32(gift.price), environment.dateTimeFormat.groupingSeparator))") } case let .unique(gift): subject = .uniqueGift(gift: gift, price: nil) @@ -773,6 +780,8 @@ final class GiftOptionsScreenComponent: Component { self.optionsPromise.set(component.context.engine.payments.starsTopUpOptions() |> map(Optional.init)) } + + self.resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) } self.component = component diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift index 5b31427f89..8d4bc8fff3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/FilterSelectorComponent.swift @@ -109,6 +109,15 @@ public final class FilterSelectorComponent: Component { return true } + func animateIn() { + for (_, item) in self.visibleItems { + if let itemView = item.title.view { + itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + itemView.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + } + } + } + func update(component: FilterSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.component = component self.state = state diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift index 1ca1c6317f..ae889947fc 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift @@ -27,6 +27,8 @@ import UndoUI import ContextUI import LottieComponent +private let minimumCountToDisplayFilters = 18 + final class GiftStoreScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -93,7 +95,8 @@ final class GiftStoreScreenComponent: Component { private var starsStateDisposable: Disposable? private var starsState: StarsContext.State? - + private var initialCount: Int? + private var component: GiftStoreScreenComponent? private(set) weak var state: State? private var environment: EnvironmentType? @@ -148,6 +151,13 @@ final class GiftStoreScreenComponent: Component { } } + private var effectiveIsLoading: Bool { + if self.state?.starGiftsState?.gifts == nil || self.state?.starGiftsState?.dataState == .loading { + return true + } + return false + } + private func updateScrolling(interactive: Bool = false, transition: ComponentTransition) { guard let environment = self.environment, let component = self.component, self.state?.starGiftsState?.dataState != .loading else { return @@ -163,6 +173,11 @@ final class GiftStoreScreenComponent: Component { transition.setAlpha(view: topSeparator, alpha: topPanelAlpha) } + var topInset = environment.navigationHeight + 39.0 + if let initialCount = self.initialCount, initialCount < minimumCountToDisplayFilters { + topInset = environment.navigationHeight + } + let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -10.0) if let starGifts = self.effectiveGifts { let sideInset: CGFloat = 16.0 + environment.safeInsets.left @@ -172,7 +187,7 @@ final class GiftStoreScreenComponent: Component { let starsOptionSize = CGSize(width: optionWidth, height: 154.0) var validIds: [AnyHashable] = [] - var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: environment.navigationHeight + 39.0 + 9.0), size: starsOptionSize) + var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 9.0), size: starsOptionSize) let controller = environment.controller @@ -337,7 +352,6 @@ final class GiftStoreScreenComponent: Component { showClearFilters = true } - let topInset: CGFloat = environment.navigationHeight + 39.0 let bottomInset: CGFloat = environment.safeInsets.bottom var emptyResultsActionFrame = CGRect( @@ -443,7 +457,7 @@ final class GiftStoreScreenComponent: Component { } func openSortContextMenu(sourceView: UIView) { - guard let component = self.component, let controller = self.environment?.controller() else { + guard let component = self.component, let controller = self.environment?.controller(), !self.effectiveIsLoading else { return } @@ -486,10 +500,10 @@ final class GiftStoreScreenComponent: Component { } func openModelContextMenu(sourceView: UIView) { - guard let component = self.component, let controller = self.environment?.controller() else { + guard let component = self.component, let controller = self.environment?.controller(), !self.effectiveIsLoading else { return } - + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let searchQueryPromise = ValuePromise("") @@ -579,7 +593,7 @@ final class GiftStoreScreenComponent: Component { } func openBackdropContextMenu(sourceView: UIView) { - guard let component = self.component, let controller = self.environment?.controller() else { + guard let component = self.component, let controller = self.environment?.controller(), !self.effectiveIsLoading else { return } @@ -672,7 +686,7 @@ final class GiftStoreScreenComponent: Component { } func openSymbolContextMenu(sourceView: UIView) { - guard let component = self.component, let controller = self.environment?.controller() else { + guard let component = self.component, let controller = self.environment?.controller(), !self.effectiveIsLoading else { return } @@ -789,10 +803,7 @@ final class GiftStoreScreenComponent: Component { } self.component = component - var isLoading = false - if self.state?.starGiftsState?.gifts == nil || self.state?.starGiftsState?.dataState == .loading { - isLoading = true - } + let isLoading = self.effectiveIsLoading let theme = environment.theme let strings = environment.strings @@ -808,7 +819,10 @@ final class GiftStoreScreenComponent: Component { var contentHeight: CGFloat = 0.0 contentHeight += environment.navigationHeight - let topPanelHeight = environment.navigationHeight + 39.0 + var topPanelHeight = environment.navigationHeight + 39.0 + if let initialCount = self.initialCount, initialCount < minimumCountToDisplayFilters { + topPanelHeight = environment.navigationHeight + } let topPanelSize = self.topPanel.update( transition: transition, @@ -913,7 +927,10 @@ final class GiftStoreScreenComponent: Component { } let effectiveCount: Int32 - if let count = self.effectiveGifts?.count { + if let count = self.effectiveGifts?.count, count > 0 || self.initialCount != nil { + if self.initialCount == nil { + self.initialCount = count + } effectiveCount = Int32(count) } else if let resale = component.gift.availability?.resale { effectiveCount = Int32(resale) @@ -1028,13 +1045,15 @@ final class GiftStoreScreenComponent: Component { } )) + let loadingTransition: ComponentTransition = .easeInOut(duration: 0.25) + let filterSize = self.filterSelector.update( transition: transition, component: AnyComponent(FilterSelectorComponent( context: component.context, colors: FilterSelectorComponent.Colors( foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65), - background: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15) + background: theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85) ), items: filterItems )), @@ -1043,9 +1062,14 @@ final class GiftStoreScreenComponent: Component { ) if let filterSelectorView = self.filterSelector.view { if filterSelectorView.superview == nil { + filterSelectorView.alpha = 0.0 self.addSubview(filterSelectorView) } transition.setFrame(view: filterSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - filterSize.width) / 2.0), y: topInset + 56.0), size: filterSize)) + + if let initialCount = self.initialCount, initialCount >= minimumCountToDisplayFilters { + loadingTransition.setAlpha(view: filterSelectorView, alpha: 1.0) + } } if let starGifts = self.state?.starGiftsState?.gifts { @@ -1088,14 +1112,13 @@ final class GiftStoreScreenComponent: Component { self.updateScrolling(transition: transition) - let loadingTransition: ComponentTransition = .easeInOut(duration: 0.25) if isLoading { self.loadingNode.update(size: availableSize, theme: environment.theme, transition: .immediate) loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 1.0) } else { loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 0.0) } - transition.setFrame(view: self.loadingNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight + 39.0 + 7.0), size: availableSize)) + transition.setFrame(view: self.loadingNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight), size: availableSize)) return availableSize } @@ -1108,19 +1131,22 @@ final class GiftStoreScreenComponent: Component { final class State: ComponentState { private let context: AccountContext var peerId: EnginePeer.Id + private let gift: StarGift.Gift + private var disposable: Disposable? fileprivate let starGiftsContext: ResaleGiftsContext fileprivate var starGiftsState: ResaleGiftsContext.State? - + init( context: AccountContext, peerId: EnginePeer.Id, - giftId: Int64 + gift: StarGift.Gift ) { self.context = context self.peerId = peerId - self.starGiftsContext = ResaleGiftsContext(account: context.account, giftId: giftId) + self.gift = gift + self.starGiftsContext = ResaleGiftsContext(account: context.account, giftId: gift.id) super.init() @@ -1140,7 +1166,7 @@ final class GiftStoreScreenComponent: Component { } func makeState() -> State { - return State(context: self.context, peerId: self.peerId, giftId: self.gift.id) + return State(context: self.context, peerId: self.peerId, gift: self.gift) } func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/LoadingShimmerComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/LoadingShimmerComponent.swift index b9f6c04b2f..73e314820f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/LoadingShimmerComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/LoadingShimmerComponent.swift @@ -154,10 +154,17 @@ final class LoadingShimmerNode: ASDisplayNode { context.setFillColor(theme.list.blocksBackgroundColor.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) - var currentY: CGFloat = 0.0 + let sideInset: CGFloat = 16.0 + + let filterSpacing: CGFloat = 6.0 + let filterWidth = (size.width - sideInset * 2.0 - filterSpacing * 3.0) / 4.0 + for i in 0 ..< 4 { + context.addPath(CGPath(roundedRect: CGRect(origin: CGPoint(x: sideInset + (filterWidth + filterSpacing) * CGFloat(i), y: 0.0), size: CGSize(width: filterWidth, height: 28.0)), cornerWidth: 14.0, cornerHeight: 14.0, transform: nil)) + } + + var currentY: CGFloat = 39.0 + 7.0 var rowIndex: Int = 0 - let sideInset: CGFloat = 16.0// + environment.safeInsets.left let optionSpacing: CGFloat = 10.0 let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0 let itemSize = CGSize(width: optionWidth, height: 154.0) @@ -167,7 +174,7 @@ final class LoadingShimmerNode: ASDisplayNode { while currentY < size.height { for i in 0 ..< 3 { - let itemOrigin = CGPoint(x: sideInset + CGFloat(i) * (itemSize.width + optionSpacing), y: 2.0 + CGFloat(rowIndex) * (itemSize.height + optionSpacing)) + let itemOrigin = CGPoint(x: sideInset + CGFloat(i) * (itemSize.width + optionSpacing), y: 39.0 + 9.0 + CGFloat(rowIndex) * (itemSize.height + optionSpacing)) context.addPath(CGPath(roundedRect: CGRect(origin: itemOrigin, size: itemSize), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)) } currentY += itemSize.height diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index ff8d1fb812..3d8134dfbc 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -496,7 +496,7 @@ private final class GiftViewSheetContent: CombinedComponent { if currentTime > starsConvertMaxDate { let days: Int32 = Int32(ceil(Float(configuration.convertToStarsPeriod) / 86400.0)) - let controller = textAlertController( + let alertController = textAlertController( context: self.context, title: presentationData.strings.Gift_Convert_Title, text: presentationData.strings.Gift_Convert_Period_Unavailable_Text(presentationData.strings.Gift_Convert_Period_Unavailable_Days(days)).string, @@ -505,7 +505,7 @@ private final class GiftViewSheetContent: CombinedComponent { ], parseMarkdown: true ) - controller.present(controller, in: .window(.root)) + controller.present(alertController, in: .window(.root)) } else { let delta = starsConvertMaxDate - currentTime let days: Int32 = Int32(ceil(Float(delta) / 86400.0)) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD index b07b9b4bfb..814fcb4edd 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD @@ -67,6 +67,7 @@ swift_library( "//submodules/TelegramUI/Components/SaveProgressScreen", "//submodules/TelegramUI/Components/MediaAssetsContext", "//submodules/CheckNode", + "//submodules/TelegramNotices", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 3cee6aa8cd..ecacd3939b 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -49,6 +49,7 @@ import StickerPickerScreen import UIKitRuntimeUtils import ImageObjectSeparation import SaveProgressScreen +import TelegramNotices private let playbackButtonTag = GenericComponentViewTag() private let muteButtonTag = GenericComponentViewTag() @@ -58,6 +59,7 @@ private let drawButtonTag = GenericComponentViewTag() private let textButtonTag = GenericComponentViewTag() private let stickerButtonTag = GenericComponentViewTag() private let dayNightButtonTag = GenericComponentViewTag() +private let selectionButtonTag = GenericComponentViewTag() final class MediaEditorScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -2320,7 +2322,8 @@ final class MediaEditorScreenComponent: Component { controller.hapticFeedback.impact(.light) } }, - animateAlpha: false + animateAlpha: false, + tag: selectionButtonTag )), environment: {}, containerSize: CGSize(width: 33.0, height: 33.0) @@ -4744,6 +4747,33 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID self.controller?.present(tooltipController, in: .current) } + private var displayedSelectionTooltip = false + func presentSelectionTooltip() { + guard let sourceView = self.componentHost.findTaggedView(tag: selectionButtonTag), !self.displayedSelectionTooltip, self.items.count > 1 else { + return + } + + self.displayedSelectionTooltip = true + + let _ = (ApplicationSpecificNotice.getMultipleStoriesTooltip(accountManager: self.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { [weak self] count in + guard let self, count < 3 else { + return + } + let parentFrame = self.view.convert(self.bounds, to: nil) + let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) + let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 3.0), size: CGSize()) + + let text = self.presentationData.strings.Story_Editor_TooltipSelection(Int32(self.items.count)) + let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), location: .point(location, .bottom), displayDuration: .default, inset: 8.0, shouldDismissOnTouch: { _, _ in + return .dismiss(consume: false) + }) + self.controller?.present(tooltipController, in: .current) + + let _ = ApplicationSpecificNotice.incrementMultipleStoriesTooltip(accountManager: self.context.sharedContext.accountManager).start() + }) + } + fileprivate weak var saveTooltip: SaveProgressScreen? func presentSaveTooltip() { guard let controller = self.controller else { @@ -5725,6 +5755,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID if hasAppeared && !self.hasAppeared { self.hasAppeared = hasAppeared + + self.presentSelectionTooltip() } let componentSize = self.componentHost.update( diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index 00f309ace7..9bdfe72622 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -347,7 +347,7 @@ extension PeerInfoScreenImpl { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) - let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false) + let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom) if [.suggest, .fallback].contains(mode) { } else { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 922679abce..4324257087 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -895,8 +895,13 @@ public final class WebAppController: ViewController, AttachmentContainable { if let controller = self.controller { webView.updateMetrics(height: viewportFrame.height, isExpanded: controller.isContainerExpanded(), isStable: !controller.isContainerPanning(), transition: transition) - let contentInsetsData = "{top:\(contentTopInset), bottom:0.0, left:0.0, right:0.0}" - webView.sendEvent(name: "content_safe_area_changed", data: contentInsetsData) + let data: JSON = [ + "top": Double(contentTopInset), + "bottom": 0.0, + "left": 0.0, + "right": 0.0 + ] + webView.sendEvent(name: "content_safe_area_changed", data: data.string) if self.updateWebViewWhenStable && !controller.isContainerPanning() { self.updateWebViewWhenStable = false @@ -1333,7 +1338,7 @@ public final class WebAppController: ViewController, AttachmentContainable { controller.completion = { [weak self] result in if let strongSelf = self { if let result = result { - strongSelf.sendQrCodeScannedEvent(data: result) + strongSelf.sendQrCodeScannedEvent(dataString: result) } else { strongSelf.sendQrCodeScannerClosedEvent() } @@ -1923,8 +1928,11 @@ public final class WebAppController: ViewController, AttachmentContainable { } private func sendInvoiceClosedEvent(slug: String, result: InvoiceCloseResult) { - let paramsString = "{slug: \"\(slug)\", status: \"\(result.string)\"}" - self.webView?.sendEvent(name: "invoice_closed", data: paramsString) + let data: JSON = [ + "slug": slug, + "status": result.string + ] + self.webView?.sendEvent(name: "invoice_closed", data: data.string) } fileprivate func sendBackButtonEvent() { @@ -1936,24 +1944,23 @@ public final class WebAppController: ViewController, AttachmentContainable { } fileprivate func sendAlertButtonEvent(id: String?) { - var paramsString: String? - if let id = id { - paramsString = "{button_id: \"\(id)\"}" + var data: [String: Any] = [:] + if let id { + data["button_id"] = id } - self.webView?.sendEvent(name: "popup_closed", data: paramsString ?? "{}") - } - - fileprivate func sendPhoneRequestedEvent(phone: String?) { - var paramsString: String? - if let phone = phone { - paramsString = "{phone_number: \"\(phone)\"}" + if let serializedData = JSON(dictionary: data)?.string { + self.webView?.sendEvent(name: "popup_closed", data: serializedData) } - self.webView?.sendEvent(name: "phone_requested", data: paramsString) } - - fileprivate func sendQrCodeScannedEvent(data: String?) { - let paramsString = data.flatMap { "{data: \"\($0)\"}" } ?? "{}" - self.webView?.sendEvent(name: "qr_text_received", data: paramsString) + + fileprivate func sendQrCodeScannedEvent(dataString: String?) { + var data: [String: Any] = [:] + if let dataString { + data["data"] = dataString + } + if let serializedData = JSON(dictionary: data)?.string { + self.webView?.sendEvent(name: "qr_text_received", data: serializedData) + } } fileprivate func sendQrCodeScannerClosedEvent() { @@ -1961,14 +1968,15 @@ public final class WebAppController: ViewController, AttachmentContainable { } fileprivate func sendClipboardTextEvent(requestId: String, fillData: Bool) { - var paramsString: String + var data: [String: Any] = [:] + data["req_id"] = requestId if fillData { - let data = UIPasteboard.general.string ?? "" - paramsString = "{req_id: \"\(requestId)\", data: \"\(data)\"}" - } else { - paramsString = "{req_id: \"\(requestId)\"}" + let pasteboardData = UIPasteboard.general.string ?? "" + data["data"] = pasteboardData + } + if let serializedData = JSON(dictionary: data)?.string { + self.webView?.sendEvent(name: "clipboard_text_received", data: serializedData) } - self.webView?.sendEvent(name: "clipboard_text_received", data: paramsString) } fileprivate func requestWriteAccess() { @@ -1977,13 +1985,10 @@ public final class WebAppController: ViewController, AttachmentContainable { } let sendEvent: (Bool) -> Void = { success in - var paramsString: String - if success { - paramsString = "{status: \"allowed\"}" - } else { - paramsString = "{status: \"cancelled\"}" - } - self.webView?.sendEvent(name: "write_access_requested", data: paramsString) + let data: JSON = [ + "status": success ? "allowed" : "cancelled" + ] + self.webView?.sendEvent(name: "write_access_requested", data: data.string) } let _ = (self.context.engine.messages.canBotSendMessages(botId: controller.botId) @@ -2021,13 +2026,10 @@ public final class WebAppController: ViewController, AttachmentContainable { return } let sendEvent: (Bool) -> Void = { success in - var paramsString: String - if success { - paramsString = "{status: \"sent\"}" - } else { - paramsString = "{status: \"cancelled\"}" - } - self.webView?.sendEvent(name: "phone_requested", data: paramsString) + let data: JSON = [ + "status": success ? "sent" : "cancelled" + ] + self.webView?.sendEvent(name: "phone_requested", data: data.string) } let _ = (self.context.engine.data.get( @@ -2348,28 +2350,15 @@ public final class WebAppController: ViewController, AttachmentContainable { state.opaqueToken = encryptedData return state }) - - var data: [String: Any] = [:] - data["status"] = "updated" - - guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else { - return - } - guard let jsonDataString = String(data: jsonData, encoding: .utf8) else { - return - } - self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString) + let data: JSON = [ + "status": "updated" + ] + self.webView?.sendEvent(name: "biometry_token_updated", data: data.string) } else { - var data: [String: Any] = [:] - data["status"] = "failed" - - guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else { - return - } - guard let jsonDataString = String(data: jsonData, encoding: .utf8) else { - return - } - self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString) + let data: JSON = [ + "status": "failed" + ] + self.webView?.sendEvent(name: "biometry_token_updated", data: data.string) } } }.start() @@ -2379,17 +2368,10 @@ public final class WebAppController: ViewController, AttachmentContainable { state.opaqueToken = nil return state }) - - var data: [String: Any] = [:] - data["status"] = "removed" - - guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else { - return - } - guard let jsonDataString = String(data: jsonData, encoding: .utf8) else { - return - } - self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString) + let data: JSON = [ + "status": "removed" + ] + self.webView?.sendEvent(name: "biometry_token_updated", data: data.string) } } @@ -2410,13 +2392,18 @@ public final class WebAppController: ViewController, AttachmentContainable { return } guard controller.isFullscreen != isFullscreen else { - self.webView?.sendEvent(name: "fullscreen_failed", data: "{error: \"ALREADY_FULLSCREEN\"}") + let data: JSON = [ + "error": "ALREADY_FULLSCREEN" + ] + self.webView?.sendEvent(name: "fullscreen_failed", data: data.string) return } - let paramsString = "{is_fullscreen: \( isFullscreen ? "true" : "false" )}" - self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString) - + let data: JSON = [ + "is_fullscreen": isFullscreen + ] + self.webView?.sendEvent(name: "fullscreen_changed", data: data.string) + controller.isFullscreen = isFullscreen if isFullscreen { controller.requestAttachmentMenuExpansion() @@ -2436,7 +2423,10 @@ public final class WebAppController: ViewController, AttachmentContainable { private var isAccelerometerActive = false fileprivate func setIsAccelerometerActive(_ isActive: Bool, refreshRate: Double? = nil) { guard self.motionManager.isAccelerometerAvailable else { - self.webView?.sendEvent(name: "accelerometer_failed", data: "{error: \"UNSUPPORTED\"}") + let data: JSON = [ + "error": "UNSUPPORTED" + ] + self.webView?.sendEvent(name: "accelerometer_failed", data: data.string) return } guard self.isAccelerometerActive != isActive else { @@ -2451,15 +2441,17 @@ public final class WebAppController: ViewController, AttachmentContainable { } else { self.motionManager.accelerometerUpdateInterval = 1.0 } - self.motionManager.startAccelerometerUpdates(to: OperationQueue.main) { [weak self] data, error in - guard let self, let data else { + self.motionManager.startAccelerometerUpdates(to: OperationQueue.main) { [weak self] accelerometerData, error in + guard let self, let accelerometerData else { return } - let gravityConstant = 9.81 - self.webView?.sendEvent( - name: "accelerometer_changed", - data: "{x: \(data.acceleration.x * gravityConstant), y: \(data.acceleration.y * gravityConstant), z: \(data.acceleration.z * gravityConstant)}" - ) + let gravityConstant: Double = 9.81 + let data: JSON = [ + "x": Double(accelerometerData.acceleration.x * gravityConstant), + "y": Double(accelerometerData.acceleration.y * gravityConstant), + "z": Double(accelerometerData.acceleration.z * gravityConstant) + ] + self.webView?.sendEvent(name: "accelerometer_changed", data: data.string) } } else { if self.motionManager.isAccelerometerActive { @@ -2472,7 +2464,10 @@ public final class WebAppController: ViewController, AttachmentContainable { private var isDeviceOrientationActive = false fileprivate func setIsDeviceOrientationActive(_ isActive: Bool, refreshRate: Double? = nil, absolute: Bool = false) { guard self.motionManager.isDeviceMotionAvailable else { - self.webView?.sendEvent(name: "device_orientation_failed", data: "{error: \"UNSUPPORTED\"}") + let data: JSON = [ + "error": "UNSUPPORTED" + ] + self.webView?.sendEvent(name: "device_orientation_failed", data: data.string) return } guard self.isDeviceOrientationActive != isActive else { @@ -2505,25 +2500,29 @@ public final class WebAppController: ViewController, AttachmentContainable { } effectiveIsAbsolute = false } - self.motionManager.startDeviceMotionUpdates(using: referenceFrame, to: OperationQueue.main) { [weak self] data, error in - guard let self, let data else { + self.motionManager.startDeviceMotionUpdates(using: referenceFrame, to: OperationQueue.main) { [weak self] motionData, error in + guard let self, let motionData else { return } var alpha: Double if effectiveIsAbsolute { - alpha = data.heading * .pi / 180.0 + alpha = motionData.heading * .pi / 180.0 if alpha > .pi { alpha -= 2.0 * .pi } else if alpha < -.pi { alpha += 2.0 * .pi } } else { - alpha = data.attitude.yaw + alpha = motionData.attitude.yaw } - self.webView?.sendEvent( - name: "device_orientation_changed", - data: "{absolute: \(effectiveIsAbsolute ? "true" : "false"), alpha: \(alpha), beta: \(data.attitude.pitch), gamma: \(data.attitude.roll)}" - ) + + let data: JSON = [ + "absolute": effectiveIsAbsolute, + "alpha": Double(alpha), + "beta": Double(motionData.attitude.pitch), + "gamma": Double(motionData.attitude.roll) + ] + self.webView?.sendEvent(name: "device_orientation_changed", data: data.string) } } else { if self.motionManager.isDeviceMotionActive { @@ -2536,7 +2535,10 @@ public final class WebAppController: ViewController, AttachmentContainable { private var isGyroscopeActive = false fileprivate func setIsGyroscopeActive(_ isActive: Bool, refreshRate: Double? = nil) { guard self.motionManager.isGyroAvailable else { - self.webView?.sendEvent(name: "gyroscope_failed", data: "{error: \"UNSUPPORTED\"}") + let data: JSON = [ + "error": "UNSUPPORTED" + ] + self.webView?.sendEvent(name: "gyroscope_failed", data: data.string) return } guard self.isGyroscopeActive != isActive else { @@ -2551,14 +2553,16 @@ public final class WebAppController: ViewController, AttachmentContainable { } else { self.motionManager.gyroUpdateInterval = 1.0 } - self.motionManager.startGyroUpdates(to: OperationQueue.main) { [weak self] data, error in - guard let self, let data else { + self.motionManager.startGyroUpdates(to: OperationQueue.main) { [weak self] gyroData, error in + guard let self, let gyroData else { return } - self.webView?.sendEvent( - name: "gyroscope_changed", - data: "{x: \(data.rotationRate.x), y: \(data.rotationRate.y), z: \(data.rotationRate.z)}" - ) + let data: JSON = [ + "x": Double(gyroData.rotationRate.x), + "y": Double(gyroData.rotationRate.y), + "z": Double(gyroData.rotationRate.z) + ] + self.webView?.sendEvent(name: "gyroscope_changed", data: data.string) } } else { if self.motionManager.isGyroActive { @@ -2575,7 +2579,10 @@ public final class WebAppController: ViewController, AttachmentContainable { let _ = (self.context.engine.messages.getPreparedInlineMessage(botId: controller.botId, id: id) |> deliverOnMainQueue).start(next: { [weak self, weak controller] preparedMessage in guard let self, let controller, let preparedMessage else { - self?.webView?.sendEvent(name: "prepared_message_failed", data: "{error: \"MESSAGE_EXPIRED\"}") + let data: JSON = [ + "error": "MESSAGE_EXPIRED" + ] + self?.webView?.sendEvent(name: "prepared_message_failed", data: data.string) return } let previewController = WebAppMessagePreviewScreen(context: controller.context, botName: controller.botName, botAddress: controller.botAddress, preparedMessage: preparedMessage, completion: { [weak self] result in @@ -2585,7 +2592,10 @@ public final class WebAppController: ViewController, AttachmentContainable { if result { self.webView?.sendEvent(name: "prepared_message_sent", data: nil) } else { - self.webView?.sendEvent(name: "prepared_message_failed", data: "{error: \"USER_DECLINED\"}") + let data: JSON = [ + "error": "USER_DECLINED" + ] + self.webView?.sendEvent(name: "prepared_message_failed", data: data.string) } }) previewController.navigationPresentation = .flatModal @@ -2599,7 +2609,10 @@ public final class WebAppController: ViewController, AttachmentContainable { } guard !fileName.contains("/") && fileName.lengthOfBytes(using: .utf8) < 256 && url.lengthOfBytes(using: .utf8) < 32768 else { - self.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}") + let data: JSON = [ + "status": "cancelled" + ] + self.webView?.sendEvent(name: "file_download_requested", data: data.string) return } @@ -2635,7 +2648,10 @@ public final class WebAppController: ViewController, AttachmentContainable { return } guard canDownload else { - self.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}") + let data: JSON = [ + "status": "cancelled" + ] + self.webView?.sendEvent(name: "file_download_requested", data: data.string) return } var fileSizeString = "" @@ -2646,14 +2662,20 @@ public final class WebAppController: ViewController, AttachmentContainable { let text: String = self.presentationData.strings.WebApp_Download_Text(controller.botName, fileName, fileSizeString).string let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: title, text: text, actions: [ TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { [weak self] in - self?.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}") + let data: JSON = [ + "status": "cancelled" + ] + self?.webView?.sendEvent(name: "file_download_requested", data: data.string) }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.WebApp_Download_Download, action: { [weak self] in self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia) }) ], parseMarkdown: true) alertController.dismissed = { [weak self] byOutsideTap in - self?.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}") + let data: JSON = [ + "status": "cancelled" + ] + self?.webView?.sendEvent(name: "file_download_requested", data: data.string) } controller.present(alertController, in: .window(.root)) }) @@ -2664,7 +2686,10 @@ public final class WebAppController: ViewController, AttachmentContainable { guard let controller = self.controller else { return } - self.webView?.sendEvent(name: "file_download_requested", data: "{status: \"downloading\"}") + let data: JSON = [ + "status": "downloading" + ] + self.webView?.sendEvent(name: "file_download_requested", data: data.string) var removeImpl: (() -> Void)? let fileDownload = FileDownload( @@ -2840,13 +2865,20 @@ public final class WebAppController: ViewController, AttachmentContainable { demoController?.replace(with: c) } controller.parentController()?.push(demoController) - self.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"cancelled\"}") + + let data: JSON = [ + "status": "cancelled" + ] + self.webView?.sendEvent(name: "emoji_status_access_requested", data: data.string) return } let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: botId, enabled: true) |> deliverOnMainQueue).startStandalone(completed: { [weak self] in - self?.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"allowed\"}") + let data: JSON = [ + "status": "allowed" + ] + self?.webView?.sendEvent(name: "emoji_status_access_requested", data: data.string) }) if let botPeer { @@ -2865,7 +2897,10 @@ public final class WebAppController: ViewController, AttachmentContainable { controller.present(resultController, in: .window(.root)) } } else { - self.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"cancelled\"}") + let data: JSON = [ + "status": "cancelled" + ] + self.webView?.sendEvent(name: "emoji_status_access_requested", data: data.string) } let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in @@ -2874,7 +2909,10 @@ public final class WebAppController: ViewController, AttachmentContainable { } ) alertController.dismissed = { [weak self] byOutsideTap in - self?.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"cancelled\"}") + let data: JSON = [ + "status": "cancelled" + ] + self?.webView?.sendEvent(name: "emoji_status_access_requested", data: data.string) } controller.present(alertController, in: .window(.root)) }) @@ -2894,7 +2932,10 @@ public final class WebAppController: ViewController, AttachmentContainable { return } guard let file = files[fileId] else { - self.webView?.sendEvent(name: "emoji_status_failed", data: "{error: \"SUGGESTED_EMOJI_INVALID\"}") + let data: JSON = [ + "error": "SUGGESTED_EMOJI_INVALID" + ] + self.webView?.sendEvent(name: "emoji_status_failed", data: data.string) return } let confirmController = WebAppSetEmojiStatusScreen( @@ -2919,7 +2960,11 @@ public final class WebAppController: ViewController, AttachmentContainable { demoController?.replace(with: c) } controller.parentController()?.push(demoController) - self.webView?.sendEvent(name: "emoji_status_failed", data: "{error: \"USER_DECLINED\"}") + + let data: JSON = [ + "error": "USER_DECLINED" + ] + self.webView?.sendEvent(name: "emoji_status_failed", data: data.string) return } @@ -2951,7 +2996,10 @@ public final class WebAppController: ViewController, AttachmentContainable { ) controller.present(resultController, in: .window(.root)) } else { - self.webView?.sendEvent(name: "emoji_status_failed", data: "{error: \"USER_DECLINED\"}") + let data: JSON = [ + "error": "USER_DECLINED" + ] + self.webView?.sendEvent(name: "emoji_status_failed", data: data.string) } } ) @@ -3302,6 +3350,24 @@ public final class WebAppController: ViewController, AttachmentContainable { } } }) + + self.longTapWithTabBar = { [weak self] in + guard let self else { + return + } + + let _ = (context.engine.messages.attachMenuBots() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] attachMenuBots in + guard let self else { + return + } + let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == self.botId && !$0.flags.contains(.notActivated) }) + if let _ = attachMenuBot, [.attachMenu, .settings, .generic].contains(self.source) { + self.removeAttachBot() + } + }) + } } required public init(coder aDecoder: NSCoder) { @@ -3561,14 +3627,8 @@ public final class WebAppController: ViewController, AttachmentContainable { }, action: { [weak self] c, _ in c?.dismiss(completion: nil) - if let strongSelf = self { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - strongSelf.present(textAlertController(context: context, title: presentationData.strings.WebApp_RemoveConfirmationTitle, text: presentationData.strings.WebApp_RemoveAllConfirmationText(strongSelf.botName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in - if let strongSelf = self { - let _ = context.engine.messages.removeBotFromAttachMenu(botId: strongSelf.botId).start() - strongSelf.dismiss() - } - })], parseMarkdown: true), in: .window(.root)) + if let self { + self.removeAttachBot() } }))) } @@ -3580,6 +3640,17 @@ public final class WebAppController: ViewController, AttachmentContainable { self.presentInGlobalOverlay(contextController) } + private func removeAttachBot() { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.present(textAlertController(context: context, title: presentationData.strings.WebApp_RemoveConfirmationTitle, text: presentationData.strings.WebApp_RemoveAllConfirmationText(self.botName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { [weak self] in + guard let self else { + return + } + let _ = self.context.engine.messages.removeBotFromAttachMenu(botId: self.botId).start() + self.dismiss() + })], parseMarkdown: true), in: .window(.root)) + } + override public func loadDisplayNode() { self.displayNode = Node(context: self.context, controller: self) @@ -3660,7 +3731,10 @@ public final class WebAppController: ViewController, AttachmentContainable { self.controllerNode.webView?.setNeedsLayout() } - self.controllerNode.webView?.sendEvent(name: "visibility_changed", data: "{is_visible: \(self.isMinimized ? "false" : "true")}") + let data: JSON = [ + "is_visible": !self.isMinimized, + ] + self.controllerNode.webView?.sendEvent(name: "visibility_changed", data: data.string) } } }