Various improvements

This commit is contained in:
Ilya Laktyushin 2023-08-10 23:47:34 +02:00
parent 707450eb9e
commit cd66f78799
32 changed files with 859 additions and 127 deletions

View File

@ -9756,3 +9756,18 @@ Sorry for the inconvenience.";
"Premium.New" = "NEW";
"MediaEditor.AddGif" = "Add GIF";
"Premium.Stories" = "Upgraded Stories";
"Premium.StoriesInfo" = "Priority order, stealth mode, permanent views history and more.";
"Premium.MaxExpiringStoriesText" = "You can post **%@** stories in **24** hours. Subscribe to **Telegram Premium** to increase this limit to **%@**.";
"Premium.MaxExpiringStoriesNoPremiumText" = "You have reached the limit of **%@** stories per **24** hours.";
"Premium.MaxExpiringStoriesFinalText" = "You have reached the limit of **%@** stories per **24** hours.";
"Premium.MaxStoriesWeeklyText" = "You can post **%@** stories in a week. Upgrade to **Telegram Premium** to increase this limit to **%@**.";
"Premium.MaxStoriesWeeklyNoPremiumText" = "You have reached the limit of **%@** stories per week.";
"Premium.MaxStoriesWeeklyFinalText" = "You have reached the limit of **%@** stories per week.";
"Premium.MaxStoriesMonthlyText" = "You can post **%@** stories in a month. Upgrade to **Telegram Premium** to increase this limit to **%@**.";
"Premium.MaxStoriesMonthlyNoPremiumText" = "You have reached the limit of **%@** stories per month.";
"Premium.MaxStoriesMonthlyFinalText" = "You have reached the limit of **%@** stories per month.";

View File

@ -896,7 +896,7 @@ public protocol SharedAccountContext: AnyObject {
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, action: @escaping () -> Void) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Void) -> ViewController
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
@ -976,6 +976,9 @@ public enum PremiumLimitSubject {
case linksPerSharedFolder
case membershipInSharedFolders
case channels
case expiringStories
case storiesWeekly
case storiesMonthly
}
public protocol ComposeController: ViewController {

View File

@ -2586,7 +2586,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
if reachedCountLimit || premiumNeeded || hasActiveCall || hasActiveGroupCall {
if reachedCountLimit {
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .expiringStories, count: Int32(storiesCount), action: {
let controller = PremiumIntroScreen(context: context, source: .stories)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
if let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController {
navigationController.pushViewController(controller)
}
return
}
if premiumNeeded || hasActiveCall || hasActiveGroupCall {
if let storyCameraTooltip = self.storyCameraTooltip {
self.storyCameraTooltip = nil
storyCameraTooltip.dismiss()

View File

@ -1912,28 +1912,28 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
case .generic:
text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
case let .sharedFolderLimitExceeded(limit, _):
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .membershipInSharedFolders, count: limit, action: {
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .membershipInSharedFolders, count: limit, forceDark: false, cancel: {}, action: {
pushPremiumController(PremiumIntroScreen(context: context, source: .membershipInSharedFolders))
})
pushController(limitController)
return
case let .limitExceeded(limit, _):
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, forceDark: false, cancel: {}, action: {
pushPremiumController(PremiumIntroScreen(context: context, source: .linksPerSharedFolder))
})
pushController(limitController)
return
case let .tooManyChannels(limit, _):
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, forceDark: false, cancel: {}, action: {
pushPremiumController(PremiumIntroScreen(context: context, source: .groupsAndChannels))
})
pushController(limitController)
return
case let .tooManyChannelsInAccount(limit, _):
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .channels, count: limit, action: {
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .channels, count: limit, forceDark: false, cancel: {}, action: {
pushPremiumController(PremiumIntroScreen(context: context, source: .groupsAndChannels))
})
pushController(limitController)

View File

@ -293,6 +293,15 @@ open class ViewControllerComponentContainer: ViewController {
self.displayNodeDidLoad()
}
private var didDismiss = false
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if !self.didDismiss {
self.didDismiss = true
self.wasDismissed?()
}
}
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
@ -306,10 +315,8 @@ open class ViewControllerComponentContainer: ViewController {
}
open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
let wasDismissed = self.wasDismissed
super.dismiss(animated: flag, completion: {
completion?()
wasDismissed?()
})
}

View File

@ -1835,7 +1835,7 @@ private final class DrawingScreenComponent: CombinedComponent {
performAction.invoke(.clear)
}
).tagged(clearAllButtonTag),
availableSize: CGSize(width: 100.0, height: 30.0),
availableSize: CGSize(width: 180.0, height: 30.0),
transition: context.transition
)
context.add(clearAllButton

View File

@ -1135,7 +1135,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
if canTranslate {
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language)
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
controller.pushController = { [weak self] c in
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
self?.controller?.push(c)

View File

@ -100,6 +100,7 @@ swift_library(
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/AttachmentUI:AttachmentUI",
"//submodules/Components/BalancedTextComponent"
],
visibility = [
"//visibility:public",

View File

@ -395,7 +395,7 @@ public enum PremiumPerk: CaseIterable {
case .translation:
return strings.Premium_Translation
case .stories:
return "Upgraded Stories"
return strings.Premium_Stories
}
}
@ -430,7 +430,7 @@ public enum PremiumPerk: CaseIterable {
case .translation:
return strings.Premium_TranslationInfo
case .stories:
return "Priority order, stealth mode, permanent views history and more."
return strings.Premium_StoriesInfo
}
}

View File

@ -14,6 +14,7 @@ import MultilineTextComponent
import BundleIconComponent
import SolidRoundedButtonComponent
import Markdown
import BalancedTextComponent
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
@ -645,13 +646,15 @@ private final class LimitSheetContent: CombinedComponent {
let context: AccountContext
let subject: PremiumLimitScreen.Subject
let count: Int32
let cancel: () -> Void
let action: () -> Void
let dismiss: () -> Void
init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void, dismiss: @escaping () -> Void) {
init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, cancel: @escaping () -> Void, action: @escaping () -> Void, dismiss: @escaping () -> Void) {
self.context = context
self.subject = subject
self.count = count
self.cancel = cancel
self.action = action
self.dismiss = dismiss
}
@ -715,7 +718,7 @@ private final class LimitSheetContent: CombinedComponent {
static var body: Body {
let closeButton = Child(Button.self)
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let text = Child(BalancedTextComponent.self)
let limit = Child(PremiumLimitDisplayComponent.self)
let button = Child(SolidRoundedButtonComponent.self)
@ -732,7 +735,7 @@ private final class LimitSheetContent: CombinedComponent {
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let textSideInset: CGFloat = 24.0 + environment.safeInsets.left
let textSideInset: CGFloat = 32.0 + environment.safeInsets.left
let closeImage: UIImage
if let (image, theme) = state.cachedCloseImage, theme === environment.theme {
@ -747,6 +750,7 @@ private final class LimitSheetContent: CombinedComponent {
content: AnyComponent(Image(image: closeImage)),
action: { [weak component] in
component?.dismiss()
component?.cancel()
}
),
availableSize: CGSize(width: 30.0, height: 30.0),
@ -922,6 +926,54 @@ private final class LimitSheetContent: CombinedComponent {
badgeText = "\(limit)"
string = strings.Premium_MaxAccountsNoPremiumText("\(limit)").string
}
case .expiringStories:
let limit = state.limits.maxExpiringStoriesCount
let premiumLimit = state.premiumLimits.maxExpiringStoriesCount
iconName = "Premium/Stories"
badgeText = "\(limit)"
string = component.count >= premiumLimit ? strings.Premium_MaxExpiringStoriesFinalText("\(premiumLimit)").string : strings.Premium_MaxExpiringStoriesText("\(limit)", "\(premiumLimit)").string
defaultValue = ""
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
badgePosition = max(0.32, CGFloat(component.count) / CGFloat(premiumLimit))
badgeGraphPosition = badgePosition
if isPremiumDisabled {
badgeText = "\(limit)"
string = strings.Premium_MaxExpiringStoriesNoPremiumText("\(limit)").string
}
buttonAnimationName = nil
case .storiesWeekly:
let limit = state.limits.maxStoriesWeeklyCount
let premiumLimit = state.premiumLimits.maxStoriesWeeklyCount
iconName = "Premium/Stories"
badgeText = "\(limit)"
string = component.count >= premiumLimit ? strings.Premium_MaxStoriesWeeklyFinalText("\(premiumLimit)").string : strings.Premium_MaxStoriesWeeklyText("\(limit)", "\(premiumLimit)").string
defaultValue = ""
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
badgePosition = max(0.32, CGFloat(component.count) / CGFloat(premiumLimit))
badgeGraphPosition = badgePosition
if isPremiumDisabled {
badgeText = "\(limit)"
string = strings.Premium_MaxStoriesWeeklyNoPremiumText("\(limit)").string
}
buttonAnimationName = nil
case .storiesMonthly:
let limit = state.limits.maxStoriesMonthlyCount
let premiumLimit = state.premiumLimits.maxStoriesMonthlyCount
iconName = "Premium/Stories"
badgeText = "\(limit)"
string = component.count >= premiumLimit ? strings.Premium_MaxStoriesMonthlyFinalText("\(premiumLimit)").string : strings.Premium_MaxStoriesMonthlyText("\(limit)", "\(premiumLimit)").string
defaultValue = ""
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
badgePosition = max(0.32, CGFloat(component.count) / CGFloat(premiumLimit))
badgeGraphPosition = badgePosition
if isPremiumDisabled {
badgeText = "\(limit)"
string = strings.Premium_MaxStoriesMonthlyNoPremiumText("\(limit)").string
}
buttonAnimationName = nil
}
var reachedMaximumLimit = badgePosition >= 1.0
if case .folders = subject, !state.isPremium {
@ -953,7 +1005,7 @@ private final class LimitSheetContent: CombinedComponent {
})
let text = text.update(
component: MultilineTextComponent(
component: BalancedTextComponent(
text: .markdown(text: string, attributes: markdownAttributes),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
@ -1066,12 +1118,14 @@ private final class LimitSheetComponent: CombinedComponent {
let context: AccountContext
let subject: PremiumLimitScreen.Subject
let count: Int32
let cancel: () -> Void
let action: () -> Void
init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {
init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, cancel: @escaping () -> Void, action: @escaping () -> Void) {
self.context = context
self.subject = subject
self.count = count
self.cancel = cancel
self.action = action
}
@ -1101,6 +1155,7 @@ private final class LimitSheetComponent: CombinedComponent {
context: context.component.context,
subject: context.component.subject,
count: context.component.count,
cancel: context.component.cancel,
action: context.component.action,
dismiss: {
animateOut.invoke(Action { _ in
@ -1158,12 +1213,25 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
case linksPerSharedFolder
case membershipInSharedFolders
case channels
case expiringStories
case storiesWeekly
case storiesMonthly
}
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {
super.init(context: context, component: LimitSheetComponent(context: context, subject: subject, count: count, action: action), navigationBarAppearance: .none)
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, forceDark: Bool = false, cancel: @escaping () -> Void = {}, action: @escaping () -> Void) {
var actionImpl: (() -> Void)?
super.init(context: context, component: LimitSheetComponent(context: context, subject: subject, count: count, cancel: {}, action: {
actionImpl?()
}), navigationBarAppearance: .none, theme: forceDark ? .dark : .default)
self.navigationPresentation = .flatModal
self.wasDismissed = cancel
actionImpl = { [weak self] in
self?.wasDismissed = nil
action()
}
}
required public init(coder aDecoder: NSCoder) {

View File

@ -8486,6 +8486,21 @@ public extension Api.functions.stories {
})
}
}
public extension Api.functions.stories {
static func canSendStory() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1325345699)
return (FunctionDescription(name: "stories.canSendStory", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.stories {
static func deleteStories(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) {
let buffer = Buffer()

View File

@ -19,6 +19,8 @@ public struct UserLimitsConfiguration: Equatable {
public let maxSharedFolderJoin: Int32
public let maxStoryCaptionLength: Int32
public let maxExpiringStoriesCount: Int32
public let maxStoriesWeeklyCount: Int32
public let maxStoriesMonthlyCount: Int32
public static var defaultValue: UserLimitsConfiguration {
return UserLimitsConfiguration(
@ -38,7 +40,9 @@ public struct UserLimitsConfiguration: Equatable {
maxSharedFolderInviteLinks: 3,
maxSharedFolderJoin: 2,
maxStoryCaptionLength: 200,
maxExpiringStoriesCount: 100
maxExpiringStoriesCount: 3,
maxStoriesWeeklyCount: 7,
maxStoriesMonthlyCount: 30
)
}
@ -59,7 +63,9 @@ public struct UserLimitsConfiguration: Equatable {
maxSharedFolderInviteLinks: Int32,
maxSharedFolderJoin: Int32,
maxStoryCaptionLength: Int32,
maxExpiringStoriesCount: Int32
maxExpiringStoriesCount: Int32,
maxStoriesWeeklyCount: Int32,
maxStoriesMonthlyCount: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
@ -78,6 +84,8 @@ public struct UserLimitsConfiguration: Equatable {
self.maxSharedFolderJoin = maxSharedFolderJoin
self.maxStoryCaptionLength = maxStoryCaptionLength
self.maxExpiringStoriesCount = maxExpiringStoriesCount
self.maxStoriesWeeklyCount = maxStoriesWeeklyCount
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
}
}
@ -119,5 +127,7 @@ extension UserLimitsConfiguration {
self.maxSharedFolderJoin = getValue("chatlists_joined_limit", orElse: isPremium ? 100 : 2)
self.maxStoryCaptionLength = getValue("story_caption_length_limit", orElse: defaultValue.maxStoryCaptionLength)
self.maxExpiringStoriesCount = getValue("story_expiring_limit", orElse: defaultValue.maxExpiringStoriesCount)
self.maxStoriesWeeklyCount = getValue("stories_sent_weekly_limit", orElse: defaultValue.maxStoriesWeeklyCount)
self.maxStoriesMonthlyCount = getValue("stories_sent_monthly_limit", orElse: defaultValue.maxStoriesMonthlyCount)
}
}

View File

@ -53,6 +53,8 @@ public enum EngineConfiguration {
public let maxSharedFolderJoin: Int32
public let maxStoryCaptionLength: Int32
public let maxExpiringStoriesCount: Int32
public let maxStoriesWeeklyCount: Int32
public let maxStoriesMonthlyCount: Int32
public static var defaultValue: UserLimits {
return UserLimits(UserLimitsConfiguration.defaultValue)
@ -75,7 +77,9 @@ public enum EngineConfiguration {
maxSharedFolderInviteLinks: Int32,
maxSharedFolderJoin: Int32,
maxStoryCaptionLength: Int32,
maxExpiringStoriesCount: Int32
maxExpiringStoriesCount: Int32,
maxStoriesWeeklyCount: Int32,
maxStoriesMonthlyCount: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
@ -94,6 +98,8 @@ public enum EngineConfiguration {
self.maxSharedFolderJoin = maxSharedFolderJoin
self.maxStoryCaptionLength = maxStoryCaptionLength
self.maxExpiringStoriesCount = maxExpiringStoriesCount
self.maxStoriesWeeklyCount = maxStoriesWeeklyCount
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
}
}
}
@ -147,7 +153,9 @@ public extension EngineConfiguration.UserLimits {
maxSharedFolderInviteLinks: userLimitsConfiguration.maxSharedFolderInviteLinks,
maxSharedFolderJoin: userLimitsConfiguration.maxSharedFolderJoin,
maxStoryCaptionLength: userLimitsConfiguration.maxStoryCaptionLength,
maxExpiringStoriesCount: userLimitsConfiguration.maxExpiringStoriesCount
maxExpiringStoriesCount: userLimitsConfiguration.maxExpiringStoriesCount,
maxStoriesWeeklyCount: userLimitsConfiguration.maxStoriesWeeklyCount,
maxStoriesMonthlyCount: userLimitsConfiguration.maxStoriesMonthlyCount
)
}
}

View File

@ -1200,6 +1200,38 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
}
}
public enum StoriesUploadAvailability {
case available
case weeklyLimit
case monthlyLimit
case expiringLimit
case premiumRequired
case unknownLimit
}
func _internal_checkStoriesUploadAvailability(account: Account) -> Signal<StoriesUploadAvailability, NoError> {
return account.network.request(Api.functions.stories.canSendStory())
|> map { result -> StoriesUploadAvailability in
if result == .boolTrue {
return .available
} else {
return .unknownLimit
}
}
|> `catch` { error -> Signal<StoriesUploadAvailability, NoError> in
if error.errorDescription.hasPrefix("STORY_SEND_FLOOD_WEEKLY_") {
return .single(.weeklyLimit)
} else if error.errorDescription.hasPrefix("STORY_SEND_FLOOD_MONTHLY_") {
return .single(.monthlyLimit)
} else if error.errorDescription.hasPrefix("PREMIUM_ACCOUNT_REQUIRED") {
return .single(.premiumRequired)
} else if error.errorDescription.hasPrefix("STORIES_TOO_MUCH") {
return .single(.expiringLimit)
}
return .single(.unknownLimit)
}
}
func _internal_deleteStories(account: Account, ids: [Int32]) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Void in
var items = transaction.getStoryItems(peerId: account.peerId)

View File

@ -1090,6 +1090,10 @@ public extension TelegramEngine {
return _internal_editStoryPrivacy(account: self.account, id: id, privacy: privacy)
}
public func checkStoriesUploadAvailability() -> Signal<StoriesUploadAvailability, NoError> {
return _internal_checkStoriesUploadAvailability(account: self.account)
}
public func deleteStories(ids: [Int32]) -> Signal<Never, NoError> {
return _internal_deleteStories(account: self.account, ids: ids)
}

View File

@ -1175,6 +1175,8 @@ public class CameraScreen: ViewController {
private var presentationData: PresentationData
private var validLayout: ContainerViewLayout?
fileprivate var didAppear: () -> Void = {}
private let completion = ActionSlot<Signal<CameraScreen.Result, NoError>>()
var cameraState: CameraState {
@ -2140,6 +2142,7 @@ public class CameraScreen: ViewController {
} else if case .notDetermined = self.microphoneAuthorizationStatus {
self.requestDeviceAccess()
}
self.didAppear()
}
let componentSize = self.componentHost.update(
@ -2359,6 +2362,9 @@ public class CameraScreen: ViewController {
private var audioSessionDisposable: Disposable?
private let postingAvailabilityPromise = Promise<StoriesUploadAvailability>()
private var postingAvailabilityDisposable: Disposable?
private let hapticFeedback = HapticFeedback()
private var validLayout: ContainerViewLayout?
@ -2399,6 +2405,8 @@ public class CameraScreen: ViewController {
self.navigationPresentation = .flatModal
self.requestAudioSession()
self.postingAvailabilityPromise.set(self.context.engine.messages.checkStoriesUploadAvailability())
}
required public init(coder: NSCoder) {
@ -2407,6 +2415,7 @@ public class CameraScreen: ViewController {
deinit {
self.audioSessionDisposable?.dispose()
self.postingAvailabilityDisposable?.dispose()
if #available(iOS 13.0, *) {
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(false)
}
@ -2416,6 +2425,58 @@ public class CameraScreen: ViewController {
self.displayNode = Node(controller: self)
super.displayNodeDidLoad()
self.node.didAppear = { [weak self] in
guard let self else {
return
}
self.postingAvailabilityDisposable = (self.postingAvailabilityPromise.get()
|> deliverOnMainQueue).start(next: { [weak self] availability in
guard let self, availability != .available else {
return
}
let subject: PremiumLimitSubject
switch availability {
case .expiringLimit:
subject = .expiringStories
case .weeklyLimit:
subject = .storiesWeekly
case .monthlyLimit:
subject = .storiesMonthly
default:
subject = .expiringStories
}
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let controller = self.context.sharedContext.makePremiumLimitController(context: self.context, subject: subject, count: 10, forceDark: true, cancel: { [weak self] in
self?.requestDismiss(animated: true)
}, action: { [weak self] in
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories, forceDark: true, dismissed: { [weak self] in
guard let self else {
return
}
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self else {
return
}
let isPremium = peer?.isPremium ?? false
if !isPremium {
self.requestDismiss(animated: true)
}
})
})
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
navigationController.pushViewController(controller)
}
})
}
}
private func requestAudioSession() {

View File

@ -1408,7 +1408,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
case .generic:
text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
case let .sharedFolderLimitExceeded(limit, _):
let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, action: { [weak navigationController] in
let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in
guard let navigationController else {
return
}
@ -1419,7 +1419,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
return
case let .limitExceeded(limit, _):
let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .linksPerSharedFolder, count: limit, action: { [weak navigationController] in
let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .linksPerSharedFolder, count: limit, forceDark: false, cancel: {}, action: { [weak navigationController] in
guard let navigationController else {
return
}

View File

@ -3251,9 +3251,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.interaction?.containerLayoutUpdated(layout: layout, transition: transition)
// var layout = layout
// layout.intrinsicInsets.top = topInset
// layout.intrinsicInsets.bottom = bottomInset + 60.0
var layout = layout
layout.intrinsicInsets.top = topInset
controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition)
if isFirstTime {
@ -3499,12 +3498,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
timeout: privacy.timeout,
mentions: mentions,
stateContext: stateContext,
completion: { [weak self] privacy, allowScreenshots, pin, _ in
completion: { [weak self] privacy, allowScreenshots, pin, _, completed in
guard let self else {
return
}
self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, isForwardingDisabled: !allowScreenshots, pin: pin)
completion()
if completed {
completion()
}
},
editCategory: { [weak self] privacy, allowScreenshots, pin in
guard let self else {
@ -3571,8 +3572,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
allowScreenshots: !isForwardingDisabled,
pin: pin,
stateContext: stateContext,
completion: { [weak self] result, isForwardingDisabled, pin, peers in
guard let self else {
completion: { [weak self] result, isForwardingDisabled, pin, peers, completed in
guard let self, completed else {
return
}
if blockedPeers {
@ -3889,7 +3890,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
let path = "\(Int64.random(in: .min ... .max)).mp4"
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, duration: duration, values: values, caption: caption, privacy: privacy, timestamp: timestamp, location: location, expiresOn: expiresOn)
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath(engine: context.engine))
try? FileManager.default.copyItem(atPath: videoPath, toPath: draft.fullPath(engine: context.engine))
if let id {
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
} else {

View File

@ -20,6 +20,7 @@ swift_library(
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramPresentationData",
"//submodules/TelegramUIPreferences",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",

View File

@ -24,6 +24,7 @@ import LottieComponent
import TooltipUI
import OverlayStatusController
import Markdown
import TelegramUIPreferences
final class ShareWithPeersScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -37,7 +38,7 @@ final class ShareWithPeersScreenComponent: Component {
let mentions: [String]
let categoryItems: [CategoryItem]
let optionItems: [OptionItem]
let completion: (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void
let completion: (EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void
let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void
let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void
@ -51,7 +52,7 @@ final class ShareWithPeersScreenComponent: Component {
mentions: [String],
categoryItems: [CategoryItem],
optionItems: [OptionItem],
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void,
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
) {
@ -311,7 +312,6 @@ final class ShareWithPeersScreenComponent: Component {
private var ignoreScrolling: Bool = false
private var isDismissed: Bool = false
private var savedSelectedPeers: [EnginePeer.Id] = []
private var selectedPeers: [EnginePeer.Id] = []
private var selectedGroups: [EnginePeer.Id] = []
private var groupPeersMap: [EnginePeer.Id: [EnginePeer.Id]] = [:]
@ -925,20 +925,24 @@ final class ShareWithPeersScreenComponent: Component {
}
if self.selectedCategories.contains(categoryId) {
} else {
if self.selectedCategories.contains(.selectedContacts) {
self.savedSelectedPeers = self.selectedPeers
}
if categoryId == .selectedContacts {
self.selectedPeers = self.savedSelectedPeers
} else {
self.selectedPeers = []
let base: EngineStoryPrivacy.Base
switch categoryId {
case .everyone:
base = .everyone
case .contacts:
base = .contacts
case .closeFriends:
base = .closeFriends
case .selectedContacts:
base = .nobody
}
let selectedPeers = component.stateContext.stateValue?.savedSelectedPeers[base] ?? []
self.selectedCategories.removeAll()
self.selectedCategories.insert(categoryId)
let closeFriends = self.component?.stateContext.stateValue?.closeFriendsPeers ?? []
if categoryId == .selectedContacts && self.selectedPeers.isEmpty {
if categoryId == .selectedContacts && selectedPeers.isEmpty {
component.editCategory(
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
self.selectedOptions.contains(.screenshot),
@ -962,7 +966,7 @@ final class ShareWithPeersScreenComponent: Component {
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
return
}
let base: EngineStoryPrivacy.Base?
let base: EngineStoryPrivacy.Base
switch categoryId {
case .everyone:
base = .everyone
@ -973,15 +977,15 @@ final class ShareWithPeersScreenComponent: Component {
case .selectedContacts:
base = .nobody
}
if let base {
component.editCategory(
EngineStoryPrivacy(base: base, additionallyIncludePeers: self.selectedPeers),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
)
controller.dismissAllTooltips()
controller.dismiss()
}
let selectedPeers = component.stateContext.stateValue?.savedSelectedPeers[base] ?? []
component.editCategory(
EngineStoryPrivacy(base: base, additionallyIncludePeers: selectedPeers),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin)
)
controller.dismissAllTooltips()
controller.dismiss()
}
)),
environment: {},
@ -1806,6 +1810,28 @@ final class ShareWithPeersScreenComponent: Component {
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
return
}
let base: EngineStoryPrivacy.Base
if self.selectedCategories.contains(.everyone) {
base = .everyone
} else if self.selectedCategories.contains(.closeFriends) {
base = .closeFriends
} else if self.selectedCategories.contains(.contacts) {
base = .contacts
} else if self.selectedCategories.contains(.selectedContacts) {
base = .nobody
} else {
base = .nobody
}
component.completion(
EngineStoryPrivacy(
base: base,
additionallyIncludePeers: self.selectedPeers
),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin),
self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? [],
false
)
controller.requestDismiss()
}
).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
@ -1950,18 +1976,60 @@ final class ShareWithPeersScreenComponent: Component {
}
let proceed = {
component.completion(
EngineStoryPrivacy(
base: base,
additionallyIncludePeers: self.selectedPeers
),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin),
self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? []
)
var savePeers = true
if base == .closeFriends {
savePeers = false
} else {
if case .stories = component.stateContext.subject {
savePeers = false
} else if case .chats(true) = component.stateContext.subject {
savePeers = false
}
}
var selectedPeers = self.selectedPeers
if case .stories = component.stateContext.subject {
if case .closeFriends = base {
selectedPeers = []
} else {
selectedPeers = component.stateContext.stateValue?.savedSelectedPeers[base] ?? []
}
}
let complete = {
let peers = component.context.engine.data.get(EngineDataMap(selectedPeers.map { id in
return TelegramEngine.EngineData.Item.Peer.Peer(id: id)
}))
let _ = (peers
|> deliverOnMainQueue).start(next: { [weak controller, weak component] peers in
guard let controller, let component else {
return
}
component.completion(
EngineStoryPrivacy(
base: base,
additionallyIncludePeers: selectedPeers
),
self.selectedOptions.contains(.screenshot),
self.selectedOptions.contains(.pin),
peers.values.compactMap { $0 },
true
)
controller.dismissAllTooltips()
controller.dismiss()
controller.dismissAllTooltips()
controller.dismiss()
})
}
if savePeers {
let _ = (updatePeersListStoredStateInteractively(engine: component.context.engine, base: base, peerIds: self.selectedPeers)
|> deliverOnMainQueue).start(completed: {
complete()
})
} else {
complete()
}
}
let presentAlert: ([String]) -> Void = { usernames in
@ -2163,6 +2231,8 @@ final class ShareWithPeersScreenComponent: Component {
public class ShareWithPeersScreen: ViewControllerComponentContainer {
public final class State {
let peers: [EnginePeer]
let peersMap: [EnginePeer.Id: EnginePeer]
let savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]]
let presences: [EnginePeer.Id: EnginePeer.Presence]
let participants: [EnginePeer.Id: Int]
let closeFriendsPeers: [EnginePeer]
@ -2170,12 +2240,16 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
fileprivate init(
peers: [EnginePeer],
peersMap: [EnginePeer.Id: EnginePeer],
savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]],
presences: [EnginePeer.Id: EnginePeer.Presence],
participants: [EnginePeer.Id: Int],
closeFriendsPeers: [EnginePeer],
grayListPeers: [EnginePeer]
) {
self.peers = peers
self.peersMap = peersMap
self.savedSelectedPeers = savedSelectedPeers
self.presences = presences
self.participants = participants
self.closeFriendsPeers = closeFriendsPeers
@ -2211,7 +2285,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
context: AccountContext,
subject: Subject = .chats(blocked: false),
initialPeerIds: Set<EnginePeer.Id> = Set(),
savedSelectedPeers: Set<EnginePeer.Id> = Set(),
closeFriends: Signal<[EnginePeer], NoError> = .single([]),
blockedPeersContext: BlockedPeersContext? = nil
) {
@ -2231,23 +2304,83 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
switch subject {
case .stories:
var peerSignals: [Signal<EnginePeer?, NoError>] = []
if initialPeerIds.count < 3 {
for peerId in initialPeerIds {
peerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
let savedEveryoneExceptionPeers = peersListStoredState(engine: context.engine, base: .everyone)
let savedContactsExceptionPeers = peersListStoredState(engine: context.engine, base: .contacts)
let savedSelectedPeers = peersListStoredState(engine: context.engine, base: .nobody)
let savedPeers = combineLatest(
savedEveryoneExceptionPeers,
savedContactsExceptionPeers,
savedSelectedPeers
) |> mapToSignal { everyone, contacts, selected -> Signal<([EnginePeer.Id: EnginePeer], [EnginePeer.Id], [EnginePeer.Id], [EnginePeer.Id]), NoError> in
var everyonePeerSignals: [Signal<EnginePeer?, NoError>] = []
if everyone.count < 3 {
for peerId in everyone {
everyonePeerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
}
}
var contactsPeerSignals: [Signal<EnginePeer?, NoError>] = []
if contacts.count < 3 {
for peerId in contacts {
contactsPeerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
}
}
var selectedPeerSignals: [Signal<EnginePeer?, NoError>] = []
if contacts.count < 3 {
for peerId in selected {
selectedPeerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
}
}
return combineLatest(
combineLatest(everyonePeerSignals),
combineLatest(contactsPeerSignals),
combineLatest(selectedPeerSignals)
) |> map { everyonePeers, contactsPeers, selectedPeers -> ([EnginePeer.Id: EnginePeer], [EnginePeer.Id], [EnginePeer.Id], [EnginePeer.Id]) in
var peersMap: [EnginePeer.Id: EnginePeer] = [:]
for peer in everyonePeers {
if let peer {
peersMap[peer.id] = peer
}
}
for peer in contactsPeers {
if let peer {
peersMap[peer.id] = peer
}
}
for peer in selectedPeers {
if let peer {
peersMap[peer.id] = peer
}
}
return (
peersMap,
everyone,
contacts,
selected
)
}
}
let peers = combineLatest(peerSignals)
self.stateDisposable = combineLatest(queue: Queue.mainQueue(), peers, closeFriends, grayListPeers)
.start(next: { [weak self] peers, closeFriends, grayListPeers in
self.stateDisposable = combineLatest(
queue: Queue.mainQueue(),
savedPeers,
closeFriends,
grayListPeers
)
.start(next: { [weak self] savedPeers, closeFriends, grayListPeers in
guard let self else {
return
}
let (peersMap, everyonePeers, contactsPeers, selectedPeers) = savedPeers
var savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]] = [:]
savedSelectedPeers[.everyone] = everyonePeers
savedSelectedPeers[.contacts] = contactsPeers
savedSelectedPeers[.nobody] = selectedPeers
let state = State(
peers: peers.compactMap { $0 },
peers: [],
peersMap: peersMap,
savedSelectedPeers: savedSelectedPeers,
presences: [:],
participants: [:],
closeFriendsPeers: closeFriends,
@ -2255,7 +2388,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
)
self.stateValue = state
self.stateSubject.set(.single(state))
self.readySubject.set(true)
})
case let .chats(isGrayList):
@ -2364,6 +2497,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
let state = State(
peers: peers,
peersMap: [:],
savedSelectedPeers: [:],
presences: presences,
participants: participants,
closeFriendsPeers: [],
@ -2427,6 +2562,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
let state = State(
peers: peers,
peersMap: [:],
savedSelectedPeers: [:],
presences: contactList.presences,
participants: [:],
closeFriendsPeers: [],
@ -2499,6 +2636,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
return true
}
},
peersMap: [:],
savedSelectedPeers: [:],
presences: [:],
participants: participants,
closeFriendsPeers: [],
@ -2537,7 +2676,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
timeout: Int = 0,
mentions: [String] = [],
stateContext: StateContext,
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void,
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
) {
@ -2548,14 +2687,20 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
if case let .stories(editing) = stateContext.subject {
var peerNames = ""
if let peers = stateContext.stateValue?.peers, !peers.isEmpty {
peerNames = String(peers.map { $0.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) }.joined(separator: ", "))
}
var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
if initialPrivacy.base == .everyone, initialPrivacy.additionallyIncludePeers.count > 0 {
if initialPrivacy.additionallyIncludePeers.count == 1 {
if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 {
var peerNamesArray: [String] = []
var peersCount = 0
if let state = stateContext.stateValue, let peerIds = state.savedSelectedPeers[.everyone] {
peersCount = peerIds.count
for peerId in peerIds {
if let peer = state.peersMap[peerId] {
peerNamesArray.append(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))
}
}
}
let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", "))
if peersCount == 1 {
if !peerNames.isEmpty {
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
} else {
@ -2565,7 +2710,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
if !peerNames.isEmpty {
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
} else {
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExcept(Int32(initialPrivacy.additionallyIncludePeers.count))
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExcept(Int32(peersCount))
}
}
}
@ -2576,10 +2721,21 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
iconColor: .blue,
actionTitle: everyoneSubtitle
))
var contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
if initialPrivacy.base == .contacts, initialPrivacy.additionallyIncludePeers.count > 0 {
if initialPrivacy.additionallyIncludePeers.count == 1 {
if (stateContext.stateValue?.savedSelectedPeers[.contacts]?.count ?? 0) > 0 {
var peerNamesArray: [String] = []
var peersCount = 0
if let state = stateContext.stateValue, let peerIds = state.savedSelectedPeers[.contacts] {
peersCount = peerIds.count
for peerId in peerIds {
if let peer = state.peersMap[peerId] {
peerNamesArray.append(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))
}
}
}
let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", "))
if peersCount == 1 {
if !peerNames.isEmpty {
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
} else {
@ -2589,7 +2745,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
if !peerNames.isEmpty {
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
} else {
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExcept(Int32(initialPrivacy.additionallyIncludePeers.count))
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExcept(Int32(peersCount))
}
}
}
@ -2618,8 +2774,19 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
))
var selectedContactsSubtitle = presentationData.strings.Story_Privacy_Choose
if initialPrivacy.base == .nobody, initialPrivacy.additionallyIncludePeers.count > 0 {
if initialPrivacy.additionallyIncludePeers.count == 1 {
if (stateContext.stateValue?.savedSelectedPeers[.nobody]?.count ?? 0) > 0 {
var peerNamesArray: [String] = []
var peersCount = 0
if let state = stateContext.stateValue, let peerIds = state.savedSelectedPeers[.nobody] {
peersCount = peerIds.count
for peerId in peerIds {
if let peer = state.peersMap[peerId] {
peerNamesArray.append(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))
}
}
}
let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", "))
if peersCount == 1 {
if !peerNames.isEmpty {
selectedContactsSubtitle = peerNames
} else {
@ -2629,7 +2796,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
if !peerNames.isEmpty {
selectedContactsSubtitle = peerNames
} else {
selectedContactsSubtitle = presentationData.strings.Story_Privacy_People(Int32(initialPrivacy.additionallyIncludePeers.count))
selectedContactsSubtitle = presentationData.strings.Story_Privacy_People(Int32(peersCount))
}
}
}
@ -2734,3 +2901,45 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
}
}
}
final class PeersListStoredState: Codable {
private enum CodingKeys: String, CodingKey {
case peerIds
}
public let peerIds: [EnginePeer.Id]
public init(peerIds: [EnginePeer.Id]) {
self.peerIds = peerIds
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.peerIds = try container.decode([Int64].self, forKey: .peerIds).map { EnginePeer.Id($0) }
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.peerIds.map { $0.toInt64() }, forKey: .peerIds)
}
}
private func peersListStoredState(engine: TelegramEngine, base: Stories.Item.Privacy.Base) -> Signal<[EnginePeer.Id], NoError> {
let key = EngineDataBuffer(length: 4)
key.setInt32(0, value: base.rawValue)
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.shareWithPeersState, id: key))
|> map { entry -> [EnginePeer.Id] in
return entry?.get(PeersListStoredState.self)?.peerIds ?? []
}
}
private func updatePeersListStoredStateInteractively(engine: TelegramEngine, base: Stories.Item.Privacy.Base, peerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
let key = EngineDataBuffer(length: 4)
key.setInt32(0, value: base.rawValue)
let state = PeersListStoredState(peerIds: peerIds)
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.shareWithPeersState, id: key, item: state)
}

View File

@ -4256,8 +4256,8 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
initialPrivacy: privacy,
stateContext: stateContext,
completion: { [weak self] privacy, _, _, _ in
guard let self, let component = self.component else {
completion: { [weak self] privacy, _, _, _, completed in
guard let self, let component = self.component, completed else {
return
}
let _ = component.context.engine.messages.editStoryPrivacy(id: component.slice.item.storyItem.id, privacy: privacy).start()
@ -4331,7 +4331,10 @@ public final class StoryItemSetContainerComponent: Component {
context: context,
initialPrivacy: privacy,
stateContext: stateContext,
completion: { [weak self] result, _, _, peers in
completion: { [weak self] result, _, _, peers, completed in
guard completed else {
return
}
if blockedPeers, let blockedPeers = self?.component?.blockedPeers {
let _ = blockedPeers.updatePeerIds(result.additionallyIncludePeers).start()
completion(privacy)

View File

@ -1151,7 +1151,7 @@ final class StoryItemSetContainerSendMessage {
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, canCopy: true, fromLanguage: language)
let translateController = TranslateScreen(context: component.context, forceTheme: defaultDarkPresentationTheme, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
translateController.pushController = { [weak view] c in
guard let view, let component = view.component else {
return

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "StoriesLimit.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,245 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.000000 1.400391 cm
1.000000 1.000000 1.000000 scn
11.000000 1.299610 m
11.717970 1.299610 12.300000 1.881639 12.300000 2.599609 c
12.300000 3.317579 11.717970 3.899609 11.000000 3.899609 c
11.000000 1.299610 l
h
11.000000 23.299610 m
11.717970 23.299610 12.300000 23.881639 12.300000 24.599609 c
12.300000 25.317579 11.717970 25.899609 11.000000 25.899609 c
11.000000 23.299610 l
h
11.000000 3.899609 m
5.642838 3.899609 1.300000 8.242447 1.300000 13.599609 c
-1.300000 13.599609 l
-1.300000 6.806507 4.206897 1.299610 11.000000 1.299610 c
11.000000 3.899609 l
h
1.300000 13.599609 m
1.300000 18.956772 5.642838 23.299610 11.000000 23.299610 c
11.000000 25.899609 l
4.206897 25.899609 -1.300000 20.392712 -1.300000 13.599609 c
1.300000 13.599609 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 4.000000 1.400391 cm
1.000000 1.000000 1.000000 scn
23.115070 15.735053 m
22.991184 16.442253 22.317453 16.915123 21.610252 16.791237 c
20.903051 16.667349 20.430183 15.993619 20.554070 15.286419 c
23.115070 15.735053 l
h
18.947247 19.162838 m
19.359564 18.575066 20.170296 18.432831 20.758068 18.845148 c
21.345839 19.257465 21.488073 20.068195 21.075758 20.655968 c
18.947247 19.162838 l
h
18.056358 23.675367 m
17.468586 24.087685 16.657854 23.945450 16.245537 23.357677 c
15.833220 22.769905 15.975455 21.959173 16.563227 21.546856 c
18.056358 23.675367 l
h
12.686809 23.153679 m
13.394010 23.029793 14.067739 23.502663 14.191627 24.209862 c
14.315513 24.917063 13.842644 25.590794 13.135443 25.714680 c
12.686809 23.153679 l
h
8.864556 25.714680 m
8.157355 25.590794 7.684486 24.917063 7.808373 24.209862 c
7.932260 23.502661 8.605990 23.029793 9.313190 23.153679 c
8.864556 25.714680 l
h
5.436772 21.546856 m
6.024544 21.959171 6.166779 22.769905 5.754462 23.357677 c
5.342145 23.945448 4.531413 24.087683 3.943641 23.675367 c
5.436772 21.546856 l
h
0.924242 20.655968 m
0.511925 20.068195 0.654160 19.257463 1.241932 18.845146 c
1.829705 18.432831 2.640437 18.575066 3.052753 19.162838 c
0.924242 20.655968 l
h
1.445930 15.286418 m
1.569817 15.993619 1.096948 16.667349 0.389747 16.791235 c
-0.317454 16.915123 -0.991184 16.442253 -1.115071 15.735052 c
1.445930 15.286418 l
h
-1.115071 11.464166 m
-0.991184 10.756965 -0.317454 10.284096 0.389747 10.407983 c
1.096948 10.531870 1.569817 11.205600 1.445930 11.912800 c
-1.115071 11.464166 l
h
3.052754 8.036381 m
2.640437 8.624153 1.829705 8.766388 1.241933 8.354071 c
0.654161 7.941755 0.511926 7.131021 0.924243 6.543251 c
3.052754 8.036381 l
h
3.943642 3.523851 m
4.531414 3.111534 5.342146 3.253769 5.754463 3.841541 c
6.166780 4.429314 6.024545 5.240046 5.436772 5.652363 c
3.943642 3.523851 l
h
9.313191 4.045540 m
8.605990 4.169426 7.932261 3.696556 7.808374 2.989357 c
7.684487 2.282156 8.157356 1.608425 8.864557 1.484539 c
9.313191 4.045540 l
h
13.135444 1.484539 m
13.842645 1.608425 14.315514 2.282156 14.191627 2.989357 c
14.067739 3.696558 13.394010 4.169426 12.686810 4.045540 c
13.135444 1.484539 l
h
16.563229 5.652363 m
15.975456 5.240046 15.833221 4.429314 16.245539 3.841541 c
16.657854 3.253771 17.468588 3.111536 18.056358 3.523851 c
16.563229 5.652363 l
h
21.075758 6.543251 m
21.488075 7.131023 21.345840 7.941755 20.758068 8.354073 c
20.170296 8.766389 19.359564 8.624154 18.947247 8.036383 c
21.075758 6.543251 l
h
20.554070 11.912801 m
20.430183 11.205600 20.903053 10.531870 21.610252 10.407983 c
22.317453 10.284097 22.991184 10.756966 23.115070 11.464167 c
20.554070 11.912801 l
h
23.299999 13.599609 m
23.299999 14.326972 23.236721 15.040618 23.115070 15.735053 c
20.554070 15.286419 l
20.649883 14.739470 20.700001 14.175924 20.700001 13.599609 c
23.299999 13.599609 l
h
21.075758 20.655968 m
20.252605 21.829401 19.229792 22.852215 18.056358 23.675367 c
16.563227 21.546856 l
17.489597 20.897017 18.297407 20.089207 18.947247 19.162838 c
21.075758 20.655968 l
h
13.135443 25.714680 m
12.441008 25.836330 11.727363 25.899609 11.000000 25.899609 c
11.000000 23.299610 l
11.576314 23.299610 12.139861 23.249493 12.686809 23.153679 c
13.135443 25.714680 l
h
11.000000 25.899609 m
10.272637 25.899609 9.558991 25.836330 8.864556 25.714680 c
9.313190 23.153679 l
9.860139 23.249493 10.423685 23.299610 11.000000 23.299610 c
11.000000 25.899609 l
h
3.943641 23.675367 m
2.770208 22.852215 1.747394 21.829401 0.924242 20.655968 c
3.052753 19.162838 l
3.702592 20.089207 4.510403 20.897017 5.436772 21.546856 c
3.943641 23.675367 l
h
-1.115071 15.735052 m
-1.236722 15.040617 -1.300000 14.326972 -1.300000 13.599609 c
1.300000 13.599609 l
1.300000 14.175923 1.350116 14.739470 1.445930 15.286418 c
-1.115071 15.735052 l
h
-1.300000 13.599609 m
-1.300000 12.872247 -1.236722 12.158601 -1.115071 11.464166 c
1.445930 11.912800 l
1.350116 12.459748 1.300000 13.023294 1.300000 13.599609 c
-1.300000 13.599609 l
h
0.924243 6.543251 m
1.747395 5.369818 2.770209 4.347004 3.943642 3.523851 c
5.436772 5.652363 l
4.510404 6.302202 3.702593 7.110012 3.052754 8.036381 c
0.924243 6.543251 l
h
8.864557 1.484539 m
9.558992 1.362888 10.272637 1.299610 11.000000 1.299610 c
11.000000 3.899609 l
10.423686 3.899609 9.860139 3.949726 9.313191 4.045540 c
8.864557 1.484539 l
h
11.000000 1.299610 m
11.727363 1.299610 12.441009 1.362888 13.135444 1.484539 c
12.686810 4.045540 l
12.139861 3.949726 11.576315 3.899609 11.000000 3.899609 c
11.000000 1.299610 l
h
18.056358 3.523851 m
19.229792 4.347004 20.252605 5.369818 21.075758 6.543251 c
18.947247 8.036383 l
18.297407 7.110012 17.489597 6.302202 16.563229 5.652363 c
18.056358 3.523851 l
h
23.115070 11.464167 m
23.236721 12.158602 23.299999 12.872247 23.299999 13.599609 c
20.700001 13.599609 l
20.700001 13.023295 20.649883 12.459748 20.554070 11.912801 c
23.115070 11.464167 l
h
f
n
Q
endstream
endobj
3 0 obj
5770
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000005860 00000 n
0000005883 00000 n
0000006056 00000 n
0000006130 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
6189
%%EOF

View File

@ -3761,7 +3761,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
let controller = TranslateScreen(context: context, text: text.string, canCopy: canCopy, fromLanguage: language)
let controller = TranslateScreen(context: context, text: text.string, canCopy: canCopy, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
controller.pushController = { [weak self] c in
self?.effectiveNavigationController?._keepModalDismissProgress = true
self?.push(c)

View File

@ -7256,7 +7256,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if canTranslate {
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language)
let controller = TranslateScreen(context: context, text: text, canCopy: true, fromLanguage: language, ignoredLanguages: translationSettings.ignoredLanguages)
controller.pushController = { [weak self] c in
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
self?.controller?.push(c)

View File

@ -200,7 +200,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
if isDisabled {
let context = strongSelf.context
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.tabContainerNode?.filtersCount ?? 0, action: {
let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.tabContainerNode?.filtersCount ?? 0, forceDark: false, cancel: {}, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})

View File

@ -292,7 +292,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
return
}
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.controller?.tabContainerNode?.filtersCount ?? 0, action: {
let controller = context.sharedContext.makePremiumLimitController(context: context, subject: .folders, count: strongSelf.controller?.tabContainerNode?.filtersCount ?? 0, forceDark: false, cancel: {}, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})

View File

@ -1780,7 +1780,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
case .stories:
mappedSource = .stories
}
return PremiumIntroScreen(context: context, source: mappedSource, forceDark: forceDark)
let controller = PremiumIntroScreen(context: context, source: mappedSource, forceDark: forceDark)
controller.wasDismissed = dismissed
return controller
}
public func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController {
@ -1820,27 +1822,33 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
}
public func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, action: @escaping () -> Void) -> ViewController {
public func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Void) -> ViewController {
let mappedSubject: PremiumLimitScreen.Subject
switch subject {
case .folders:
mappedSubject = .folders
case .chatsPerFolder:
mappedSubject = .chatsPerFolder
mappedSubject = .chatsPerFolder
case .pins:
mappedSubject = .pins
mappedSubject = .pins
case .files:
mappedSubject = .files
mappedSubject = .files
case .accounts:
mappedSubject = .accounts
mappedSubject = .accounts
case .linksPerSharedFolder:
mappedSubject = .linksPerSharedFolder
case .membershipInSharedFolders:
mappedSubject = .membershipInSharedFolders
case .channels:
mappedSubject = .channels
case .expiringStories:
mappedSubject = .expiringStories
case .storiesWeekly:
mappedSubject = .storiesWeekly
case .storiesMonthly:
mappedSubject = .storiesMonthly
}
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, action: action)
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, forceDark: forceDark, cancel: cancel, action: action)
}
public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController {

View File

@ -78,6 +78,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
case translationState = 10
case storySource = 11
case mediaEditorState = 12
case shareWithPeersState = 13
}
public struct ApplicationSpecificItemCacheCollectionId {
@ -92,6 +93,7 @@ public struct ApplicationSpecificItemCacheCollectionId {
public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue)
public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue)
public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue)
public static let shareWithPeersState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.shareWithPeersState.rawValue)
}
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {

View File

@ -138,6 +138,25 @@ public var popularTranslationLanguages = [
@available(iOS 12.0, *)
private let languageRecognizer = NLLanguageRecognizer()
public func effectiveIgnoredTranslationLanguages(context: AccountContext, ignoredLanguages: [String]?) -> Set<String> {
var baseLang = context.sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
let rawSuffix = "-raw"
if baseLang.hasSuffix(rawSuffix) {
baseLang = String(baseLang.dropLast(rawSuffix.count))
}
var dontTranslateLanguages = Set<String>()
if let ignoredLanguages = ignoredLanguages {
dontTranslateLanguages = Set(ignoredLanguages)
} else {
dontTranslateLanguages.insert(baseLang)
for language in systemLanguageCodes() {
dontTranslateLanguages.insert(language)
}
}
return dontTranslateLanguages
}
public func canTranslateText(context: AccountContext, text: String, showTranslate: Bool, showTranslateIfTopical: Bool = false, ignoredLanguages: [String]?) -> (canTranslate: Bool, language: String?) {
guard showTranslate || showTranslateIfTopical, text.count > 0 else {
return (false, nil)
@ -147,22 +166,8 @@ public func canTranslateText(context: AccountContext, text: String, showTranslat
if context.sharedContext.immediateExperimentalUISettings.disableLanguageRecognition {
return (true, nil)
}
var baseLang = context.sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
let rawSuffix = "-raw"
if baseLang.hasSuffix(rawSuffix) {
baseLang = String(baseLang.dropLast(rawSuffix.count))
}
var dontTranslateLanguages = Set<String>()
if let ignoredLanguages = ignoredLanguages {
dontTranslateLanguages = Set(ignoredLanguages)
} else {
dontTranslateLanguages.insert(baseLang)
for language in systemLanguageCodes() {
dontTranslateLanguages.insert(language)
}
}
let dontTranslateLanguages = effectiveIgnoredTranslationLanguages(context: context, ignoredLanguages: ignoredLanguages)
let text = String(text.prefix(64))
languageRecognizer.processString(text)

View File

@ -994,7 +994,7 @@ public class TranslateScreen: ViewController {
public var wasDismissed: (() -> Void)?
public convenience init(context: AccountContext, forceTheme: PresentationTheme? = nil, text: String, canCopy: Bool, fromLanguage: String?, toLanguage: String? = nil, isExpanded: Bool = false) {
public convenience init(context: AccountContext, forceTheme: PresentationTheme? = nil, text: String, canCopy: Bool, fromLanguage: String?, toLanguage: String? = nil, isExpanded: Bool = false, ignoredLanguages: [String]? = nil) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var baseLanguageCode = presentationData.strings.baseLanguageCode
@ -1003,9 +1003,15 @@ public class TranslateScreen: ViewController {
baseLanguageCode = String(baseLanguageCode.dropLast(rawSuffix.count))
}
let dontTranslateLanguages = effectiveIgnoredTranslationLanguages(context: context, ignoredLanguages: ignoredLanguages)
var toLanguage = toLanguage ?? baseLanguageCode
if toLanguage == fromLanguage {
toLanguage = "en"
if fromLanguage == "en" {
toLanguage = dontTranslateLanguages.first(where: { $0 != "en" }) ?? "en"
} else {
toLanguage = "en"
}
}
if toLanguage == "nb" {
@ -1044,7 +1050,7 @@ public class TranslateScreen: ViewController {
let pushController = self?.pushController
let presentController = self?.presentController
let controller = languageSelectionController(context: context, forceTheme: forceTheme, fromLanguage: fromLang, toLanguage: toLang, completion: { fromLang, toLang in
let controller = TranslateScreen(context: context, forceTheme: forceTheme, text: text, canCopy: canCopy, fromLanguage: fromLang, toLanguage: toLang, isExpanded: true)
let controller = TranslateScreen(context: context, forceTheme: forceTheme, text: text, canCopy: canCopy, fromLanguage: fromLang, toLanguage: toLang, isExpanded: true, ignoredLanguages: ignoredLanguages)
controller.pushController = pushController ?? { _ in }
controller.presentController = presentController ?? { _ in }
presentController?(controller)