mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge commit '131736b966a308538181018840678ce4a92320bf'
# Conflicts: # submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift # submodules/PremiumUI/Sources/PremiumIntroScreen.swift
This commit is contained in:
commit
1a04361cb7
@ -11218,6 +11218,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"GroupBoost.AdditionalFeatures" = "Additional Features";
|
||||
"GroupBoost.AdditionalFeaturesText" = "By gaining **boosts**, your group reaches higher levels and unlocks more features.";
|
||||
"ChannelBoost.AdditionalFeaturesText" = "By gaining **boosts**, your channel reaches higher levels and unlocks more features.";
|
||||
|
||||
"Stats.Boosts.Group.NoBoostersYet" = "No users currently boost your group";
|
||||
"Stats.Boosts.Group.BoostersInfo" = "Your group is currently boosted by these members.";
|
||||
|
@ -941,7 +941,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, all: Bool) -> ViewController
|
||||
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
||||
func makeArchiveSettingsController(context: AccountContext) -> ViewController
|
||||
func makeFilterSettingsController(context: AccountContext, modal: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||
func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController
|
||||
func makeBusinessSetupScreen(context: AccountContext) -> ViewController
|
||||
func makeChatbotSetupScreen(context: AccountContext, initialData: ChatbotSetupScreenInitialData) -> ViewController
|
||||
func makeChatbotSetupScreenInitialData(context: AccountContext) -> Signal<ChatbotSetupScreenInitialData, NoError>
|
||||
|
@ -70,6 +70,14 @@ public enum PremiumDemoSubject {
|
||||
case messageTags
|
||||
case lastSeen
|
||||
case messagePrivacy
|
||||
case folderTags
|
||||
|
||||
case businessLocation
|
||||
case businessHours
|
||||
case businessGreetingMessage
|
||||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
}
|
||||
|
||||
public enum PremiumLimitSubject {
|
||||
|
@ -5617,7 +5617,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
private func openFilterSettings() {
|
||||
self.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(false)
|
||||
if let navigationController = self.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: true, dismissed: { [weak self] in
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: true, scrollToTags: false, dismissed: { [weak self] in
|
||||
self?.chatListDisplayNode.mainContainerNode.updateEnableAdjacentFilterLoading(true)
|
||||
})
|
||||
navigationController.pushViewController(controller)
|
||||
|
@ -43,6 +43,19 @@ private enum ChatListFilterPresetListSection: Int32 {
|
||||
case tags
|
||||
}
|
||||
|
||||
public enum ChatListFilterPresetListEntryTag: ItemListItemTag {
|
||||
case displayTags
|
||||
|
||||
public func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? ChatListFilterPresetListEntryTag, self == other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String {
|
||||
if peers.isEmpty {
|
||||
return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder
|
||||
@ -208,7 +221,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
}
|
||||
}, activatedWhileDisabled: {
|
||||
arguments.updateDisplayTagsLocked()
|
||||
})
|
||||
}, tag: ChatListFilterPresetListEntryTag.displayTags)
|
||||
case .displayTagsFooter:
|
||||
//TODO:localize
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain("Display folder names for each chat in the chat list."), sectionId: self.section)
|
||||
@ -306,7 +319,7 @@ public enum ChatListFilterPresetListControllerMode {
|
||||
case modal
|
||||
}
|
||||
|
||||
public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, dismissed: (() -> Void)? = nil) -> ViewController {
|
||||
public func chatListFilterPresetListController(context: AccountContext, mode: ChatListFilterPresetListControllerMode, scrollToTags: Bool = false, dismissed: (() -> Void)? = nil) -> ViewController {
|
||||
let initialState = ChatListFilterPresetListControllerState()
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
@ -657,7 +670,8 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChatListFolderSettings_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, displayTags: displayTags, isPremium: isPremium, limits: limits, premiumLimits: premiumLimits), style: .blocks, animateChanges: true)
|
||||
let entries = chatListFilterPresetListControllerEntries(presentationData: presentationData, state: state, filters: filtersWithCountsValue, updatedFilterOrder: updatedFilterOrderValue, suggestedFilters: suggestedFilters, displayTags: displayTags, isPremium: isPremium, limits: limits, premiumLimits: premiumLimits)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, initialScrollToItem: scrollToTags ? ListViewScrollToItem(index: entries.count - 1, position: .center(.bottom), animated: true, curve: .Spring(duration: 0.4), directionHint: .Down) : nil, animateChanges: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
private let context: AccountContext
|
||||
private let size: CGSize
|
||||
private let hasBin: Bool
|
||||
private let isStickerEditor: Bool
|
||||
|
||||
weak var drawingView: DrawingView?
|
||||
public weak var selectionContainerView: DrawingSelectionContainerView?
|
||||
@ -95,16 +96,20 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
private let yAxisView = UIView()
|
||||
private let angleLayer = SimpleShapeLayer()
|
||||
private let bin = ComponentView<Empty>()
|
||||
|
||||
|
||||
private let stickerOverlayLayer = SimpleShapeLayer()
|
||||
private let stickerFrameLayer = SimpleShapeLayer()
|
||||
|
||||
public var onInteractionUpdated: (Bool) -> Void = { _ in }
|
||||
public var edgePreviewUpdated: (Bool) -> Void = { _ in }
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
public init(context: AccountContext, size: CGSize, hasBin: Bool = false) {
|
||||
public init(context: AccountContext, size: CGSize, hasBin: Bool = false, isStickerEditor: Bool = false) {
|
||||
self.context = context
|
||||
self.size = size
|
||||
self.hasBin = hasBin
|
||||
self.isStickerEditor = isStickerEditor
|
||||
|
||||
super.init(frame: CGRect(origin: .zero, size: size))
|
||||
|
||||
@ -140,6 +145,13 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
self.angleLayer.opacity = 0.0
|
||||
self.angleLayer.lineDashPattern = [12, 12] as [NSNumber]
|
||||
|
||||
self.stickerOverlayLayer.fillColor = UIColor(rgb: 0x000000, alpha: 0.6).cgColor
|
||||
|
||||
self.stickerFrameLayer.fillColor = UIColor.clear.cgColor
|
||||
self.stickerFrameLayer.strokeColor = UIColor(rgb: 0xffffff, alpha: 0.55).cgColor
|
||||
self.stickerFrameLayer.lineDashPattern = [24, 24] as [NSNumber]
|
||||
self.stickerFrameLayer.lineCap = .round
|
||||
|
||||
self.addSubview(self.topEdgeView)
|
||||
self.addSubview(self.leftEdgeView)
|
||||
self.addSubview(self.rightEdgeView)
|
||||
@ -148,12 +160,25 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
self.addSubview(self.xAxisView)
|
||||
self.addSubview(self.yAxisView)
|
||||
self.layer.addSublayer(self.angleLayer)
|
||||
|
||||
if isStickerEditor {
|
||||
self.layer.addSublayer(self.stickerOverlayLayer)
|
||||
self.layer.addSublayer(self.stickerFrameLayer)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public override func addSubview(_ view: UIView) {
|
||||
super.addSubview(view)
|
||||
if self.stickerOverlayLayer.superlayer != nil, view is DrawingEntityView {
|
||||
self.layer.addSublayer(self.stickerOverlayLayer)
|
||||
self.layer.addSublayer(self.stickerFrameLayer)
|
||||
}
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
@ -189,6 +214,25 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
self.angleLayer.path = anglePath
|
||||
self.angleLayer.lineWidth = width
|
||||
self.angleLayer.bounds = CGRect(origin: .zero, size: CGSize(width: 3000.0, height: width))
|
||||
|
||||
let frameWidth = floor(self.bounds.width * 0.97)
|
||||
let frameRect = CGRect(origin: CGPoint(x: floor((self.bounds.width - frameWidth) / 2.0), y: floor((self.bounds.height - frameWidth) / 2.0)), size: CGSize(width: frameWidth, height: frameWidth))
|
||||
|
||||
self.stickerOverlayLayer.frame = self.bounds
|
||||
|
||||
let overlayOuterRect = UIBezierPath(rect: self.bounds)
|
||||
let overlayInnerRect = UIBezierPath(cgPath: CGPath(roundedRect: frameRect, cornerWidth: frameWidth / 8.0, cornerHeight: frameWidth / 8.0, transform: nil))
|
||||
let overlayLineWidth: CGFloat = 2.0 * 2.2
|
||||
|
||||
overlayOuterRect.append(overlayInnerRect)
|
||||
overlayOuterRect.usesEvenOddFillRule = true
|
||||
|
||||
self.stickerOverlayLayer.path = overlayOuterRect.cgPath
|
||||
self.stickerOverlayLayer.fillRule = .evenOdd
|
||||
|
||||
self.stickerFrameLayer.frame = self.bounds
|
||||
self.stickerFrameLayer.lineWidth = overlayLineWidth
|
||||
self.stickerFrameLayer.path = CGPath(roundedRect: frameRect.insetBy(dx: -overlayLineWidth / 2.0, dy: -overlayLineWidth / 2.0), cornerWidth: frameWidth / 8.0 * 1.02, cornerHeight: frameWidth / 8.0 * 1.02, transform: nil)
|
||||
}
|
||||
|
||||
public var entities: [DrawingEntity] {
|
||||
@ -841,7 +885,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
||||
} else if self.autoSelectEntities, gestureRecognizer.numberOfTouches == 1, let viewToSelect = self.entity(at: location) {
|
||||
self.selectEntity(viewToSelect.entity, animate: false)
|
||||
self.onInteractionUpdated(true)
|
||||
} else if gestureRecognizer.numberOfTouches == 2, let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView {
|
||||
} else if gestureRecognizer.numberOfTouches == 2 || self.isStickerEditor, let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView {
|
||||
mediaEntityView.handlePan(gestureRecognizer)
|
||||
}
|
||||
}
|
||||
|
@ -192,6 +192,7 @@ private final class StickerSelectionComponent: Component {
|
||||
insertText: { _ in
|
||||
},
|
||||
backwardsDeleteText: {},
|
||||
openStickerEditor: {},
|
||||
presentController: { [weak self] c, a in
|
||||
if let self, let controller = self.component?.getController() {
|
||||
controller.present(c, in: .window(.root), with: a)
|
||||
|
@ -889,11 +889,15 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM
|
||||
}
|
||||
})
|
||||
|
||||
if case let .share(_, selfPeer, _) = self.mode {
|
||||
switch self.mode {
|
||||
case let .share(_, selfPeer, _):
|
||||
if let selfPeer {
|
||||
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, peer: selfPeer)
|
||||
}
|
||||
self.headerNode.mapNode.hasPickerAnnotation = true
|
||||
case .pick:
|
||||
self.headerNode.mapNode.userLocationAnnotation = LocationPinAnnotation(context: context, theme: self.presentationData.theme, location: TelegramMediaMap(coordinate: CLLocationCoordinate2DMake(0, 0)), queryId: nil, resultId: nil, forcedSelection: true)
|
||||
self.headerNode.mapNode.hasPickerAnnotation = true
|
||||
}
|
||||
|
||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, listTransition in
|
||||
|
@ -155,6 +155,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
case wallpaper
|
||||
case story
|
||||
case addImage
|
||||
case createSticker
|
||||
}
|
||||
|
||||
case assets(PHAssetCollection?, AssetsMode)
|
||||
@ -273,7 +274,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.presentationData = controller.presentationData
|
||||
|
||||
var assetType: PHAssetMediaType?
|
||||
if case let .assets(_, mode) = controller.subject, [.wallpaper, .addImage].contains(mode) {
|
||||
if case let .assets(_, mode) = controller.subject, [.wallpaper, .addImage, .createSticker].contains(mode) {
|
||||
assetType = .image
|
||||
}
|
||||
let mediaAssetsContext = MediaAssetsContext(assetType: assetType)
|
||||
@ -432,7 +433,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.gridNode.scrollView.alwaysBounceVertical = true
|
||||
self.gridNode.scrollView.showsVerticalScrollIndicator = false
|
||||
|
||||
if case let .assets(_, mode) = controller.subject, [.wallpaper, .story, .addImage].contains(mode) {
|
||||
if case let .assets(_, mode) = controller.subject, [.wallpaper, .story, .addImage, .createSticker].contains(mode) {
|
||||
|
||||
} else {
|
||||
let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>()
|
||||
@ -1566,7 +1567,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery
|
||||
} else {
|
||||
switch mode {
|
||||
case .default:
|
||||
case .default, .createSticker:
|
||||
self.titleView.title = presentationData.strings.MediaPicker_Recents
|
||||
self.titleView.isEnabled = true
|
||||
case .story:
|
||||
@ -2258,15 +2259,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
return self.controllerNode.defaultTransitionView()
|
||||
}
|
||||
|
||||
fileprivate func transitionView(for identifier: String, snapshot: Bool, hideSource: Bool = false) -> UIView? {
|
||||
public func transitionView(for identifier: String, snapshot: Bool, hideSource: Bool = false) -> UIView? {
|
||||
return self.controllerNode.transitionView(for: identifier, snapshot: snapshot, hideSource: hideSource)
|
||||
}
|
||||
|
||||
fileprivate func transitionImage(for identifier: String) -> UIImage? {
|
||||
public func transitionImage(for identifier: String) -> UIImage? {
|
||||
return self.controllerNode.transitionImage(for: identifier)
|
||||
}
|
||||
|
||||
func updateHiddenMediaId(_ id: String?) {
|
||||
public func updateHiddenMediaId(_ id: String?) {
|
||||
self.controllerNode.hiddenMediaId.set(.single(id))
|
||||
}
|
||||
|
||||
|
BIN
submodules/PremiumUI/Resources/badge
Normal file
BIN
submodules/PremiumUI/Resources/badge
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/boost
Normal file
BIN
submodules/PremiumUI/Resources/boost
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/business.png
Normal file
BIN
submodules/PremiumUI/Resources/business.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
submodules/PremiumUI/Resources/business.scn
Normal file
BIN
submodules/PremiumUI/Resources/business.scn
Normal file
Binary file not shown.
BIN
submodules/PremiumUI/Resources/coin
Normal file
BIN
submodules/PremiumUI/Resources/coin
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/emoji
Normal file
BIN
submodules/PremiumUI/Resources/emoji
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/gift
Normal file
BIN
submodules/PremiumUI/Resources/gift
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/lightspeed
Normal file
BIN
submodules/PremiumUI/Resources/lightspeed
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/star
Normal file
BIN
submodules/PremiumUI/Resources/star
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/swirl
Normal file
BIN
submodules/PremiumUI/Resources/swirl
Normal file
Binary file not shown.
Binary file not shown.
BIN
submodules/PremiumUI/Resources/tag
Normal file
BIN
submodules/PremiumUI/Resources/tag
Normal file
Binary file not shown.
Binary file not shown.
61
submodules/PremiumUI/Sources/BadgeBusinessView.swift
Normal file
61
submodules/PremiumUI/Sources/BadgeBusinessView.swift
Normal file
@ -0,0 +1,61 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SceneKit
|
||||
import Display
|
||||
import AppBundle
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class BadgeBusinessView: UIView, PhoneDemoDecorationView {
|
||||
private let sceneView: SCNView
|
||||
|
||||
private var leftParticles: SCNNode?
|
||||
private var rightParticles: SCNNode?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let scene = loadCompressedScene(name: "business", version: sceneVersion) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.alpha = 0.0
|
||||
|
||||
self.addSubview(self.sceneView)
|
||||
|
||||
self.leftParticles = self.sceneView.scene?.rootNode.childNode(withName: "leftParticles", recursively: false)
|
||||
self.rightParticles = self.sceneView.scene?.rootNode.childNode(withName: "rightParticles", recursively: false)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setVisible(_ visible: Bool) {
|
||||
if visible, let leftParticles = self.leftParticles, let rightParticles = self.rightParticles, leftParticles.parent == nil {
|
||||
self.sceneView.scene?.rootNode.addChildNode(leftParticles)
|
||||
self.sceneView.scene?.rootNode.addChildNode(rightParticles)
|
||||
}
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear)
|
||||
transition.updateAlpha(layer: self.layer, alpha: visible ? 0.5 : 0.0, completion: { [weak self] finished in
|
||||
if let strongSelf = self, finished && !visible && strongSelf.leftParticles?.parent != nil {
|
||||
strongSelf.leftParticles?.removeFromParentNode()
|
||||
strongSelf.rightParticles?.removeFromParentNode()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func resetAnimation() {
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ final class BadgeStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "badge", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "badge", version: 1) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
@ -67,8 +67,8 @@ final class EmojiStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "emoji", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "emoji", version: 1) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
@ -121,8 +121,8 @@ final class TagStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "tag", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "tag", version: 1) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
@ -12,7 +12,7 @@ import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
private let sceneVersion: Int = 3
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
public final class BoostHeaderBackgroundComponent: Component {
|
||||
let isVisible: Bool
|
||||
@ -58,7 +58,7 @@ public final class BoostHeaderBackgroundComponent: Component {
|
||||
|
||||
|
||||
private func setup() {
|
||||
guard let url = getAppBundle().url(forResource: "boost", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "boost", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,6 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EmojiStatusComponent
|
||||
|
||||
private let sceneVersion: Int = 3
|
||||
|
||||
class EmojiHeaderComponent: Component {
|
||||
let context: AccountContext
|
||||
let animationCache: AnimationCache
|
||||
|
@ -5,6 +5,8 @@ import Display
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class FasterStarsView: UIView, PhoneDemoDecorationView {
|
||||
private let sceneView: SCNView
|
||||
|
||||
@ -13,8 +15,8 @@ final class FasterStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "lightspeed", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "lightspeed", version: sceneVersion) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
@ -14,7 +14,7 @@ import MergedAvatarsNode
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
|
||||
private let sceneVersion: Int = 3
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class GiftAvatarComponent: Component {
|
||||
let context: AccountContext
|
||||
@ -106,7 +106,7 @@ final class GiftAvatarComponent: Component {
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
guard let url = getAppBundle().url(forResource: "gift", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "gift", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -371,6 +371,7 @@ final class PhoneDemoComponent: Component {
|
||||
case emoji
|
||||
case hello
|
||||
case tag
|
||||
case business
|
||||
}
|
||||
|
||||
enum Model {
|
||||
@ -547,6 +548,13 @@ final class PhoneDemoComponent: Component {
|
||||
self.decorationView = starsView
|
||||
self.decorationContainerView.addSubview(starsView)
|
||||
}
|
||||
case .business:
|
||||
if let _ = self.decorationView as? BadgeBusinessView {
|
||||
} else {
|
||||
let starsView = BadgeBusinessView(frame: self.decorationContainerView.bounds)
|
||||
self.decorationView = starsView
|
||||
self.decorationContainerView.addSubview(starsView)
|
||||
}
|
||||
}
|
||||
|
||||
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)
|
||||
|
@ -698,7 +698,7 @@ private final class SheetContent: CombinedComponent {
|
||||
isCurrent = mode == .current
|
||||
}
|
||||
case .features:
|
||||
textString = strings.GroupBoost_AdditionalFeaturesText
|
||||
textString = isGroup ? strings.GroupBoost_AdditionalFeaturesText : strings.ChannelBoost_AdditionalFeaturesText
|
||||
}
|
||||
|
||||
let defaultTitle = strings.ChannelBoost_Level("\(level)").string
|
||||
|
@ -8,7 +8,7 @@ import GZip
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
private let sceneVersion: Int = 2
|
||||
|
||||
private func deg2rad(_ number: Float) -> Float {
|
||||
return number * .pi / 180
|
||||
@ -223,24 +223,7 @@ class PremiumCoinComponent: Component {
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
let resourceUrl: URL
|
||||
if let url = getAppBundle().url(forResource: "coin", withExtension: "scn") {
|
||||
resourceUrl = url
|
||||
} else {
|
||||
let fileName = "coin_\(sceneVersion).scn"
|
||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
|
||||
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
|
||||
guard let url = getAppBundle().url(forResource: "coin", withExtension: ""),
|
||||
let compressedData = try? Data(contentsOf: url),
|
||||
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
|
||||
return
|
||||
}
|
||||
try? decompressedData.write(to: tmpUrl)
|
||||
}
|
||||
resourceUrl = tmpUrl
|
||||
}
|
||||
|
||||
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "coin", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -316,8 +299,8 @@ class PremiumCoinComponent: Component {
|
||||
return
|
||||
}
|
||||
|
||||
let fromScale: Float = 0.85
|
||||
let toScale: Float = 0.9
|
||||
let fromScale: Float = 0.9
|
||||
let toScale: Float = 1.0
|
||||
|
||||
let animation = CABasicAnimation(keyPath: "scale")
|
||||
animation.duration = 2.0
|
||||
|
@ -1177,7 +1177,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
text = strings.Premium_LastSeenInfo
|
||||
case .messagePrivacy:
|
||||
text = strings.Premium_MessagePrivacyInfo
|
||||
case .doubleLimits, .stories, .business:
|
||||
default:
|
||||
text = ""
|
||||
}
|
||||
|
||||
@ -1392,6 +1392,14 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
||||
case lastSeen
|
||||
case messagePrivacy
|
||||
case business
|
||||
case folderTags
|
||||
|
||||
case businessLocation
|
||||
case businessHours
|
||||
case businessGreetingMessage
|
||||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
}
|
||||
|
||||
public enum Source: Equatable {
|
||||
|
@ -533,6 +533,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
|
||||
demoSubject = .messagePrivacy
|
||||
case .business:
|
||||
demoSubject = .business
|
||||
default:
|
||||
demoSubject = .doubleLimits
|
||||
}
|
||||
|
||||
let buttonText: String
|
||||
|
@ -439,6 +439,15 @@ public enum PremiumPerk: CaseIterable {
|
||||
case lastSeen
|
||||
case messagePrivacy
|
||||
case business
|
||||
case folderTags
|
||||
|
||||
case businessLocation
|
||||
case businessHours
|
||||
case businessGreetingMessage
|
||||
case businessQuickReplies
|
||||
case businessAwayMessage
|
||||
case businessChatBots
|
||||
|
||||
|
||||
public static var allCases: [PremiumPerk] {
|
||||
return [
|
||||
@ -520,6 +529,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return "message_privacy"
|
||||
case .business:
|
||||
return "business"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -567,6 +578,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return strings.Premium_MessagePrivacy
|
||||
case .business:
|
||||
return strings.Premium_Business
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,6 +627,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return strings.Premium_MessagePrivacyInfo
|
||||
case .business:
|
||||
return strings.Premium_BusinessInfo
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -661,6 +676,8 @@ public enum PremiumPerk: CaseIterable {
|
||||
return "Premium/Perk/MessagePrivacy"
|
||||
case .business:
|
||||
return "Premium/Perk/Business"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1948,75 +1965,77 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
))),
|
||||
action: { [weak state] _ in
|
||||
var demoSubject: PremiumDemoScreen.Subject
|
||||
switch perk {
|
||||
case .doubleLimits:
|
||||
demoSubject = .doubleLimits
|
||||
case .moreUpload:
|
||||
demoSubject = .moreUpload
|
||||
case .fasterDownload:
|
||||
demoSubject = .fasterDownload
|
||||
case .voiceToText:
|
||||
demoSubject = .voiceToText
|
||||
case .noAds:
|
||||
demoSubject = .noAds
|
||||
case .uniqueReactions:
|
||||
demoSubject = .uniqueReactions
|
||||
case .premiumStickers:
|
||||
demoSubject = .premiumStickers
|
||||
case .advancedChatManagement:
|
||||
demoSubject = .advancedChatManagement
|
||||
case .profileBadge:
|
||||
demoSubject = .profileBadge
|
||||
case .animatedUserpics:
|
||||
demoSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
demoSubject = .appIcons
|
||||
case .animatedEmoji:
|
||||
demoSubject = .animatedEmoji
|
||||
case .emojiStatus:
|
||||
demoSubject = .emojiStatus
|
||||
case .translation:
|
||||
demoSubject = .translation
|
||||
case .stories:
|
||||
demoSubject = .stories
|
||||
case .colors:
|
||||
demoSubject = .colors
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumColorsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .wallpapers:
|
||||
demoSubject = .wallpapers
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumWallpapersBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messageTags:
|
||||
demoSubject = .messageTags
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessageTagsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .lastSeen:
|
||||
demoSubject = .lastSeen
|
||||
let _ = ApplicationSpecificNotice.setDismissedLastSeenBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messagePrivacy:
|
||||
demoSubject = .messagePrivacy
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .business:
|
||||
demoSubject = .business
|
||||
switch perk {
|
||||
case .doubleLimits:
|
||||
demoSubject = .doubleLimits
|
||||
case .moreUpload:
|
||||
demoSubject = .moreUpload
|
||||
case .fasterDownload:
|
||||
demoSubject = .fasterDownload
|
||||
case .voiceToText:
|
||||
demoSubject = .voiceToText
|
||||
case .noAds:
|
||||
demoSubject = .noAds
|
||||
case .uniqueReactions:
|
||||
demoSubject = .uniqueReactions
|
||||
case .premiumStickers:
|
||||
demoSubject = .premiumStickers
|
||||
case .advancedChatManagement:
|
||||
demoSubject = .advancedChatManagement
|
||||
case .profileBadge:
|
||||
demoSubject = .profileBadge
|
||||
case .animatedUserpics:
|
||||
demoSubject = .animatedUserpics
|
||||
case .appIcons:
|
||||
demoSubject = .appIcons
|
||||
case .animatedEmoji:
|
||||
demoSubject = .animatedEmoji
|
||||
case .emojiStatus:
|
||||
demoSubject = .emojiStatus
|
||||
case .translation:
|
||||
demoSubject = .translation
|
||||
case .stories:
|
||||
demoSubject = .stories
|
||||
case .colors:
|
||||
demoSubject = .colors
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumColorsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .wallpapers:
|
||||
demoSubject = .wallpapers
|
||||
let _ = ApplicationSpecificNotice.setDismissedPremiumWallpapersBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messageTags:
|
||||
demoSubject = .messageTags
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessageTagsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .lastSeen:
|
||||
demoSubject = .lastSeen
|
||||
let _ = ApplicationSpecificNotice.setDismissedLastSeenBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .messagePrivacy:
|
||||
demoSubject = .messagePrivacy
|
||||
let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
|
||||
case .business:
|
||||
demoSubject = .business
|
||||
default:
|
||||
demoSubject = .doubleLimits
|
||||
}
|
||||
|
||||
let isPremium = state?.isPremium == true
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.perks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
|
||||
let isPremium = state?.isPremium == true
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.perks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
|
||||
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
|
||||
addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier])
|
||||
}
|
||||
))))
|
||||
i += 1
|
||||
@ -2100,64 +2119,92 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
foregroundColor: .white,
|
||||
iconName: perk.iconName
|
||||
))),
|
||||
action: { _ in
|
||||
switch perk {
|
||||
case .location:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessLocation(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessLocation in
|
||||
guard let accountContext else {
|
||||
return
|
||||
action: { [weak state] _ in
|
||||
let isPremium = state?.isPremium == true
|
||||
if isPremium {
|
||||
switch perk {
|
||||
case .location:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessLocation(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessLocation in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessLocationSetupScreen(context: accountContext, initialValue: businessLocation, completion: { _ in }))
|
||||
})
|
||||
case .hours:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessHours(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessHours in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessHoursSetupScreen(context: accountContext, initialValue: businessHours, completion: { _ in }))
|
||||
})
|
||||
case .quickReplies:
|
||||
let _ = (accountContext.sharedContext.makeQuickReplySetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeQuickReplySetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
case .greetings:
|
||||
let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: false))
|
||||
})
|
||||
case .awayMessages:
|
||||
let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: true))
|
||||
})
|
||||
case .chatbots:
|
||||
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext))
|
||||
}
|
||||
} else {
|
||||
var demoSubject: PremiumDemoScreen.Subject
|
||||
switch perk {
|
||||
case .location:
|
||||
demoSubject = .businessLocation
|
||||
case .hours:
|
||||
demoSubject = .businessHours
|
||||
case .quickReplies:
|
||||
demoSubject = .businessQuickReplies
|
||||
case .greetings:
|
||||
demoSubject = .businessGreetingMessage
|
||||
case .awayMessages:
|
||||
demoSubject = .businessAwayMessage
|
||||
case .chatbots:
|
||||
demoSubject = .businessChatBots
|
||||
}
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: [.businessLocation, .businessHours, .businessQuickReplies, .businessGreetingMessage, .businessAwayMessage, .businessChatBots], buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark)
|
||||
controller.action = { [weak state] in
|
||||
dismissImpl?()
|
||||
if state?.isPremium == false {
|
||||
buy()
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessLocationSetupScreen(context: accountContext, initialValue: businessLocation, completion: { _ in }))
|
||||
})
|
||||
case .hours:
|
||||
let _ = (accountContext.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessHours(id: accountContext.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] businessHours in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeBusinessHoursSetupScreen(context: accountContext, initialValue: businessHours, completion: { _ in }))
|
||||
})
|
||||
case .quickReplies:
|
||||
let _ = (accountContext.sharedContext.makeQuickReplySetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeQuickReplySetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
case .greetings:
|
||||
let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: false))
|
||||
})
|
||||
case .awayMessages:
|
||||
let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: true))
|
||||
})
|
||||
case .chatbots:
|
||||
let _ = (accountContext.sharedContext.makeChatbotSetupScreenInitialData(context: accountContext)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak accountContext] initialData in
|
||||
guard let accountContext else {
|
||||
return
|
||||
}
|
||||
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData))
|
||||
})
|
||||
}
|
||||
controller.disposed = {
|
||||
updateIsFocused(false)
|
||||
}
|
||||
present(controller)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
updateIsFocused(true)
|
||||
}
|
||||
}
|
||||
))))
|
||||
@ -2259,7 +2306,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
iconName: "Premium/BusinessPerk/Tag"
|
||||
))),
|
||||
action: { _ in
|
||||
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, dismissed: nil))
|
||||
push(accountContext.sharedContext.makeFilterSettingsController(context: accountContext, modal: false, scrollToTags: true, dismissed: nil))
|
||||
}
|
||||
))))
|
||||
|
||||
|
@ -833,6 +833,129 @@ public class PremiumLimitsListScreen: ViewController {
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
availableItems[.businessLocation] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessLocation,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["business_location"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_Location,
|
||||
text: strings.Business_LocationInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessHours] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessHours,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["business_hours"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_OpeningHours,
|
||||
text: strings.Business_OpeningHoursInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessQuickReplies] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessQuickReplies,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["greeting_message"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_QuickReplies,
|
||||
text: strings.Business_QuickRepliesInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessGreetingMessage] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessGreetingMessage,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["greeting_message"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_GreetingMessages,
|
||||
text: strings.Business_GreetingMessagesInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessAwayMessage] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessAwayMessage,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["away_message"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_AwayMessages,
|
||||
text: strings.Business_AwayMessagesInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
availableItems[.businessChatBots] = DemoPagerComponent.Item(
|
||||
AnyComponentWithIdentity(
|
||||
id: PremiumDemoScreen.Subject.businessChatBots,
|
||||
component: AnyComponent(
|
||||
PageComponent(
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: context,
|
||||
position: .top,
|
||||
model: .island,
|
||||
videoFile: configuration.videos["business_bots"],
|
||||
decoration: .business
|
||||
)),
|
||||
title: strings.Business_Chatbots,
|
||||
text: strings.Business_ChatbotsInfo,
|
||||
textColor: textColor
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
if let order = controller.order {
|
||||
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
|
||||
|
@ -8,7 +8,7 @@ import GZip
|
||||
import AppBundle
|
||||
import LegacyComponents
|
||||
|
||||
private let sceneVersion: Int = 6
|
||||
private let sceneVersion: Int = 7
|
||||
|
||||
private func deg2rad(_ number: Float) -> Float {
|
||||
return number * .pi / 180
|
||||
@ -45,7 +45,31 @@ private func generateDiffuseTexture() -> UIImage {
|
||||
})!
|
||||
}
|
||||
|
||||
class PremiumStarComponent: Component {
|
||||
func loadCompressedScene(name: String, version: Int) -> SCNScene? {
|
||||
let resourceUrl: URL
|
||||
if let url = getAppBundle().url(forResource: name, withExtension: "scn") {
|
||||
resourceUrl = url
|
||||
} else {
|
||||
let fileName = "\(name)_\(version).scn"
|
||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
|
||||
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
|
||||
guard let url = getAppBundle().url(forResource: name, withExtension: ""),
|
||||
let compressedData = try? Data(contentsOf: url),
|
||||
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
|
||||
return nil
|
||||
}
|
||||
try? decompressedData.write(to: tmpUrl)
|
||||
}
|
||||
resourceUrl = tmpUrl
|
||||
}
|
||||
|
||||
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
|
||||
return nil
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
final class PremiumStarComponent: Component {
|
||||
let isIntro: Bool
|
||||
let isVisible: Bool
|
||||
let hasIdleAnimations: Bool
|
||||
@ -251,24 +275,7 @@ class PremiumStarComponent: Component {
|
||||
}
|
||||
|
||||
private func setup() {
|
||||
let resourceUrl: URL
|
||||
if let url = getAppBundle().url(forResource: "star", withExtension: "scn") {
|
||||
resourceUrl = url
|
||||
} else {
|
||||
let fileName = "star_\(sceneVersion).scn"
|
||||
let tmpUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName)
|
||||
if !FileManager.default.fileExists(atPath: tmpUrl.path) {
|
||||
guard let url = getAppBundle().url(forResource: "star", withExtension: ""),
|
||||
let compressedData = try? Data(contentsOf: url),
|
||||
let decompressedData = TGGUnzipData(compressedData, 8 * 1024 * 1024) else {
|
||||
return
|
||||
}
|
||||
try? decompressedData.write(to: tmpUrl)
|
||||
}
|
||||
resourceUrl = tmpUrl
|
||||
}
|
||||
|
||||
guard let scene = try? SCNScene(url: resourceUrl, options: nil) else {
|
||||
guard let scene = loadCompressedScene(name: "star", version: sceneVersion) else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ import Display
|
||||
import AppBundle
|
||||
import SwiftSignalKit
|
||||
|
||||
private let sceneVersion: Int = 1
|
||||
|
||||
final class SwirlStarsView: UIView, PhoneDemoDecorationView {
|
||||
private let sceneView: SCNView
|
||||
|
||||
@ -13,8 +15,8 @@ final class SwirlStarsView: UIView, PhoneDemoDecorationView {
|
||||
override init(frame: CGRect) {
|
||||
self.sceneView = SCNView(frame: CGRect(origin: .zero, size: frame.size))
|
||||
self.sceneView.backgroundColor = .clear
|
||||
if let url = getAppBundle().url(forResource: "swirl", withExtension: "scn") {
|
||||
self.sceneView.scene = try? SCNScene(url: url, options: nil)
|
||||
if let scene = loadCompressedScene(name: "swirl", version: sceneVersion) {
|
||||
self.sceneView.scene = scene
|
||||
}
|
||||
self.sceneView.isUserInteractionEnabled = false
|
||||
self.sceneView.preferredFramesPerSecond = 60
|
||||
|
@ -572,6 +572,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, openPremiumStatusInfo: { _, _, _, _ in
|
||||
}, openRecommendedChannelContextMenu: { _, _, _ in
|
||||
}, openGroupBoostInfo: { _, _ in
|
||||
}, openStickerEditor: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
@ -232,6 +232,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
public let openPremiumStatusInfo: (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void
|
||||
public let openRecommendedChannelContextMenu: (EnginePeer, UIView, ContextGesture?) -> Void
|
||||
public let openGroupBoostInfo: (EnginePeer.Id?, Int) -> Void
|
||||
public let openStickerEditor: () -> Void
|
||||
|
||||
public let requestMessageUpdate: (MessageId, Bool) -> Void
|
||||
public let cancelInteractiveKeyboardGestures: () -> Void
|
||||
@ -355,6 +356,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
openPremiumStatusInfo: @escaping (EnginePeer.Id, UIView, Int64?, PeerNameColor) -> Void,
|
||||
openRecommendedChannelContextMenu: @escaping (EnginePeer, UIView, ContextGesture?) -> Void,
|
||||
openGroupBoostInfo: @escaping (EnginePeer.Id?, Int) -> Void,
|
||||
openStickerEditor: @escaping () -> Void,
|
||||
requestMessageUpdate: @escaping (MessageId, Bool) -> Void,
|
||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||
dismissTextInput: @escaping () -> Void,
|
||||
@ -458,6 +460,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
self.openPremiumStatusInfo = openPremiumStatusInfo
|
||||
self.openRecommendedChannelContextMenu = openRecommendedChannelContextMenu
|
||||
self.openGroupBoostInfo = openGroupBoostInfo
|
||||
self.openStickerEditor = openStickerEditor
|
||||
|
||||
self.requestMessageUpdate = requestMessageUpdate
|
||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||
|
@ -56,6 +56,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
let dismissTextInput: () -> Void
|
||||
let insertText: (NSAttributedString) -> Void
|
||||
let backwardsDeleteText: () -> Void
|
||||
let openStickerEditor: () -> Void
|
||||
let presentController: (ViewController, Any?) -> Void
|
||||
let presentGlobalOverlayController: (ViewController, Any?) -> Void
|
||||
let getNavigationController: () -> NavigationController?
|
||||
@ -72,6 +73,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
dismissTextInput: @escaping () -> Void,
|
||||
insertText: @escaping (NSAttributedString) -> Void,
|
||||
backwardsDeleteText: @escaping () -> Void,
|
||||
openStickerEditor: @escaping () -> Void,
|
||||
presentController: @escaping (ViewController, Any?) -> Void,
|
||||
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
|
||||
getNavigationController: @escaping () -> NavigationController?,
|
||||
@ -86,6 +88,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
self.dismissTextInput = dismissTextInput
|
||||
self.insertText = insertText
|
||||
self.backwardsDeleteText = backwardsDeleteText
|
||||
self.openStickerEditor = openStickerEditor
|
||||
self.presentController = presentController
|
||||
self.presentGlobalOverlayController = presentGlobalOverlayController
|
||||
self.getNavigationController = getNavigationController
|
||||
@ -106,6 +109,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
self.dismissTextInput = chatControllerInteraction.dismissTextInput
|
||||
self.insertText = panelInteraction.insertText
|
||||
self.backwardsDeleteText = panelInteraction.backwardsDeleteText
|
||||
self.openStickerEditor = chatControllerInteraction.openStickerEditor
|
||||
self.presentController = chatControllerInteraction.presentController
|
||||
self.presentGlobalOverlayController = chatControllerInteraction.presentGlobalOverlayController
|
||||
self.getNavigationController = chatControllerInteraction.navigationController
|
||||
@ -1140,6 +1144,9 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
return
|
||||
}
|
||||
guard let file = item.itemFile else {
|
||||
if groupId == AnyHashable("recent"), case .icon(.add) = item.content {
|
||||
interaction.openStickerEditor()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
public let text: String
|
||||
public let actionTitle: String?
|
||||
public let action: () -> Void
|
||||
public let additionalActionTitle: String?
|
||||
public let additionalAction: () -> Void
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -24,7 +26,9 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
title: String,
|
||||
text: String,
|
||||
actionTitle: String?,
|
||||
action: @escaping () -> Void
|
||||
action: @escaping () -> Void,
|
||||
additionalActionTitle: String?,
|
||||
additionalAction: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
@ -33,6 +37,8 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
self.text = text
|
||||
self.actionTitle = actionTitle
|
||||
self.action = action
|
||||
self.additionalActionTitle = additionalActionTitle
|
||||
self.additionalAction = additionalAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmptyStateIndicatorComponent, rhs: EmptyStateIndicatorComponent) -> Bool {
|
||||
@ -54,6 +60,9 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
if lhs.actionTitle != rhs.actionTitle {
|
||||
return false
|
||||
}
|
||||
if lhs.additionalActionTitle != rhs.additionalActionTitle {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -65,6 +74,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private var button: ComponentView<Empty>?
|
||||
private var additionalButton: ComponentView<Empty>?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -139,7 +149,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 240.0, height: 50.0)
|
||||
containerSize: CGSize(width: 260.0, height: 50.0)
|
||||
)
|
||||
} else {
|
||||
if let button = self.button {
|
||||
@ -148,14 +158,52 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
var additionalButtonSize: CGSize?
|
||||
if let additionalActionTitle = component.additionalActionTitle {
|
||||
let additionalButton: ComponentView<Empty>
|
||||
if let current = self.additionalButton {
|
||||
additionalButton = current
|
||||
} else {
|
||||
additionalButton = ComponentView()
|
||||
self.additionalButton = additionalButton
|
||||
}
|
||||
|
||||
additionalButtonSize = additionalButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Text(
|
||||
text: additionalActionTitle, font:
|
||||
Font.regular(17.0),
|
||||
color: component.theme.list.itemAccentColor)
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.additionalAction()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 262.0, height: 50.0)
|
||||
)
|
||||
} else {
|
||||
if let additionalButton = self.additionalButton {
|
||||
self.additionalButton = nil
|
||||
additionalButton.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let animationSpacing: CGFloat = 11.0
|
||||
let titleSpacing: CGFloat = 17.0
|
||||
let buttonSpacing: CGFloat = 17.0
|
||||
let buttonSpacing: CGFloat = 21.0
|
||||
|
||||
var totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height
|
||||
if let buttonSize {
|
||||
totalHeight += buttonSpacing + buttonSize.height
|
||||
}
|
||||
if let additionalButtonSize {
|
||||
totalHeight += buttonSpacing + additionalButtonSize.height
|
||||
}
|
||||
|
||||
var contentY = floor((availableSize.height - totalHeight) * 0.5)
|
||||
|
||||
@ -185,7 +233,14 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
transition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) * 0.5), y: contentY), size: buttonSize))
|
||||
contentY += buttonSize.height
|
||||
contentY += buttonSize.height + buttonSpacing
|
||||
}
|
||||
if let additionalButtonSize, let additionalButtonView = self.additionalButton?.view {
|
||||
if additionalButtonView.superview == nil {
|
||||
self.addSubview(additionalButtonView)
|
||||
}
|
||||
transition.setFrame(view: additionalButtonView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - additionalButtonSize.width) * 0.5), y: contentY), size: additionalButtonSize))
|
||||
contentY += additionalButtonSize.height
|
||||
}
|
||||
|
||||
return availableSize
|
||||
|
@ -2519,6 +2519,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
case premiumStar
|
||||
case topic(String, Int32)
|
||||
case stop
|
||||
case add
|
||||
}
|
||||
|
||||
case animation(EntityKeyboardAnimationData)
|
||||
@ -3559,6 +3560,15 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
|
||||
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
}
|
||||
case .add:
|
||||
context.setFillColor(UIColor.black.withAlphaComponent(0.08).cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0))
|
||||
context.setFillColor(UIColor.black.withAlphaComponent(0.16).cgColor)
|
||||
|
||||
let plusSize = CGSize(width: 4.5, height: 31.5)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath)
|
||||
context.fillPath()
|
||||
}
|
||||
|
||||
UIGraphicsPopContext()
|
||||
|
@ -1784,6 +1784,7 @@ public extension EmojiPagerContentComponent {
|
||||
}
|
||||
|
||||
if let recentStickers = recentStickers {
|
||||
let groupId = "recent"
|
||||
for item in recentStickers.items {
|
||||
guard let item = item.contents.get(RecentMediaItem.self) else {
|
||||
continue
|
||||
@ -1807,7 +1808,6 @@ public extension EmojiPagerContentComponent {
|
||||
tintMode: tintMode
|
||||
)
|
||||
|
||||
let groupId = "recent"
|
||||
if let groupIndex = itemGroupIndexById[groupId] {
|
||||
itemGroups[groupIndex].items.append(resultItem)
|
||||
} else {
|
||||
|
@ -182,7 +182,11 @@ public final class ListActionItemComponent: Component {
|
||||
let contentRightInset: CGFloat
|
||||
switch component.accessory {
|
||||
case .none:
|
||||
contentRightInset = 16.0
|
||||
if let _ = component.icon {
|
||||
contentRightInset = 42.0
|
||||
} else {
|
||||
contentRightInset = 16.0
|
||||
}
|
||||
case .arrow:
|
||||
contentRightInset = 30.0
|
||||
case .toggle:
|
||||
@ -193,7 +197,7 @@ public final class ListActionItemComponent: Component {
|
||||
contentHeight += component.contentInsets.top
|
||||
|
||||
if component.leftIcon != nil {
|
||||
contentLeftInset += 46.0
|
||||
contentLeftInset += 52.0
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
@ -243,7 +247,7 @@ public final class ListActionItemComponent: Component {
|
||||
|
||||
var iconOffset: CGFloat = 0.0
|
||||
if case .none = component.accessory {
|
||||
iconOffset = 6.0
|
||||
iconOffset = 26.0
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: availableSize.width - contentRightInset - iconSize.width + iconOffset, y: floor((contentHeight - iconSize.height) * 0.5)), size: iconSize)
|
||||
|
@ -25,8 +25,8 @@ vertex RasterizerData defaultVertexShader(uint vertexID [[vertex_id]],
|
||||
fragment half4 defaultFragmentShader(RasterizerData in [[stage_in]],
|
||||
texture2d<half, access::sample> texture [[texture(0)]]) {
|
||||
constexpr sampler samplr(filter::linear, mag_filter::linear, min_filter::linear);
|
||||
half3 color = texture.sample(samplr, in.texCoord).rgb;
|
||||
return half4(color, 1.0);
|
||||
half4 color = texture.sample(samplr, in.texCoord);
|
||||
return color;
|
||||
}
|
||||
|
||||
fragment half histogramPrepareFragmentShader(RasterizerData in [[stage_in]],
|
||||
@ -39,12 +39,12 @@ fragment half histogramPrepareFragmentShader(RasterizerData in [[stage_in]],
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
float3 topColor;
|
||||
float3 bottomColor;
|
||||
float4 topColor;
|
||||
float4 bottomColor;
|
||||
} GradientColors;
|
||||
|
||||
fragment half4 gradientFragmentShader(RasterizerData in [[stage_in]],
|
||||
constant GradientColors& colors [[buffer(0)]]) {
|
||||
|
||||
return half4(half3(mix(colors.topColor, colors.bottomColor, in.texCoord.y)), 1.0);
|
||||
return half4(half3(mix(colors.topColor.rgb, colors.bottomColor.rgb, in.texCoord.y)), 1.0);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import VideoToolbox
|
||||
|
||||
private let queue = Queue()
|
||||
|
||||
public func cutoutStickerImage(from image: UIImage) -> Signal<UIImage?, NoError> {
|
||||
public func cutoutStickerImage(from image: UIImage, onlyCheck: Bool = false) -> Signal<UIImage?, NoError> {
|
||||
if #available(iOS 17.0, *) {
|
||||
guard let cgImage = image.cgImage else {
|
||||
return .single(nil)
|
||||
@ -23,21 +23,26 @@ public func cutoutStickerImage(from image: UIImage) -> Signal<UIImage?, NoError>
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
let instances = instances(atPoint: nil, inObservation: result)
|
||||
if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) {
|
||||
let filter = CIFilter.blendWithMask()
|
||||
filter.inputImage = inputImage
|
||||
filter.backgroundImage = CIImage(color: .clear)
|
||||
filter.maskImage = CIImage(cvPixelBuffer: mask)
|
||||
if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) {
|
||||
let image = UIImage(cgImage: cgImage)
|
||||
subscriber.putNext(image)
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
if onlyCheck {
|
||||
subscriber.putNext(UIImage())
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
let instances = instances(atPoint: nil, inObservation: result)
|
||||
if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) {
|
||||
let filter = CIFilter.blendWithMask()
|
||||
filter.inputImage = inputImage
|
||||
filter.backgroundImage = CIImage(color: .clear)
|
||||
filter.maskImage = CIImage(cvPixelBuffer: mask)
|
||||
if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) {
|
||||
let image = UIImage(cgImage: cgImage)
|
||||
subscriber.putNext(image)
|
||||
subscriber.putCompletion()
|
||||
return
|
||||
}
|
||||
}
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
subscriber.putNext(nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
try? handler.perform([request])
|
||||
return ActionDisposable {
|
@ -94,6 +94,11 @@ public final class MediaEditor {
|
||||
}
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
case `default`
|
||||
case sticker
|
||||
}
|
||||
|
||||
public enum Subject {
|
||||
case image(UIImage, PixelDimensions)
|
||||
case video(String, UIImage?, Bool, String?, PixelDimensions, Double)
|
||||
@ -116,6 +121,7 @@ public final class MediaEditor {
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let mode: Mode
|
||||
private let subject: Subject
|
||||
|
||||
private let clock = CMClockGetHostTimeClock()
|
||||
@ -182,6 +188,9 @@ public final class MediaEditor {
|
||||
}
|
||||
}
|
||||
|
||||
public private(set) var canCutout: Bool = false
|
||||
public var canCutoutUpdated: (Bool) -> Void = { _ in }
|
||||
|
||||
private var textureCache: CVMetalTextureCache!
|
||||
|
||||
public var hasPortraitMask: Bool {
|
||||
@ -391,8 +400,9 @@ public final class MediaEditor {
|
||||
}
|
||||
}
|
||||
|
||||
public init(context: AccountContext, subject: Subject, values: MediaEditorValues? = nil, hasHistogram: Bool = false) {
|
||||
public init(context: AccountContext, mode: Mode, subject: Subject, values: MediaEditorValues? = nil, hasHistogram: Bool = false) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.subject = subject
|
||||
if let values {
|
||||
self.values = values
|
||||
@ -668,6 +678,19 @@ public final class MediaEditor {
|
||||
} else {
|
||||
textureSource.setMainInput(.image(image))
|
||||
}
|
||||
|
||||
|
||||
if case .sticker = self.mode {
|
||||
let _ = (cutoutStickerImage(from: image, onlyCheck: true)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let self, result != nil else {
|
||||
return
|
||||
}
|
||||
self.canCutout = true
|
||||
self.canCutoutUpdated(true)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
if let player, let playerItem = player.currentItem, !textureSourceResult.playerIsReference {
|
||||
textureSource.setMainInput(.video(playerItem))
|
||||
@ -677,7 +700,12 @@ public final class MediaEditor {
|
||||
}
|
||||
self.renderer.textureSource = textureSource
|
||||
|
||||
self.setGradientColors(textureSourceResult.gradientColors)
|
||||
switch self.mode {
|
||||
case .default:
|
||||
self.setGradientColors(textureSourceResult.gradientColors)
|
||||
case .sticker:
|
||||
self.setGradientColors(GradientColors(top: .clear, bottom: .clear))
|
||||
}
|
||||
|
||||
if let _ = textureSourceResult.player {
|
||||
self.updateRenderChain()
|
||||
@ -1615,7 +1643,7 @@ public final class MediaEditor {
|
||||
|
||||
public func setGradientColors(_ gradientColors: GradientColors) {
|
||||
self.gradientColorsPromise.set(.single(gradientColors))
|
||||
self.updateValues(mode: .skipRendering) { values in
|
||||
self.updateValues(mode: self.sourceIsVideo ? .skipRendering : .generic) { values in
|
||||
return values.withUpdatedGradientColors(gradientColors: gradientColors.array)
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ final class MediaEditorComposer {
|
||||
}
|
||||
}
|
||||
|
||||
public func makeEditorImageComposition(context: CIContext, postbox: Postbox, inputImage: UIImage, dimensions: CGSize, values: MediaEditorValues, time: CMTime, textScale: CGFloat, completion: @escaping (UIImage?) -> Void) {
|
||||
public func makeEditorImageComposition(context: CIContext, postbox: Postbox, inputImage: UIImage, dimensions: CGSize, outputDimensions: CGSize? = nil, values: MediaEditorValues, time: CMTime, textScale: CGFloat, completion: @escaping (UIImage?) -> Void) {
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let inputImage = CIImage(image: inputImage, options: [.colorSpace: colorSpace])!
|
||||
var drawingImage: CIImage?
|
||||
@ -192,7 +192,7 @@ public func makeEditorImageComposition(context: CIContext, postbox: Postbox, inp
|
||||
entities.append(contentsOf: composerEntitiesForDrawingEntity(postbox: postbox, textScale: textScale, entity: entity.entity, colorSpace: colorSpace))
|
||||
}
|
||||
|
||||
makeEditorImageFrameComposition(context: context, inputImage: inputImage, drawingImage: drawingImage, dimensions: dimensions, outputDimensions: dimensions, values: values, entities: entities, time: time, textScale: textScale, completion: { ciImage in
|
||||
makeEditorImageFrameComposition(context: context, inputImage: inputImage, drawingImage: drawingImage, dimensions: dimensions, outputDimensions: outputDimensions ?? dimensions, values: values, entities: entities, time: time, textScale: textScale, completion: { ciImage in
|
||||
if let ciImage {
|
||||
if let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) {
|
||||
Queue.mainQueue().async {
|
||||
@ -206,20 +206,19 @@ public func makeEditorImageComposition(context: CIContext, postbox: Postbox, inp
|
||||
}
|
||||
|
||||
private func makeEditorImageFrameComposition(context: CIContext, inputImage: CIImage, drawingImage: CIImage?, dimensions: CGSize, outputDimensions: CGSize, values: MediaEditorValues, entities: [MediaEditorComposerEntity], time: CMTime, textScale: CGFloat = 1.0, completion: @escaping (CIImage?) -> Void) {
|
||||
var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
|
||||
var isClear = false
|
||||
if let gradientColor = values.gradientColors?.first, gradientColor.alpha.isZero {
|
||||
isClear = true
|
||||
}
|
||||
|
||||
var resultImage = CIImage(color: isClear ? .clear : .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
|
||||
|
||||
var mediaImage = inputImage.samplingLinear().transformed(by: CGAffineTransform(translationX: -inputImage.extent.midX, y: -inputImage.extent.midY))
|
||||
|
||||
var initialScale: CGFloat
|
||||
if mediaImage.extent.height > mediaImage.extent.width && values.isStory {
|
||||
initialScale = max(dimensions.width / mediaImage.extent.width, dimensions.height / mediaImage.extent.height)
|
||||
} else {
|
||||
initialScale = dimensions.width / mediaImage.extent.width
|
||||
}
|
||||
|
||||
if values.isStory {
|
||||
resultImage = mediaImage.samplingLinear().composited(over: resultImage)
|
||||
} else {
|
||||
let initialScale = dimensions.width / mediaImage.extent.width
|
||||
var horizontalScale = initialScale
|
||||
if values.cropMirroring {
|
||||
horizontalScale *= -1.0
|
||||
|
@ -634,6 +634,10 @@ public final class MediaEditorValues: Codable, Equatable {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedCropRect(cropRect: CGRect, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
@ -716,6 +720,10 @@ public final class MediaEditorValues: Codable, Equatable {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedQualityPreset(_ qualityPreset: MediaQualityPreset?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: qualityPreset)
|
||||
}
|
||||
|
||||
public var resultDimensions: PixelDimensions {
|
||||
if self.videoIsFullHd {
|
||||
return PixelDimensions(width: 1080, height: 1920)
|
||||
|
@ -165,6 +165,7 @@ final class OutputRenderPass: DefaultRenderPass {
|
||||
renderPassDescriptor.colorAttachments[0].texture = (drawable as? CAMetalDrawable)?.texture
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = .clear
|
||||
renderPassDescriptor.colorAttachments[0].storeAction = .store
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
|
||||
|
||||
let drawableSize = renderTarget.drawableSize
|
||||
|
||||
|
@ -128,15 +128,6 @@ final class UniversalTextureSource: TextureSource {
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
//
|
||||
// private func setupDisplayLink(frameRate: Int) {
|
||||
// self.displayLink?.invalidate()
|
||||
// self.displayLink = nil
|
||||
//
|
||||
// if self.playerItemOutput != nil {
|
||||
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private protocol InputContext {
|
||||
|
@ -233,7 +233,7 @@ final class VideoFinishPass: RenderPass {
|
||||
}
|
||||
|
||||
private let canvasSize = CGSize(width: 1080.0, height: 1920.0)
|
||||
private var gradientColors = GradientColors(topColor: simd_float3(0.0, 0.0, 0.0), bottomColor: simd_float3(0.0, 0.0, 0.0))
|
||||
private var gradientColors = GradientColors(topColor: simd_float4(0.0, 0.0, 0.0, 0.0), bottomColor: simd_float4(0.0, 0.0, 0.0, 0.0))
|
||||
func update(values: MediaEditorValues, videoDuration: Double?, additionalVideoDuration: Double?) {
|
||||
let position = CGPoint(
|
||||
x: canvasSize.width / 2.0 + values.cropOffset.x,
|
||||
@ -241,6 +241,7 @@ final class VideoFinishPass: RenderPass {
|
||||
)
|
||||
|
||||
self.isStory = values.isStory
|
||||
self.isSticker = values.gradientColors?.first?.alpha == 0.0
|
||||
self.mainPosition = VideoFinishPass.VideoPosition(position: position, size: self.mainPosition.size, scale: values.cropScale, rotation: values.cropRotation, baseScale: self.mainPosition.baseScale)
|
||||
|
||||
if let position = values.additionalVideoPosition, let scale = values.additionalVideoScale, let rotation = values.additionalVideoRotation {
|
||||
@ -262,12 +263,12 @@ final class VideoFinishPass: RenderPass {
|
||||
}
|
||||
|
||||
if let gradientColors = values.gradientColors, let top = gradientColors.first, let bottom = gradientColors.last {
|
||||
let (topRed, topGreen, topBlue, _) = top.components
|
||||
let (bottomRed, bottomGreen, bottomBlue, _) = bottom.components
|
||||
let (topRed, topGreen, topBlue, topAlpha) = top.components
|
||||
let (bottomRed, bottomGreen, bottomBlue, bottomAlpha) = bottom.components
|
||||
|
||||
self.gradientColors = GradientColors(
|
||||
topColor: simd_float3(Float(topRed), Float(topGreen), Float(topBlue)),
|
||||
bottomColor: simd_float3(Float(bottomRed), Float(bottomGreen), Float(bottomBlue))
|
||||
topColor: simd_float4(Float(topRed), Float(topGreen), Float(topBlue), Float(topAlpha)),
|
||||
bottomColor: simd_float4(Float(bottomRed), Float(bottomGreen), Float(bottomBlue), Float(bottomAlpha))
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -289,6 +290,7 @@ final class VideoFinishPass: RenderPass {
|
||||
)
|
||||
|
||||
private var isStory = true
|
||||
private var isSticker = true
|
||||
private var videoPositionChanges: [VideoPositionChange] = []
|
||||
private var videoDuration: Double?
|
||||
private var additionalVideoDuration: Double?
|
||||
@ -482,10 +484,18 @@ final class VideoFinishPass: RenderPass {
|
||||
}
|
||||
|
||||
let baseScale: CGFloat
|
||||
if input.height > input.width {
|
||||
baseScale = max(canvasSize.width / CGFloat(input.width), canvasSize.height / CGFloat(input.height))
|
||||
if !self.isSticker {
|
||||
if input.height > input.width {
|
||||
baseScale = max(canvasSize.width / CGFloat(input.width), canvasSize.height / CGFloat(input.height))
|
||||
} else {
|
||||
baseScale = canvasSize.width / CGFloat(input.width)
|
||||
}
|
||||
} else {
|
||||
baseScale = canvasSize.width / CGFloat(input.width)
|
||||
if input.height > input.width {
|
||||
baseScale = canvasSize.width / CGFloat(input.width)
|
||||
} else {
|
||||
baseScale = canvasSize.width / CGFloat(input.height)
|
||||
}
|
||||
}
|
||||
self.mainPosition = self.mainPosition.with(size: CGSize(width: input.width, height: input.height), baseScale: baseScale)
|
||||
|
||||
@ -508,9 +518,13 @@ final class VideoFinishPass: RenderPass {
|
||||
|
||||
let renderPassDescriptor = MTLRenderPassDescriptor()
|
||||
renderPassDescriptor.colorAttachments[0].texture = self.cachedTexture!
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = .dontCare
|
||||
if self.gradientColors.topColor.w > 0.0 {
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = .dontCare
|
||||
} else {
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = .clear
|
||||
}
|
||||
renderPassDescriptor.colorAttachments[0].storeAction = .store
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
|
||||
guard let renderCommandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else {
|
||||
return input
|
||||
}
|
||||
@ -521,12 +535,14 @@ final class VideoFinishPass: RenderPass {
|
||||
znear: -1.0, zfar: 1.0)
|
||||
)
|
||||
|
||||
renderCommandEncoder.setRenderPipelineState(self.gradientPipelineState!)
|
||||
self.encodeGradient(
|
||||
using: renderCommandEncoder,
|
||||
containerSize: containerSize,
|
||||
device: device
|
||||
)
|
||||
if self.gradientColors.topColor.w > 0.0 {
|
||||
renderCommandEncoder.setRenderPipelineState(self.gradientPipelineState!)
|
||||
self.encodeGradient(
|
||||
using: renderCommandEncoder,
|
||||
containerSize: containerSize,
|
||||
device: device
|
||||
)
|
||||
}
|
||||
|
||||
renderCommandEncoder.setRenderPipelineState(self.mainPipelineState!)
|
||||
|
||||
@ -578,8 +594,8 @@ final class VideoFinishPass: RenderPass {
|
||||
}
|
||||
|
||||
struct GradientColors {
|
||||
var topColor: simd_float3
|
||||
var bottomColor: simd_float3
|
||||
var topColor: simd_float4
|
||||
var bottomColor: simd_float4
|
||||
}
|
||||
|
||||
func encodeGradient(
|
||||
|
@ -50,6 +50,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Stories/ForwardInfoPanelComponent",
|
||||
"//submodules/TelegramUI/Components/ContextReferenceButtonComponent",
|
||||
"//submodules/TelegramUI/Components/MediaScrubberComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -0,0 +1,363 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import DrawingUI
|
||||
import MediaEditor
|
||||
import Photos
|
||||
import LottieAnimationComponent
|
||||
import MessageInputPanelComponent
|
||||
|
||||
private final class MediaCutoutScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let mediaEditor: MediaEditor
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
mediaEditor: MediaEditor
|
||||
) {
|
||||
self.context = context
|
||||
self.mediaEditor = mediaEditor
|
||||
}
|
||||
|
||||
static func ==(lhs: MediaCutoutScreenComponent, rhs: MediaCutoutScreenComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let buttonsContainerView = UIView()
|
||||
private let buttonsBackgroundView = UIView()
|
||||
private let cancelButton = ComponentView<Empty>()
|
||||
private let label = ComponentView<Empty>()
|
||||
private let doneButton = ComponentView<Empty>()
|
||||
|
||||
private var component: MediaCutoutScreenComponent?
|
||||
private weak var state: State?
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.buttonsContainerView.clipsToBounds = true
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundColor = .clear
|
||||
|
||||
self.addSubview(self.buttonsContainerView)
|
||||
self.buttonsContainerView.addSubview(self.buttonsBackgroundView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func animateInFromEditor() {
|
||||
self.buttonsBackgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.label.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
private var animatingOut = false
|
||||
func animateOutToEditor(completion: @escaping () -> Void) {
|
||||
self.animatingOut = true
|
||||
|
||||
self.cancelButton.view?.isHidden = true
|
||||
|
||||
self.buttonsBackgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.label.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
|
||||
self.state?.updated()
|
||||
}
|
||||
|
||||
func update(component: MediaCutoutScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
self.environment = environment
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let isTablet: Bool
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
isTablet = true
|
||||
} else {
|
||||
isTablet = false
|
||||
}
|
||||
|
||||
// let mediaEditor = (environment.controller() as? MediaCutoutScreen)?.mediaEditor
|
||||
|
||||
let buttonSideInset: CGFloat
|
||||
let buttonBottomInset: CGFloat = 8.0
|
||||
var controlsBottomInset: CGFloat = 0.0
|
||||
let previewSize: CGSize
|
||||
var topInset: CGFloat = environment.statusBarHeight + 5.0
|
||||
if isTablet {
|
||||
let previewHeight = availableSize.height - topInset - 75.0
|
||||
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
||||
buttonSideInset = 30.0
|
||||
} else {
|
||||
previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778))
|
||||
buttonSideInset = 10.0
|
||||
if availableSize.height < previewSize.height + 30.0 {
|
||||
topInset = 0.0
|
||||
controlsBottomInset = -75.0
|
||||
} else {
|
||||
self.buttonsBackgroundView.backgroundColor = .clear
|
||||
}
|
||||
}
|
||||
|
||||
// var previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset))
|
||||
let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom + controlsBottomInset), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom - controlsBottomInset))
|
||||
|
||||
let cancelButtonSize = self.cancelButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(
|
||||
LottieAnimationComponent(
|
||||
animation: LottieAnimationComponent.AnimationItem(
|
||||
name: "media_backToCancel",
|
||||
mode: .animating(loop: false),
|
||||
range: self.animatingOut ? (0.5, 1.0) : (0.0, 0.5)
|
||||
),
|
||||
colors: ["__allcolors__": .white],
|
||||
size: CGSize(width: 33.0, height: 33.0)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
guard let controller = environment.controller() as? MediaCutoutScreen else {
|
||||
return
|
||||
}
|
||||
controller.requestDismiss(reset: true, animated: true)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 44.0, height: 44.0)
|
||||
)
|
||||
let cancelButtonFrame = CGRect(
|
||||
origin: CGPoint(x: buttonSideInset, y: buttonBottomInset),
|
||||
size: cancelButtonSize
|
||||
)
|
||||
if let cancelButtonView = self.cancelButton.view {
|
||||
if cancelButtonView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(cancelButtonView)
|
||||
}
|
||||
transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame)
|
||||
}
|
||||
|
||||
let labelSize = self.label.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Text(text: "Tap an object to cut it out", font: Font.regular(17.0), color: .white)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 88.0, height: 44.0)
|
||||
)
|
||||
let labelFrame = CGRect(
|
||||
origin: CGPoint(x: floorToScreenPixels((availableSize.width - labelSize.width) / 2.0), y: buttonBottomInset + 4.0),
|
||||
size: labelSize
|
||||
)
|
||||
if let labelView = self.label.view {
|
||||
if labelView.superview == nil {
|
||||
self.buttonsContainerView.addSubview(labelView)
|
||||
}
|
||||
transition.setFrame(view: labelView, frame: labelFrame)
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame)
|
||||
transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size))
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class MediaCutoutScreen: ViewController {
|
||||
fileprivate final class Node: ViewControllerTracingNode, UIGestureRecognizerDelegate {
|
||||
private weak var controller: MediaCutoutScreen?
|
||||
private let context: AccountContext
|
||||
|
||||
fileprivate let componentHost: ComponentView<ViewControllerComponentContainer.Environment>
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
init(controller: MediaCutoutScreen) {
|
||||
self.controller = controller
|
||||
self.context = controller.context
|
||||
|
||||
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.componentHost = ComponentView<ViewControllerComponentContainer.Environment>()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = .clear
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
self.view.disablesInteractiveKeyboardGestureRecognizer = true
|
||||
}
|
||||
|
||||
@objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func animateInFromEditor() {
|
||||
if let view = self.componentHost.view as? MediaCutoutScreenComponent.View {
|
||||
view.animateInFromEditor()
|
||||
}
|
||||
}
|
||||
|
||||
func animateOutToEditor(completion: @escaping () -> Void) {
|
||||
if let mediaEditor = self.controller?.mediaEditor {
|
||||
mediaEditor.play()
|
||||
}
|
||||
if let view = self.componentHost.view as? MediaCutoutScreenComponent.View {
|
||||
view.animateOutToEditor(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: Transition) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let isFirstTime = self.validLayout == nil
|
||||
self.validLayout = layout
|
||||
|
||||
let isTablet = layout.metrics.isTablet
|
||||
|
||||
let previewSize: CGSize
|
||||
let topInset: CGFloat = (layout.statusBarHeight ?? 0.0) + 5.0
|
||||
if isTablet {
|
||||
let previewHeight = layout.size.height - topInset - 75.0
|
||||
previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight)
|
||||
} else {
|
||||
previewSize = CGSize(width: layout.size.width, height: floorToScreenPixels(layout.size.width * 1.77778))
|
||||
}
|
||||
let bottomInset = layout.size.height - previewSize.height - topInset
|
||||
|
||||
let environment = ViewControllerComponentContainer.Environment(
|
||||
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
||||
navigationHeight: 0.0,
|
||||
safeInsets: UIEdgeInsets(
|
||||
top: topInset,
|
||||
left: layout.safeInsets.left,
|
||||
bottom: bottomInset,
|
||||
right: layout.safeInsets.right
|
||||
),
|
||||
inputHeight: layout.inputHeight ?? 0.0,
|
||||
metrics: layout.metrics,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
orientation: nil,
|
||||
isVisible: true,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
dateTimeFormat: self.presentationData.dateTimeFormat,
|
||||
controller: { [weak self] in
|
||||
return self?.controller
|
||||
}
|
||||
)
|
||||
|
||||
let componentSize = self.componentHost.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MediaCutoutScreenComponent(
|
||||
context: self.context,
|
||||
mediaEditor: controller.mediaEditor
|
||||
)
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
},
|
||||
forceUpdate: forceUpdate || animateOut,
|
||||
containerSize: layout.size
|
||||
)
|
||||
if let componentView = self.componentHost.view {
|
||||
if componentView.superview == nil {
|
||||
self.view.insertSubview(componentView, at: 3)
|
||||
componentView.clipsToBounds = true
|
||||
}
|
||||
let componentFrame = CGRect(origin: .zero, size: componentSize)
|
||||
transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height)))
|
||||
}
|
||||
|
||||
if isFirstTime {
|
||||
self.animateInFromEditor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var node: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
fileprivate let context: AccountContext
|
||||
fileprivate let mediaEditor: MediaEditor
|
||||
|
||||
public var dismissed: () -> Void = {}
|
||||
|
||||
private var initialValues: MediaEditorValues
|
||||
|
||||
public init(context: AccountContext, mediaEditor: MediaEditor) {
|
||||
self.context = context
|
||||
self.mediaEditor = mediaEditor
|
||||
self.initialValues = mediaEditor.values.makeCopy()
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
self.navigationPresentation = .flatModal
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(controller: self)
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
func requestDismiss(reset: Bool, animated: Bool) {
|
||||
if reset {
|
||||
self.mediaEditor.values = self.initialValues
|
||||
}
|
||||
|
||||
self.dismissed()
|
||||
|
||||
self.node.animateOutToEditor(completion: {
|
||||
self.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: Transition(transition))
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -3074,6 +3074,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}, openPremiumStatusInfo: { _, _, _, _ in
|
||||
}, openRecommendedChannelContextMenu: { _, _, _ in
|
||||
}, openGroupBoostInfo: { _, _ in
|
||||
}, openStickerEditor: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
@ -8795,7 +8796,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
})
|
||||
case .chatFolders:
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, dismissed: nil)
|
||||
let controller = self.context.sharedContext.makeFilterSettingsController(context: self.context, modal: false, scrollToTags: false, dismissed: nil)
|
||||
push(controller)
|
||||
case .notificationsAndSounds:
|
||||
if let settings = self.data?.globalSettings {
|
||||
|
@ -295,7 +295,23 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
let _ = paneNode.scrollToTop()
|
||||
}
|
||||
|
||||
func openCreateStory() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if let rootController = component.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||
let coordinator = rootController.openStoryCamera(customTarget: nil, transitionIn: nil, transitionedIn: {}, transitionOut: { _, _ in return nil })
|
||||
coordinator?.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
private var isUpdating = false
|
||||
func update(component: PeerInfoStoryGridScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
@ -313,7 +329,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
|
||||
var bottomInset: CGFloat = environment.safeInsets.bottom
|
||||
|
||||
if self.selectedCount != 0 {
|
||||
if self.selectedCount != 0 || (component.scope == .saved && self.paneNode?.isEmpty == false) {
|
||||
let selectionPanel: ComponentView<Empty>
|
||||
var selectionPanelTransition = transition
|
||||
if let current = self.selectionPanel {
|
||||
@ -327,7 +343,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
let buttonText: String
|
||||
switch component.scope {
|
||||
case .saved:
|
||||
buttonText = environment.strings.ChatList_Context_Archive
|
||||
buttonText = self.selectedCount > 0 ? environment.strings.ChatList_Context_Archive : environment.strings.StoryList_SavedAddAction
|
||||
case .archive:
|
||||
buttonText = environment.strings.StoryList_SaveToProfile
|
||||
}
|
||||
@ -344,7 +360,7 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
guard let paneNode = self.paneNode, !paneNode.selectedIds.isEmpty else {
|
||||
guard let paneNode = self.paneNode else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -361,21 +377,25 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
switch component.scope {
|
||||
case .saved:
|
||||
let selectedCount = paneNode.selectedItems.count
|
||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
|
||||
|
||||
paneNode.setIsSelectionModeActive(false)
|
||||
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||
|
||||
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount))
|
||||
environment.controller()?.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: title, timeout: nil, customUndoText: nil),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
if selectedCount == 0 {
|
||||
self.openCreateStory()
|
||||
} else {
|
||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: false).start()
|
||||
|
||||
paneNode.setIsSelectionModeActive(false)
|
||||
(self.environment?.controller() as? PeerInfoStoryGridScreen)?.updateTitle()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: environment.theme)
|
||||
|
||||
let title: String = presentationData.strings.StoryList_TooltipStoriesSavedToProfile(Int32(selectedCount))
|
||||
environment.controller()?.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .info(title: nil, text: title, timeout: nil, customUndoText: nil),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
), in: .current)
|
||||
}
|
||||
case .archive:
|
||||
let _ = component.context.engine.messages.updateStoriesArePinned(peerId: component.peerId, ids: paneNode.selectedItems, isPinned: true).start()
|
||||
|
||||
@ -449,10 +469,28 @@ final class PeerInfoStoryGridScreenComponent: Component {
|
||||
},
|
||||
listContext: nil
|
||||
)
|
||||
paneNode.isEmptyUpdated = { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
self.paneNode = paneNode
|
||||
self.addSubview(paneNode.view)
|
||||
if let selectionPanelView = self.selectionPanel?.view {
|
||||
self.insertSubview(paneNode.view, belowSubview: selectionPanelView)
|
||||
} else {
|
||||
self.addSubview(paneNode.view)
|
||||
}
|
||||
|
||||
paneNode.emptyAction = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openCreateStory()
|
||||
}
|
||||
paneNode.additionalEmptyAction = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
@ -948,6 +948,8 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
}
|
||||
|
||||
public var isEmptyUpdated: (Bool) -> Void = { _ in }
|
||||
|
||||
public private(set) var isSelectionModeActive: Bool
|
||||
|
||||
private var currentParams: (size: CGSize, topInset: CGFloat, sideInset: CGFloat, bottomInset: CGFloat, deviceMetrics: DeviceMetrics, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, navigationHeight: CGFloat, presentationData: PresentationData)?
|
||||
@ -985,6 +987,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
public var openCurrentDate: (() -> Void)?
|
||||
public var paneDidScroll: (() -> Void)?
|
||||
public var emptyAction: (() -> Void)?
|
||||
public var additionalEmptyAction: (() -> Void)?
|
||||
|
||||
public var ensureRectVisible: ((UIView, CGRect) -> Void)?
|
||||
|
||||
@ -1729,6 +1732,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
private func updateHistory(items: SparseItemGrid.Items, synchronous: Bool, reloadAtTop: Bool) {
|
||||
self.items = items
|
||||
self.isEmptyUpdated(self.isEmpty)
|
||||
|
||||
if let (size, topInset, sideInset, bottomInset, deviceMetrics, visibleHeight, isScrollingLockedAtTop, expandProgress, navigationHeight, presentationData) = self.currentParams {
|
||||
var gridSnapshot: UIView?
|
||||
@ -2027,14 +2031,21 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
context: self.context,
|
||||
theme: presentationData.theme,
|
||||
animationName: "StoryListEmpty",
|
||||
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyState_Title,
|
||||
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyState_Text,
|
||||
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||
title: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Title : presentationData.strings.StoryList_SavedEmptyPosts_Title,
|
||||
text: self.isArchive ? presentationData.strings.StoryList_ArchivedEmptyState_Text : presentationData.strings.StoryList_SavedEmptyPosts_Text,
|
||||
actionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedAddAction,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.emptyAction?()
|
||||
},
|
||||
additionalActionTitle: self.isArchive ? nil : presentationData.strings.StoryList_SavedEmptyAction,
|
||||
additionalAction: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.additionalEmptyAction?()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
@ -134,6 +134,9 @@ final class StoryAuthorInfoComponent: Component {
|
||||
if timeString.count < 6 {
|
||||
combinedString.append(NSAttributedString(string: " • \(timeString)", font: Font.regular(11.0), textColor: subtitleColor))
|
||||
}
|
||||
if component.isEdited {
|
||||
combinedString.append(NSAttributedString(string: " • \(component.strings.Story_HeaderEdited)", font: Font.regular(11.0), textColor: subtitleColor))
|
||||
}
|
||||
subtitle = combinedString
|
||||
subtitleTruncationType = .middle
|
||||
} else {
|
||||
|
@ -5420,6 +5420,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
var updateProgressImpl: ((Float) -> Void)?
|
||||
let controller = MediaEditorScreen(
|
||||
context: context,
|
||||
mode: .storyEditor,
|
||||
subject: subject,
|
||||
isEditing: !repost,
|
||||
forwardSource: repost ? (component.slice.peer, item) : nil,
|
||||
|
@ -179,6 +179,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
self.inputPanelExternalState?.deleteBackward()
|
||||
}
|
||||
},
|
||||
openStickerEditor: {},
|
||||
presentController: { [weak self] c, a in
|
||||
if let self {
|
||||
self.view?.component?.controller()?.present(c, in: .window(.root), with: a)
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "check.pdf",
|
||||
"filename" : "arrowhead_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
93
submodules/TelegramUI/Images.xcassets/Media Editor/Apply.imageset/arrowhead_30.pdf
vendored
Normal file
93
submodules/TelegramUI/Images.xcassets/Media Editor/Apply.imageset/arrowhead_30.pdf
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
%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 10.000000 9.771912 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
0.742462 6.970551 m
|
||||
0.332412 7.380601 -0.332412 7.380601 -0.742462 6.970551 c
|
||||
-1.152513 6.560500 -1.152513 5.895677 -0.742462 5.485626 c
|
||||
0.742462 6.970551 l
|
||||
h
|
||||
4.000000 2.228088 m
|
||||
3.257538 1.485626 l
|
||||
3.462021 1.281143 3.741785 1.170047 4.030842 1.178541 c
|
||||
4.319900 1.187036 4.592658 1.314369 4.784780 1.530506 c
|
||||
4.000000 2.228088 l
|
||||
h
|
||||
12.784780 10.530506 m
|
||||
13.170044 10.963928 13.131004 11.627604 12.697582 12.012868 c
|
||||
12.264160 12.398132 11.600484 12.359093 11.215220 11.925671 c
|
||||
12.784780 10.530506 l
|
||||
h
|
||||
-0.742462 5.485626 m
|
||||
3.257538 1.485626 l
|
||||
4.742462 2.970551 l
|
||||
0.742462 6.970551 l
|
||||
-0.742462 5.485626 l
|
||||
h
|
||||
4.784780 1.530506 m
|
||||
12.784780 10.530506 l
|
||||
11.215220 11.925671 l
|
||||
3.215220 2.925671 l
|
||||
4.784780 1.530506 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
839
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 33.000000 33.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
|
||||
0000000929 00000 n
|
||||
0000000951 00000 n
|
||||
0000001124 00000 n
|
||||
0000001198 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1257
|
||||
%%EOF
|
@ -1,150 +0,0 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 2 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 11.669922 11.450928 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
16.557842 15.662290 m
|
||||
17.172628 15.260314 17.345146 14.436065 16.943171 13.821279 c
|
||||
8.443170 0.821279 l
|
||||
8.223975 0.486040 7.865371 0.267438 7.466962 0.226191 c
|
||||
7.068553 0.184944 6.672771 0.325444 6.389548 0.608668 c
|
||||
0.389548 6.608668 l
|
||||
-0.129849 7.128065 -0.129849 7.970175 0.389548 8.489573 c
|
||||
0.908945 9.008969 1.751055 9.008969 2.270452 8.489573 c
|
||||
7.112782 3.647242 l
|
||||
14.716830 15.276961 l
|
||||
15.118806 15.891748 15.943055 16.064266 16.557842 15.662290 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
584
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
<< /Type /XObject
|
||||
/Length 4 0 R
|
||||
/Group << /Type /Group
|
||||
/S /Transparency
|
||||
>>
|
||||
/Subtype /Form
|
||||
/Resources << >>
|
||||
/BBox [ 0.000000 0.000000 40.000000 40.000000 ]
|
||||
>>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 20.000000 m
|
||||
0.000000 31.045694 8.954306 40.000000 20.000000 40.000000 c
|
||||
20.000000 40.000000 l
|
||||
31.045694 40.000000 40.000000 31.045694 40.000000 20.000000 c
|
||||
40.000000 20.000000 l
|
||||
40.000000 8.954306 31.045694 0.000000 20.000000 0.000000 c
|
||||
20.000000 0.000000 l
|
||||
8.954306 0.000000 0.000000 8.954306 0.000000 20.000000 c
|
||||
0.000000 20.000000 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
472
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /XObject << /X1 1 0 R >>
|
||||
/ExtGState << /E1 << /SMask << /Type /Mask
|
||||
/G 3 0 R
|
||||
/S /Alpha
|
||||
>>
|
||||
/Type /ExtGState
|
||||
>> >>
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Length 7 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
/E1 gs
|
||||
/X1 Do
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
7 0 obj
|
||||
46
|
||||
endobj
|
||||
|
||||
8 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 40.000000 40.000000 ]
|
||||
/Resources 5 0 R
|
||||
/Contents 6 0 R
|
||||
/Parent 9 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
9 0 obj
|
||||
<< /Kids [ 8 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
10 0 obj
|
||||
<< /Pages 9 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 11
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000842 00000 n
|
||||
0000000864 00000 n
|
||||
0000001584 00000 n
|
||||
0000001606 00000 n
|
||||
0000001904 00000 n
|
||||
0000002006 00000 n
|
||||
0000002027 00000 n
|
||||
0000002200 00000 n
|
||||
0000002274 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 10 0 R
|
||||
/Size 11
|
||||
>>
|
||||
startxref
|
||||
2334
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Media Editor/Cutout.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Editor/Cutout.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "magic_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
149
submodules/TelegramUI/Images.xcassets/Media Editor/Cutout.imageset/magic_30.pdf
vendored
Normal file
149
submodules/TelegramUI/Images.xcassets/Media Editor/Cutout.imageset/magic_30.pdf
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
%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 2.694702 2.078613 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
9.817018 17.062620 m
|
||||
9.747100 17.099379 9.663571 17.099379 9.593654 17.062620 c
|
||||
8.489970 16.482380 l
|
||||
8.313900 16.389814 8.108116 16.539326 8.141742 16.735382 c
|
||||
8.352527 17.964352 l
|
||||
8.365880 18.042206 8.340069 18.121647 8.283504 18.176785 c
|
||||
7.390605 19.047144 l
|
||||
7.248161 19.185993 7.326764 19.427906 7.523617 19.456511 c
|
||||
8.757572 19.635815 l
|
||||
8.835742 19.647175 8.903318 19.696270 8.938277 19.767105 c
|
||||
9.490119 20.885258 l
|
||||
9.578154 21.063637 9.832517 21.063639 9.920552 20.885260 c
|
||||
10.472394 19.767105 l
|
||||
10.507353 19.696270 10.574928 19.647175 10.653099 19.635815 c
|
||||
11.887054 19.456511 l
|
||||
12.083907 19.427906 12.162510 19.185993 12.020066 19.047144 c
|
||||
11.127168 18.176785 l
|
||||
11.070602 18.121647 11.044791 18.042206 11.058144 17.964352 c
|
||||
11.268929 16.735382 l
|
||||
11.302555 16.539326 11.096771 16.389814 10.920701 16.482380 c
|
||||
9.817018 17.062620 l
|
||||
h
|
||||
1.785109 15.879396 m
|
||||
1.735167 15.905652 1.675504 15.905652 1.625563 15.879396 c
|
||||
0.837217 15.464938 l
|
||||
0.711453 15.398820 0.564465 15.505613 0.588483 15.645655 c
|
||||
0.739044 16.523491 l
|
||||
0.748582 16.579102 0.730145 16.635843 0.689742 16.675226 c
|
||||
0.051957 17.296913 l
|
||||
-0.049789 17.396091 0.006356 17.568886 0.146965 17.589317 c
|
||||
1.028362 17.717392 l
|
||||
1.084198 17.725506 1.132466 17.760574 1.157437 17.811171 c
|
||||
1.551610 18.609852 l
|
||||
1.614492 18.737267 1.796180 18.737267 1.859062 18.609852 c
|
||||
2.253235 17.811171 l
|
||||
2.278205 17.760574 2.326474 17.725506 2.382310 17.717392 c
|
||||
3.263707 17.589317 l
|
||||
3.404316 17.568886 3.460460 17.396091 3.358715 17.296913 c
|
||||
2.720930 16.675226 l
|
||||
2.680526 16.635843 2.662090 16.579100 2.671628 16.523491 c
|
||||
2.822188 15.645655 l
|
||||
2.846207 15.505613 2.699219 15.398820 2.573454 15.464939 c
|
||||
1.785109 15.879396 l
|
||||
h
|
||||
3.225563 7.079397 m
|
||||
3.275504 7.105653 3.335167 7.105653 3.385108 7.079397 c
|
||||
4.173454 6.664939 l
|
||||
4.299219 6.598821 4.446208 6.705614 4.422188 6.845654 c
|
||||
4.271628 7.723491 l
|
||||
4.262090 7.779101 4.280527 7.835844 4.320930 7.875227 c
|
||||
4.958715 8.496914 l
|
||||
5.060461 8.596091 5.004316 8.768887 4.863707 8.789319 c
|
||||
3.982310 8.917393 l
|
||||
3.926474 8.925507 3.878206 8.960575 3.853235 9.011171 c
|
||||
3.459062 9.809853 l
|
||||
3.396180 9.937266 3.214492 9.937266 3.151610 9.809853 c
|
||||
2.757437 9.011171 l
|
||||
2.732466 8.960575 2.684198 8.925507 2.628362 8.917393 c
|
||||
1.746965 8.789319 l
|
||||
1.606356 8.768887 1.550211 8.596092 1.651957 8.496914 c
|
||||
2.289742 7.875228 l
|
||||
2.330145 7.835844 2.348582 7.779101 2.339044 7.723491 c
|
||||
2.188483 6.845655 l
|
||||
2.164464 6.705614 2.311453 6.598821 2.437217 6.664939 c
|
||||
3.225563 7.079397 l
|
||||
h
|
||||
5.069424 14.825876 m
|
||||
5.641542 15.397993 6.569129 15.397994 7.141247 14.825876 c
|
||||
18.809877 3.157246 l
|
||||
19.381996 2.585127 19.381994 1.657541 18.809877 1.085423 c
|
||||
18.341248 0.616795 l
|
||||
17.769131 0.044676 16.841543 0.044676 16.269424 0.616795 c
|
||||
4.600795 12.285424 l
|
||||
4.028677 12.857543 4.028677 13.785130 4.600795 14.357246 c
|
||||
5.069424 14.825876 l
|
||||
h
|
||||
8.835109 11.251109 m
|
||||
8.175561 10.591561 l
|
||||
17.209877 1.557245 l
|
||||
17.262598 1.504526 17.348076 1.504526 17.400797 1.557245 c
|
||||
17.869425 2.025875 l
|
||||
17.922146 2.078596 17.922146 2.164074 17.869425 2.216793 c
|
||||
8.835109 11.251109 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
3139
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.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
|
||||
0000003229 00000 n
|
||||
0000003252 00000 n
|
||||
0000003425 00000 n
|
||||
0000003499 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3558
|
||||
%%EOF
|
@ -4584,6 +4584,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
)
|
||||
self.push(boostController)
|
||||
})
|
||||
}, openStickerEditor: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openStickerEditor()
|
||||
}, requestMessageUpdate: { [weak self] id, scroll in
|
||||
if let self {
|
||||
self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
|
||||
@ -16857,6 +16862,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
let controller = MediaEditorScreen(
|
||||
context: context,
|
||||
mode: .storyEditor,
|
||||
subject: subject,
|
||||
transitionIn: nil,
|
||||
transitionOut: { _, _ in
|
||||
|
@ -30,6 +30,7 @@ import PremiumUI
|
||||
import PremiumGiftAttachmentScreen
|
||||
import TelegramCallsUI
|
||||
import AutomaticBusinessMessageSetupScreen
|
||||
import MediaEditorScreen
|
||||
|
||||
extension ChatControllerImpl {
|
||||
enum AttachMenuSubject {
|
||||
@ -1698,4 +1699,71 @@ extension ChatControllerImpl {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func openStickerEditor() {
|
||||
let mainController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
|
||||
return nil
|
||||
})
|
||||
// controller.forceSourceRect = true
|
||||
// controller.getSourceRect = getSourceRect
|
||||
mainController.requestController = { [weak self, weak mainController] _, present in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let mediaPickerController = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, subject: .assets(nil, .createSticker))
|
||||
mediaPickerController.customSelection = { [weak self, weak mainController] controller, result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let result = result as? PHAsset {
|
||||
controller.updateHiddenMediaId(result.localIdentifier)
|
||||
if let transitionView = controller.transitionView(for: result.localIdentifier, snapshot: false) {
|
||||
let editorController = MediaEditorScreen(
|
||||
context: self.context,
|
||||
mode: .stickerEditor,
|
||||
subject: .single(.asset(result)),
|
||||
transitionIn: .gallery(
|
||||
MediaEditorScreen.TransitionIn.GalleryTransitionIn(
|
||||
sourceView: transitionView,
|
||||
sourceRect: transitionView.bounds,
|
||||
sourceImage: controller.transitionImage(for: result.localIdentifier)
|
||||
)
|
||||
),
|
||||
transitionOut: { finished, isNew in
|
||||
if !finished {
|
||||
return MediaEditorScreen.TransitionOut(
|
||||
destinationView: transitionView,
|
||||
destinationRect: transitionView.bounds,
|
||||
destinationCornerRadius: 0.0
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}, completion: { [weak self, weak mainController] result, commit in
|
||||
mainController?.dismiss()
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
commit({})
|
||||
if let mediaResult = result.media, case let .image(image, _) = mediaResult {
|
||||
self?.enqueueStickerImage(image, isMemoji: false)
|
||||
}
|
||||
}
|
||||
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
||||
)
|
||||
editorController.dismissed = { [weak controller] in
|
||||
controller?.updateHiddenMediaId(nil)
|
||||
}
|
||||
self.push(editorController)
|
||||
|
||||
// completion(result, transitionView, transitionView.bounds, controller.transitionImage(for: result.localIdentifier), transitionOut, { [weak controller] in
|
||||
// controller?.updateHiddenMediaId(nil)
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
present(mediaPickerController, mediaPickerController.mediaPickerContext)
|
||||
}
|
||||
mainController.navigationPresentation = .flatModal
|
||||
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.push(mainController)
|
||||
}
|
||||
}
|
||||
|
@ -173,6 +173,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, openPremiumStatusInfo: { _, _, _, _ in
|
||||
}, openRecommendedChannelContextMenu: { _, _, _ in
|
||||
}, openGroupBoostInfo: { _, _ in
|
||||
}, openStickerEditor: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
@ -1754,6 +1754,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, openPremiumStatusInfo: { _, _, _, _ in
|
||||
}, openRecommendedChannelContextMenu: { _, _, _ in
|
||||
}, openGroupBoostInfo: { _, _ in
|
||||
}, openStickerEditor: {
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
@ -1882,8 +1883,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return archiveSettingsController(context: context)
|
||||
}
|
||||
|
||||
public func makeFilterSettingsController(context: AccountContext, modal: Bool, dismissed: (() -> Void)?) -> ViewController {
|
||||
return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, dismissed: dismissed)
|
||||
public func makeFilterSettingsController(context: AccountContext, modal: Bool, scrollToTags: Bool, dismissed: (() -> Void)?) -> ViewController {
|
||||
return chatListFilterPresetListController(context: context, mode: modal ? .modal : .default, scrollToTags: scrollToTags, dismissed: dismissed)
|
||||
}
|
||||
|
||||
public func makeBusinessSetupScreen(context: AccountContext) -> ViewController {
|
||||
@ -2048,6 +2049,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSubject = .lastSeen
|
||||
case .messagePrivacy:
|
||||
mappedSubject = .messagePrivacy
|
||||
default:
|
||||
mappedSubject = .doubleLimits
|
||||
}
|
||||
return PremiumDemoScreen(context: context, subject: mappedSubject, action: action)
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ import ImageCompression
|
||||
import TextFormat
|
||||
import MediaEditor
|
||||
import PeerInfoScreen
|
||||
import PeerInfoStoryGridScreen
|
||||
import ShareWithPeersScreen
|
||||
|
||||
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
|
||||
private var presentationData: PresentationData
|
||||
@ -354,6 +356,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
|
||||
let controller = MediaEditorScreen(
|
||||
context: context,
|
||||
mode: .storyEditor,
|
||||
subject: subject,
|
||||
customTarget: customTarget,
|
||||
transitionIn: transitionIn,
|
||||
@ -509,6 +512,19 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
viewControllers.removeSubrange(range)
|
||||
self.setViewControllers(viewControllers, animated: false)
|
||||
}
|
||||
} else if self.viewControllers.contains(where: { $0 is PeerInfoStoryGridScreen }) {
|
||||
var viewControllers: [UIViewController] = []
|
||||
for i in (0 ..< self.viewControllers.count) {
|
||||
let controller = self.viewControllers[i]
|
||||
if i == 0 {
|
||||
viewControllers.append(controller)
|
||||
} else if controller is MediaEditorScreen {
|
||||
viewControllers.append(controller)
|
||||
} else if controller is ShareWithPeersScreen {
|
||||
viewControllers.append(controller)
|
||||
}
|
||||
}
|
||||
self.setViewControllers(viewControllers, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user