mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Various fixes
This commit is contained in:
parent
fa46338010
commit
ee38ee55d4
@ -14315,3 +14315,8 @@ Sorry for the inconvenience.";
|
|||||||
"Gift.Buy.Confirm.Text.Stars_any" = "**%@** Stars";
|
"Gift.Buy.Confirm.Text.Stars_any" = "**%@** Stars";
|
||||||
"Gift.Buy.Confirm.BuyFor_1" = "Buy for %@ Star";
|
"Gift.Buy.Confirm.BuyFor_1" = "Buy for %@ Star";
|
||||||
"Gift.Buy.Confirm.BuyFor_any" = "Buy for %@ Stars";
|
"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";
|
||||||
|
@ -16,6 +16,7 @@ import TelegramBaseController
|
|||||||
import InviteLinksUI
|
import InviteLinksUI
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import TelegramCallsUI
|
import TelegramCallsUI
|
||||||
|
import TelegramUIPreferences
|
||||||
|
|
||||||
public enum CallListControllerMode {
|
public enum CallListControllerMode {
|
||||||
case tab
|
case tab
|
||||||
@ -734,10 +735,22 @@ public final class CallListController: TelegramBaseController {
|
|||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] c, f in
|
}, action: { [weak self] c, f in
|
||||||
c?.dismiss(completion: { [weak self] in
|
c?.dismiss(completion: { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let self else {
|
||||||
return
|
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()
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
@ -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)
|
itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: itemInlineNavigationTransitionFraction, storiesInset: storiesInset, transition: nodeTransition)
|
||||||
if let scrollingOffset = self.scrollingOffset {
|
if let scrollingOffset = self.scrollingOffset {
|
||||||
itemNode.updateScrollingOffset(navigationHeight: scrollingOffset.navigationHeight, offset: scrollingOffset.offset, transition: nodeTransition)
|
itemNode.updateScrollingOffset(navigationHeight: scrollingOffset.navigationHeight, offset: scrollingOffset.offset, transition: nodeTransition)
|
||||||
|
@ -2092,8 +2092,6 @@ public final class ChatListNode: ListView {
|
|||||||
return .single(.setupPhoto(accountPeer))
|
return .single(.setupPhoto(accountPeer))
|
||||||
} else if suggestions.contains(.gracePremium) {
|
} else if suggestions.contains(.gracePremium) {
|
||||||
return .single(.premiumGrace)
|
return .single(.premiumGrace)
|
||||||
} else if suggestions.contains(.setupBirthday) && birthday == nil {
|
|
||||||
return .single(.setupBirthday)
|
|
||||||
} else if suggestions.contains(.xmasPremiumGift) {
|
} else if suggestions.contains(.xmasPremiumGift) {
|
||||||
return .single(.xmasPremiumGift)
|
return .single(.xmasPremiumGift)
|
||||||
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
|
} 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)
|
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} }) {
|
} 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))
|
return .single(.link(id: id, url: url, title: title, subtitle: subtitle))
|
||||||
} else {
|
} else {
|
||||||
|
@ -501,7 +501,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
|
|||||||
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
|
animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration))
|
||||||
animationFraction = animationState.curve.solve(at: animationFraction)
|
animationFraction = animationState.curve.solve(at: animationFraction)
|
||||||
if animationState.fromExtracted != isExtracted {
|
if animationState.fromExtracted != isExtracted {
|
||||||
fixedTransitionDirection = isExtracted ? true : false
|
fixedTransitionDirection = isExtracted
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
animationFraction = 1.0
|
animationFraction = 1.0
|
||||||
|
@ -527,7 +527,12 @@ final class ContextControllerNode: ViewControllerTracingNode, ASScrollViewDelega
|
|||||||
guard let strongSelf = self, let _ = gesture else {
|
guard let strongSelf = self, let _ = gesture else {
|
||||||
return
|
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
|
let initialPoint: CGPoint
|
||||||
if let current = strongSelf.initialContinueGesturePoint {
|
if let current = strongSelf.initialContinueGesturePoint {
|
||||||
initialPoint = current
|
initialPoint = current
|
||||||
|
@ -697,7 +697,7 @@ final class ColorGridComponent: Component {
|
|||||||
bottomRightRadius = largeCornerRadius
|
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)
|
var selectionKnobImage = ColorSelectionImage(size: CGSize(width: squareSize, height: squareSize), topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius, isLight: isLight)
|
||||||
if selectionKnobImage != self.selectionKnobImage {
|
if selectionKnobImage != self.selectionKnobImage {
|
||||||
|
@ -203,6 +203,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
|||||||
case channelSendGiftTooltip = 76
|
case channelSendGiftTooltip = 76
|
||||||
case starGiftWearTips = 77
|
case starGiftWearTips = 77
|
||||||
case channelSuggestTooltip = 78
|
case channelSuggestTooltip = 78
|
||||||
|
case multipleStoriesTooltip = 79
|
||||||
|
|
||||||
var key: ValueBoxKey {
|
var key: ValueBoxKey {
|
||||||
let v = ValueBoxKey(length: 4)
|
let v = ValueBoxKey(length: 4)
|
||||||
@ -564,6 +565,10 @@ private struct ApplicationSpecificNoticeKeys {
|
|||||||
static func channelSuggestTooltip() -> NoticeEntryKey {
|
static func channelSuggestTooltip() -> NoticeEntryKey {
|
||||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.channelSuggestTooltip.key)
|
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 {
|
public struct ApplicationSpecificNotice {
|
||||||
@ -2426,4 +2431,31 @@ public struct ApplicationSpecificNotice {
|
|||||||
return Int(previousValue)
|
return Int(previousValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func getMultipleStoriesTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||||
|
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<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3630,7 +3630,13 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
self.node.resumeCameraCapture(fromGallery: true)
|
self.node.resumeCameraCapture(fromGallery: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
var dismissControllerImpl: (() -> Void)?
|
class DismissArgs {
|
||||||
|
var resumeOnDismiss = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var dismissControllerImpl: ((Bool) -> Void)?
|
||||||
|
let dismissArgs = DismissArgs()
|
||||||
|
|
||||||
let controller: ViewController
|
let controller: ViewController
|
||||||
if let current = self.galleryController {
|
if let current = self.galleryController {
|
||||||
controller = current
|
controller = current
|
||||||
@ -3686,7 +3692,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissControllerImpl?()
|
dismissControllerImpl?(true)
|
||||||
} else {
|
} else {
|
||||||
stopCameraCapture()
|
stopCameraCapture()
|
||||||
|
|
||||||
@ -3759,17 +3765,19 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
self.node.collage?.addResults(signals: results)
|
self.node.collage?.addResults(signals: results)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
self.node.animateOutToEditor()
|
||||||
if let assets = results as? [PHAsset] {
|
if let assets = results as? [PHAsset] {
|
||||||
self.completion(.single(.assets(assets)), nil, self.remainingStoryCount, {
|
self.completion(.single(.assets(assets)), nil, self.remainingStoryCount, {
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.galleryController = nil
|
self.galleryController = nil
|
||||||
|
|
||||||
dismissControllerImpl?()
|
dismissControllerImpl?(false)
|
||||||
}, dismissed: { [weak self] in
|
}, dismissed: { [weak self] in
|
||||||
resumeCameraCapture()
|
if dismissArgs.resumeOnDismiss {
|
||||||
|
resumeCameraCapture()
|
||||||
|
}
|
||||||
if let self {
|
if let self {
|
||||||
self.node.hasGallery = false
|
self.node.hasGallery = false
|
||||||
self.node.requestUpdateLayout(transition: .immediate)
|
self.node.requestUpdateLayout(transition: .immediate)
|
||||||
@ -3780,7 +3788,8 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||||||
)
|
)
|
||||||
self.galleryController = controller
|
self.galleryController = controller
|
||||||
|
|
||||||
dismissControllerImpl = { [weak controller] in
|
dismissControllerImpl = { [weak controller] resume in
|
||||||
|
dismissArgs.resumeOnDismiss = resume
|
||||||
controller?.dismiss(animated: true)
|
controller?.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,6 +235,8 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
|
|
||||||
private var chevronImage: (UIImage, PresentationTheme)?
|
private var chevronImage: (UIImage, PresentationTheme)?
|
||||||
|
|
||||||
|
private var resaleConfiguration: StarsSubscriptionConfiguration?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.scrollView = ScrollView()
|
self.scrollView = ScrollView()
|
||||||
self.scrollView.showsVerticalScrollIndicator = true
|
self.scrollView.showsVerticalScrollIndicator = true
|
||||||
@ -408,9 +410,14 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
switch gift {
|
switch gift {
|
||||||
case let .generic(gift):
|
case let .generic(gift):
|
||||||
if let availability = gift.availability, availability.remains == 0, let minResaleStars = availability.minResaleStars {
|
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 {
|
} else {
|
||||||
subject = .starGift(gift: gift, price: "⭐️ \(gift.price)")
|
subject = .starGift(gift: gift, price: "⭐️ \(presentationStringsFormattedNumber(Int32(gift.price), environment.dateTimeFormat.groupingSeparator))")
|
||||||
}
|
}
|
||||||
case let .unique(gift):
|
case let .unique(gift):
|
||||||
subject = .uniqueGift(gift: gift, price: nil)
|
subject = .uniqueGift(gift: gift, price: nil)
|
||||||
@ -773,6 +780,8 @@ final class GiftOptionsScreenComponent: Component {
|
|||||||
self.optionsPromise.set(component.context.engine.payments.starsTopUpOptions()
|
self.optionsPromise.set(component.context.engine.payments.starsTopUpOptions()
|
||||||
|> map(Optional.init))
|
|> map(Optional.init))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||||
}
|
}
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
|
@ -109,6 +109,15 @@ public final class FilterSelectorComponent: Component {
|
|||||||
return true
|
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<Empty>, transition: ComponentTransition) -> CGSize {
|
func update(component: FilterSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
@ -27,6 +27,8 @@ import UndoUI
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import LottieComponent
|
import LottieComponent
|
||||||
|
|
||||||
|
private let minimumCountToDisplayFilters = 18
|
||||||
|
|
||||||
final class GiftStoreScreenComponent: Component {
|
final class GiftStoreScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
@ -93,7 +95,8 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
private var starsStateDisposable: Disposable?
|
private var starsStateDisposable: Disposable?
|
||||||
private var starsState: StarsContext.State?
|
private var starsState: StarsContext.State?
|
||||||
|
private var initialCount: Int?
|
||||||
|
|
||||||
private var component: GiftStoreScreenComponent?
|
private var component: GiftStoreScreenComponent?
|
||||||
private(set) weak var state: State?
|
private(set) weak var state: State?
|
||||||
private var environment: EnvironmentType?
|
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) {
|
private func updateScrolling(interactive: Bool = false, transition: ComponentTransition) {
|
||||||
guard let environment = self.environment, let component = self.component, self.state?.starGiftsState?.dataState != .loading else {
|
guard let environment = self.environment, let component = self.component, self.state?.starGiftsState?.dataState != .loading else {
|
||||||
return
|
return
|
||||||
@ -163,6 +173,11 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
transition.setAlpha(view: topSeparator, alpha: topPanelAlpha)
|
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)
|
let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -10.0)
|
||||||
if let starGifts = self.effectiveGifts {
|
if let starGifts = self.effectiveGifts {
|
||||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||||
@ -172,7 +187,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
let starsOptionSize = CGSize(width: optionWidth, height: 154.0)
|
let starsOptionSize = CGSize(width: optionWidth, height: 154.0)
|
||||||
|
|
||||||
var validIds: [AnyHashable] = []
|
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
|
let controller = environment.controller
|
||||||
|
|
||||||
@ -337,7 +352,6 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
showClearFilters = true
|
showClearFilters = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let topInset: CGFloat = environment.navigationHeight + 39.0
|
|
||||||
let bottomInset: CGFloat = environment.safeInsets.bottom
|
let bottomInset: CGFloat = environment.safeInsets.bottom
|
||||||
|
|
||||||
var emptyResultsActionFrame = CGRect(
|
var emptyResultsActionFrame = CGRect(
|
||||||
@ -443,7 +457,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openSortContextMenu(sourceView: UIView) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,10 +500,10 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openModelContextMenu(sourceView: UIView) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let searchQueryPromise = ValuePromise<String>("")
|
let searchQueryPromise = ValuePromise<String>("")
|
||||||
|
|
||||||
@ -579,7 +593,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openBackdropContextMenu(sourceView: UIView) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,7 +686,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openSymbolContextMenu(sourceView: UIView) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,10 +803,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
var isLoading = false
|
let isLoading = self.effectiveIsLoading
|
||||||
if self.state?.starGiftsState?.gifts == nil || self.state?.starGiftsState?.dataState == .loading {
|
|
||||||
isLoading = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let theme = environment.theme
|
let theme = environment.theme
|
||||||
let strings = environment.strings
|
let strings = environment.strings
|
||||||
@ -808,7 +819,10 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
contentHeight += environment.navigationHeight
|
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(
|
let topPanelSize = self.topPanel.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
@ -913,7 +927,10 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let effectiveCount: Int32
|
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)
|
effectiveCount = Int32(count)
|
||||||
} else if let resale = component.gift.availability?.resale {
|
} else if let resale = component.gift.availability?.resale {
|
||||||
effectiveCount = Int32(resale)
|
effectiveCount = Int32(resale)
|
||||||
@ -1028,13 +1045,15 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
let loadingTransition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||||
|
|
||||||
let filterSize = self.filterSelector.update(
|
let filterSize = self.filterSelector.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(FilterSelectorComponent(
|
component: AnyComponent(FilterSelectorComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
colors: FilterSelectorComponent.Colors(
|
colors: FilterSelectorComponent.Colors(
|
||||||
foreground: theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.65),
|
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
|
items: filterItems
|
||||||
)),
|
)),
|
||||||
@ -1043,9 +1062,14 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
if let filterSelectorView = self.filterSelector.view {
|
if let filterSelectorView = self.filterSelector.view {
|
||||||
if filterSelectorView.superview == nil {
|
if filterSelectorView.superview == nil {
|
||||||
|
filterSelectorView.alpha = 0.0
|
||||||
self.addSubview(filterSelectorView)
|
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))
|
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 {
|
if let starGifts = self.state?.starGiftsState?.gifts {
|
||||||
@ -1088,14 +1112,13 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
|
|
||||||
self.updateScrolling(transition: transition)
|
self.updateScrolling(transition: transition)
|
||||||
|
|
||||||
let loadingTransition: ComponentTransition = .easeInOut(duration: 0.25)
|
|
||||||
if isLoading {
|
if isLoading {
|
||||||
self.loadingNode.update(size: availableSize, theme: environment.theme, transition: .immediate)
|
self.loadingNode.update(size: availableSize, theme: environment.theme, transition: .immediate)
|
||||||
loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 1.0)
|
loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 1.0)
|
||||||
} else {
|
} else {
|
||||||
loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 0.0)
|
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
|
return availableSize
|
||||||
}
|
}
|
||||||
@ -1108,19 +1131,22 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
final class State: ComponentState {
|
final class State: ComponentState {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
var peerId: EnginePeer.Id
|
var peerId: EnginePeer.Id
|
||||||
|
private let gift: StarGift.Gift
|
||||||
|
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
|
|
||||||
fileprivate let starGiftsContext: ResaleGiftsContext
|
fileprivate let starGiftsContext: ResaleGiftsContext
|
||||||
fileprivate var starGiftsState: ResaleGiftsContext.State?
|
fileprivate var starGiftsState: ResaleGiftsContext.State?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
peerId: EnginePeer.Id,
|
peerId: EnginePeer.Id,
|
||||||
giftId: Int64
|
gift: StarGift.Gift
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.peerId = peerId
|
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()
|
super.init()
|
||||||
|
|
||||||
@ -1140,7 +1166,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeState() -> State {
|
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<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
|
@ -154,10 +154,17 @@ final class LoadingShimmerNode: ASDisplayNode {
|
|||||||
context.setFillColor(theme.list.blocksBackgroundColor.cgColor)
|
context.setFillColor(theme.list.blocksBackgroundColor.cgColor)
|
||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
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
|
var rowIndex: Int = 0
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0// + environment.safeInsets.left
|
|
||||||
let optionSpacing: CGFloat = 10.0
|
let optionSpacing: CGFloat = 10.0
|
||||||
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
||||||
let itemSize = CGSize(width: optionWidth, height: 154.0)
|
let itemSize = CGSize(width: optionWidth, height: 154.0)
|
||||||
@ -167,7 +174,7 @@ final class LoadingShimmerNode: ASDisplayNode {
|
|||||||
|
|
||||||
while currentY < size.height {
|
while currentY < size.height {
|
||||||
for i in 0 ..< 3 {
|
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))
|
context.addPath(CGPath(roundedRect: CGRect(origin: itemOrigin, size: itemSize), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil))
|
||||||
}
|
}
|
||||||
currentY += itemSize.height
|
currentY += itemSize.height
|
||||||
|
@ -496,7 +496,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
|
|
||||||
if currentTime > starsConvertMaxDate {
|
if currentTime > starsConvertMaxDate {
|
||||||
let days: Int32 = Int32(ceil(Float(configuration.convertToStarsPeriod) / 86400.0))
|
let days: Int32 = Int32(ceil(Float(configuration.convertToStarsPeriod) / 86400.0))
|
||||||
let controller = textAlertController(
|
let alertController = textAlertController(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
title: presentationData.strings.Gift_Convert_Title,
|
title: presentationData.strings.Gift_Convert_Title,
|
||||||
text: presentationData.strings.Gift_Convert_Period_Unavailable_Text(presentationData.strings.Gift_Convert_Period_Unavailable_Days(days)).string,
|
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
|
parseMarkdown: true
|
||||||
)
|
)
|
||||||
controller.present(controller, in: .window(.root))
|
controller.present(alertController, in: .window(.root))
|
||||||
} else {
|
} else {
|
||||||
let delta = starsConvertMaxDate - currentTime
|
let delta = starsConvertMaxDate - currentTime
|
||||||
let days: Int32 = Int32(ceil(Float(delta) / 86400.0))
|
let days: Int32 = Int32(ceil(Float(delta) / 86400.0))
|
||||||
|
@ -67,6 +67,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||||
"//submodules/TelegramUI/Components/MediaAssetsContext",
|
"//submodules/TelegramUI/Components/MediaAssetsContext",
|
||||||
"//submodules/CheckNode",
|
"//submodules/CheckNode",
|
||||||
|
"//submodules/TelegramNotices",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -49,6 +49,7 @@ import StickerPickerScreen
|
|||||||
import UIKitRuntimeUtils
|
import UIKitRuntimeUtils
|
||||||
import ImageObjectSeparation
|
import ImageObjectSeparation
|
||||||
import SaveProgressScreen
|
import SaveProgressScreen
|
||||||
|
import TelegramNotices
|
||||||
|
|
||||||
private let playbackButtonTag = GenericComponentViewTag()
|
private let playbackButtonTag = GenericComponentViewTag()
|
||||||
private let muteButtonTag = GenericComponentViewTag()
|
private let muteButtonTag = GenericComponentViewTag()
|
||||||
@ -58,6 +59,7 @@ private let drawButtonTag = GenericComponentViewTag()
|
|||||||
private let textButtonTag = GenericComponentViewTag()
|
private let textButtonTag = GenericComponentViewTag()
|
||||||
private let stickerButtonTag = GenericComponentViewTag()
|
private let stickerButtonTag = GenericComponentViewTag()
|
||||||
private let dayNightButtonTag = GenericComponentViewTag()
|
private let dayNightButtonTag = GenericComponentViewTag()
|
||||||
|
private let selectionButtonTag = GenericComponentViewTag()
|
||||||
|
|
||||||
final class MediaEditorScreenComponent: Component {
|
final class MediaEditorScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
@ -2320,7 +2322,8 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
controller.hapticFeedback.impact(.light)
|
controller.hapticFeedback.impact(.light)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
animateAlpha: false
|
animateAlpha: false,
|
||||||
|
tag: selectionButtonTag
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 33.0, height: 33.0)
|
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)
|
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?
|
fileprivate weak var saveTooltip: SaveProgressScreen?
|
||||||
func presentSaveTooltip() {
|
func presentSaveTooltip() {
|
||||||
guard let controller = self.controller else {
|
guard let controller = self.controller else {
|
||||||
@ -5725,6 +5755,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
if hasAppeared && !self.hasAppeared {
|
if hasAppeared && !self.hasAppeared {
|
||||||
self.hasAppeared = hasAppeared
|
self.hasAppeared = hasAppeared
|
||||||
|
|
||||||
|
self.presentSelectionTooltip()
|
||||||
}
|
}
|
||||||
|
|
||||||
let componentSize = self.componentHost.update(
|
let componentSize = self.componentHost.update(
|
||||||
|
@ -347,7 +347,7 @@ extension PeerInfoScreenImpl {
|
|||||||
|
|
||||||
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
||||||
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
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) {
|
if [.suggest, .fallback].contains(mode) {
|
||||||
} else {
|
} else {
|
||||||
|
@ -895,8 +895,13 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
if let controller = self.controller {
|
if let controller = self.controller {
|
||||||
webView.updateMetrics(height: viewportFrame.height, isExpanded: controller.isContainerExpanded(), isStable: !controller.isContainerPanning(), transition: transition)
|
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}"
|
let data: JSON = [
|
||||||
webView.sendEvent(name: "content_safe_area_changed", data: contentInsetsData)
|
"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() {
|
if self.updateWebViewWhenStable && !controller.isContainerPanning() {
|
||||||
self.updateWebViewWhenStable = false
|
self.updateWebViewWhenStable = false
|
||||||
@ -1333,7 +1338,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
controller.completion = { [weak self] result in
|
controller.completion = { [weak self] result in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let result = result {
|
if let result = result {
|
||||||
strongSelf.sendQrCodeScannedEvent(data: result)
|
strongSelf.sendQrCodeScannedEvent(dataString: result)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.sendQrCodeScannerClosedEvent()
|
strongSelf.sendQrCodeScannerClosedEvent()
|
||||||
}
|
}
|
||||||
@ -1923,8 +1928,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func sendInvoiceClosedEvent(slug: String, result: InvoiceCloseResult) {
|
private func sendInvoiceClosedEvent(slug: String, result: InvoiceCloseResult) {
|
||||||
let paramsString = "{slug: \"\(slug)\", status: \"\(result.string)\"}"
|
let data: JSON = [
|
||||||
self.webView?.sendEvent(name: "invoice_closed", data: paramsString)
|
"slug": slug,
|
||||||
|
"status": result.string
|
||||||
|
]
|
||||||
|
self.webView?.sendEvent(name: "invoice_closed", data: data.string)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func sendBackButtonEvent() {
|
fileprivate func sendBackButtonEvent() {
|
||||||
@ -1936,24 +1944,23 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func sendAlertButtonEvent(id: String?) {
|
fileprivate func sendAlertButtonEvent(id: String?) {
|
||||||
var paramsString: String?
|
var data: [String: Any] = [:]
|
||||||
if let id = id {
|
if let id {
|
||||||
paramsString = "{button_id: \"\(id)\"}"
|
data["button_id"] = id
|
||||||
}
|
}
|
||||||
self.webView?.sendEvent(name: "popup_closed", data: paramsString ?? "{}")
|
if let serializedData = JSON(dictionary: data)?.string {
|
||||||
}
|
self.webView?.sendEvent(name: "popup_closed", data: serializedData)
|
||||||
|
|
||||||
fileprivate func sendPhoneRequestedEvent(phone: String?) {
|
|
||||||
var paramsString: String?
|
|
||||||
if let phone = phone {
|
|
||||||
paramsString = "{phone_number: \"\(phone)\"}"
|
|
||||||
}
|
}
|
||||||
self.webView?.sendEvent(name: "phone_requested", data: paramsString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func sendQrCodeScannedEvent(data: String?) {
|
fileprivate func sendQrCodeScannedEvent(dataString: String?) {
|
||||||
let paramsString = data.flatMap { "{data: \"\($0)\"}" } ?? "{}"
|
var data: [String: Any] = [:]
|
||||||
self.webView?.sendEvent(name: "qr_text_received", data: paramsString)
|
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() {
|
fileprivate func sendQrCodeScannerClosedEvent() {
|
||||||
@ -1961,14 +1968,15 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func sendClipboardTextEvent(requestId: String, fillData: Bool) {
|
fileprivate func sendClipboardTextEvent(requestId: String, fillData: Bool) {
|
||||||
var paramsString: String
|
var data: [String: Any] = [:]
|
||||||
|
data["req_id"] = requestId
|
||||||
if fillData {
|
if fillData {
|
||||||
let data = UIPasteboard.general.string ?? ""
|
let pasteboardData = UIPasteboard.general.string ?? ""
|
||||||
paramsString = "{req_id: \"\(requestId)\", data: \"\(data)\"}"
|
data["data"] = pasteboardData
|
||||||
} else {
|
}
|
||||||
paramsString = "{req_id: \"\(requestId)\"}"
|
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() {
|
fileprivate func requestWriteAccess() {
|
||||||
@ -1977,13 +1985,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sendEvent: (Bool) -> Void = { success in
|
let sendEvent: (Bool) -> Void = { success in
|
||||||
var paramsString: String
|
let data: JSON = [
|
||||||
if success {
|
"status": success ? "allowed" : "cancelled"
|
||||||
paramsString = "{status: \"allowed\"}"
|
]
|
||||||
} else {
|
self.webView?.sendEvent(name: "write_access_requested", data: data.string)
|
||||||
paramsString = "{status: \"cancelled\"}"
|
|
||||||
}
|
|
||||||
self.webView?.sendEvent(name: "write_access_requested", data: paramsString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (self.context.engine.messages.canBotSendMessages(botId: controller.botId)
|
let _ = (self.context.engine.messages.canBotSendMessages(botId: controller.botId)
|
||||||
@ -2021,13 +2026,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let sendEvent: (Bool) -> Void = { success in
|
let sendEvent: (Bool) -> Void = { success in
|
||||||
var paramsString: String
|
let data: JSON = [
|
||||||
if success {
|
"status": success ? "sent" : "cancelled"
|
||||||
paramsString = "{status: \"sent\"}"
|
]
|
||||||
} else {
|
self.webView?.sendEvent(name: "phone_requested", data: data.string)
|
||||||
paramsString = "{status: \"cancelled\"}"
|
|
||||||
}
|
|
||||||
self.webView?.sendEvent(name: "phone_requested", data: paramsString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (self.context.engine.data.get(
|
let _ = (self.context.engine.data.get(
|
||||||
@ -2348,28 +2350,15 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
state.opaqueToken = encryptedData
|
state.opaqueToken = encryptedData
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
let data: JSON = [
|
||||||
var data: [String: Any] = [:]
|
"status": "updated"
|
||||||
data["status"] = "updated"
|
]
|
||||||
|
self.webView?.sendEvent(name: "biometry_token_updated", data: data.string)
|
||||||
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)
|
|
||||||
} else {
|
} else {
|
||||||
var data: [String: Any] = [:]
|
let data: JSON = [
|
||||||
data["status"] = "failed"
|
"status": "failed"
|
||||||
|
]
|
||||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
self.webView?.sendEvent(name: "biometry_token_updated", data: data.string)
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
@ -2379,17 +2368,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
state.opaqueToken = nil
|
state.opaqueToken = nil
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
let data: JSON = [
|
||||||
var data: [String: Any] = [:]
|
"status": "removed"
|
||||||
data["status"] = "removed"
|
]
|
||||||
|
self.webView?.sendEvent(name: "biometry_token_updated", data: data.string)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2410,13 +2392,18 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard controller.isFullscreen != isFullscreen else {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let paramsString = "{is_fullscreen: \( isFullscreen ? "true" : "false" )}"
|
let data: JSON = [
|
||||||
self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString)
|
"is_fullscreen": isFullscreen
|
||||||
|
]
|
||||||
|
self.webView?.sendEvent(name: "fullscreen_changed", data: data.string)
|
||||||
|
|
||||||
controller.isFullscreen = isFullscreen
|
controller.isFullscreen = isFullscreen
|
||||||
if isFullscreen {
|
if isFullscreen {
|
||||||
controller.requestAttachmentMenuExpansion()
|
controller.requestAttachmentMenuExpansion()
|
||||||
@ -2436,7 +2423,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
private var isAccelerometerActive = false
|
private var isAccelerometerActive = false
|
||||||
fileprivate func setIsAccelerometerActive(_ isActive: Bool, refreshRate: Double? = nil) {
|
fileprivate func setIsAccelerometerActive(_ isActive: Bool, refreshRate: Double? = nil) {
|
||||||
guard self.motionManager.isAccelerometerAvailable else {
|
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
|
return
|
||||||
}
|
}
|
||||||
guard self.isAccelerometerActive != isActive else {
|
guard self.isAccelerometerActive != isActive else {
|
||||||
@ -2451,15 +2441,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
} else {
|
} else {
|
||||||
self.motionManager.accelerometerUpdateInterval = 1.0
|
self.motionManager.accelerometerUpdateInterval = 1.0
|
||||||
}
|
}
|
||||||
self.motionManager.startAccelerometerUpdates(to: OperationQueue.main) { [weak self] data, error in
|
self.motionManager.startAccelerometerUpdates(to: OperationQueue.main) { [weak self] accelerometerData, error in
|
||||||
guard let self, let data else {
|
guard let self, let accelerometerData else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let gravityConstant = 9.81
|
let gravityConstant: Double = 9.81
|
||||||
self.webView?.sendEvent(
|
let data: JSON = [
|
||||||
name: "accelerometer_changed",
|
"x": Double(accelerometerData.acceleration.x * gravityConstant),
|
||||||
data: "{x: \(data.acceleration.x * gravityConstant), y: \(data.acceleration.y * gravityConstant), z: \(data.acceleration.z * gravityConstant)}"
|
"y": Double(accelerometerData.acceleration.y * gravityConstant),
|
||||||
)
|
"z": Double(accelerometerData.acceleration.z * gravityConstant)
|
||||||
|
]
|
||||||
|
self.webView?.sendEvent(name: "accelerometer_changed", data: data.string)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if self.motionManager.isAccelerometerActive {
|
if self.motionManager.isAccelerometerActive {
|
||||||
@ -2472,7 +2464,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
private var isDeviceOrientationActive = false
|
private var isDeviceOrientationActive = false
|
||||||
fileprivate func setIsDeviceOrientationActive(_ isActive: Bool, refreshRate: Double? = nil, absolute: Bool = false) {
|
fileprivate func setIsDeviceOrientationActive(_ isActive: Bool, refreshRate: Double? = nil, absolute: Bool = false) {
|
||||||
guard self.motionManager.isDeviceMotionAvailable else {
|
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
|
return
|
||||||
}
|
}
|
||||||
guard self.isDeviceOrientationActive != isActive else {
|
guard self.isDeviceOrientationActive != isActive else {
|
||||||
@ -2505,25 +2500,29 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
effectiveIsAbsolute = false
|
effectiveIsAbsolute = false
|
||||||
}
|
}
|
||||||
self.motionManager.startDeviceMotionUpdates(using: referenceFrame, to: OperationQueue.main) { [weak self] data, error in
|
self.motionManager.startDeviceMotionUpdates(using: referenceFrame, to: OperationQueue.main) { [weak self] motionData, error in
|
||||||
guard let self, let data else {
|
guard let self, let motionData else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var alpha: Double
|
var alpha: Double
|
||||||
if effectiveIsAbsolute {
|
if effectiveIsAbsolute {
|
||||||
alpha = data.heading * .pi / 180.0
|
alpha = motionData.heading * .pi / 180.0
|
||||||
if alpha > .pi {
|
if alpha > .pi {
|
||||||
alpha -= 2.0 * .pi
|
alpha -= 2.0 * .pi
|
||||||
} else if alpha < -.pi {
|
} else if alpha < -.pi {
|
||||||
alpha += 2.0 * .pi
|
alpha += 2.0 * .pi
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alpha = data.attitude.yaw
|
alpha = motionData.attitude.yaw
|
||||||
}
|
}
|
||||||
self.webView?.sendEvent(
|
|
||||||
name: "device_orientation_changed",
|
let data: JSON = [
|
||||||
data: "{absolute: \(effectiveIsAbsolute ? "true" : "false"), alpha: \(alpha), beta: \(data.attitude.pitch), gamma: \(data.attitude.roll)}"
|
"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 {
|
} else {
|
||||||
if self.motionManager.isDeviceMotionActive {
|
if self.motionManager.isDeviceMotionActive {
|
||||||
@ -2536,7 +2535,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
private var isGyroscopeActive = false
|
private var isGyroscopeActive = false
|
||||||
fileprivate func setIsGyroscopeActive(_ isActive: Bool, refreshRate: Double? = nil) {
|
fileprivate func setIsGyroscopeActive(_ isActive: Bool, refreshRate: Double? = nil) {
|
||||||
guard self.motionManager.isGyroAvailable else {
|
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
|
return
|
||||||
}
|
}
|
||||||
guard self.isGyroscopeActive != isActive else {
|
guard self.isGyroscopeActive != isActive else {
|
||||||
@ -2551,14 +2553,16 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
} else {
|
} else {
|
||||||
self.motionManager.gyroUpdateInterval = 1.0
|
self.motionManager.gyroUpdateInterval = 1.0
|
||||||
}
|
}
|
||||||
self.motionManager.startGyroUpdates(to: OperationQueue.main) { [weak self] data, error in
|
self.motionManager.startGyroUpdates(to: OperationQueue.main) { [weak self] gyroData, error in
|
||||||
guard let self, let data else {
|
guard let self, let gyroData else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.webView?.sendEvent(
|
let data: JSON = [
|
||||||
name: "gyroscope_changed",
|
"x": Double(gyroData.rotationRate.x),
|
||||||
data: "{x: \(data.rotationRate.x), y: \(data.rotationRate.y), z: \(data.rotationRate.z)}"
|
"y": Double(gyroData.rotationRate.y),
|
||||||
)
|
"z": Double(gyroData.rotationRate.z)
|
||||||
|
]
|
||||||
|
self.webView?.sendEvent(name: "gyroscope_changed", data: data.string)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if self.motionManager.isGyroActive {
|
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)
|
let _ = (self.context.engine.messages.getPreparedInlineMessage(botId: controller.botId, id: id)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self, weak controller] preparedMessage in
|
|> deliverOnMainQueue).start(next: { [weak self, weak controller] preparedMessage in
|
||||||
guard let self, let controller, let preparedMessage else {
|
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
|
return
|
||||||
}
|
}
|
||||||
let previewController = WebAppMessagePreviewScreen(context: controller.context, botName: controller.botName, botAddress: controller.botAddress, preparedMessage: preparedMessage, completion: { [weak self] result in
|
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 {
|
if result {
|
||||||
self.webView?.sendEvent(name: "prepared_message_sent", data: nil)
|
self.webView?.sendEvent(name: "prepared_message_sent", data: nil)
|
||||||
} else {
|
} 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
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2635,7 +2648,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard canDownload else {
|
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
|
return
|
||||||
}
|
}
|
||||||
var fileSizeString = ""
|
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 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: [
|
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
|
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
|
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.WebApp_Download_Download, action: { [weak self] in
|
||||||
self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia)
|
self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia)
|
||||||
})
|
})
|
||||||
], parseMarkdown: true)
|
], parseMarkdown: true)
|
||||||
alertController.dismissed = { [weak self] byOutsideTap in
|
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))
|
controller.present(alertController, in: .window(.root))
|
||||||
})
|
})
|
||||||
@ -2664,7 +2686,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
guard let controller = self.controller else {
|
guard let controller = self.controller else {
|
||||||
return
|
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)?
|
var removeImpl: (() -> Void)?
|
||||||
let fileDownload = FileDownload(
|
let fileDownload = FileDownload(
|
||||||
@ -2840,13 +2865,20 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
demoController?.replace(with: c)
|
demoController?.replace(with: c)
|
||||||
}
|
}
|
||||||
controller.parentController()?.push(demoController)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: botId, enabled: true)
|
let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: botId, enabled: true)
|
||||||
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in
|
|> 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 {
|
if let botPeer {
|
||||||
@ -2865,7 +2897,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
controller.present(resultController, in: .window(.root))
|
controller.present(resultController, in: .window(.root))
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in
|
||||||
@ -2874,7 +2909,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
alertController.dismissed = { [weak self] byOutsideTap in
|
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))
|
controller.present(alertController, in: .window(.root))
|
||||||
})
|
})
|
||||||
@ -2894,7 +2932,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let file = files[fileId] else {
|
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
|
return
|
||||||
}
|
}
|
||||||
let confirmController = WebAppSetEmojiStatusScreen(
|
let confirmController = WebAppSetEmojiStatusScreen(
|
||||||
@ -2919,7 +2960,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
demoController?.replace(with: c)
|
demoController?.replace(with: c)
|
||||||
}
|
}
|
||||||
controller.parentController()?.push(demoController)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2951,7 +2996,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
)
|
)
|
||||||
controller.present(resultController, in: .window(.root))
|
controller.present(resultController, in: .window(.root))
|
||||||
} else {
|
} 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) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
@ -3561,14 +3627,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
}, action: { [weak self] c, _ in
|
}, action: { [weak self] c, _ in
|
||||||
c?.dismiss(completion: nil)
|
c?.dismiss(completion: nil)
|
||||||
|
|
||||||
if let strongSelf = self {
|
if let self {
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.removeAttachBot()
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@ -3580,6 +3640,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.presentInGlobalOverlay(contextController)
|
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() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = Node(context: self.context, controller: self)
|
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?.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user