Add story stealth mode shortcut in chat list

This commit is contained in:
Ilya Laktyushin 2024-04-23 02:51:25 +04:00
parent 28b48c1758
commit 891fed3189
24 changed files with 366 additions and 66 deletions

View File

@ -12134,3 +12134,6 @@ Sorry for the inconvenience.";
"Channel.AdminLogFilter.Section.Messages" = "Messages";
"Premium.Gift.ContactSelection.AddBirthday" = "Add Your Birthday";
"Story.StealthMode.EnableAndOpenAction" = "Enable and Open the Story";

View File

@ -1012,7 +1012,7 @@ public protocol SharedAccountContext: AnyObject {
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (() -> Void)?) -> ViewController
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController

View File

@ -72,6 +72,7 @@ public enum PremiumDemoSubject {
case lastSeen
case messagePrivacy
case folderTags
case business
case businessLocation
case businessHours

View File

@ -103,6 +103,7 @@ swift_library(
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen",
],
visibility = [
"//visibility:public",

View File

@ -210,6 +210,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private var sharedOpenStoryProgressDisposable = MetaDisposable()
var currentTooltipUpdateTimer: Foundation.Timer?
private var fullScreenEffectView: RippleEffectView?
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
@ -3082,20 +3084,29 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
})))
// items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryFeed_ViewAnonymously, icon: { theme in
// return generateTintedImage(image: UIImage(bundleImageName: self.context.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor)
// }, action: { [weak self] _, a in
// a(.default)
//
// guard let self else {
// return
// }
// if self.context.isPremium {
//// self.sendMessageContext.requestStealthMode(view: self)
// } else {
//// self.presentStealthModeUpgradeScreen()
// }
// })))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.StoryFeed_ViewAnonymously, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: self.context.isPremium ? "Chat/Context Menu/Eye" : "Chat/Context Menu/EyeLocked"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
if self.context.isPremium {
self.requestStealthMode(openStory: { [weak self] presentTooltip in
guard let self else {
return
}
self.openStories(peerId: peer.id, completion: { storyController in
presentTooltip(storyController)
})
})
} else {
self.presentStealthModeUpgrade(action: { [weak self] in
self?.presentUpgradeStoriesScreen()
})
}
})))
let hideText: String
if self.location == .chatList(groupId: .archive) {
@ -3985,6 +3996,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
public func openStories(peerId: EnginePeer.Id) {
self.openStories(peerId: peerId, completion: { _ in })
}
public func openStories(peerId: EnginePeer.Id, completion: @escaping (StoryContainerScreen) -> Void = { _ in }) {
if let navigationBarView = self.chatListDisplayNode.navigationBarView.view as? ChatListNavigationBar.View {
if navigationBarView.storiesUnlocked {
self.shouldFixStorySubscriptionOrder = true
@ -4087,7 +4102,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.sharedOpenStoryProgressDisposable.set(nil)
componentView.storyPeerListView()?.setLoadingItem(peerId: peerId, signal: signal)
}
}
},
completion: completion
)
return

View File

@ -0,0 +1,162 @@
import Foundation
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import AccountContext
import StoryContainerScreen
import StoryStealthModeSheetScreen
import UndoUI
extension ChatListControllerImpl {
func requestStealthMode(openStory: @escaping (@escaping (StoryContainerScreen) -> Void) -> Void) {
let context = self.context
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.StoryConfigurationState(),
TelegramEngine.EngineData.Item.Configuration.App()
)
|> deliverOnMainQueue).start(next: { [weak self] config, appConfig in
guard let self else {
return
}
let timestamp = Int32(Date().timeIntervalSince1970)
if let activeUntilTimestamp = config.stealthModeState.actualizedNow().activeUntilTimestamp, activeUntilTimestamp > timestamp {
let remainingActiveSeconds = activeUntilTimestamp - timestamp
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let text = presentationData.strings.Story_ToastStealthModeActiveText(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds)).string
let tooltipScreen = UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: presentationData.strings.Story_ToastStealthModeActiveTitle, text: text, cancel: "", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in
return false
}
)
tooltipScreen.tag = "no_auto_dismiss"
weak var tooltipScreenValue: UndoOverlayController? = tooltipScreen
self.currentTooltipUpdateTimer?.invalidate()
self.currentTooltipUpdateTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
guard let self else {
return
}
guard let tooltipScreenValue else {
self.currentTooltipUpdateTimer?.invalidate()
self.currentTooltipUpdateTimer = nil
return
}
let timestamp = Int32(Date().timeIntervalSince1970)
let remainingActiveSeconds = max(1, activeUntilTimestamp - timestamp)
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let text = presentationData.strings.Story_ToastStealthModeActiveText(timeIntervalString(strings: presentationData.strings, value: remainingActiveSeconds)).string
tooltipScreenValue.content = .actionSucceeded(title: presentationData.strings.Story_ToastStealthModeActiveTitle, text: text, cancel: "", destructive: false)
})
openStory({ storyController in
storyController.presentExternalTooltip(tooltipScreen)
})
return
}
let pastPeriod: Int32
let futurePeriod: Int32
if let data = appConfig.data, let futurePeriodF = data["stories_stealth_future_period"] as? Double, let pastPeriodF = data["stories_stealth_past_period"] as? Double {
futurePeriod = Int32(futurePeriodF)
pastPeriod = Int32(pastPeriodF)
} else {
pastPeriod = 5 * 60
futurePeriod = 25 * 60
}
let sheet = StoryStealthModeSheetScreen(
context: context,
mode: .control(external: true, cooldownUntilTimestamp: config.stealthModeState.actualizedNow().cooldownUntilTimestamp),
forceDark: false,
backwardDuration: pastPeriod,
forwardDuration: futurePeriod,
buttonAction: {
let _ = (context.engine.messages.enableStoryStealthMode()
|> deliverOnMainQueue).start(completed: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme)
let text = presentationData.strings.Story_ToastStealthModeActivatedText(timeIntervalString(strings: presentationData.strings, value: pastPeriod), timeIntervalString(strings: presentationData.strings, value: futurePeriod)).string
let tooltipScreen = UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: presentationData.strings.Story_ToastStealthModeActivatedTitle, text: text, cancel: "", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in
return false
}
)
openStory({ storyController in
storyController.presentExternalTooltip(tooltipScreen)
})
HapticFeedback().success()
})
}
)
self.push(sheet)
})
}
func presentStealthModeUpgrade(action: @escaping () -> Void) {
let context = self.context
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.StoryConfigurationState(),
TelegramEngine.EngineData.Item.Configuration.App()
)
|> deliverOnMainQueue).start(next: { [weak self] config, appConfig in
guard let self else {
return
}
let pastPeriod: Int32
let futurePeriod: Int32
if let data = appConfig.data, let futurePeriodF = data["stories_stealth_future_period"] as? Double, let pastPeriodF = data["stories_stealth_past_period"] as? Double {
futurePeriod = Int32(futurePeriodF)
pastPeriod = Int32(pastPeriodF)
} else {
pastPeriod = 5 * 60
futurePeriod = 25 * 60
}
let sheet = StoryStealthModeSheetScreen(
context: context,
mode: .upgrade,
forceDark: false,
backwardDuration: pastPeriod,
forwardDuration: futurePeriod,
buttonAction: {
action()
}
)
self.push(sheet)
})
}
func presentUpgradeStoriesScreen() {
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .stories, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesStealthMode, forceDark: false, dismissed: nil)
replaceImpl?(controller)
}, dismissed: nil)
replaceImpl = { [weak self, weak controller] c in
controller?.dismiss(animated: true, completion: {
guard let self else {
return
}
self.push(c)
})
}
self.push(controller)
}
}

View File

@ -1579,10 +1579,10 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
},
openTagColorPremium: {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .folderTags, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .folderTags, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folderTags, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -559,10 +559,10 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
context.engine.peers.updateChatListFiltersDisplayTags(isEnabled: value)
}, updateDisplayTagsLocked: {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .folderTags, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .folderTags, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folderTags, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -1425,6 +1425,71 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
case businessChatBots
case businessIntro
case businessLinks
public var perk: PremiumPerk {
switch self {
case .doubleLimits:
return .doubleLimits
case .moreUpload:
return .moreUpload
case .fasterDownload:
return .fasterDownload
case .voiceToText:
return .voiceToText
case .noAds:
return .noAds
case .uniqueReactions:
return .uniqueReactions
case .premiumStickers:
return .premiumStickers
case .advancedChatManagement:
return .advancedChatManagement
case .profileBadge:
return .profileBadge
case .animatedUserpics:
return .animatedUserpics
case .appIcons:
return .appIcons
case .animatedEmoji:
return .animatedEmoji
case .emojiStatus:
return .emojiStatus
case .translation:
return .translation
case .stories:
return .stories
case .colors:
return .colors
case .wallpapers:
return .wallpapers
case .messageTags:
return .messageTags
case .lastSeen:
return .lastSeen
case .messagePrivacy:
return .messagePrivacy
case .business:
return .business
case .folderTags:
return .folderTags
case .businessLocation:
return .businessLocation
case .businessHours:
return .businessHours
case .businessGreetingMessage:
return .businessGreetingMessage
case .businessQuickReplies:
return .businessQuickReplies
case .businessAwayMessage:
return .businessAwayMessage
case .businessChatBots:
return .businessChatBots
case .businessIntro:
return .businessIntro
case .businessLinks:
return .businessLinks
}
}
}
public enum Source: Equatable {

View File

@ -369,7 +369,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
)
}
private func updateScrolling(transition: Transition) {
private func updateScrolling(isFirstTime: Bool = false, transition: Transition) {
guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else {
return
}
@ -389,16 +389,23 @@ private final class RecentActionsSettingsSheetComponent: Component {
var topOffsetFraction = topOffset / topOffsetDistance
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
let modalStyleOverlayTransition: ContainedViewLayoutTransition
if isFirstTime {
modalStyleOverlayTransition = .animated(duration: 0.4, curve: .spring)
} else {
modalStyleOverlayTransition = transition.containedViewLayoutTransition
}
let transitionFactor: CGFloat = 1.0 - topOffsetFraction
if self.isUpdating {
DispatchQueue.main.async { [weak controller] in
guard let controller else {
return
}
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalStyleOverlayTransition)
}
} else {
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: transition.containedViewLayoutTransition)
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalStyleOverlayTransition)
}
}
@ -444,7 +451,9 @@ private final class RecentActionsSettingsSheetComponent: Component {
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
var isFirstTime = false
if self.component == nil {
isFirstTime = true
self.selectedMembersActions = Set(MembersActionType.actionTypesFromFlags(component.initialValue.events))
self.selectedSettingsActions = Set(SettingsActionType.actionTypesFromFlags(component.initialValue.events))
self.selectedMessagesActions = Set(MessagesActionType.actionTypesFromFlags(component.initialValue.events))
@ -924,7 +933,7 @@ private final class RecentActionsSettingsSheetComponent: Component {
}
}
self.ignoreScrolling = false
self.updateScrolling(transition: transition)
self.updateScrolling(isFirstTime: isFirstTime, transition: transition)
return availableSize
}

View File

@ -625,10 +625,10 @@ public final class AdsReportScreen: ViewControllerComponentContainer {
})
case .premiumRequired:
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -370,10 +370,10 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_voiceToText", scale: 0.065, colors: [:], title: nil, text: presentationData.strings.Message_AudioTranscription_SubscribeToPremium, customUndoText: presentationData.strings.Message_AudioTranscription_SubscribeToPremiumAction, timeout: nil), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
if case .undo = action {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
@ -537,10 +537,10 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "Transcribe", scale: 0.06, colors: [:], title: nil, text: text, customUndoText: nil, timeout: timeout), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
if case .info = action {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -1829,10 +1829,10 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if case .undo = action {
let context = item.context
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
@ -1941,10 +1941,10 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let tipController = UndoOverlayController(presentationData: presentationData, content: .universal(animation: "Transcribe", scale: 0.06, colors: [:], title: nil, text: text, customUndoText: nil, timeout: timeout), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in
if case .info = action {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .voiceToText, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -2244,7 +2244,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
}
}
private let deletedMessagesDisplayedLimit = 5
private let deletedMessagesDisplayedLimit = 4
func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentationData: ChatPresentationData, expandedDeletedMessages: Set<EngineMessage.Id>) -> [ChatRecentActionsEntry] {
var result: [ChatRecentActionsEntry] = []
@ -2252,7 +2252,7 @@ func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentation
func appendCurrentDeleteEntries() {
if !deleteMessageEntries.isEmpty, let lastEntry = deleteMessageEntries.last, let lastMessageId = lastEntry.event.action.messageId {
let isExpandable = deleteMessageEntries.count > deletedMessagesDisplayedLimit
let isExpandable = deleteMessageEntries.count >= deletedMessagesDisplayedLimit
let isExpanded = expandedDeletedMessages.contains(lastMessageId) || !isExpandable
let isGroup = deleteMessageEntries.count > 1

View File

@ -726,10 +726,10 @@ public func PeerNameColorScreen(
action: { action in
if case .info = action {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .colors, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .colors, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -527,10 +527,10 @@ public class WallpaperGalleryController: ViewController {
if forBoth && !strongSelf.context.isPremium {
let context = strongSelf.context
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .wallpapers, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .wallpapers, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .wallpapers, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -170,7 +170,8 @@ public extension StoryContainerScreen {
transitionIn: @escaping () -> StoryContainerScreen.TransitionIn?,
transitionOut: @escaping (EnginePeer.Id) -> StoryContainerScreen.TransitionOut?,
setFocusedItem: @escaping (Signal<StoryId?, NoError>) -> Void,
setProgress: @escaping (Signal<Never, NoError>) -> Void
setProgress: @escaping (Signal<Never, NoError>) -> Void,
completion: @escaping (StoryContainerScreen) -> Void = { _ in }
) {
let storyContent = StoryContentContextImpl(context: context, isHidden: isHidden, focusedPeerId: peerId, singlePeer: singlePeer, fixedOrder: initialOrder)
let signal = storyContent.state
@ -211,6 +212,7 @@ public extension StoryContainerScreen {
)
setFocusedItem(storyContainerScreen.focusedItem)
parentController?.push(storyContainerScreen)
completion(storyContainerScreen)
}
|> ignoreValues

View File

@ -1223,6 +1223,16 @@ private final class StoryContainerScreenComponent: Component {
}
}
func presentExternalTooltip(_ tooltipScreen: UndoOverlayController) {
guard let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
return
}
itemSetComponentView.sendMessageContext.tooltipScreen = tooltipScreen
itemSetComponentView.updateIsProgressPaused()
self.environment?.controller()?.present(tooltipScreen, in: .current)
}
func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
if self.didAnimateOut {
return availableSize
@ -2057,6 +2067,12 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
}
}
public func presentExternalTooltip(_ tooltipScreen: UndoOverlayController) {
if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View {
componentView.presentExternalTooltip(tooltipScreen)
}
}
func dismissWithoutTransitionOut() {
self.focusedItemPromise.set(.single(nil))

View File

@ -5757,22 +5757,27 @@ public final class StoryItemSetContainerComponent: Component {
})
}
private func presentStoriesUpgradeScreen(source: PremiumSource) {
private func presentStoriesUpgradeScreen(source: PremiumIntroSource) {
guard let component = self.component else {
return
}
let context = component.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitsListScreen(context: context, subject: .stories, source: .other, order: [.stories], buttonText: component.strings.Story_PremiumUpgradeStoriesButton, isPremium: false, forceDark: true)
controller.action = { [weak self] in
var dismissedImpl: (() -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .stories, forceDark: true, action: { [weak self] in
guard let self else {
return
}
let controller = PremiumIntroScreen(context: context, source: source, forceDark: true)
var dismissedImpl: (() -> Void)?
let controller = context.sharedContext.makePremiumIntroController(context: context, source: source, forceDark: true, dismissed: {
dismissedImpl?()
})
self.sendMessageContext.actionSheet = controller
controller.wasDismissed = { [weak self, weak controller]in
replaceImpl?(controller)
dismissedImpl = { [weak self, weak controller] in
guard let self else {
return
}
@ -5782,10 +5787,10 @@ public final class StoryItemSetContainerComponent: Component {
}
self.updateIsProgressPaused()
}
replaceImpl?(controller)
}
controller.disposed = { [weak self, weak controller] in
}, dismissed: {
dismissedImpl?()
})
dismissedImpl = { [weak self, weak controller] in
guard let self else {
return
}

View File

@ -3186,7 +3186,8 @@ final class StoryItemSetContainerSendMessage {
let sheet = StoryStealthModeSheetScreen(
context: component.context,
mode: .control(cooldownUntilTimestamp: config.stealthModeState.actualizedNow().cooldownUntilTimestamp),
mode: .control(external: false, cooldownUntilTimestamp: config.stealthModeState.actualizedNow().cooldownUntilTimestamp),
forceDark: true,
backwardDuration: pastPeriod,
forwardDuration: futurePeriod,
buttonAction: { [weak self, weak view] in
@ -3261,6 +3262,7 @@ final class StoryItemSetContainerSendMessage {
let sheet = StoryStealthModeSheetScreen(
context: component.context,
mode: .upgrade,
forceDark: true,
backwardDuration: pastPeriod,
forwardDuration: futurePeriod,
buttonAction: {

View File

@ -100,7 +100,7 @@ private final class StoryStealthModeSheetContentComponent: Component {
self.state = state
var remainingCooldownSeconds: Int32 = 0
if case let .control(cooldownUntilTimestamp) = component.mode {
if case let .control(_, cooldownUntilTimestamp) = component.mode {
if let cooldownUntilTimestamp {
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
remainingCooldownSeconds = max(0, remainingCooldownSeconds)
@ -243,9 +243,9 @@ private final class StoryStealthModeSheetContentComponent: Component {
let buttonText: String
let content: AnyComponentWithIdentity<Empty>
switch component.mode {
case .control:
case let .control(external, _):
if remainingCooldownSeconds <= 0 {
buttonText = environment.strings.Story_StealthMode_EnableAction
buttonText = external ? environment.strings.Story_StealthMode_EnableAndOpenAction : environment.strings.Story_StealthMode_EnableAction
} else {
buttonText = environment.strings.Story_StealthMode_CooldownAction(stringForDuration(remainingCooldownSeconds)).string
}
@ -283,7 +283,7 @@ private final class StoryStealthModeSheetContentComponent: Component {
}
switch component.mode {
case let .control(cooldownUntilTimestamp):
case let .control(_, cooldownUntilTimestamp):
var remainingCooldownSeconds: Int32 = 0
if let cooldownUntilTimestamp {
remainingCooldownSeconds = cooldownUntilTimestamp - Int32(Date().timeIntervalSince1970)
@ -468,13 +468,14 @@ private final class StoryStealthModeSheetScreenComponent: Component {
public class StoryStealthModeSheetScreen: ViewControllerComponentContainer {
public enum Mode: Equatable {
case control(cooldownUntilTimestamp: Int32?)
case control(external: Bool, cooldownUntilTimestamp: Int32?)
case upgrade
}
public init(
context: AccountContext,
mode: Mode,
forceDark: Bool,
backwardDuration: Int32,
forwardDuration: Int32,
buttonAction: (() -> Void)? = nil
@ -485,7 +486,7 @@ public class StoryStealthModeSheetScreen: ViewControllerComponentContainer {
backwardDuration: backwardDuration,
forwardDuration: forwardDuration,
buttonAction: buttonAction
), navigationBarAppearance: .none, theme: .dark)
), navigationBarAppearance: .none, theme: forceDark ? .dark : .default)
self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal

View File

@ -11930,10 +11930,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .info:
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .fasterDownload, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .fasterDownload, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .fasterDownload, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -591,10 +591,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}, iconSource: nil, action: { c, _ in
c.dismiss(completion: {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
@ -1088,10 +1088,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}, action: { _, f in
let context = context
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .fasterDownload, action: {
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .fasterDownload, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .fasterDownload, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}

View File

@ -2062,7 +2062,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return controller
}
public func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController {
public func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var buttonText: String = presentationData.strings.Common_OK
let mappedSubject: PremiumDemoScreen.Subject
switch subject {
case .doubleLimits:
@ -2095,6 +2097,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .translation
case .stories:
mappedSubject = .stories
buttonText = presentationData.strings.Story_PremiumUpgradeStoriesButton
case .colors:
mappedSubject = .colors
case .wallpapers:
@ -2107,10 +2110,24 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .messagePrivacy
case .folderTags:
mappedSubject = .folderTags
case .business:
mappedSubject = .business
buttonText = presentationData.strings.Chat_EmptyStateIntroFooterPremiumActionButton
default:
mappedSubject = .doubleLimits
}
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
switch mappedSubject {
case .stories, .business, .doubleLimits:
let controller = PremiumLimitsListScreen(context: context, subject: mappedSubject, source: .other, order: [mappedSubject.perk], buttonText: buttonText, isPremium: false, forceDark: forceDark)
controller.action = action
if let dismissed {
controller.disposed = dismissed
}
return controller
default:
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
}
}
public func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController {