mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Stories
This commit is contained in:
parent
75c01b0897
commit
53aa40353a
@ -939,6 +939,7 @@ public enum PremiumIntroSource {
|
||||
case voiceToText
|
||||
case fasterDownload
|
||||
case translation
|
||||
case stories
|
||||
}
|
||||
|
||||
public enum PremiumDemoSubject {
|
||||
@ -956,6 +957,7 @@ public enum PremiumDemoSubject {
|
||||
case animatedEmoji
|
||||
case emojiStatus
|
||||
case translation
|
||||
case stories
|
||||
}
|
||||
|
||||
public enum PremiumLimitSubject {
|
||||
|
@ -158,7 +158,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
private var activeDownloadsDisposable: Disposable?
|
||||
private var clearUnseenDownloadsTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var isPremium: Bool = false
|
||||
private(set) var isPremium: Bool = false
|
||||
|
||||
private var didSetupTabs = false
|
||||
|
||||
@ -2341,6 +2341,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
|
||||
fileprivate func openStoryCamera() {
|
||||
guard self.isPremium else {
|
||||
let context = self.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: self.context, subject: .stories, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .stories)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
self.push(controller)
|
||||
return
|
||||
}
|
||||
var cameraTransitionIn: StoryCameraTransitionIn?
|
||||
if let componentView = self.chatListHeaderView() {
|
||||
if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
|
||||
@ -2501,7 +2514,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
})))
|
||||
|
||||
let isMuted = notificationSettings.storiesMuted == true
|
||||
items.append(.action(ContextMenuActionItem(text: isMuted ? "Unmute" : "Mute", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Not Notify", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Muted": "Chat/Context Menu/Unmute"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
@ -1191,7 +1191,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
|
||||
}
|
||||
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
if case .compact = layout.metrics.widthClass, self.controller?.isPremium == true {
|
||||
let cameraIsAlreadyOpened = self.controller?.hasStoryCameraTransition ?? false
|
||||
if selectedIndex <= 0 && translation.x > 0.0 {
|
||||
transitionFraction = 0.0
|
||||
@ -1204,6 +1204,11 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele
|
||||
transitionFraction = 0.0
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if selectedIndex <= 0 && translation.x > 0.0 {
|
||||
let overscroll = translation.x
|
||||
transitionFraction = rubberBandingOffset(offset: overscroll, bandingStart: 0.0) / layout.size.width
|
||||
}
|
||||
}
|
||||
|
||||
if selectedIndex >= maxFilterIndex && translation.x < 0.0 {
|
||||
|
@ -68,6 +68,9 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
private var presentationDataDisposable: Disposable?
|
||||
private let stringsPromise = Promise<PresentationStrings>()
|
||||
|
||||
private var isPremium = false
|
||||
private var isPremiumDisposable: Disposable?
|
||||
|
||||
weak var controller: ContactsController?
|
||||
|
||||
private var initialScrollingOffset: CGFloat?
|
||||
@ -258,11 +261,22 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
|
||||
self.storiesReady.set(.single(true))
|
||||
})
|
||||
|
||||
self.isPremiumDisposable = (self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> map {
|
||||
return $0?.isPremium ?? false
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isPremium in
|
||||
if let self {
|
||||
self.isPremium = isPremium
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.storySubscriptionsDisposable?.dispose()
|
||||
self.isPremiumDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -293,6 +307,10 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
return false
|
||||
}
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return self.isPremium
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
self.searchDisplayController?.updatePresentationData(self.presentationData)
|
||||
|
@ -471,7 +471,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
self.source = source
|
||||
self.order = order ?? [.moreUpload, .fasterDownload, .voiceToText, .noAds, .uniqueReactions, .premiumStickers, .animatedEmoji, .advancedChatManagement, .profileBadge, .animatedUserpics, .appIcons, .translation]
|
||||
self.order = order ?? [.moreUpload, .fasterDownload, .voiceToText, .noAds, .uniqueReactions, .premiumStickers, .animatedEmoji, .advancedChatManagement, .profileBadge, .animatedUserpics, .appIcons, .translation, .stories]
|
||||
self.action = action
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
@ -939,6 +939,25 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
)
|
||||
)
|
||||
)
|
||||
//TODO:localize
|
||||
availableItems[.stories] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.stories,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: component.context,
|
||||
position: .top,
|
||||
videoFile: configuration.videos["voice_to_text"],
|
||||
decoration: .badgeStars
|
||||
)),
|
||||
title: "Story Posting",
|
||||
text: "Be one of the first to share your stories with your contacts or an unlimited audience.",
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
var items: [DemoPagerComponent.Item] = component.order.compactMap { availableItems[$0] }
|
||||
let index: Int
|
||||
@ -1029,6 +1048,10 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
buttonAnimationName = "premium_unlock"
|
||||
case .translation:
|
||||
buttonText = strings.Premium_Translation_Proceed
|
||||
case .stories:
|
||||
//TODO:localize
|
||||
buttonText = "Unlock Story Posting"
|
||||
buttonAnimationName = "premium_unlock"
|
||||
default:
|
||||
buttonText = strings.Common_OK
|
||||
}
|
||||
@ -1210,6 +1233,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
||||
case animatedEmoji
|
||||
case emojiStatus
|
||||
case translation
|
||||
case stories
|
||||
}
|
||||
|
||||
public enum Source: Equatable {
|
||||
|
@ -397,6 +397,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
||||
demoSubject = .emojiStatus
|
||||
case .translation:
|
||||
demoSubject = .translation
|
||||
case .stories:
|
||||
demoSubject = .stories
|
||||
}
|
||||
|
||||
let buttonText: String
|
||||
|
@ -184,6 +184,12 @@ public enum PremiumSource: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .stories:
|
||||
if case .stories = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,6 +219,7 @@ public enum PremiumSource: Equatable {
|
||||
case translation
|
||||
case linksPerSharedFolder
|
||||
case membershipInSharedFolders
|
||||
case stories
|
||||
|
||||
var identifier: String? {
|
||||
switch self {
|
||||
@ -270,6 +277,8 @@ public enum PremiumSource: Equatable {
|
||||
return "double_limits__community_invites"
|
||||
case .membershipInSharedFolders:
|
||||
return "double_limits__communities_joined"
|
||||
case .stories:
|
||||
return "stories"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -289,6 +298,7 @@ enum PremiumPerk: CaseIterable {
|
||||
case animatedEmoji
|
||||
case emojiStatus
|
||||
case translation
|
||||
case stories
|
||||
|
||||
static var allCases: [PremiumPerk] {
|
||||
return [
|
||||
@ -349,6 +359,8 @@ enum PremiumPerk: CaseIterable {
|
||||
return "emoji_status"
|
||||
case .translation:
|
||||
return "translations"
|
||||
case .stories:
|
||||
return "stories"
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,6 +394,9 @@ enum PremiumPerk: CaseIterable {
|
||||
return strings.Premium_EmojiStatus
|
||||
case .translation:
|
||||
return strings.Premium_Translation
|
||||
case .stories:
|
||||
//TODO:localize
|
||||
return "Story Posting"
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,6 +430,8 @@ enum PremiumPerk: CaseIterable {
|
||||
return strings.Premium_EmojiStatusInfo
|
||||
case .translation:
|
||||
return strings.Premium_TranslationInfo
|
||||
case .stories:
|
||||
return "Be one of the first to share your stories with your contacts or an unlimited audience."
|
||||
}
|
||||
}
|
||||
|
||||
@ -448,6 +465,8 @@ enum PremiumPerk: CaseIterable {
|
||||
return "Premium/Perk/Status"
|
||||
case .translation:
|
||||
return "Premium/Perk/Translation"
|
||||
case .stories:
|
||||
return "Premium/Perk/Translation"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1714,6 +1733,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
demoSubject = .emojiStatus
|
||||
case .translation:
|
||||
demoSubject = .translation
|
||||
case .stories:
|
||||
demoSubject = .stories
|
||||
}
|
||||
|
||||
let isPremium = state?.isPremium == true
|
||||
|
@ -888,7 +888,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
timeoutSelected = false
|
||||
|
||||
var inputPanelAvailableWidth = previewSize.width
|
||||
var inputPanelAvailableHeight = 115.0
|
||||
var inputPanelAvailableHeight = 103.0
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
if (self.inputPanelExternalState.isEditing || self.inputPanelExternalState.hasText) {
|
||||
inputPanelAvailableWidth += 200.0
|
||||
@ -1547,6 +1547,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
private var isDismissing = false
|
||||
private var dismissOffset: CGFloat = 0.0
|
||||
private var isDismissed = false
|
||||
private var isDismissBySwipeSuppressed = false
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var validLayout: ContainerViewLayout?
|
||||
@ -1955,6 +1956,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
if abs(translation.y) > 10.0 && !self.isEnhancing && hasSwipeToDismiss {
|
||||
if !self.isDismissing {
|
||||
self.isDismissing = true
|
||||
self.isDismissBySwipeSuppressed = controller.isEligibleForDraft()
|
||||
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
|
||||
}
|
||||
} else if abs(translation.x) > 10.0 && !self.isDismissing {
|
||||
@ -1965,6 +1967,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
if self.isDismissing {
|
||||
self.dismissOffset = translation.y
|
||||
controller.requestLayout(transition: .immediate)
|
||||
|
||||
if abs(self.dismissOffset) > 20.0, controller.isEligibleForDraft() {
|
||||
gestureRecognizer.isEnabled = false
|
||||
gestureRecognizer.isEnabled = true
|
||||
controller.maybePresentDiscardAlert()
|
||||
}
|
||||
} else if self.isEnhancing {
|
||||
if let mediaEditor = self.mediaEditor {
|
||||
let value = mediaEditor.getToolValue(.enhance) as? Float ?? 0.0
|
||||
@ -1977,7 +1985,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
case .ended, .cancelled:
|
||||
if self.isDismissing {
|
||||
if abs(translation.y) > self.view.frame.height * 0.33 || abs(velocity.y) > 1000.0 {
|
||||
if abs(translation.y) > self.view.frame.height * 0.33 || abs(velocity.y) > 1000.0, !controller.isEligibleForDraft() {
|
||||
controller.requestDismiss(saveDraft: false, animated: true)
|
||||
} else {
|
||||
self.dismissOffset = 0.0
|
||||
@ -2532,7 +2540,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
isInteractingWithEntities: self.isInteractingWithEntities,
|
||||
isSavingAvailable: controller.isSavingAvailable,
|
||||
hasAppeared: self.hasAppeared,
|
||||
isDismissing: self.isDismissing,
|
||||
isDismissing: self.isDismissing && !self.isDismissBySwipeSuppressed,
|
||||
bottomSafeInset: layout.intrinsicInsets.bottom,
|
||||
mediaEditor: self.mediaEditor,
|
||||
privacy: controller.state.privacy,
|
||||
@ -2697,7 +2705,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundDimView, frame: CGRect(origin: .zero, size: layout.size))
|
||||
transition.setAlpha(view: self.backgroundDimView, alpha: self.isDismissing ? 0.0 : 1.0)
|
||||
transition.setAlpha(view: self.backgroundDimView, alpha: self.isDismissing && !self.isDismissBySwipeSuppressed ? 0.0 : 1.0)
|
||||
|
||||
var bottomInputOffset: CGFloat = 0.0
|
||||
if inputHeight > 0.0 {
|
||||
@ -3042,19 +3050,27 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
self.present(controller, in: .current)
|
||||
}
|
||||
|
||||
func maybePresentDiscardAlert() {
|
||||
func isEligibleForDraft() -> Bool {
|
||||
guard let mediaEditor = self.node.mediaEditor else {
|
||||
return
|
||||
return false
|
||||
}
|
||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||
|
||||
self.hapticFeedback.impact(.light)
|
||||
if let subject = self.node.subject, case .asset = subject, self.node.mediaEditor?.values.hasChanges == false {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func maybePresentDiscardAlert() {
|
||||
self.hapticFeedback.impact(.light)
|
||||
if !self.isEligibleForDraft() {
|
||||
self.requestDismiss(saveDraft: false, animated: true)
|
||||
return
|
||||
}
|
||||
|
||||
let title: String
|
||||
let save: String
|
||||
if case .draft = self.node.subject {
|
||||
|
@ -1768,6 +1768,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSource = .fasterDownload
|
||||
case .translation:
|
||||
mappedSource = .translation
|
||||
case .stories:
|
||||
mappedSource = .stories
|
||||
}
|
||||
return PremiumIntroScreen(context: context, source: mappedSource)
|
||||
}
|
||||
@ -1803,6 +1805,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSubject = .emojiStatus
|
||||
case .translation:
|
||||
mappedSubject = .translation
|
||||
case .stories:
|
||||
mappedSubject = .stories
|
||||
}
|
||||
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user