mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
707450eb9e
commit
cd66f78799
@ -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.";
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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?()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -20,6 +20,7 @@ swift_library(
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Premium/Stories.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Stories.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "StoriesLimit.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
245
submodules/TelegramUI/Images.xcassets/Premium/Stories.imageset/StoriesLimit.pdf
vendored
Normal file
245
submodules/TelegramUI/Images.xcassets/Premium/Stories.imageset/StoriesLimit.pdf
vendored
Normal 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
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user