mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Add story stealth mode shortcut in chat list
This commit is contained in:
parent
28b48c1758
commit
891fed3189
@ -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";
|
||||
|
@ -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
|
||||
|
@ -72,6 +72,7 @@ public enum PremiumDemoSubject {
|
||||
case lastSeen
|
||||
case messagePrivacy
|
||||
case folderTags
|
||||
case business
|
||||
|
||||
case businessLocation
|
||||
case businessHours
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user