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";
|
"Premium.New" = "NEW";
|
||||||
|
|
||||||
"MediaEditor.AddGif" = "Add GIF";
|
"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 makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||||
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, action: @escaping () -> Void) -> ViewController
|
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, 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
|
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 linksPerSharedFolder
|
||||||
case membershipInSharedFolders
|
case membershipInSharedFolders
|
||||||
case channels
|
case channels
|
||||||
|
case expiringStories
|
||||||
|
case storiesWeekly
|
||||||
|
case storiesMonthly
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol ComposeController: ViewController {
|
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 {
|
if let storyCameraTooltip = self.storyCameraTooltip {
|
||||||
self.storyCameraTooltip = nil
|
self.storyCameraTooltip = nil
|
||||||
storyCameraTooltip.dismiss()
|
storyCameraTooltip.dismiss()
|
||||||
|
@ -1912,28 +1912,28 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
|
|||||||
case .generic:
|
case .generic:
|
||||||
text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
|
text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
|
||||||
case let .sharedFolderLimitExceeded(limit, _):
|
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))
|
pushPremiumController(PremiumIntroScreen(context: context, source: .membershipInSharedFolders))
|
||||||
})
|
})
|
||||||
pushController(limitController)
|
pushController(limitController)
|
||||||
|
|
||||||
return
|
return
|
||||||
case let .limitExceeded(limit, _):
|
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))
|
pushPremiumController(PremiumIntroScreen(context: context, source: .linksPerSharedFolder))
|
||||||
})
|
})
|
||||||
pushController(limitController)
|
pushController(limitController)
|
||||||
|
|
||||||
return
|
return
|
||||||
case let .tooManyChannels(limit, _):
|
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))
|
pushPremiumController(PremiumIntroScreen(context: context, source: .groupsAndChannels))
|
||||||
})
|
})
|
||||||
pushController(limitController)
|
pushController(limitController)
|
||||||
|
|
||||||
return
|
return
|
||||||
case let .tooManyChannelsInAccount(limit, _):
|
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))
|
pushPremiumController(PremiumIntroScreen(context: context, source: .groupsAndChannels))
|
||||||
})
|
})
|
||||||
pushController(limitController)
|
pushController(limitController)
|
||||||
|
@ -293,6 +293,15 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
self.displayNodeDidLoad()
|
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) {
|
override open func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
@ -306,10 +315,8 @@ open class ViewControllerComponentContainer: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||||
let wasDismissed = self.wasDismissed
|
|
||||||
super.dismiss(animated: flag, completion: {
|
super.dismiss(animated: flag, completion: {
|
||||||
completion?()
|
completion?()
|
||||||
wasDismissed?()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1835,7 +1835,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
|||||||
performAction.invoke(.clear)
|
performAction.invoke(.clear)
|
||||||
}
|
}
|
||||||
).tagged(clearAllButtonTag),
|
).tagged(clearAllButtonTag),
|
||||||
availableSize: CGSize(width: 100.0, height: 30.0),
|
availableSize: CGSize(width: 180.0, height: 30.0),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
context.add(clearAllButton
|
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)
|
let (canTranslate, language) = canTranslateText(context: context, text: text, showTranslate: translationSettings.showTranslate, showTranslateIfTopical: false, ignoredLanguages: translationSettings.ignoredLanguages)
|
||||||
if canTranslate {
|
if canTranslate {
|
||||||
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
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
|
controller.pushController = { [weak self] c in
|
||||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||||
self?.controller?.push(c)
|
self?.controller?.push(c)
|
||||||
|
@ -100,6 +100,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||||
"//submodules/AttachmentUI:AttachmentUI",
|
"//submodules/AttachmentUI:AttachmentUI",
|
||||||
|
"//submodules/Components/BalancedTextComponent"
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -395,7 +395,7 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
case .translation:
|
case .translation:
|
||||||
return strings.Premium_Translation
|
return strings.Premium_Translation
|
||||||
case .stories:
|
case .stories:
|
||||||
return "Upgraded Stories"
|
return strings.Premium_Stories
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +430,7 @@ public enum PremiumPerk: CaseIterable {
|
|||||||
case .translation:
|
case .translation:
|
||||||
return strings.Premium_TranslationInfo
|
return strings.Premium_TranslationInfo
|
||||||
case .stories:
|
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 BundleIconComponent
|
||||||
import SolidRoundedButtonComponent
|
import SolidRoundedButtonComponent
|
||||||
import Markdown
|
import Markdown
|
||||||
|
import BalancedTextComponent
|
||||||
|
|
||||||
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
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 context: AccountContext
|
||||||
let subject: PremiumLimitScreen.Subject
|
let subject: PremiumLimitScreen.Subject
|
||||||
let count: Int32
|
let count: Int32
|
||||||
|
let cancel: () -> Void
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
let dismiss: () -> 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.context = context
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.count = count
|
self.count = count
|
||||||
|
self.cancel = cancel
|
||||||
self.action = action
|
self.action = action
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
}
|
}
|
||||||
@ -715,7 +718,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
static var body: Body {
|
static var body: Body {
|
||||||
let closeButton = Child(Button.self)
|
let closeButton = Child(Button.self)
|
||||||
let title = Child(MultilineTextComponent.self)
|
let title = Child(MultilineTextComponent.self)
|
||||||
let text = Child(MultilineTextComponent.self)
|
let text = Child(BalancedTextComponent.self)
|
||||||
let limit = Child(PremiumLimitDisplayComponent.self)
|
let limit = Child(PremiumLimitDisplayComponent.self)
|
||||||
let button = Child(SolidRoundedButtonComponent.self)
|
let button = Child(SolidRoundedButtonComponent.self)
|
||||||
|
|
||||||
@ -732,7 +735,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
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
|
let closeImage: UIImage
|
||||||
if let (image, theme) = state.cachedCloseImage, theme === environment.theme {
|
if let (image, theme) = state.cachedCloseImage, theme === environment.theme {
|
||||||
@ -747,6 +750,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
content: AnyComponent(Image(image: closeImage)),
|
content: AnyComponent(Image(image: closeImage)),
|
||||||
action: { [weak component] in
|
action: { [weak component] in
|
||||||
component?.dismiss()
|
component?.dismiss()
|
||||||
|
component?.cancel()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||||
@ -922,6 +926,54 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
badgeText = "\(limit)"
|
badgeText = "\(limit)"
|
||||||
string = strings.Premium_MaxAccountsNoPremiumText("\(limit)").string
|
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
|
var reachedMaximumLimit = badgePosition >= 1.0
|
||||||
if case .folders = subject, !state.isPremium {
|
if case .folders = subject, !state.isPremium {
|
||||||
@ -953,7 +1005,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let text = text.update(
|
let text = text.update(
|
||||||
component: MultilineTextComponent(
|
component: BalancedTextComponent(
|
||||||
text: .markdown(text: string, attributes: markdownAttributes),
|
text: .markdown(text: string, attributes: markdownAttributes),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
@ -1066,12 +1118,14 @@ private final class LimitSheetComponent: CombinedComponent {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let subject: PremiumLimitScreen.Subject
|
let subject: PremiumLimitScreen.Subject
|
||||||
let count: Int32
|
let count: Int32
|
||||||
|
let cancel: () -> Void
|
||||||
let action: () -> 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.context = context
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.count = count
|
self.count = count
|
||||||
|
self.cancel = cancel
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1101,6 +1155,7 @@ private final class LimitSheetComponent: CombinedComponent {
|
|||||||
context: context.component.context,
|
context: context.component.context,
|
||||||
subject: context.component.subject,
|
subject: context.component.subject,
|
||||||
count: context.component.count,
|
count: context.component.count,
|
||||||
|
cancel: context.component.cancel,
|
||||||
action: context.component.action,
|
action: context.component.action,
|
||||||
dismiss: {
|
dismiss: {
|
||||||
animateOut.invoke(Action { _ in
|
animateOut.invoke(Action { _ in
|
||||||
@ -1158,12 +1213,25 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
|||||||
case linksPerSharedFolder
|
case linksPerSharedFolder
|
||||||
case membershipInSharedFolders
|
case membershipInSharedFolders
|
||||||
case channels
|
case channels
|
||||||
|
case expiringStories
|
||||||
|
case storiesWeekly
|
||||||
|
case storiesMonthly
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {
|
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, forceDark: Bool = false, cancel: @escaping () -> Void = {}, action: @escaping () -> Void) {
|
||||||
super.init(context: context, component: LimitSheetComponent(context: context, subject: subject, count: count, action: action), navigationBarAppearance: .none)
|
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.navigationPresentation = .flatModal
|
||||||
|
|
||||||
|
self.wasDismissed = cancel
|
||||||
|
|
||||||
|
actionImpl = { [weak self] in
|
||||||
|
self?.wasDismissed = nil
|
||||||
|
action()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
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 {
|
public extension Api.functions.stories {
|
||||||
static func deleteStories(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) {
|
static func deleteStories(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
@ -19,6 +19,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
public let maxSharedFolderJoin: Int32
|
public let maxSharedFolderJoin: Int32
|
||||||
public let maxStoryCaptionLength: Int32
|
public let maxStoryCaptionLength: Int32
|
||||||
public let maxExpiringStoriesCount: Int32
|
public let maxExpiringStoriesCount: Int32
|
||||||
|
public let maxStoriesWeeklyCount: Int32
|
||||||
|
public let maxStoriesMonthlyCount: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimitsConfiguration {
|
public static var defaultValue: UserLimitsConfiguration {
|
||||||
return UserLimitsConfiguration(
|
return UserLimitsConfiguration(
|
||||||
@ -38,7 +40,9 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxSharedFolderInviteLinks: 3,
|
maxSharedFolderInviteLinks: 3,
|
||||||
maxSharedFolderJoin: 2,
|
maxSharedFolderJoin: 2,
|
||||||
maxStoryCaptionLength: 200,
|
maxStoryCaptionLength: 200,
|
||||||
maxExpiringStoriesCount: 100
|
maxExpiringStoriesCount: 3,
|
||||||
|
maxStoriesWeeklyCount: 7,
|
||||||
|
maxStoriesMonthlyCount: 30
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +63,9 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxSharedFolderInviteLinks: Int32,
|
maxSharedFolderInviteLinks: Int32,
|
||||||
maxSharedFolderJoin: Int32,
|
maxSharedFolderJoin: Int32,
|
||||||
maxStoryCaptionLength: Int32,
|
maxStoryCaptionLength: Int32,
|
||||||
maxExpiringStoriesCount: Int32
|
maxExpiringStoriesCount: Int32,
|
||||||
|
maxStoriesWeeklyCount: Int32,
|
||||||
|
maxStoriesMonthlyCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
@ -78,6 +84,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
self.maxSharedFolderJoin = maxSharedFolderJoin
|
self.maxSharedFolderJoin = maxSharedFolderJoin
|
||||||
self.maxStoryCaptionLength = maxStoryCaptionLength
|
self.maxStoryCaptionLength = maxStoryCaptionLength
|
||||||
self.maxExpiringStoriesCount = maxExpiringStoriesCount
|
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.maxSharedFolderJoin = getValue("chatlists_joined_limit", orElse: isPremium ? 100 : 2)
|
||||||
self.maxStoryCaptionLength = getValue("story_caption_length_limit", orElse: defaultValue.maxStoryCaptionLength)
|
self.maxStoryCaptionLength = getValue("story_caption_length_limit", orElse: defaultValue.maxStoryCaptionLength)
|
||||||
self.maxExpiringStoriesCount = getValue("story_expiring_limit", orElse: defaultValue.maxExpiringStoriesCount)
|
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 maxSharedFolderJoin: Int32
|
||||||
public let maxStoryCaptionLength: Int32
|
public let maxStoryCaptionLength: Int32
|
||||||
public let maxExpiringStoriesCount: Int32
|
public let maxExpiringStoriesCount: Int32
|
||||||
|
public let maxStoriesWeeklyCount: Int32
|
||||||
|
public let maxStoriesMonthlyCount: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimits {
|
public static var defaultValue: UserLimits {
|
||||||
return UserLimits(UserLimitsConfiguration.defaultValue)
|
return UserLimits(UserLimitsConfiguration.defaultValue)
|
||||||
@ -75,7 +77,9 @@ public enum EngineConfiguration {
|
|||||||
maxSharedFolderInviteLinks: Int32,
|
maxSharedFolderInviteLinks: Int32,
|
||||||
maxSharedFolderJoin: Int32,
|
maxSharedFolderJoin: Int32,
|
||||||
maxStoryCaptionLength: Int32,
|
maxStoryCaptionLength: Int32,
|
||||||
maxExpiringStoriesCount: Int32
|
maxExpiringStoriesCount: Int32,
|
||||||
|
maxStoriesWeeklyCount: Int32,
|
||||||
|
maxStoriesMonthlyCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
@ -94,6 +98,8 @@ public enum EngineConfiguration {
|
|||||||
self.maxSharedFolderJoin = maxSharedFolderJoin
|
self.maxSharedFolderJoin = maxSharedFolderJoin
|
||||||
self.maxStoryCaptionLength = maxStoryCaptionLength
|
self.maxStoryCaptionLength = maxStoryCaptionLength
|
||||||
self.maxExpiringStoriesCount = maxExpiringStoriesCount
|
self.maxExpiringStoriesCount = maxExpiringStoriesCount
|
||||||
|
self.maxStoriesWeeklyCount = maxStoriesWeeklyCount
|
||||||
|
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,7 +153,9 @@ public extension EngineConfiguration.UserLimits {
|
|||||||
maxSharedFolderInviteLinks: userLimitsConfiguration.maxSharedFolderInviteLinks,
|
maxSharedFolderInviteLinks: userLimitsConfiguration.maxSharedFolderInviteLinks,
|
||||||
maxSharedFolderJoin: userLimitsConfiguration.maxSharedFolderJoin,
|
maxSharedFolderJoin: userLimitsConfiguration.maxSharedFolderJoin,
|
||||||
maxStoryCaptionLength: userLimitsConfiguration.maxStoryCaptionLength,
|
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> {
|
func _internal_deleteStories(account: Account, ids: [Int32]) -> Signal<Never, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Void in
|
return account.postbox.transaction { transaction -> Void in
|
||||||
var items = transaction.getStoryItems(peerId: account.peerId)
|
var items = transaction.getStoryItems(peerId: account.peerId)
|
||||||
|
@ -1090,6 +1090,10 @@ public extension TelegramEngine {
|
|||||||
return _internal_editStoryPrivacy(account: self.account, id: id, privacy: privacy)
|
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> {
|
public func deleteStories(ids: [Int32]) -> Signal<Never, NoError> {
|
||||||
return _internal_deleteStories(account: self.account, ids: ids)
|
return _internal_deleteStories(account: self.account, ids: ids)
|
||||||
}
|
}
|
||||||
|
@ -1175,6 +1175,8 @@ public class CameraScreen: ViewController {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
|
fileprivate var didAppear: () -> Void = {}
|
||||||
|
|
||||||
private let completion = ActionSlot<Signal<CameraScreen.Result, NoError>>()
|
private let completion = ActionSlot<Signal<CameraScreen.Result, NoError>>()
|
||||||
|
|
||||||
var cameraState: CameraState {
|
var cameraState: CameraState {
|
||||||
@ -2140,6 +2142,7 @@ public class CameraScreen: ViewController {
|
|||||||
} else if case .notDetermined = self.microphoneAuthorizationStatus {
|
} else if case .notDetermined = self.microphoneAuthorizationStatus {
|
||||||
self.requestDeviceAccess()
|
self.requestDeviceAccess()
|
||||||
}
|
}
|
||||||
|
self.didAppear()
|
||||||
}
|
}
|
||||||
|
|
||||||
let componentSize = self.componentHost.update(
|
let componentSize = self.componentHost.update(
|
||||||
@ -2359,6 +2362,9 @@ public class CameraScreen: ViewController {
|
|||||||
|
|
||||||
private var audioSessionDisposable: Disposable?
|
private var audioSessionDisposable: Disposable?
|
||||||
|
|
||||||
|
private let postingAvailabilityPromise = Promise<StoriesUploadAvailability>()
|
||||||
|
private var postingAvailabilityDisposable: Disposable?
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
@ -2399,6 +2405,8 @@ public class CameraScreen: ViewController {
|
|||||||
self.navigationPresentation = .flatModal
|
self.navigationPresentation = .flatModal
|
||||||
|
|
||||||
self.requestAudioSession()
|
self.requestAudioSession()
|
||||||
|
|
||||||
|
self.postingAvailabilityPromise.set(self.context.engine.messages.checkStoriesUploadAvailability())
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder: NSCoder) {
|
required public init(coder: NSCoder) {
|
||||||
@ -2407,6 +2415,7 @@ public class CameraScreen: ViewController {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.audioSessionDisposable?.dispose()
|
self.audioSessionDisposable?.dispose()
|
||||||
|
self.postingAvailabilityDisposable?.dispose()
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(false)
|
try? AVAudioSession.sharedInstance().setAllowHapticsAndSystemSoundsDuringRecording(false)
|
||||||
}
|
}
|
||||||
@ -2416,6 +2425,58 @@ public class CameraScreen: ViewController {
|
|||||||
self.displayNode = Node(controller: self)
|
self.displayNode = Node(controller: self)
|
||||||
|
|
||||||
super.displayNodeDidLoad()
|
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() {
|
private func requestAudioSession() {
|
||||||
|
@ -1408,7 +1408,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
case .generic:
|
case .generic:
|
||||||
text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
|
text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
|
||||||
case let .sharedFolderLimitExceeded(limit, _):
|
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 {
|
guard let navigationController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1419,7 +1419,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
|
|
||||||
return
|
return
|
||||||
case let .limitExceeded(limit, _):
|
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 {
|
guard let navigationController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -3251,9 +3251,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
|
|
||||||
self.interaction?.containerLayoutUpdated(layout: layout, transition: transition)
|
self.interaction?.containerLayoutUpdated(layout: layout, transition: transition)
|
||||||
|
|
||||||
// var layout = layout
|
var layout = layout
|
||||||
// layout.intrinsicInsets.top = topInset
|
layout.intrinsicInsets.top = topInset
|
||||||
// layout.intrinsicInsets.bottom = bottomInset + 60.0
|
|
||||||
controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition)
|
controller.presentationContext.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition)
|
||||||
|
|
||||||
if isFirstTime {
|
if isFirstTime {
|
||||||
@ -3499,12 +3498,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
timeout: privacy.timeout,
|
timeout: privacy.timeout,
|
||||||
mentions: mentions,
|
mentions: mentions,
|
||||||
stateContext: stateContext,
|
stateContext: stateContext,
|
||||||
completion: { [weak self] privacy, allowScreenshots, pin, _ in
|
completion: { [weak self] privacy, allowScreenshots, pin, _, completed in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, isForwardingDisabled: !allowScreenshots, pin: pin)
|
self.state.privacy = MediaEditorResultPrivacy(privacy: privacy, timeout: timeout, isForwardingDisabled: !allowScreenshots, pin: pin)
|
||||||
completion()
|
if completed {
|
||||||
|
completion()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
editCategory: { [weak self] privacy, allowScreenshots, pin in
|
editCategory: { [weak self] privacy, allowScreenshots, pin in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -3571,8 +3572,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
allowScreenshots: !isForwardingDisabled,
|
allowScreenshots: !isForwardingDisabled,
|
||||||
pin: pin,
|
pin: pin,
|
||||||
stateContext: stateContext,
|
stateContext: stateContext,
|
||||||
completion: { [weak self] result, isForwardingDisabled, pin, peers in
|
completion: { [weak self] result, isForwardingDisabled, pin, peers, completed in
|
||||||
guard let self else {
|
guard let self, completed else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if blockedPeers {
|
if blockedPeers {
|
||||||
@ -3889,7 +3890,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
||||||
let path = "\(Int64.random(in: .min ... .max)).mp4"
|
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)
|
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 {
|
if let id {
|
||||||
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
|
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
|
||||||
} else {
|
} else {
|
||||||
|
@ -20,6 +20,7 @@ swift_library(
|
|||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
"//submodules/TelegramPresentationData",
|
"//submodules/TelegramPresentationData",
|
||||||
|
"//submodules/TelegramUIPreferences",
|
||||||
"//submodules/AccountContext",
|
"//submodules/AccountContext",
|
||||||
"//submodules/AppBundle",
|
"//submodules/AppBundle",
|
||||||
"//submodules/TelegramStringFormatting",
|
"//submodules/TelegramStringFormatting",
|
||||||
|
@ -24,6 +24,7 @@ import LottieComponent
|
|||||||
import TooltipUI
|
import TooltipUI
|
||||||
import OverlayStatusController
|
import OverlayStatusController
|
||||||
import Markdown
|
import Markdown
|
||||||
|
import TelegramUIPreferences
|
||||||
|
|
||||||
final class ShareWithPeersScreenComponent: Component {
|
final class ShareWithPeersScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
@ -37,7 +38,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
let mentions: [String]
|
let mentions: [String]
|
||||||
let categoryItems: [CategoryItem]
|
let categoryItems: [CategoryItem]
|
||||||
let optionItems: [OptionItem]
|
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 editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void
|
||||||
let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void
|
let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
mentions: [String],
|
mentions: [String],
|
||||||
categoryItems: [CategoryItem],
|
categoryItems: [CategoryItem],
|
||||||
optionItems: [OptionItem],
|
optionItems: [OptionItem],
|
||||||
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void,
|
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
|
||||||
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
|
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
|
||||||
editBlockedPeers: @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 ignoreScrolling: Bool = false
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
private var savedSelectedPeers: [EnginePeer.Id] = []
|
|
||||||
private var selectedPeers: [EnginePeer.Id] = []
|
private var selectedPeers: [EnginePeer.Id] = []
|
||||||
private var selectedGroups: [EnginePeer.Id] = []
|
private var selectedGroups: [EnginePeer.Id] = []
|
||||||
private var groupPeersMap: [EnginePeer.Id: [EnginePeer.Id]] = [:]
|
private var groupPeersMap: [EnginePeer.Id: [EnginePeer.Id]] = [:]
|
||||||
@ -925,20 +925,24 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
if self.selectedCategories.contains(categoryId) {
|
if self.selectedCategories.contains(categoryId) {
|
||||||
} else {
|
} else {
|
||||||
if self.selectedCategories.contains(.selectedContacts) {
|
let base: EngineStoryPrivacy.Base
|
||||||
self.savedSelectedPeers = self.selectedPeers
|
switch categoryId {
|
||||||
}
|
case .everyone:
|
||||||
if categoryId == .selectedContacts {
|
base = .everyone
|
||||||
self.selectedPeers = self.savedSelectedPeers
|
case .contacts:
|
||||||
} else {
|
base = .contacts
|
||||||
self.selectedPeers = []
|
case .closeFriends:
|
||||||
|
base = .closeFriends
|
||||||
|
case .selectedContacts:
|
||||||
|
base = .nobody
|
||||||
}
|
}
|
||||||
|
let selectedPeers = component.stateContext.stateValue?.savedSelectedPeers[base] ?? []
|
||||||
|
|
||||||
self.selectedCategories.removeAll()
|
self.selectedCategories.removeAll()
|
||||||
self.selectedCategories.insert(categoryId)
|
self.selectedCategories.insert(categoryId)
|
||||||
|
|
||||||
let closeFriends = self.component?.stateContext.stateValue?.closeFriendsPeers ?? []
|
let closeFriends = self.component?.stateContext.stateValue?.closeFriendsPeers ?? []
|
||||||
if categoryId == .selectedContacts && self.selectedPeers.isEmpty {
|
if categoryId == .selectedContacts && selectedPeers.isEmpty {
|
||||||
component.editCategory(
|
component.editCategory(
|
||||||
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: []),
|
||||||
self.selectedOptions.contains(.screenshot),
|
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 {
|
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let base: EngineStoryPrivacy.Base?
|
let base: EngineStoryPrivacy.Base
|
||||||
switch categoryId {
|
switch categoryId {
|
||||||
case .everyone:
|
case .everyone:
|
||||||
base = .everyone
|
base = .everyone
|
||||||
@ -973,15 +977,15 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
case .selectedContacts:
|
case .selectedContacts:
|
||||||
base = .nobody
|
base = .nobody
|
||||||
}
|
}
|
||||||
if let base {
|
let selectedPeers = component.stateContext.stateValue?.savedSelectedPeers[base] ?? []
|
||||||
component.editCategory(
|
|
||||||
EngineStoryPrivacy(base: base, additionallyIncludePeers: self.selectedPeers),
|
component.editCategory(
|
||||||
self.selectedOptions.contains(.screenshot),
|
EngineStoryPrivacy(base: base, additionallyIncludePeers: selectedPeers),
|
||||||
self.selectedOptions.contains(.pin)
|
self.selectedOptions.contains(.screenshot),
|
||||||
)
|
self.selectedOptions.contains(.pin)
|
||||||
controller.dismissAllTooltips()
|
)
|
||||||
controller.dismiss()
|
controller.dismissAllTooltips()
|
||||||
}
|
controller.dismiss()
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -1806,6 +1810,28 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
|
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
|
||||||
return
|
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()
|
controller.requestDismiss()
|
||||||
}
|
}
|
||||||
).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
|
).minSize(CGSize(width: navigationHeight, height: navigationHeight))),
|
||||||
@ -1950,18 +1976,60 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let proceed = {
|
let proceed = {
|
||||||
component.completion(
|
var savePeers = true
|
||||||
EngineStoryPrivacy(
|
if base == .closeFriends {
|
||||||
base: base,
|
savePeers = false
|
||||||
additionallyIncludePeers: self.selectedPeers
|
} else {
|
||||||
),
|
if case .stories = component.stateContext.subject {
|
||||||
self.selectedOptions.contains(.screenshot),
|
savePeers = false
|
||||||
self.selectedOptions.contains(.pin),
|
} else if case .chats(true) = component.stateContext.subject {
|
||||||
self.component?.stateContext.stateValue?.peers.filter { self.selectedPeers.contains($0.id) } ?? []
|
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.dismissAllTooltips()
|
||||||
controller.dismiss()
|
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
|
let presentAlert: ([String]) -> Void = { usernames in
|
||||||
@ -2163,6 +2231,8 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||||
public final class State {
|
public final class State {
|
||||||
let peers: [EnginePeer]
|
let peers: [EnginePeer]
|
||||||
|
let peersMap: [EnginePeer.Id: EnginePeer]
|
||||||
|
let savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]]
|
||||||
let presences: [EnginePeer.Id: EnginePeer.Presence]
|
let presences: [EnginePeer.Id: EnginePeer.Presence]
|
||||||
let participants: [EnginePeer.Id: Int]
|
let participants: [EnginePeer.Id: Int]
|
||||||
let closeFriendsPeers: [EnginePeer]
|
let closeFriendsPeers: [EnginePeer]
|
||||||
@ -2170,12 +2240,16 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
fileprivate init(
|
fileprivate init(
|
||||||
peers: [EnginePeer],
|
peers: [EnginePeer],
|
||||||
|
peersMap: [EnginePeer.Id: EnginePeer],
|
||||||
|
savedSelectedPeers: [Stories.Item.Privacy.Base: [EnginePeer.Id]],
|
||||||
presences: [EnginePeer.Id: EnginePeer.Presence],
|
presences: [EnginePeer.Id: EnginePeer.Presence],
|
||||||
participants: [EnginePeer.Id: Int],
|
participants: [EnginePeer.Id: Int],
|
||||||
closeFriendsPeers: [EnginePeer],
|
closeFriendsPeers: [EnginePeer],
|
||||||
grayListPeers: [EnginePeer]
|
grayListPeers: [EnginePeer]
|
||||||
) {
|
) {
|
||||||
self.peers = peers
|
self.peers = peers
|
||||||
|
self.peersMap = peersMap
|
||||||
|
self.savedSelectedPeers = savedSelectedPeers
|
||||||
self.presences = presences
|
self.presences = presences
|
||||||
self.participants = participants
|
self.participants = participants
|
||||||
self.closeFriendsPeers = closeFriendsPeers
|
self.closeFriendsPeers = closeFriendsPeers
|
||||||
@ -2211,7 +2285,6 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
subject: Subject = .chats(blocked: false),
|
subject: Subject = .chats(blocked: false),
|
||||||
initialPeerIds: Set<EnginePeer.Id> = Set(),
|
initialPeerIds: Set<EnginePeer.Id> = Set(),
|
||||||
savedSelectedPeers: Set<EnginePeer.Id> = Set(),
|
|
||||||
closeFriends: Signal<[EnginePeer], NoError> = .single([]),
|
closeFriends: Signal<[EnginePeer], NoError> = .single([]),
|
||||||
blockedPeersContext: BlockedPeersContext? = nil
|
blockedPeersContext: BlockedPeersContext? = nil
|
||||||
) {
|
) {
|
||||||
@ -2231,23 +2304,83 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
switch subject {
|
switch subject {
|
||||||
case .stories:
|
case .stories:
|
||||||
var peerSignals: [Signal<EnginePeer?, NoError>] = []
|
let savedEveryoneExceptionPeers = peersListStoredState(engine: context.engine, base: .everyone)
|
||||||
if initialPeerIds.count < 3 {
|
let savedContactsExceptionPeers = peersListStoredState(engine: context.engine, base: .contacts)
|
||||||
for peerId in initialPeerIds {
|
let savedSelectedPeers = peersListStoredState(engine: context.engine, base: .nobody)
|
||||||
peerSignals.append(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
|
|
||||||
|
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(),
|
||||||
self.stateDisposable = combineLatest(queue: Queue.mainQueue(), peers, closeFriends, grayListPeers)
|
savedPeers,
|
||||||
.start(next: { [weak self] peers, closeFriends, grayListPeers in
|
closeFriends,
|
||||||
|
grayListPeers
|
||||||
|
)
|
||||||
|
.start(next: { [weak self] savedPeers, closeFriends, grayListPeers in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
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(
|
let state = State(
|
||||||
peers: peers.compactMap { $0 },
|
peers: [],
|
||||||
|
peersMap: peersMap,
|
||||||
|
savedSelectedPeers: savedSelectedPeers,
|
||||||
presences: [:],
|
presences: [:],
|
||||||
participants: [:],
|
participants: [:],
|
||||||
closeFriendsPeers: closeFriends,
|
closeFriendsPeers: closeFriends,
|
||||||
@ -2255,7 +2388,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
)
|
)
|
||||||
self.stateValue = state
|
self.stateValue = state
|
||||||
self.stateSubject.set(.single(state))
|
self.stateSubject.set(.single(state))
|
||||||
|
|
||||||
self.readySubject.set(true)
|
self.readySubject.set(true)
|
||||||
})
|
})
|
||||||
case let .chats(isGrayList):
|
case let .chats(isGrayList):
|
||||||
@ -2364,6 +2497,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
let state = State(
|
let state = State(
|
||||||
peers: peers,
|
peers: peers,
|
||||||
|
peersMap: [:],
|
||||||
|
savedSelectedPeers: [:],
|
||||||
presences: presences,
|
presences: presences,
|
||||||
participants: participants,
|
participants: participants,
|
||||||
closeFriendsPeers: [],
|
closeFriendsPeers: [],
|
||||||
@ -2427,6 +2562,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
let state = State(
|
let state = State(
|
||||||
peers: peers,
|
peers: peers,
|
||||||
|
peersMap: [:],
|
||||||
|
savedSelectedPeers: [:],
|
||||||
presences: contactList.presences,
|
presences: contactList.presences,
|
||||||
participants: [:],
|
participants: [:],
|
||||||
closeFriendsPeers: [],
|
closeFriendsPeers: [],
|
||||||
@ -2499,6 +2636,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
peersMap: [:],
|
||||||
|
savedSelectedPeers: [:],
|
||||||
presences: [:],
|
presences: [:],
|
||||||
participants: participants,
|
participants: participants,
|
||||||
closeFriendsPeers: [],
|
closeFriendsPeers: [],
|
||||||
@ -2537,7 +2676,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
timeout: Int = 0,
|
timeout: Int = 0,
|
||||||
mentions: [String] = [],
|
mentions: [String] = [],
|
||||||
stateContext: StateContext,
|
stateContext: StateContext,
|
||||||
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer]) -> Void,
|
completion: @escaping (EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void,
|
||||||
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
|
editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void,
|
||||||
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
|
editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void
|
||||||
) {
|
) {
|
||||||
@ -2548,14 +2687,20 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
|
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
|
||||||
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
|
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
|
||||||
if case let .stories(editing) = stateContext.subject {
|
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
|
var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
|
||||||
if initialPrivacy.base == .everyone, initialPrivacy.additionallyIncludePeers.count > 0 {
|
if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 {
|
||||||
if initialPrivacy.additionallyIncludePeers.count == 1 {
|
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 {
|
if !peerNames.isEmpty {
|
||||||
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
||||||
} else {
|
} else {
|
||||||
@ -2565,7 +2710,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
if !peerNames.isEmpty {
|
if !peerNames.isEmpty {
|
||||||
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
||||||
} else {
|
} 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,
|
iconColor: .blue,
|
||||||
actionTitle: everyoneSubtitle
|
actionTitle: everyoneSubtitle
|
||||||
))
|
))
|
||||||
|
|
||||||
var contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
|
var contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
|
||||||
if initialPrivacy.base == .contacts, initialPrivacy.additionallyIncludePeers.count > 0 {
|
if (stateContext.stateValue?.savedSelectedPeers[.contacts]?.count ?? 0) > 0 {
|
||||||
if initialPrivacy.additionallyIncludePeers.count == 1 {
|
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 {
|
if !peerNames.isEmpty {
|
||||||
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
||||||
} else {
|
} else {
|
||||||
@ -2589,7 +2745,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
if !peerNames.isEmpty {
|
if !peerNames.isEmpty {
|
||||||
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
contactsSubtitle = presentationData.strings.Story_Privacy_ExcludePeopleExceptNames(peerNames).string
|
||||||
} else {
|
} 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
|
var selectedContactsSubtitle = presentationData.strings.Story_Privacy_Choose
|
||||||
if initialPrivacy.base == .nobody, initialPrivacy.additionallyIncludePeers.count > 0 {
|
if (stateContext.stateValue?.savedSelectedPeers[.nobody]?.count ?? 0) > 0 {
|
||||||
if initialPrivacy.additionallyIncludePeers.count == 1 {
|
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 {
|
if !peerNames.isEmpty {
|
||||||
selectedContactsSubtitle = peerNames
|
selectedContactsSubtitle = peerNames
|
||||||
} else {
|
} else {
|
||||||
@ -2629,7 +2796,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
if !peerNames.isEmpty {
|
if !peerNames.isEmpty {
|
||||||
selectedContactsSubtitle = peerNames
|
selectedContactsSubtitle = peerNames
|
||||||
} else {
|
} 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,
|
context: context,
|
||||||
initialPrivacy: privacy,
|
initialPrivacy: privacy,
|
||||||
stateContext: stateContext,
|
stateContext: stateContext,
|
||||||
completion: { [weak self] privacy, _, _, _ in
|
completion: { [weak self] privacy, _, _, _, completed in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component, completed else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = component.context.engine.messages.editStoryPrivacy(id: component.slice.item.storyItem.id, privacy: privacy).start()
|
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,
|
context: context,
|
||||||
initialPrivacy: privacy,
|
initialPrivacy: privacy,
|
||||||
stateContext: stateContext,
|
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 {
|
if blockedPeers, let blockedPeers = self?.component?.blockedPeers {
|
||||||
let _ = blockedPeers.updatePeerIds(result.additionallyIncludePeers).start()
|
let _ = blockedPeers.updatePeerIds(result.additionallyIncludePeers).start()
|
||||||
completion(privacy)
|
completion(privacy)
|
||||||
|
@ -1151,7 +1151,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
|
|
||||||
let _ = ApplicationSpecificNotice.incrementTranslationSuggestion(accountManager: component.context.sharedContext.accountManager, timestamp: Int32(Date().timeIntervalSince1970)).start()
|
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
|
translateController.pushController = { [weak view] c in
|
||||||
guard let view, let component = view.component else {
|
guard let view, let component = view.component else {
|
||||||
return
|
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 _ = 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
|
controller.pushController = { [weak self] c in
|
||||||
self?.effectiveNavigationController?._keepModalDismissProgress = true
|
self?.effectiveNavigationController?._keepModalDismissProgress = true
|
||||||
self?.push(c)
|
self?.push(c)
|
||||||
|
@ -7256,7 +7256,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
if canTranslate {
|
if canTranslate {
|
||||||
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
|
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
|
controller.pushController = { [weak self] c in
|
||||||
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||||
self?.controller?.push(c)
|
self?.controller?.push(c)
|
||||||
|
@ -200,7 +200,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
|
|||||||
if isDisabled {
|
if isDisabled {
|
||||||
let context = strongSelf.context
|
let context = strongSelf.context
|
||||||
var replaceImpl: ((ViewController) -> Void)?
|
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)
|
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders, forceDark: false, dismissed: nil)
|
||||||
replaceImpl?(controller)
|
replaceImpl?(controller)
|
||||||
})
|
})
|
||||||
|
@ -292,7 +292,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var replaceImpl: ((ViewController) -> Void)?
|
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)
|
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folders, forceDark: false, dismissed: nil)
|
||||||
replaceImpl?(controller)
|
replaceImpl?(controller)
|
||||||
})
|
})
|
||||||
|
@ -1780,7 +1780,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
case .stories:
|
case .stories:
|
||||||
mappedSource = .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 {
|
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)
|
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
|
let mappedSubject: PremiumLimitScreen.Subject
|
||||||
switch subject {
|
switch subject {
|
||||||
case .folders:
|
case .folders:
|
||||||
mappedSubject = .folders
|
mappedSubject = .folders
|
||||||
case .chatsPerFolder:
|
case .chatsPerFolder:
|
||||||
mappedSubject = .chatsPerFolder
|
mappedSubject = .chatsPerFolder
|
||||||
case .pins:
|
case .pins:
|
||||||
mappedSubject = .pins
|
mappedSubject = .pins
|
||||||
case .files:
|
case .files:
|
||||||
mappedSubject = .files
|
mappedSubject = .files
|
||||||
case .accounts:
|
case .accounts:
|
||||||
mappedSubject = .accounts
|
mappedSubject = .accounts
|
||||||
case .linksPerSharedFolder:
|
case .linksPerSharedFolder:
|
||||||
mappedSubject = .linksPerSharedFolder
|
mappedSubject = .linksPerSharedFolder
|
||||||
case .membershipInSharedFolders:
|
case .membershipInSharedFolders:
|
||||||
mappedSubject = .membershipInSharedFolders
|
mappedSubject = .membershipInSharedFolders
|
||||||
case .channels:
|
case .channels:
|
||||||
mappedSubject = .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 {
|
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 translationState = 10
|
||||||
case storySource = 11
|
case storySource = 11
|
||||||
case mediaEditorState = 12
|
case mediaEditorState = 12
|
||||||
|
case shareWithPeersState = 13
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ApplicationSpecificItemCacheCollectionId {
|
public struct ApplicationSpecificItemCacheCollectionId {
|
||||||
@ -92,6 +93,7 @@ public struct ApplicationSpecificItemCacheCollectionId {
|
|||||||
public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue)
|
public static let translationState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.translationState.rawValue)
|
||||||
public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue)
|
public static let storySource = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.storySource.rawValue)
|
||||||
public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue)
|
public static let mediaEditorState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.mediaEditorState.rawValue)
|
||||||
|
public static let shareWithPeersState = applicationSpecificItemCacheCollectionId(ApplicationSpecificItemCacheCollectionIdValues.shareWithPeersState.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
private enum ApplicationSpecificOrderedItemListCollectionIdValues: Int32 {
|
||||||
|
@ -138,6 +138,25 @@ public var popularTranslationLanguages = [
|
|||||||
@available(iOS 12.0, *)
|
@available(iOS 12.0, *)
|
||||||
private let languageRecognizer = NLLanguageRecognizer()
|
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?) {
|
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 {
|
guard showTranslate || showTranslateIfTopical, text.count > 0 else {
|
||||||
return (false, nil)
|
return (false, nil)
|
||||||
@ -147,22 +166,8 @@ public func canTranslateText(context: AccountContext, text: String, showTranslat
|
|||||||
if context.sharedContext.immediateExperimentalUISettings.disableLanguageRecognition {
|
if context.sharedContext.immediateExperimentalUISettings.disableLanguageRecognition {
|
||||||
return (true, nil)
|
return (true, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseLang = context.sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
|
let dontTranslateLanguages = effectiveIgnoredTranslationLanguages(context: context, ignoredLanguages: ignoredLanguages)
|
||||||
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 text = String(text.prefix(64))
|
let text = String(text.prefix(64))
|
||||||
languageRecognizer.processString(text)
|
languageRecognizer.processString(text)
|
||||||
|
@ -994,7 +994,7 @@ public class TranslateScreen: ViewController {
|
|||||||
|
|
||||||
public var wasDismissed: (() -> Void)?
|
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 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
var baseLanguageCode = presentationData.strings.baseLanguageCode
|
var baseLanguageCode = presentationData.strings.baseLanguageCode
|
||||||
@ -1003,9 +1003,15 @@ public class TranslateScreen: ViewController {
|
|||||||
baseLanguageCode = String(baseLanguageCode.dropLast(rawSuffix.count))
|
baseLanguageCode = String(baseLanguageCode.dropLast(rawSuffix.count))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dontTranslateLanguages = effectiveIgnoredTranslationLanguages(context: context, ignoredLanguages: ignoredLanguages)
|
||||||
|
|
||||||
var toLanguage = toLanguage ?? baseLanguageCode
|
var toLanguage = toLanguage ?? baseLanguageCode
|
||||||
if toLanguage == fromLanguage {
|
if toLanguage == fromLanguage {
|
||||||
toLanguage = "en"
|
if fromLanguage == "en" {
|
||||||
|
toLanguage = dontTranslateLanguages.first(where: { $0 != "en" }) ?? "en"
|
||||||
|
} else {
|
||||||
|
toLanguage = "en"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if toLanguage == "nb" {
|
if toLanguage == "nb" {
|
||||||
@ -1044,7 +1050,7 @@ public class TranslateScreen: ViewController {
|
|||||||
let pushController = self?.pushController
|
let pushController = self?.pushController
|
||||||
let presentController = self?.presentController
|
let presentController = self?.presentController
|
||||||
let controller = languageSelectionController(context: context, forceTheme: forceTheme, fromLanguage: fromLang, toLanguage: toLang, completion: { fromLang, toLang in
|
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.pushController = pushController ?? { _ in }
|
||||||
controller.presentController = presentController ?? { _ in }
|
controller.presentController = presentController ?? { _ in }
|
||||||
presentController?(controller)
|
presentController?(controller)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user