mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
f696cfb915
commit
a8c7b217a4
@ -173,8 +173,11 @@ private final class CameraContext {
|
|||||||
self.positionValue = configuration.position
|
self.positionValue = configuration.position
|
||||||
self._positionPromise = ValuePromise<Camera.Position>(configuration.position)
|
self._positionPromise = ValuePromise<Camera.Position>(configuration.position)
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
#else
|
||||||
self.setDualCameraEnabled(configuration.isDualEnabled, change: false)
|
self.setDualCameraEnabled(configuration.isDualEnabled, change: false)
|
||||||
|
#endif
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
self,
|
self,
|
||||||
selector: #selector(self.sessionRuntimeError),
|
selector: #selector(self.sessionRuntimeError),
|
||||||
|
@ -67,6 +67,7 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||||||
public let externalState: ExternalState?
|
public let externalState: ExternalState?
|
||||||
public let animateOut: ActionSlot<Action<()>>
|
public let animateOut: ActionSlot<Action<()>>
|
||||||
public let onPan: () -> Void
|
public let onPan: () -> Void
|
||||||
|
public let willDismiss: () -> Void
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
content: AnyComponent<ChildEnvironmentType>,
|
content: AnyComponent<ChildEnvironmentType>,
|
||||||
@ -76,7 +77,8 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||||||
isScrollEnabled: Bool = true,
|
isScrollEnabled: Bool = true,
|
||||||
externalState: ExternalState? = nil,
|
externalState: ExternalState? = nil,
|
||||||
animateOut: ActionSlot<Action<()>>,
|
animateOut: ActionSlot<Action<()>>,
|
||||||
onPan: @escaping () -> Void = {}
|
onPan: @escaping () -> Void = {},
|
||||||
|
willDismiss: @escaping () -> Void = {}
|
||||||
) {
|
) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.backgroundColor = backgroundColor
|
self.backgroundColor = backgroundColor
|
||||||
@ -86,6 +88,7 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||||||
self.externalState = externalState
|
self.externalState = externalState
|
||||||
self.animateOut = animateOut
|
self.animateOut = animateOut
|
||||||
self.onPan = onPan
|
self.onPan = onPan
|
||||||
|
self.willDismiss = willDismiss
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: SheetComponent, rhs: SheetComponent) -> Bool {
|
public static func ==(lhs: SheetComponent, rhs: SheetComponent) -> Bool {
|
||||||
@ -222,6 +225,7 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||||||
let currentContentOffset = scrollView.contentOffset
|
let currentContentOffset = scrollView.contentOffset
|
||||||
targetContentOffset.pointee = currentContentOffset
|
targetContentOffset.pointee = currentContentOffset
|
||||||
if velocity.y > 300.0 {
|
if velocity.y > 300.0 {
|
||||||
|
self.component?.willDismiss()
|
||||||
self.animateOut(initialVelocity: initialVelocity, completion: {
|
self.animateOut(initialVelocity: initialVelocity, completion: {
|
||||||
self.dismiss?(false)
|
self.dismiss?(false)
|
||||||
})
|
})
|
||||||
@ -233,6 +237,7 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||||||
scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentSize.height - scrollView.contentInset.top), animated: true)
|
scrollView.setContentOffset(CGPoint(x: 0.0, y: scrollView.contentSize.height - scrollView.contentInset.top), animated: true)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
self.component?.willDismiss()
|
||||||
self.animateOut(initialVelocity: initialVelocity, completion: {
|
self.animateOut(initialVelocity: initialVelocity, completion: {
|
||||||
self.dismiss?(false)
|
self.dismiss?(false)
|
||||||
})
|
})
|
||||||
|
@ -467,6 +467,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[583071445] = { return Api.InputReplyTo.parse_inputReplyToMessage($0) }
|
dict[583071445] = { return Api.InputReplyTo.parse_inputReplyToMessage($0) }
|
||||||
dict[1484862010] = { return Api.InputReplyTo.parse_inputReplyToStory($0) }
|
dict[1484862010] = { return Api.InputReplyTo.parse_inputReplyToStory($0) }
|
||||||
dict[-251549057] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftChat($0) }
|
dict[-251549057] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftChat($0) }
|
||||||
|
dict[545636920] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftSlug($0) }
|
||||||
dict[1764202389] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftUser($0) }
|
dict[1764202389] = { return Api.InputSavedStarGift.parse_inputSavedStarGiftUser($0) }
|
||||||
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }
|
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }
|
||||||
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
|
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
|
||||||
|
@ -367,6 +367,7 @@ public extension Api {
|
|||||||
public extension Api {
|
public extension Api {
|
||||||
indirect enum InputSavedStarGift: TypeConstructorDescription {
|
indirect enum InputSavedStarGift: TypeConstructorDescription {
|
||||||
case inputSavedStarGiftChat(peer: Api.InputPeer, savedId: Int64)
|
case inputSavedStarGiftChat(peer: Api.InputPeer, savedId: Int64)
|
||||||
|
case inputSavedStarGiftSlug(slug: String)
|
||||||
case inputSavedStarGiftUser(msgId: Int32)
|
case inputSavedStarGiftUser(msgId: Int32)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
@ -378,6 +379,12 @@ public extension Api {
|
|||||||
peer.serialize(buffer, true)
|
peer.serialize(buffer, true)
|
||||||
serializeInt64(savedId, buffer: buffer, boxed: false)
|
serializeInt64(savedId, buffer: buffer, boxed: false)
|
||||||
break
|
break
|
||||||
|
case .inputSavedStarGiftSlug(let slug):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(545636920)
|
||||||
|
}
|
||||||
|
serializeString(slug, buffer: buffer, boxed: false)
|
||||||
|
break
|
||||||
case .inputSavedStarGiftUser(let msgId):
|
case .inputSavedStarGiftUser(let msgId):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(1764202389)
|
buffer.appendInt32(1764202389)
|
||||||
@ -391,6 +398,8 @@ public extension Api {
|
|||||||
switch self {
|
switch self {
|
||||||
case .inputSavedStarGiftChat(let peer, let savedId):
|
case .inputSavedStarGiftChat(let peer, let savedId):
|
||||||
return ("inputSavedStarGiftChat", [("peer", peer as Any), ("savedId", savedId as Any)])
|
return ("inputSavedStarGiftChat", [("peer", peer as Any), ("savedId", savedId as Any)])
|
||||||
|
case .inputSavedStarGiftSlug(let slug):
|
||||||
|
return ("inputSavedStarGiftSlug", [("slug", slug as Any)])
|
||||||
case .inputSavedStarGiftUser(let msgId):
|
case .inputSavedStarGiftUser(let msgId):
|
||||||
return ("inputSavedStarGiftUser", [("msgId", msgId as Any)])
|
return ("inputSavedStarGiftUser", [("msgId", msgId as Any)])
|
||||||
}
|
}
|
||||||
@ -412,6 +421,17 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_inputSavedStarGiftSlug(_ reader: BufferReader) -> InputSavedStarGift? {
|
||||||
|
var _1: String?
|
||||||
|
_1 = parseString(reader)
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
if _c1 {
|
||||||
|
return Api.InputSavedStarGift.inputSavedStarGiftSlug(slug: _1!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
public static func parse_inputSavedStarGiftUser(_ reader: BufferReader) -> InputSavedStarGift? {
|
public static func parse_inputSavedStarGiftUser(_ reader: BufferReader) -> InputSavedStarGift? {
|
||||||
var _1: Int32?
|
var _1: Int32?
|
||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
|
@ -9791,12 +9791,12 @@ public extension Api.functions.payments {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.payments {
|
public extension Api.functions.payments {
|
||||||
static func updateStarGiftPrice(slug: String, resellStars: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
static func updateStarGiftPrice(stargift: Api.InputSavedStarGift, resellStars: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-489360582)
|
buffer.appendInt32(1001301217)
|
||||||
serializeString(slug, buffer: buffer, boxed: false)
|
stargift.serialize(buffer, true)
|
||||||
serializeInt64(resellStars, buffer: buffer, boxed: false)
|
serializeInt64(resellStars, buffer: buffer, boxed: false)
|
||||||
return (FunctionDescription(name: "payments.updateStarGiftPrice", parameters: [("slug", String(describing: slug)), ("resellStars", String(describing: resellStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
return (FunctionDescription(name: "payments.updateStarGiftPrice", parameters: [("stargift", String(describing: stargift)), ("resellStars", String(describing: resellStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.Updates?
|
var result: Api.Updates?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
|
@ -1509,14 +1509,14 @@ private final class ProfileGiftsContextImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateStarGiftResellPrice(slug: String, price: Int64?) {
|
func updateStarGiftResellPrice(reference: StarGiftReference, price: Int64?) {
|
||||||
self.actionDisposable.set(
|
self.actionDisposable.set(
|
||||||
_internal_updateStarGiftResalePrice(account: self.account, slug: slug, price: price).startStrict()
|
_internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price).startStrict()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if let index = self.gifts.firstIndex(where: { gift in
|
if let index = self.gifts.firstIndex(where: { gift in
|
||||||
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
if gift.reference == reference {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -1529,7 +1529,7 @@ private final class ProfileGiftsContextImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let index = self.filteredGifts.firstIndex(where: { gift in
|
if let index = self.filteredGifts.firstIndex(where: { gift in
|
||||||
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
if gift.reference == reference {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -1939,9 +1939,9 @@ public final class ProfileGiftsContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateStarGiftResellPrice(slug: String, price: Int64?) {
|
public func updateStarGiftResellPrice(reference: StarGiftReference, price: Int64?) {
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
impl.updateStarGiftResellPrice(slug: slug, price: price)
|
impl.updateStarGiftResellPrice(reference: reference, price: price)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2082,10 +2082,12 @@ public enum StarGiftReference: Equatable, Hashable, Codable {
|
|||||||
case messageId
|
case messageId
|
||||||
case peerId
|
case peerId
|
||||||
case id
|
case id
|
||||||
|
case slug
|
||||||
}
|
}
|
||||||
|
|
||||||
case message(messageId: EngineMessage.Id)
|
case message(messageId: EngineMessage.Id)
|
||||||
case peer(peerId: EnginePeer.Id, id: Int64)
|
case peer(peerId: EnginePeer.Id, id: Int64)
|
||||||
|
case slug(slug: String)
|
||||||
|
|
||||||
public enum DecodingError: Error {
|
public enum DecodingError: Error {
|
||||||
case generic
|
case generic
|
||||||
@ -2100,6 +2102,8 @@ public enum StarGiftReference: Equatable, Hashable, Codable {
|
|||||||
self = .message(messageId: try container.decode(EngineMessage.Id.self, forKey: .messageId))
|
self = .message(messageId: try container.decode(EngineMessage.Id.self, forKey: .messageId))
|
||||||
case 1:
|
case 1:
|
||||||
self = .peer(peerId: try container.decode(EnginePeer.Id.self, forKey: .peerId), id: try container.decode(Int64.self, forKey: .id))
|
self = .peer(peerId: try container.decode(EnginePeer.Id.self, forKey: .peerId), id: try container.decode(Int64.self, forKey: .id))
|
||||||
|
case 2:
|
||||||
|
self = .slug(slug: try container.decode(String.self, forKey: .slug))
|
||||||
default:
|
default:
|
||||||
throw DecodingError.generic
|
throw DecodingError.generic
|
||||||
}
|
}
|
||||||
@ -2116,6 +2120,9 @@ public enum StarGiftReference: Equatable, Hashable, Codable {
|
|||||||
try container.encode(1 as Int32, forKey: .type)
|
try container.encode(1 as Int32, forKey: .type)
|
||||||
try container.encode(peerId, forKey: .peerId)
|
try container.encode(peerId, forKey: .peerId)
|
||||||
try container.encode(id, forKey: .id)
|
try container.encode(id, forKey: .id)
|
||||||
|
case let .slug(slug):
|
||||||
|
try container.encode(2 as Int32, forKey: .type)
|
||||||
|
try container.encode(slug, forKey: .slug)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2130,6 +2137,8 @@ extension StarGiftReference {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .inputSavedStarGiftChat(peer: inputPeer, savedId: id)
|
return .inputSavedStarGiftChat(peer: inputPeer, savedId: id)
|
||||||
|
case let .slug(slug):
|
||||||
|
return .inputSavedStarGiftSlug(slug: slug)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2265,19 +2274,27 @@ func _internal_toggleStarGiftsNotifications(account: Account, peerId: EnginePeer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_updateStarGiftResalePrice(account: Account, slug: String, price: Int64?) -> Signal<Never, NoError> {
|
func _internal_updateStarGiftResalePrice(account: Account, reference: StarGiftReference, price: Int64?) -> Signal<Never, NoError> {
|
||||||
return account.network.request(Api.functions.payments.updateStarGiftPrice(slug: slug, resellStars: price ?? 0))
|
return account.postbox.transaction { transaction in
|
||||||
|> map(Optional.init)
|
return reference.apiStarGiftReference(transaction: transaction)
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|
||||||
return .single(nil)
|
|
||||||
}
|
}
|
||||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
|> mapToSignal { starGift in
|
||||||
if let updates {
|
guard let starGift else {
|
||||||
account.stateManager.addUpdates(updates)
|
return .complete()
|
||||||
}
|
}
|
||||||
return .complete()
|
return account.network.request(Api.functions.payments.updateStarGiftPrice(stargift: starGift, resellStars: price ?? 0))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||||
|
if let updates {
|
||||||
|
account.stateManager.addUpdates(updates)
|
||||||
|
}
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|> ignoreValues
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension StarGift.UniqueGift {
|
public extension StarGift.UniqueGift {
|
||||||
|
@ -153,8 +153,8 @@ public extension TelegramEngine {
|
|||||||
return _internal_toggleStarGiftsNotifications(account: self.account, peerId: peerId, enabled: enabled)
|
return _internal_toggleStarGiftsNotifications(account: self.account, peerId: peerId, enabled: enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateStarGiftResalePrice(slug: String, price: Int64?) -> Signal<Never, NoError> {
|
public func updateStarGiftResalePrice(reference: StarGiftReference, price: Int64?) -> Signal<Never, NoError> {
|
||||||
return _internal_updateStarGiftResalePrice(account: self.account, slug: slug, price: price)
|
return _internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -866,7 +866,8 @@ public final class GiftItemComponent: Component {
|
|||||||
return (TelegramTextAttributes.URL, contents)
|
return (TelegramTextAttributes.URL, contents)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
let labelText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("#\(resellPrice)", attributes: attributes))
|
let dateTimeFormat = component.context.sharedContext.currentPresentationData.with { $0 }.dateTimeFormat
|
||||||
|
let labelText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("#\(presentationStringsFormattedNumber(Int32(resellPrice), dateTimeFormat.groupingSeparator))", attributes: attributes))
|
||||||
if let range = labelText.string.range(of: "#") {
|
if let range = labelText.string.range(of: "#") {
|
||||||
labelText.addAttribute(NSAttributedString.Key.font, value: Font.semibold(10.0), range: NSRange(range, in: labelText.string))
|
labelText.addAttribute(NSAttributedString.Key.font, value: Font.semibold(10.0), range: NSRange(range, in: labelText.string))
|
||||||
labelText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: NSRange(range, in: labelText.string))
|
labelText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: NSRange(range, in: labelText.string))
|
||||||
|
@ -465,7 +465,6 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
self.inProgress = false
|
self.inProgress = false
|
||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
var errorText: String?
|
var errorText: String?
|
||||||
switch error {
|
switch error {
|
||||||
case .starGiftOutOfStock:
|
case .starGiftOutOfStock:
|
||||||
|
@ -72,7 +72,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
private let loadingNode: LoadingShimmerNode
|
private let loadingNode: LoadingShimmerNode
|
||||||
private let emptyResultsAnimation = ComponentView<Empty>()
|
private let emptyResultsAnimation = ComponentView<Empty>()
|
||||||
private let emptyResultsTitle = ComponentView<Empty>()
|
private let emptyResultsTitle = ComponentView<Empty>()
|
||||||
private let emptyResultsAction = ComponentView<Empty>()
|
private let clearFilters = ComponentView<Empty>()
|
||||||
|
|
||||||
private let topPanel = ComponentView<Empty>()
|
private let topPanel = ComponentView<Empty>()
|
||||||
private let topSeparator = ComponentView<Empty>()
|
private let topSeparator = ComponentView<Empty>()
|
||||||
@ -139,10 +139,21 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
self.updateScrolling(interactive: true, transition: self.nextScrollTransition ?? .immediate)
|
self.updateScrolling(interactive: true, transition: self.nextScrollTransition ?? .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var removedStarGifts = Set<String>()
|
||||||
private var currentGifts: ([StarGift], Set<String>, Set<String>, Set<String>)?
|
private var currentGifts: ([StarGift], Set<String>, Set<String>, Set<String>)?
|
||||||
private var effectiveGifts: [StarGift]? {
|
private var effectiveGifts: [StarGift]? {
|
||||||
if let gifts = self.state?.starGiftsState?.gifts {
|
if let gifts = self.state?.starGiftsState?.gifts {
|
||||||
return gifts
|
if !self.removedStarGifts.isEmpty {
|
||||||
|
return gifts.filter { gift in
|
||||||
|
if case let .unique(uniqueGift) = gift {
|
||||||
|
return !self.removedStarGifts.contains(uniqueGift.slug)
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return gifts
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -154,6 +165,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let availableWidth = self.scrollView.bounds.width
|
let availableWidth = self.scrollView.bounds.width
|
||||||
|
let availableHeight = self.scrollView.bounds.height
|
||||||
let contentOffset = self.scrollView.contentOffset.y
|
let contentOffset = self.scrollView.contentOffset.y
|
||||||
|
|
||||||
let topPanelAlpha = min(20.0, max(0.0, contentOffset)) / 20.0
|
let topPanelAlpha = min(20.0, max(0.0, contentOffset)) / 20.0
|
||||||
@ -213,8 +225,8 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
font: .monospaced,
|
font: .monospaced,
|
||||||
color: ribbonColor
|
color: ribbonColor
|
||||||
)
|
)
|
||||||
|
|
||||||
let subject: GiftItemComponent.Subject = .uniqueGift(gift: uniqueGift, price: "⭐️\(uniqueGift.resellStars ?? 0)")
|
let subject: GiftItemComponent.Subject = .uniqueGift(gift: uniqueGift, price: "⭐️\(presentationStringsFormattedNumber(Int32(uniqueGift.resellStars ?? 0), environment.dateTimeFormat.groupingSeparator))")
|
||||||
let _ = visibleItem.update(
|
let _ = visibleItem.update(
|
||||||
transition: itemTransition,
|
transition: itemTransition,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
@ -243,6 +255,13 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
context: component.context,
|
context: component.context,
|
||||||
subject: .uniqueGift(uniqueGift, state.peerId)
|
subject: .uniqueGift(uniqueGift, state.peerId)
|
||||||
)
|
)
|
||||||
|
giftController.onBuySuccess = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.removedStarGifts.insert(uniqueGift.slug)
|
||||||
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
}
|
||||||
mainController.push(giftController)
|
mainController.push(giftController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -288,6 +307,138 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fadeTransition = ComponentTransition.easeInOut(duration: 0.25)
|
||||||
|
let emptyResultsActionSize = self.clearFilters.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
PlainButtonComponent(
|
||||||
|
content: AnyComponent(
|
||||||
|
MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "Clear Filters", font: Font.regular(17.0), textColor: environment.theme.list.itemAccentColor)),
|
||||||
|
horizontalAlignment: .center,
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
effectAlignment: .center,
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.state?.starGiftsContext.updateFilterAttributes([])
|
||||||
|
},
|
||||||
|
animateScale: false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableWidth - 44.0 * 2.0, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
var showClearFilters = false
|
||||||
|
if let filterAttributes = self.state?.starGiftsState?.filterAttributes, !filterAttributes.isEmpty {
|
||||||
|
showClearFilters = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let topInset: CGFloat = environment.navigationHeight + 39.0
|
||||||
|
let bottomInset: CGFloat = environment.safeInsets.bottom
|
||||||
|
|
||||||
|
var emptyResultsActionFrame = CGRect(
|
||||||
|
origin: CGPoint(
|
||||||
|
x: floorToScreenPixels((availableWidth - emptyResultsActionSize.width) / 2.0),
|
||||||
|
y: max(self.scrollView.contentSize.height - 8.0, availableHeight - bottomInset - emptyResultsActionSize.height - 16.0)
|
||||||
|
),
|
||||||
|
size: emptyResultsActionSize
|
||||||
|
)
|
||||||
|
|
||||||
|
if let effectiveGifts = self.effectiveGifts, effectiveGifts.isEmpty && self.state?.starGiftsState?.dataState != .loading {
|
||||||
|
showClearFilters = true
|
||||||
|
|
||||||
|
let emptyAnimationHeight = 148.0
|
||||||
|
let visibleHeight = availableHeight
|
||||||
|
let emptyAnimationSpacing: CGFloat = 20.0
|
||||||
|
let emptyTextSpacing: CGFloat = 18.0
|
||||||
|
|
||||||
|
let emptyResultsTitleSize = self.emptyResultsTitle.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: "No Matching Gifts", font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor)),
|
||||||
|
horizontalAlignment: .center
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableWidth, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let emptyResultsAnimationSize = self.emptyResultsAnimation.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(name: "ChatListNoResults")
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: emptyAnimationHeight, height: emptyAnimationHeight)
|
||||||
|
)
|
||||||
|
|
||||||
|
let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyResultsTitleSize.height + emptyResultsActionSize.height + emptyTextSpacing
|
||||||
|
let emptyAnimationY = topInset + floorToScreenPixels((visibleHeight - topInset - bottomInset - emptyTotalHeight) / 2.0)
|
||||||
|
|
||||||
|
let emptyResultsAnimationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableWidth - emptyResultsAnimationSize.width) / 2.0), y: emptyAnimationY), size: emptyResultsAnimationSize)
|
||||||
|
|
||||||
|
let emptyResultsTitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableWidth - emptyResultsTitleSize.width) / 2.0), y: emptyResultsAnimationFrame.maxY + emptyAnimationSpacing), size: emptyResultsTitleSize)
|
||||||
|
|
||||||
|
emptyResultsActionFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableWidth - emptyResultsActionSize.width) / 2.0), y: emptyResultsTitleFrame.maxY + emptyTextSpacing), size: emptyResultsActionSize)
|
||||||
|
|
||||||
|
if let view = self.emptyResultsAnimation.view as? LottieComponent.View {
|
||||||
|
if view.superview == nil {
|
||||||
|
view.alpha = 0.0
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
|
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
||||||
|
view.playOnce()
|
||||||
|
}
|
||||||
|
view.bounds = CGRect(origin: .zero, size: emptyResultsAnimationFrame.size)
|
||||||
|
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsAnimationFrame.center)
|
||||||
|
}
|
||||||
|
if let view = self.emptyResultsTitle.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
view.alpha = 0.0
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
|
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
||||||
|
}
|
||||||
|
view.bounds = CGRect(origin: .zero, size: emptyResultsTitleFrame.size)
|
||||||
|
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsTitleFrame.center)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let view = self.emptyResultsAnimation.view {
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||||
|
view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if let view = self.emptyResultsTitle.view {
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||||
|
view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if showClearFilters {
|
||||||
|
if let view = self.clearFilters.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
view.alpha = 0.0
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
||||||
|
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
||||||
|
}
|
||||||
|
view.bounds = CGRect(origin: .zero, size: emptyResultsActionFrame.size)
|
||||||
|
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsActionFrame.center)
|
||||||
|
|
||||||
|
view.alpha = self.state?.starGiftsState?.attributes.isEmpty == true ? 0.0 : 1.0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let view = self.clearFilters.view {
|
||||||
|
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||||
|
view.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height)
|
let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height)
|
||||||
if interactive, bottomContentOffset < 320.0 {
|
if interactive, bottomContentOffset < 320.0 {
|
||||||
self.state?.starGiftsContext.loadMore()
|
self.state?.starGiftsContext.loadMore()
|
||||||
@ -966,118 +1117,7 @@ final class GiftStoreScreenComponent: Component {
|
|||||||
loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 0.0)
|
loadingTransition.setAlpha(view: self.loadingNode.view, alpha: 0.0)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: self.loadingNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight + 39.0 + 7.0), size: availableSize))
|
transition.setFrame(view: self.loadingNode.view, frame: CGRect(origin: CGPoint(x: 0.0, y: environment.navigationHeight + 39.0 + 7.0), size: availableSize))
|
||||||
|
|
||||||
let fadeTransition = ComponentTransition.easeInOut(duration: 0.25)
|
|
||||||
if let effectiveGifts = self.effectiveGifts, effectiveGifts.isEmpty && self.state?.starGiftsState?.dataState != .loading {
|
|
||||||
let sideInset: CGFloat = 44.0
|
|
||||||
let emptyAnimationHeight = 148.0
|
|
||||||
let topInset: CGFloat = environment.navigationHeight + 39.0
|
|
||||||
let bottomInset: CGFloat = environment.safeInsets.bottom
|
|
||||||
let visibleHeight = availableSize.height
|
|
||||||
let emptyAnimationSpacing: CGFloat = 20.0
|
|
||||||
let emptyTextSpacing: CGFloat = 18.0
|
|
||||||
|
|
||||||
let emptyResultsTitleSize = self.emptyResultsTitle.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(
|
|
||||||
MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: "No Matching Gifts", font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor)),
|
|
||||||
horizontalAlignment: .center
|
|
||||||
)
|
|
||||||
),
|
|
||||||
environment: {},
|
|
||||||
containerSize: availableSize
|
|
||||||
)
|
|
||||||
let emptyResultsActionSize = self.emptyResultsAction.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(
|
|
||||||
PlainButtonComponent(
|
|
||||||
content: AnyComponent(
|
|
||||||
MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: "Clear Filters", font: Font.regular(17.0), textColor: theme.list.itemAccentColor)),
|
|
||||||
horizontalAlignment: .center,
|
|
||||||
maximumNumberOfLines: 0
|
|
||||||
)
|
|
||||||
),
|
|
||||||
effectAlignment: .center,
|
|
||||||
action: { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.state?.starGiftsContext.updateFilterAttributes([])
|
|
||||||
},
|
|
||||||
animateScale: false
|
|
||||||
)
|
|
||||||
),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: visibleHeight)
|
|
||||||
)
|
|
||||||
let emptyResultsAnimationSize = self.emptyResultsAnimation.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(LottieComponent(
|
|
||||||
content: LottieComponent.AppBundleContent(name: "ChatListNoResults")
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: emptyAnimationHeight, height: emptyAnimationHeight)
|
|
||||||
)
|
|
||||||
|
|
||||||
let emptyTotalHeight = emptyAnimationHeight + emptyAnimationSpacing + emptyResultsTitleSize.height + emptyResultsActionSize.height + emptyTextSpacing
|
|
||||||
let emptyAnimationY = topInset + floorToScreenPixels((visibleHeight - topInset - bottomInset - emptyTotalHeight) / 2.0)
|
|
||||||
|
|
||||||
let emptyResultsAnimationFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsAnimationSize.width) / 2.0), y: emptyAnimationY), size: emptyResultsAnimationSize)
|
|
||||||
|
|
||||||
let emptyResultsTitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsTitleSize.width) / 2.0), y: emptyResultsAnimationFrame.maxY + emptyAnimationSpacing), size: emptyResultsTitleSize)
|
|
||||||
|
|
||||||
let emptyResultsActionFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - emptyResultsActionSize.width) / 2.0), y: emptyResultsTitleFrame.maxY + emptyTextSpacing), size: emptyResultsActionSize)
|
|
||||||
|
|
||||||
if let view = self.emptyResultsAnimation.view as? LottieComponent.View {
|
|
||||||
if view.superview == nil {
|
|
||||||
view.alpha = 0.0
|
|
||||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
|
||||||
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
|
||||||
view.playOnce()
|
|
||||||
}
|
|
||||||
view.bounds = CGRect(origin: .zero, size: emptyResultsAnimationFrame.size)
|
|
||||||
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsAnimationFrame.center)
|
|
||||||
}
|
|
||||||
if let view = self.emptyResultsTitle.view {
|
|
||||||
if view.superview == nil {
|
|
||||||
view.alpha = 0.0
|
|
||||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
|
||||||
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
|
||||||
}
|
|
||||||
view.bounds = CGRect(origin: .zero, size: emptyResultsTitleFrame.size)
|
|
||||||
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsTitleFrame.center)
|
|
||||||
}
|
|
||||||
if let view = self.emptyResultsAction.view {
|
|
||||||
if view.superview == nil {
|
|
||||||
view.alpha = 0.0
|
|
||||||
fadeTransition.setAlpha(view: view, alpha: 1.0)
|
|
||||||
self.insertSubview(view, belowSubview: self.loadingNode.view)
|
|
||||||
}
|
|
||||||
view.bounds = CGRect(origin: .zero, size: emptyResultsActionFrame.size)
|
|
||||||
ComponentTransition.immediate.setPosition(view: view, position: emptyResultsActionFrame.center)
|
|
||||||
|
|
||||||
view.alpha = self.state?.starGiftsState?.attributes.isEmpty == true ? 0.0 : 1.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let view = self.emptyResultsAnimation.view {
|
|
||||||
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
|
||||||
view.removeFromSuperview()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if let view = self.emptyResultsTitle.view {
|
|
||||||
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
|
||||||
view.removeFromSuperview()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if let view = self.emptyResultsAction.view {
|
|
||||||
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
|
||||||
view.removeFromSuperview()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ import AccountContext
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
final class PriceButtonComponent: Component {
|
final class PriceButtonComponent: Component {
|
||||||
let price: Int64
|
let price: String
|
||||||
|
|
||||||
init(
|
init(
|
||||||
price: Int64
|
price: String
|
||||||
) {
|
) {
|
||||||
self.price = price
|
self.price = price
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ final class PriceButtonComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(
|
text: .plain(NSAttributedString(
|
||||||
string: "\(component.price)",
|
string: component.price,
|
||||||
font: Font.semibold(11.0),
|
font: Font.semibold(11.0),
|
||||||
textColor: UIColor(rgb: 0xffffff)
|
textColor: UIColor(rgb: 0xffffff)
|
||||||
))
|
))
|
||||||
|
@ -444,10 +444,24 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
self.updated()
|
self.updated()
|
||||||
|
|
||||||
self.buyDisposable = (self.buyGift(uniqueGift.slug, recipientPeerId)
|
self.buyDisposable = (self.buyGift(uniqueGift.slug, recipientPeerId)
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self, weak starsContext] in
|
|> deliverOnMainQueue).start(error: { [weak self] error in
|
||||||
|
guard let self, let controller = self.getController() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inProgress = false
|
||||||
|
self.updated()
|
||||||
|
|
||||||
|
let errorText = presentationData.strings.Gift_Send_ErrorUnknown
|
||||||
|
|
||||||
|
let alertController = textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})], parseMarkdown: true)
|
||||||
|
controller.present(alertController, in: .window(.root))
|
||||||
|
}, completed: { [weak self, weak starsContext] in
|
||||||
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
controller.onBuySuccess()
|
||||||
|
|
||||||
self.inProgress = false
|
self.inProgress = false
|
||||||
|
|
||||||
var animationFile: TelegramMediaFile?
|
var animationFile: TelegramMediaFile?
|
||||||
@ -459,41 +473,26 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let navigationController = controller.navigationController as? NavigationController {
|
if let navigationController = controller.navigationController as? NavigationController {
|
||||||
if recipientPeerId == self.context.account.peerId {
|
if recipientPeerId == self.context.account.peerId {
|
||||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
var controllers = navigationController.viewControllers
|
||||||
|> deliverOnMainQueue).start(next: { [weak navigationController] peer in
|
controllers = controllers.filter({ !($0 is GiftViewScreen) })
|
||||||
guard let peer, let navigationController else {
|
navigationController.setViewControllers(controllers, animated: true)
|
||||||
return
|
|
||||||
|
//TODO:localize
|
||||||
|
navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds))
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.5, {
|
||||||
|
if let lastController = navigationController.viewControllers.last as? ViewController, let animationFile {
|
||||||
|
let resultController = UndoOverlayController(
|
||||||
|
presentationData: presentationData,
|
||||||
|
content: .sticker(context: context, file: animationFile, loop: false, title: "Gift Acquired", text: "\(giftTitle) is now yours.", undoText: nil, customAction: nil),
|
||||||
|
elevatedLayout: lastController is ChatController,
|
||||||
|
action: { _ in
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
lastController.present(resultController, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
var controllers = Array(navigationController.viewControllers.prefix(1))
|
|
||||||
if let controller = context.sharedContext.makePeerInfoController(
|
|
||||||
context: context,
|
|
||||||
updatedPresentationData: nil,
|
|
||||||
peer: peer._asPeer(),
|
|
||||||
mode: .myProfileGifts,
|
|
||||||
avatarInitiallyExpanded: false,
|
|
||||||
fromChat: false,
|
|
||||||
requestsContext: nil
|
|
||||||
) {
|
|
||||||
controllers.append(controller)
|
|
||||||
}
|
|
||||||
navigationController.setViewControllers(controllers, animated: true)
|
|
||||||
navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds))
|
|
||||||
|
|
||||||
Queue.mainQueue().after(0.5, {
|
|
||||||
if let lastController = navigationController.viewControllers.last as? ViewController, let animationFile {
|
|
||||||
let resultController = UndoOverlayController(
|
|
||||||
presentationData: presentationData,
|
|
||||||
content: .sticker(context: context, file: animationFile, loop: false, title: "Gift Acquired", text: "\(giftTitle) is now yours.", undoText: nil, customAction: nil),
|
|
||||||
elevatedLayout: lastController is ChatController,
|
|
||||||
action: { _ in
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
lastController.present(resultController, in: .window(.root))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
var controllers = Array(navigationController.viewControllers.prefix(1))
|
var controllers = Array(navigationController.viewControllers.prefix(1))
|
||||||
@ -884,17 +883,16 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
headerSubject = nil
|
headerSubject = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ownerPeerId: EnginePeer.Id
|
var ownerPeerId: EnginePeer.Id?
|
||||||
if let uniqueGift, case let .peerId(peerId) = uniqueGift.owner {
|
if let uniqueGift, case let .peerId(peerId) = uniqueGift.owner {
|
||||||
ownerPeerId = peerId
|
ownerPeerId = peerId
|
||||||
} else {
|
|
||||||
ownerPeerId = component.context.account.peerId
|
|
||||||
}
|
}
|
||||||
|
let wearOwnerPeerId = ownerPeerId ?? component.context.account.peerId
|
||||||
|
|
||||||
var wearPeerNameChild: _UpdatedChildComponent?
|
var wearPeerNameChild: _UpdatedChildComponent?
|
||||||
if showWearPreview, let uniqueGift {
|
if showWearPreview, let uniqueGift {
|
||||||
var peerName = ""
|
var peerName = ""
|
||||||
if let ownerPeer = state.peerMap[ownerPeerId] {
|
if let ownerPeer = state.peerMap[wearOwnerPeerId] {
|
||||||
peerName = ownerPeer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
peerName = ownerPeer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
|
||||||
}
|
}
|
||||||
wearPeerNameChild = wearPeerName.update(
|
wearPeerNameChild = wearPeerName.update(
|
||||||
@ -1004,7 +1002,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let wearPeerNameChild {
|
if let wearPeerNameChild {
|
||||||
if let ownerPeer = state.peerMap[ownerPeerId] {
|
if let ownerPeer = state.peerMap[wearOwnerPeerId] {
|
||||||
let wearAvatar = wearAvatar.update(
|
let wearAvatar = wearAvatar.update(
|
||||||
component: AvatarComponent(
|
component: AvatarComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
@ -1488,8 +1486,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
|
|
||||||
if !soldOut {
|
if !soldOut {
|
||||||
if let uniqueGift {
|
if let uniqueGift {
|
||||||
if case let .uniqueGift(_, recipientPeerIdValue) = component.subject, let _ = recipientPeerIdValue, let recipientPeerId = state.recipientPeerId {
|
if !"".isEmpty, case let .uniqueGift(_, recipientPeerIdValue) = component.subject, let _ = recipientPeerIdValue, let recipientPeerId = state.recipientPeerId {
|
||||||
//TODO:localize
|
|
||||||
if let peer = state.peerMap[recipientPeerId] {
|
if let peer = state.peerMap[recipientPeerId] {
|
||||||
tableItems.append(.init(
|
tableItems.append(.init(
|
||||||
id: "recipient",
|
id: "recipient",
|
||||||
@ -1815,7 +1812,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let canWear: Bool
|
let canWear: Bool
|
||||||
if isChannelGift, case let .channel(channel) = state.peerMap[ownerPeerId] {
|
if isChannelGift, case let .channel(channel) = state.peerMap[wearOwnerPeerId] {
|
||||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||||
let requiredLevel = Int(BoostSubject.wearGift.requiredLevel(group: false, context: component.context, configuration: premiumConfiguration))
|
let requiredLevel = Int(BoostSubject.wearGift.requiredLevel(group: false, context: component.context, configuration: premiumConfiguration))
|
||||||
if let boostLevel = channel.approximateBoostLevel {
|
if let boostLevel = channel.approximateBoostLevel {
|
||||||
@ -2232,29 +2229,28 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
if let uniqueGift {
|
if let uniqueGift {
|
||||||
resellStars = uniqueGift.resellStars
|
resellStars = uniqueGift.resellStars
|
||||||
|
|
||||||
if incoming, let resellStars {
|
if let resellStars {
|
||||||
let priceButton = priceButton.update(
|
if incoming || ownerPeerId == component.context.account.peerId {
|
||||||
component: PlainButtonComponent(
|
let priceButton = priceButton.update(
|
||||||
content: AnyComponent(
|
component: PlainButtonComponent(
|
||||||
PriceButtonComponent(price: resellStars)
|
content: AnyComponent(
|
||||||
|
PriceButtonComponent(price: presentationStringsFormattedNumber(Int32(resellStars), environment.dateTimeFormat.groupingSeparator))
|
||||||
|
),
|
||||||
|
effectAlignment: .center,
|
||||||
|
action: {
|
||||||
|
component.resellGift(true)
|
||||||
|
},
|
||||||
|
animateScale: false
|
||||||
),
|
),
|
||||||
effectAlignment: .center,
|
availableSize: CGSize(width: 150.0, height: 30.0),
|
||||||
action: {
|
transition: context.transition
|
||||||
component.resellGift(true)
|
)
|
||||||
},
|
context.add(priceButton
|
||||||
animateScale: false
|
.position(CGPoint(x: environment.safeInsets.left + 16.0 + priceButton.size.width / 2.0, y: 28.0))
|
||||||
),
|
.appear(.default(scale: true, alpha: true))
|
||||||
availableSize: CGSize(width: 120.0, height: 30.0),
|
.disappear(.default(scale: true, alpha: true))
|
||||||
transition: context.transition
|
)
|
||||||
)
|
}
|
||||||
context.add(priceButton
|
|
||||||
.position(CGPoint(x: environment.safeInsets.left + 16.0 + priceButton.size.width / 2.0, y: 28.0))
|
|
||||||
.appear(.default(scale: true, alpha: true))
|
|
||||||
.disappear(.default(scale: true, alpha: true))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !incoming, let _ = resellStars {
|
|
||||||
if case let .uniqueGift(_, recipientPeerId) = component.subject, recipientPeerId != nil {
|
if case let .uniqueGift(_, recipientPeerId) = component.subject, recipientPeerId != nil {
|
||||||
} else {
|
} else {
|
||||||
selling = true
|
selling = true
|
||||||
@ -2361,7 +2357,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
let requiredLevel = Int(BoostSubject.wearGift.requiredLevel(group: false, context: component.context, configuration: premiumConfiguration))
|
let requiredLevel = Int(BoostSubject.wearGift.requiredLevel(group: false, context: component.context, configuration: premiumConfiguration))
|
||||||
|
|
||||||
var canWear = true
|
var canWear = true
|
||||||
if isChannelGift, case let .channel(channel) = state.peerMap[ownerPeerId], (channel.approximateBoostLevel ?? 0) < requiredLevel {
|
if isChannelGift, case let .channel(channel) = state.peerMap[wearOwnerPeerId], (channel.approximateBoostLevel ?? 0) < requiredLevel {
|
||||||
canWear = false
|
canWear = false
|
||||||
buttonContent = AnyComponentWithIdentity(
|
buttonContent = AnyComponentWithIdentity(
|
||||||
id: AnyHashable("wear_channel"),
|
id: AnyHashable("wear_channel"),
|
||||||
@ -2421,7 +2417,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
if isChannelGift {
|
if isChannelGift {
|
||||||
state.levelsDisposable.set(combineLatest(
|
state.levelsDisposable.set(combineLatest(
|
||||||
queue: Queue.mainQueue(),
|
queue: Queue.mainQueue(),
|
||||||
context.engine.peers.getChannelBoostStatus(peerId: ownerPeerId),
|
context.engine.peers.getChannelBoostStatus(peerId: wearOwnerPeerId),
|
||||||
context.engine.peers.getMyBoostStatus()
|
context.engine.peers.getMyBoostStatus()
|
||||||
).startStandalone(next: { [weak controller] boostStatus, myBoostStatus in
|
).startStandalone(next: { [weak controller] boostStatus, myBoostStatus in
|
||||||
guard let controller, let boostStatus, let myBoostStatus else {
|
guard let controller, let boostStatus, let myBoostStatus else {
|
||||||
@ -2429,7 +2425,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
component.cancel(true)
|
component.cancel(true)
|
||||||
|
|
||||||
let levelsController = context.sharedContext.makePremiumBoostLevelsController(context: context, peerId: ownerPeerId, subject: .wearGift, boostStatus: boostStatus, myBoostStatus: myBoostStatus, forceDark: false, openStats: nil)
|
let levelsController = context.sharedContext.makePremiumBoostLevelsController(context: context, peerId: wearOwnerPeerId, subject: .wearGift, boostStatus: boostStatus, myBoostStatus: myBoostStatus, forceDark: false, openStats: nil)
|
||||||
controller.push(levelsController)
|
controller.push(levelsController)
|
||||||
|
|
||||||
HapticFeedback().impact(.light)
|
HapticFeedback().impact(.light)
|
||||||
@ -2763,6 +2759,11 @@ private final class GiftViewSheetComponent: CombinedComponent {
|
|||||||
if let controller = controller() as? GiftViewScreen {
|
if let controller = controller() as? GiftViewScreen {
|
||||||
controller.dismissAllTooltips()
|
controller.dismissAllTooltips()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
willDismiss: {
|
||||||
|
if let controller = controller() as? GiftViewScreen {
|
||||||
|
controller.dismissBalanceOverlay()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
environment: {
|
environment: {
|
||||||
@ -2901,6 +2902,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
let updateSubject = ActionSlot<GiftViewScreen.Subject>()
|
let updateSubject = ActionSlot<GiftViewScreen.Subject>()
|
||||||
|
|
||||||
public var disposed: () -> Void = {}
|
public var disposed: () -> Void = {}
|
||||||
|
public var onBuySuccess: () -> Void = {}
|
||||||
|
|
||||||
fileprivate var showBalance = false {
|
fileprivate var showBalance = false {
|
||||||
didSet {
|
didSet {
|
||||||
@ -2927,7 +2929,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
|
|
||||||
var openPeerImpl: ((EnginePeer) -> Void)?
|
var openPeerImpl: ((EnginePeer, Bool) -> Void)?
|
||||||
var openAddressImpl: ((String) -> Void)?
|
var openAddressImpl: ((String) -> Void)?
|
||||||
var copyAddressImpl: ((String) -> Void)?
|
var copyAddressImpl: ((String) -> Void)?
|
||||||
var updateSavedToProfileImpl: ((Bool) -> Void)?
|
var updateSavedToProfileImpl: ((Bool) -> Void)?
|
||||||
@ -2950,7 +2952,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
context: context,
|
context: context,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
openPeer: { peerId in
|
openPeer: { peerId in
|
||||||
openPeerImpl?(peerId)
|
openPeerImpl?(peerId, false)
|
||||||
},
|
},
|
||||||
openAddress: { address in
|
openAddress: { address in
|
||||||
openAddressImpl?(address)
|
openAddressImpl?(address)
|
||||||
@ -3009,21 +3011,27 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
self.navigationPresentation = .flatModal
|
self.navigationPresentation = .flatModal
|
||||||
self.automaticallyControlPresentationContextLayout = false
|
self.automaticallyControlPresentationContextLayout = false
|
||||||
|
|
||||||
openPeerImpl = { [weak self] peer in
|
openPeerImpl = { [weak self] peer, gifts in
|
||||||
guard let self, let navigationController = self.navigationController as? NavigationController else {
|
guard let self, let navigationController = self.navigationController as? NavigationController else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
let _ = (context.engine.data.get(
|
if gifts {
|
||||||
TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)
|
if let controller = context.sharedContext.makePeerInfoController(
|
||||||
)
|
context: context,
|
||||||
|> deliverOnMainQueue).start(next: { peer in
|
updatedPresentationData: nil,
|
||||||
guard let peer else {
|
peer: peer._asPeer(),
|
||||||
return
|
mode: .gifts,
|
||||||
|
avatarInitiallyExpanded: false,
|
||||||
|
fromChat: false,
|
||||||
|
requestsContext: nil
|
||||||
|
) {
|
||||||
|
self.push(controller)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true))
|
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
@ -3379,7 +3387,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
guard let peer else {
|
guard let peer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
openPeerImpl?(peer)
|
openPeerImpl?(peer, false)
|
||||||
Queue.mainQueue().after(0.6) {
|
Queue.mainQueue().after(0.6) {
|
||||||
self?.dismiss(animated: false, completion: nil)
|
self?.dismiss(animated: false, completion: nil)
|
||||||
}
|
}
|
||||||
@ -3397,12 +3405,15 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resellGiftImpl = { [weak self] update in
|
resellGiftImpl = { [weak self] update in
|
||||||
guard let self, let arguments = self.subject.arguments, case let .profileGift(peerId, currentSubject) = self.subject, case let .unique(gift) = arguments.gift else {
|
guard let self, let arguments = self.subject.arguments, case let .unique(gift) = arguments.gift else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dismissAllTooltips()
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let giftTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
if let resellStars = gift.resellStars, resellStars > 0, !update {
|
if let resellStars = gift.resellStars, resellStars > 0, !update {
|
||||||
let alertController = textAlertController(
|
let alertController = textAlertController(
|
||||||
@ -3415,10 +3426,16 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(nil))))
|
switch self.subject {
|
||||||
|
case let .profileGift(peerId, currentSubject):
|
||||||
|
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(nil))))
|
||||||
|
case let .uniqueGift(_, recipientPeerId):
|
||||||
|
self.subject = .uniqueGift(gift.withResellStars(nil), recipientPeerId)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
self.onBuySuccess()
|
||||||
|
|
||||||
let giftTitle = "\(gift.title) #\(gift.number)"
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let text = "\(giftTitle) is removed from sale."
|
let text = "\(giftTitle) is removed from sale."
|
||||||
let tooltipController = UndoOverlayController(
|
let tooltipController = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
@ -3442,7 +3459,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
if let updateResellStars {
|
if let updateResellStars {
|
||||||
updateResellStars(nil)
|
updateResellStars(nil)
|
||||||
} else {
|
} else {
|
||||||
let _ = (context.engine.payments.updateStarGiftResalePrice(slug: gift.slug, price: nil)
|
let reference = arguments.reference ?? .slug(slug: gift.slug)
|
||||||
|
let _ = (context.engine.payments.updateStarGiftResalePrice(reference: reference, price: nil)
|
||||||
|> deliverOnMainQueue).startStandalone()
|
|> deliverOnMainQueue).startStandalone()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -3458,16 +3476,20 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(price))))
|
switch self.subject {
|
||||||
|
case let .profileGift(peerId, currentSubject):
|
||||||
let giftTitle = "\(gift.title) #\(gift.number)"
|
self.subject = .profileGift(peerId, currentSubject.withGift(.unique(gift.withResellStars(price))))
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
case let .uniqueGift(_, recipientPeerId):
|
||||||
|
self.subject = .uniqueGift(gift.withResellStars(price), recipientPeerId)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
var text = "\(giftTitle) is now for sale!"
|
var text = "\(giftTitle) is now for sale!"
|
||||||
if update {
|
if update {
|
||||||
text = "\(giftTitle) is relisted for \(price) Stars."
|
text = "\(giftTitle) is relisted for \(presentationStringsFormattedNumber(Int32(price), presentationData.dateTimeFormat.groupingSeparator)) Stars."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let tooltipController = UndoOverlayController(
|
let tooltipController = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .universalImage(
|
content: .universalImage(
|
||||||
@ -3490,7 +3512,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
if let updateResellStars {
|
if let updateResellStars {
|
||||||
updateResellStars(price)
|
updateResellStars(price)
|
||||||
} else {
|
} else {
|
||||||
let _ = (context.engine.payments.updateStarGiftResalePrice(slug: gift.slug, price: price)
|
let reference = arguments.reference ?? .slug(slug: gift.slug)
|
||||||
|
let _ = (context.engine.payments.updateStarGiftResalePrice(reference: reference, price: price)
|
||||||
|> deliverOnMainQueue).startStandalone()
|
|> deliverOnMainQueue).startStandalone()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -3607,6 +3630,28 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let _ = arguments.resellStars, case let .uniqueGift(uniqueGift, recipientPeerId) = subject, let _ = recipientPeerId {
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "View in Profile", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Peer Info/ShowIcon"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { c, _ in
|
||||||
|
c?.dismiss(completion: nil)
|
||||||
|
|
||||||
|
if case let .peerId(peerId) = uniqueGift.owner {
|
||||||
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||||
|
guard let self, let peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
openPeerImpl?(peer, true)
|
||||||
|
Queue.mainQueue().after(0.6) {
|
||||||
|
self.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
let contextController = ContextController(presentationData: presentationData, source: .reference(GiftViewContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
self.presentInGlobalOverlay(contextController)
|
self.presentInGlobalOverlay(contextController)
|
||||||
})
|
})
|
||||||
|
@ -530,10 +530,11 @@ public final class MediaEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext, mode: Mode, subject: Subject, values: MediaEditorValues? = nil, hasHistogram: Bool = false) {
|
public init(context: AccountContext, mode: Mode, subject: Subject, values: MediaEditorValues? = nil, hasHistogram: Bool = false, isStandalone: Bool = false) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
|
|
||||||
if let values {
|
if let values {
|
||||||
self.values = values
|
self.values = values
|
||||||
self.updateRenderChain()
|
self.updateRenderChain()
|
||||||
@ -581,6 +582,9 @@ public final class MediaEditor {
|
|||||||
}
|
}
|
||||||
self.valuesPromise.set(.single(self.values))
|
self.valuesPromise.set(.single(self.values))
|
||||||
|
|
||||||
|
if isStandalone, let device = MTLCreateSystemDefaultDevice() {
|
||||||
|
self.renderer.setupForStandaloneDevice(device: device)
|
||||||
|
}
|
||||||
self.renderer.addRenderChain(self.renderChain)
|
self.renderer.addRenderChain(self.renderChain)
|
||||||
if hasHistogram {
|
if hasHistogram {
|
||||||
self.renderer.addRenderPass(self.histogramCalculationPass)
|
self.renderer.addRenderPass(self.histogramCalculationPass)
|
||||||
@ -611,7 +615,7 @@ public final class MediaEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func replaceSource(_ image: UIImage, additionalImage: UIImage?, time: CMTime, mirror: Bool) {
|
public func replaceSource(_ image: UIImage, additionalImage: UIImage?, time: CMTime, mirror: Bool) {
|
||||||
guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice, let texture = loadTexture(image: image, device: device) else {
|
guard let device = self.renderer.effectiveDevice, let texture = loadTexture(image: image, device: device) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let additionalTexture = additionalImage.flatMap { loadTexture(image: $0, device: device) }
|
let additionalTexture = additionalImage.flatMap { loadTexture(image: $0, device: device) }
|
||||||
|
@ -125,7 +125,7 @@ final class MediaEditorRenderer {
|
|||||||
|
|
||||||
func addRenderPass(_ renderPass: RenderPass) {
|
func addRenderPass(_ renderPass: RenderPass) {
|
||||||
self.renderPasses.append(renderPass)
|
self.renderPasses.append(renderPass)
|
||||||
if let device = self.renderTarget?.mtlDevice, let library = self.library {
|
if let device = self.effectiveDevice, let library = self.library {
|
||||||
renderPass.setup(device: device, library: library)
|
renderPass.setup(device: device, library: library)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,6 +160,14 @@ final class MediaEditorRenderer {
|
|||||||
self.renderPasses.forEach { $0.setup(device: device, library: library) }
|
self.renderPasses.forEach { $0.setup(device: device, library: library) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var effectiveDevice: MTLDevice? {
|
||||||
|
if let device = self.renderTarget?.mtlDevice {
|
||||||
|
return device
|
||||||
|
} else {
|
||||||
|
return self.device
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setup() {
|
private func setup() {
|
||||||
guard let device = self.renderTarget?.mtlDevice else {
|
guard let device = self.renderTarget?.mtlDevice else {
|
||||||
return
|
return
|
||||||
@ -180,6 +188,11 @@ final class MediaEditorRenderer {
|
|||||||
self.commonSetup(device: device)
|
self.commonSetup(device: device)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupForStandaloneDevice(device: MTLDevice) {
|
||||||
|
self.device = device
|
||||||
|
self.commonSetup(device: device)
|
||||||
|
}
|
||||||
|
|
||||||
func setRate(_ rate: Float) {
|
func setRate(_ rate: Float) {
|
||||||
self.textureSource?.setRate(rate)
|
self.textureSource?.setRate(rate)
|
||||||
}
|
}
|
||||||
@ -240,15 +253,7 @@ final class MediaEditorRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderFrame() {
|
func renderFrame() {
|
||||||
let device: MTLDevice?
|
guard let device = self.effectiveDevice,
|
||||||
if let renderTarget = self.renderTarget {
|
|
||||||
device = renderTarget.mtlDevice
|
|
||||||
} else if let currentDevice = self.device {
|
|
||||||
device = currentDevice
|
|
||||||
} else {
|
|
||||||
device = nil
|
|
||||||
}
|
|
||||||
guard let device = device,
|
|
||||||
let commandQueue = self.commandQueue,
|
let commandQueue = self.commandQueue,
|
||||||
let textureCache = self.textureCache,
|
let textureCache = self.textureCache,
|
||||||
let commandBuffer = commandQueue.makeCommandBuffer(),
|
let commandBuffer = commandQueue.makeCommandBuffer(),
|
||||||
@ -366,7 +371,7 @@ final class MediaEditorRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func finalRenderedImage(mirror: Bool = false) -> UIImage? {
|
func finalRenderedImage(mirror: Bool = false) -> UIImage? {
|
||||||
if let finalTexture = self.resultTexture, let device = self.renderTarget?.mtlDevice {
|
if let finalTexture = self.resultTexture, let device = self.effectiveDevice {
|
||||||
return getTextureImage(device: device, texture: finalTexture, mirror: mirror)
|
return getTextureImage(device: device, texture: finalTexture, mirror: mirror)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import SwiftSignalKit
|
|
||||||
import Postbox
|
|
||||||
import TelegramCore
|
|
||||||
import AccountContext
|
|
||||||
import TextFormat
|
|
||||||
|
|
||||||
public extension MediaEditorScreenImpl {
|
|
||||||
static func makeEditVideoCoverController(
|
|
||||||
context: AccountContext,
|
|
||||||
video: MediaEditorScreenImpl.Subject,
|
|
||||||
completed: @escaping () -> Void = {},
|
|
||||||
willDismiss: @escaping () -> Void = {},
|
|
||||||
update: @escaping (Disposable?) -> Void
|
|
||||||
) -> MediaEditorScreenImpl? {
|
|
||||||
let controller = MediaEditorScreenImpl(
|
|
||||||
context: context,
|
|
||||||
mode: .storyEditor,
|
|
||||||
subject: .single(video),
|
|
||||||
isEditing: true,
|
|
||||||
isEditingCover: true,
|
|
||||||
forwardSource: nil,
|
|
||||||
initialCaption: nil,
|
|
||||||
initialPrivacy: nil,
|
|
||||||
initialMediaAreas: nil,
|
|
||||||
initialVideoPosition: 0.0,
|
|
||||||
transitionIn: .noAnimation,
|
|
||||||
transitionOut: { finished, isNew in
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
completion: { result, commit in
|
|
||||||
if let _ = result.coverTimestamp {
|
|
||||||
|
|
||||||
}
|
|
||||||
commit({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
controller.willDismiss = willDismiss
|
|
||||||
controller.navigationPresentation = .flatModal
|
|
||||||
|
|
||||||
return controller
|
|
||||||
}
|
|
||||||
}
|
|
@ -122,7 +122,10 @@ public extension MediaEditorScreenImpl {
|
|||||||
return transitionOut
|
return transitionOut
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
completion: { result, commit in
|
completion: { results, commit in
|
||||||
|
guard let result = results.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let entities = generateChatInputTextEntities(result.caption)
|
let entities = generateChatInputTextEntities(result.caption)
|
||||||
|
|
||||||
if repost {
|
if repost {
|
||||||
|
@ -338,7 +338,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
private var isEditingCaption = false
|
private var isEditingCaption = false
|
||||||
private var currentInputMode: MessageInputPanelComponent.InputMode = .text
|
private var currentInputMode: MessageInputPanelComponent.InputMode = .text
|
||||||
|
|
||||||
private var isSelectionPanelOpen = false
|
fileprivate var isSelectionPanelOpen = false
|
||||||
|
|
||||||
private var didInitializeInputMediaNodeDataPromise = false
|
private var didInitializeInputMediaNodeDataPromise = false
|
||||||
private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData?
|
private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData?
|
||||||
@ -2013,10 +2013,21 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
effectAlignment: .center,
|
effectAlignment: .center,
|
||||||
action: { [weak self] in
|
action: { [weak self, weak controller] in
|
||||||
if let self {
|
if let self, let controller {
|
||||||
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
||||||
|
if let mediaEditor = controller.node.mediaEditor {
|
||||||
|
if self.isSelectionPanelOpen {
|
||||||
|
mediaEditor.maybePauseVideo()
|
||||||
|
} else {
|
||||||
|
Queue.mainQueue().after(0.1) {
|
||||||
|
mediaEditor.maybeUnpauseVideo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
|
|
||||||
|
controller.hapticFeedback.impact(.light)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
animateAlpha: false
|
animateAlpha: false
|
||||||
@ -2034,8 +2045,8 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setPosition(view: selectionButtonView, position: selectionButtonFrame.center)
|
transition.setPosition(view: selectionButtonView, position: selectionButtonFrame.center)
|
||||||
transition.setBounds(view: selectionButtonView, bounds: CGRect(origin: .zero, size: selectionButtonFrame.size))
|
transition.setBounds(view: selectionButtonView, bounds: CGRect(origin: .zero, size: selectionButtonFrame.size))
|
||||||
transition.setScale(view: selectionButtonView, scale: displayTopButtons ? 1.0 : 0.01)
|
transition.setScale(view: selectionButtonView, scale: displayTopButtons && !isRecordingAdditionalVideo ? 1.0 : 0.01)
|
||||||
transition.setAlpha(view: selectionButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? 1.0 : 0.0)
|
transition.setAlpha(view: selectionButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities && !isRecordingAdditionalVideo ? 1.0 : 0.0)
|
||||||
|
|
||||||
if self.isSelectionPanelOpen {
|
if self.isSelectionPanelOpen {
|
||||||
let selectionPanelFrame = CGRect(
|
let selectionPanelFrame = CGRect(
|
||||||
@ -2061,10 +2072,12 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.isSelectionPanelOpen = false
|
self.isSelectionPanelOpen = false
|
||||||
self.state?.updated()
|
self.state?.updated(transition: id == nil ? .spring(duration: 0.3) : .immediate)
|
||||||
|
|
||||||
if let id {
|
if let id {
|
||||||
controller.node.switchToItem(id)
|
controller.node.switchToItem(id)
|
||||||
|
|
||||||
|
controller.hapticFeedback.impact(.light)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemSelectionToggled: { [weak self, weak controller] id in
|
itemSelectionToggled: { [weak self, weak controller] id in
|
||||||
@ -2088,6 +2101,8 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
controller.node.items[fromIndex] = toItem
|
controller.node.items[fromIndex] = toItem
|
||||||
controller.node.items[toIndex] = fromItem
|
controller.node.items[toIndex] = fromItem
|
||||||
self.state?.updated(transition: .spring(duration: 0.3))
|
self.state?.updated(transition: .spring(duration: 0.3))
|
||||||
|
|
||||||
|
controller.hapticFeedback.tap()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -2104,7 +2119,7 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
selectionPanelView.frame = CGRect(origin: .zero, size: availableSize)
|
selectionPanelView.frame = CGRect(origin: .zero, size: availableSize)
|
||||||
}
|
}
|
||||||
} else if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
} else if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
||||||
if let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
if !transition.animation.isImmediate, let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
||||||
selectionPanelView.animateOut(to: buttonView, completion: { [weak selectionPanelView] in
|
selectionPanelView.animateOut(to: buttonView, completion: { [weak selectionPanelView] in
|
||||||
selectionPanelView?.removeFromSuperview()
|
selectionPanelView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
@ -4027,7 +4042,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
if gestureRecognizer === self.dismissPanGestureRecognizer {
|
if gestureRecognizer === self.dismissPanGestureRecognizer {
|
||||||
let location = gestureRecognizer.location(in: self.entitiesView)
|
let location = gestureRecognizer.location(in: self.entitiesView)
|
||||||
if self.controller?.isEmbeddedEditor == true || self.isDisplayingTool != nil || self.entitiesView.hasSelection || self.entitiesView.getView(at: location) != nil {
|
if self.controller?.isEmbeddedEditor == true || self.isDisplayingTool != nil || self.entitiesView.hasSelection || self.entitiesView.getView(at: location) != nil || self.componentHostView?.isSelectionPanelOpen == true {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -4188,7 +4203,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
private var previousRotateTimestamp: Double?
|
private var previousRotateTimestamp: Double?
|
||||||
|
|
||||||
@objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
@objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
guard !self.isCollageTimelineOpen else {
|
guard !self.isCollageTimelineOpen && !(self.componentHostView?.isSelectionPanelOpen ?? false) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection {
|
if gestureRecognizer.numberOfTouches == 2, let subject = self.subject, !self.entitiesView.hasSelection {
|
||||||
@ -5381,7 +5396,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
var updatedCurrentItem = self.items[currentItemIndex]
|
var updatedCurrentItem = self.items[currentItemIndex]
|
||||||
updatedCurrentItem.caption = self.getCaption()
|
updatedCurrentItem.caption = self.getCaption()
|
||||||
|
|
||||||
if mediaEditor.values.hasChanges && updatedCurrentItem.values != mediaEditor.values {
|
if (mediaEditor.values.hasChanges && updatedCurrentItem.values != mediaEditor.values) || updatedCurrentItem.values?.gradientColors == nil {
|
||||||
updatedCurrentItem.values = mediaEditor.values
|
updatedCurrentItem.values = mediaEditor.values
|
||||||
updatedCurrentItem.version += 1
|
updatedCurrentItem.version += 1
|
||||||
|
|
||||||
@ -6520,7 +6535,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
public var cancelled: (Bool) -> Void = { _ in }
|
public var cancelled: (Bool) -> Void = { _ in }
|
||||||
public var willComplete: (UIImage?, Bool, @escaping () -> Void) -> Void
|
public var willComplete: (UIImage?, Bool, @escaping () -> Void) -> Void
|
||||||
public var completion: (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
public var completion: ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
public var dismissed: () -> Void = { }
|
public var dismissed: () -> Void = { }
|
||||||
public var willDismiss: () -> Void = { }
|
public var willDismiss: () -> Void = { }
|
||||||
public var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
|
public var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
|
||||||
@ -6529,7 +6544,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
private var closeFriends = Promise<[EnginePeer]>()
|
private var closeFriends = Promise<[EnginePeer]>()
|
||||||
private let storiesBlockedPeers: BlockedPeersContext
|
private let storiesBlockedPeers: BlockedPeersContext
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
fileprivate let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
private var audioSessionDisposable: Disposable?
|
private var audioSessionDisposable: Disposable?
|
||||||
private let postingAvailabilityPromise = Promise<StoriesUploadAvailability>()
|
private let postingAvailabilityPromise = Promise<StoriesUploadAvailability>()
|
||||||
@ -6554,7 +6569,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
transitionIn: TransitionIn?,
|
transitionIn: TransitionIn?,
|
||||||
transitionOut: @escaping (Bool, Bool?) -> TransitionOut?,
|
transitionOut: @escaping (Bool, Bool?) -> TransitionOut?,
|
||||||
willComplete: @escaping (UIImage?, Bool, @escaping () -> Void) -> Void = { _, _, commit in commit() },
|
willComplete: @escaping (UIImage?, Bool, @escaping () -> Void) -> Void = { _, _, commit in commit() },
|
||||||
completion: @escaping (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
completion: @escaping ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -6977,7 +6992,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
|
|
||||||
let hasPremium = self.context.isPremium
|
let hasPremium = self.context.isPremium
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
|
||||||
let title = presentationData.strings.Story_Editor_ExpirationText
|
|
||||||
let currentValue = self.state.privacy.timeout
|
let currentValue = self.state.privacy.timeout
|
||||||
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||||
|
|
||||||
@ -6994,62 +7008,56 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
let timeoutOptions: [(hours: Int, requiresPremium: Bool)] = [
|
||||||
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
|
(6, true),
|
||||||
|
(12, true),
|
||||||
|
(24, false),
|
||||||
|
(48, true)
|
||||||
|
]
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(6), icon: { theme in
|
var items: [ContextMenuItem] = [
|
||||||
if !hasPremium {
|
.action(ContextMenuActionItem(
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
text: presentationData.strings.Story_Editor_ExpirationText,
|
||||||
} else {
|
textLayout: .multiline,
|
||||||
return currentValue == 3600 * 6 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
textFont: .small,
|
||||||
}
|
icon: { _ in return nil },
|
||||||
}, action: { [weak self] _, a in
|
action: emptyAction
|
||||||
a(.default)
|
))
|
||||||
|
]
|
||||||
if hasPremium {
|
|
||||||
updateTimeout(3600 * 6)
|
|
||||||
} else {
|
|
||||||
self?.presentTimeoutPremiumSuggestion()
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(12), icon: { theme in
|
|
||||||
if !hasPremium {
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
|
||||||
} else {
|
|
||||||
return currentValue == 3600 * 12 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
if hasPremium {
|
|
||||||
updateTimeout(3600 * 12)
|
|
||||||
} else {
|
|
||||||
self?.presentTimeoutPremiumSuggestion()
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(24), icon: { theme in
|
|
||||||
return currentValue == 86400 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}, action: { _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
updateTimeout(86400)
|
|
||||||
})))
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Story_Editor_ExpirationValue(48), icon: { theme in
|
|
||||||
if !hasPremium {
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"), color: theme.contextMenu.secondaryColor)
|
|
||||||
} else {
|
|
||||||
return currentValue == 86400 * 2 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
|
||||||
}
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
if hasPremium {
|
|
||||||
updateTimeout(86400 * 2)
|
|
||||||
} else {
|
|
||||||
self?.presentTimeoutPremiumSuggestion()
|
|
||||||
}
|
|
||||||
})))
|
|
||||||
|
|
||||||
|
for option in timeoutOptions {
|
||||||
|
let text = presentationData.strings.Story_Editor_ExpirationValue(Int32(option.hours))
|
||||||
|
let value = option.hours * 3600
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(
|
||||||
|
text: text,
|
||||||
|
icon: { theme in
|
||||||
|
if option.requiresPremium && !hasPremium {
|
||||||
|
return generateTintedImage(
|
||||||
|
image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon"),
|
||||||
|
color: theme.contextMenu.secondaryColor
|
||||||
|
)
|
||||||
|
} else if currentValue == value {
|
||||||
|
return generateTintedImage(
|
||||||
|
image: UIImage(bundleImageName: "Chat/Context Menu/Check"),
|
||||||
|
color: theme.contextMenu.primaryColor
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
if !option.requiresPremium || hasPremium {
|
||||||
|
updateTimeout(value)
|
||||||
|
} else {
|
||||||
|
self?.presentTimeoutPremiumSuggestion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
self.present(contextController, in: .window(.root))
|
self.present(contextController, in: .window(.root))
|
||||||
}
|
}
|
||||||
@ -7332,30 +7340,335 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private var didComplete = false
|
private func completeWithMultipleResults(results: [MediaEditorScreenImpl.Result]) {
|
||||||
func requestStoryCompletion(animated: Bool) {
|
// Send all results to completion handler
|
||||||
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject, let actualSubject = self.node.actualSubject, !self.didComplete else {
|
self.completion(results, { [weak self] finished in
|
||||||
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
|
self?.dismiss()
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
finished()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func processMultipleItems() {
|
||||||
|
guard !self.node.items.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.didComplete = true
|
if let mediaEditor = self.node.mediaEditor, case let .asset(asset) = self.node.subject, let currentItemIndex = self.node.items.firstIndex(where: { $0.asset.localIdentifier == asset.localIdentifier }) {
|
||||||
|
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||||
self.dismissAllTooltips()
|
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||||
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
mediaEditor.stop()
|
|
||||||
mediaEditor.invalidate()
|
var updatedCurrentItem = self.node.items[currentItemIndex]
|
||||||
self.node.entitiesView.invalidate()
|
updatedCurrentItem.caption = self.node.getCaption()
|
||||||
|
updatedCurrentItem.values = mediaEditor.values
|
||||||
let context = self.context
|
self.node.items[currentItemIndex] = updatedCurrentItem
|
||||||
if let navigationController = self.navigationController as? NavigationController {
|
|
||||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let multipleResults = Atomic<[MediaEditorScreenImpl.Result]>(value: [])
|
||||||
|
let totalItems = self.node.items.count
|
||||||
|
|
||||||
|
let dispatchGroup = DispatchGroup()
|
||||||
|
|
||||||
|
let privacy = self.state.privacy
|
||||||
|
|
||||||
|
if !(self.isEditingStory || self.isEditingStoryCover) {
|
||||||
|
let _ = updateMediaEditorStoredStateInteractively(engine: self.context.engine, { current in
|
||||||
|
if let current {
|
||||||
|
return current.withUpdatedPrivacy(privacy)
|
||||||
|
} else {
|
||||||
|
return MediaEditorStoredState(privacy: privacy, textSettings: nil)
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
var order: [Int64] = []
|
||||||
|
for (index, item) in self.node.items.enumerated() {
|
||||||
|
guard item.isEnabled else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchGroup.enter()
|
||||||
|
|
||||||
|
let randomId = Int64.random(in: .min ... .max)
|
||||||
|
order.append(randomId)
|
||||||
|
|
||||||
|
if item.asset.mediaType == .video {
|
||||||
|
processVideoItem(item: item, index: index, randomId: randomId) { result in
|
||||||
|
let _ = multipleResults.modify { results in
|
||||||
|
var updatedResults = results
|
||||||
|
updatedResults.append(result)
|
||||||
|
return updatedResults
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchGroup.leave()
|
||||||
|
}
|
||||||
|
} else if item.asset.mediaType == .image {
|
||||||
|
processImageItem(item: item, index: index, randomId: randomId) { result in
|
||||||
|
let _ = multipleResults.modify { results in
|
||||||
|
var updatedResults = results
|
||||||
|
updatedResults.append(result)
|
||||||
|
return updatedResults
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchGroup.leave()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatchGroup.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchGroup.notify(queue: .main) {
|
||||||
|
let results = multipleResults.with { $0 }
|
||||||
|
if results.count == totalItems {
|
||||||
|
var orderedResults: [MediaEditorScreenImpl.Result] = []
|
||||||
|
for id in order {
|
||||||
|
if let item = results.first(where: { $0.randomId == id }) {
|
||||||
|
orderedResults.append(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.completeWithMultipleResults(results: orderedResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func processVideoItem(item: EditingItem, index: Int, randomId: Int64, completion: @escaping (MediaEditorScreenImpl.Result) -> Void) {
|
||||||
|
let asset = item.asset
|
||||||
|
|
||||||
|
let itemMediaEditor = setupMediaEditorForItem(item: item)
|
||||||
|
|
||||||
|
var caption = item.caption
|
||||||
|
caption = convertMarkdownToAttributes(caption)
|
||||||
|
|
||||||
|
var mediaAreas: [MediaArea] = []
|
||||||
|
var stickers: [TelegramMediaFile] = []
|
||||||
|
|
||||||
|
if let entities = item.values?.entities {
|
||||||
|
for entity in entities {
|
||||||
|
if let mediaArea = entity.mediaArea {
|
||||||
|
mediaAreas.append(mediaArea)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract stickers from entities
|
||||||
|
extractStickersFromEntity(entity, into: &stickers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process video
|
||||||
|
let firstFrameTime: CMTime
|
||||||
|
if let coverImageTimestamp = item.values?.coverImageTimestamp {
|
||||||
|
firstFrameTime = CMTime(seconds: coverImageTimestamp, preferredTimescale: CMTimeScale(60))
|
||||||
|
} else {
|
||||||
|
firstFrameTime = .zero
|
||||||
|
}
|
||||||
|
|
||||||
|
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { [weak self] avAsset, _, _ in
|
||||||
|
guard let avAsset else {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let self {
|
||||||
|
completion(self.createEmptyResult(randomId: randomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate duration
|
||||||
|
let duration: Double
|
||||||
|
if let videoTrimRange = item.values?.videoTrimRange {
|
||||||
|
duration = videoTrimRange.upperBound - videoTrimRange.lowerBound
|
||||||
|
} else {
|
||||||
|
duration = min(asset.duration, storyMaxVideoDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate thumbnail frame
|
||||||
|
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||||
|
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||||
|
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)]) { [weak self] _, cgImage, _, _, _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let cgImage {
|
||||||
|
let image = UIImage(cgImage: cgImage)
|
||||||
|
itemMediaEditor.replaceSource(image, additionalImage: nil, time: firstFrameTime, mirror: false)
|
||||||
|
|
||||||
|
if let resultImage = itemMediaEditor.resultImage {
|
||||||
|
makeEditorImageComposition(
|
||||||
|
context: self.node.ciContext,
|
||||||
|
postbox: self.context.account.postbox,
|
||||||
|
inputImage: resultImage,
|
||||||
|
dimensions: storyDimensions,
|
||||||
|
values: itemMediaEditor.values,
|
||||||
|
time: firstFrameTime,
|
||||||
|
textScale: 2.0
|
||||||
|
) { coverImage in
|
||||||
|
if let coverImage = coverImage {
|
||||||
|
let result = MediaEditorScreenImpl.Result(
|
||||||
|
media: .video(
|
||||||
|
video: .asset(localIdentifier: asset.localIdentifier),
|
||||||
|
coverImage: coverImage,
|
||||||
|
values: itemMediaEditor.values,
|
||||||
|
duration: duration,
|
||||||
|
dimensions: itemMediaEditor.values.resultDimensions
|
||||||
|
),
|
||||||
|
mediaAreas: mediaAreas,
|
||||||
|
caption: caption,
|
||||||
|
coverTimestamp: itemMediaEditor.values.coverImageTimestamp,
|
||||||
|
options: self.state.privacy,
|
||||||
|
stickers: stickers,
|
||||||
|
randomId: randomId
|
||||||
|
)
|
||||||
|
completion(result)
|
||||||
|
} else {
|
||||||
|
completion(self.createEmptyResult(randomId: randomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion(self.createEmptyResult(randomId: randomId))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion(self.createEmptyResult(randomId: randomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func processImageItem(item: EditingItem, index: Int, randomId: Int64, completion: @escaping (MediaEditorScreenImpl.Result) -> Void) {
|
||||||
|
let asset = item.asset
|
||||||
|
|
||||||
|
// Setup temporary media editor for this item
|
||||||
|
let itemMediaEditor = setupMediaEditorForItem(item: item)
|
||||||
|
|
||||||
|
// Get caption for this item
|
||||||
|
var caption = item.caption
|
||||||
|
caption = convertMarkdownToAttributes(caption)
|
||||||
|
|
||||||
|
// Media areas and stickers
|
||||||
|
var mediaAreas: [MediaArea] = []
|
||||||
|
var stickers: [TelegramMediaFile] = []
|
||||||
|
|
||||||
|
if let entities = item.values?.entities {
|
||||||
|
for entity in entities {
|
||||||
|
if let mediaArea = entity.mediaArea {
|
||||||
|
mediaAreas.append(mediaArea)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract stickers from entities
|
||||||
|
extractStickersFromEntity(entity, into: &stickers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request full-size image
|
||||||
|
let options = PHImageRequestOptions()
|
||||||
|
options.deliveryMode = .highQualityFormat
|
||||||
|
options.isNetworkAccessAllowed = true
|
||||||
|
|
||||||
|
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { [weak self] image, _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if let image {
|
||||||
|
itemMediaEditor.replaceSource(image, additionalImage: nil, time: .zero, mirror: false)
|
||||||
|
|
||||||
|
if let resultImage = itemMediaEditor.resultImage {
|
||||||
|
makeEditorImageComposition(
|
||||||
|
context: self.node.ciContext,
|
||||||
|
postbox: self.context.account.postbox,
|
||||||
|
inputImage: resultImage,
|
||||||
|
dimensions: storyDimensions,
|
||||||
|
values: itemMediaEditor.values,
|
||||||
|
time: .zero,
|
||||||
|
textScale: 2.0
|
||||||
|
) { resultImage in
|
||||||
|
if let resultImage = resultImage {
|
||||||
|
let result = MediaEditorScreenImpl.Result(
|
||||||
|
media: .image(
|
||||||
|
image: resultImage,
|
||||||
|
dimensions: PixelDimensions(resultImage.size)
|
||||||
|
),
|
||||||
|
mediaAreas: mediaAreas,
|
||||||
|
caption: caption,
|
||||||
|
coverTimestamp: nil,
|
||||||
|
options: self.state.privacy,
|
||||||
|
stickers: stickers,
|
||||||
|
randomId: randomId
|
||||||
|
)
|
||||||
|
completion(result)
|
||||||
|
} else {
|
||||||
|
completion(self.createEmptyResult(randomId: randomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion(self.createEmptyResult(randomId: randomId))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completion(self.createEmptyResult(randomId: randomId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupMediaEditorForItem(item: EditingItem) -> MediaEditor {
|
||||||
|
return MediaEditor(
|
||||||
|
context: self.context,
|
||||||
|
mode: .default,
|
||||||
|
subject: .asset(item.asset),
|
||||||
|
values: item.values,
|
||||||
|
hasHistogram: false,
|
||||||
|
isStandalone: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func extractStickersFromEntity(_ entity: CodableDrawingEntity, into stickers: inout [TelegramMediaFile]) {
|
||||||
|
switch entity {
|
||||||
|
case let .sticker(stickerEntity):
|
||||||
|
if case let .file(file, fileType) = stickerEntity.content, case .sticker = fileType {
|
||||||
|
stickers.append(file.media)
|
||||||
|
}
|
||||||
|
case let .text(textEntity):
|
||||||
|
if let subEntities = textEntity.renderSubEntities {
|
||||||
|
for entity in subEntities {
|
||||||
|
if let stickerEntity = entity as? DrawingStickerEntity, case let .file(file, fileType) = stickerEntity.content, case .sticker = fileType {
|
||||||
|
stickers.append(file.media)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createEmptyResult(randomId: Int64) -> MediaEditorScreenImpl.Result {
|
||||||
|
let emptyImage = UIImage()
|
||||||
|
return MediaEditorScreenImpl.Result(
|
||||||
|
media: .image(
|
||||||
|
image: emptyImage,
|
||||||
|
dimensions: PixelDimensions(emptyImage.size)
|
||||||
|
),
|
||||||
|
mediaAreas: [],
|
||||||
|
caption: NSAttributedString(),
|
||||||
|
coverTimestamp: nil,
|
||||||
|
options: self.state.privacy,
|
||||||
|
stickers: [],
|
||||||
|
randomId: randomId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func processSingleItem() {
|
||||||
|
guard let mediaEditor = self.node.mediaEditor, let subject = self.node.subject, let actualSubject = self.node.actualSubject else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
|
|
||||||
var caption = self.node.getCaption()
|
var caption = self.node.getCaption()
|
||||||
caption = convertMarkdownToAttributes(caption)
|
caption = convertMarkdownToAttributes(caption)
|
||||||
|
|
||||||
@ -7407,7 +7720,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
if self.isEmbeddedEditor && !(hasAnyChanges || hasEntityChanges) {
|
if self.isEmbeddedEditor && !(hasAnyChanges || hasEntityChanges) {
|
||||||
self.saveDraft(id: randomId, isEdit: true)
|
self.saveDraft(id: randomId, isEdit: true)
|
||||||
|
|
||||||
self.completion(MediaEditorScreenImpl.Result(media: nil, mediaAreas: [], caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in
|
self.completion([MediaEditorScreenImpl.Result(media: nil, mediaAreas: [], caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId)], { [weak self] finished in
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
@ -7737,7 +8050,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
||||||
self.completion(MediaEditorScreenImpl.Result(media: .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), mediaAreas: mediaAreas, caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in
|
self.completion([MediaEditorScreenImpl.Result(media: .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), mediaAreas: mediaAreas, caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId)], { [weak self] finished in
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
@ -7754,38 +8067,70 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
if case let .draft(draft, id) = actualSubject, id == nil {
|
if case let .draft(draft, id) = actualSubject, id == nil {
|
||||||
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false)
|
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: false)
|
||||||
}
|
}
|
||||||
} else {
|
} else if let image = mediaEditor.resultImage {
|
||||||
if let image = mediaEditor.resultImage {
|
self.saveDraft(id: randomId)
|
||||||
self.saveDraft(id: randomId)
|
|
||||||
|
var values = mediaEditor.values
|
||||||
var values = mediaEditor.values
|
var outputDimensions: CGSize?
|
||||||
var outputDimensions: CGSize?
|
if case .avatarEditor = self.mode {
|
||||||
if case .avatarEditor = self.mode {
|
outputDimensions = CGSize(width: 640.0, height: 640.0)
|
||||||
outputDimensions = CGSize(width: 640.0, height: 640.0)
|
values = values.withUpdatedQualityPreset(.profile)
|
||||||
values = values.withUpdatedQualityPreset(.profile)
|
|
||||||
}
|
|
||||||
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: image, dimensions: storyDimensions, outputDimensions: outputDimensions, values: values, time: .zero, textScale: 2.0, completion: { [weak self] resultImage in
|
|
||||||
if let self, let resultImage {
|
|
||||||
self.willComplete(resultImage, false, { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Logger.shared.log("MediaEditor", "Completed with image \(resultImage)")
|
|
||||||
self.completion(MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), mediaAreas: mediaAreas, caption: caption, coverTimestamp: nil, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in
|
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
|
||||||
self?.dismiss()
|
|
||||||
Queue.mainQueue().justDispatch {
|
|
||||||
finished()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if case let .draft(draft, id) = actualSubject, id == nil {
|
|
||||||
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
makeEditorImageComposition(
|
||||||
|
context: self.node.ciContext,
|
||||||
|
postbox: self.context.account.postbox,
|
||||||
|
inputImage: image,
|
||||||
|
dimensions: storyDimensions,
|
||||||
|
outputDimensions: outputDimensions,
|
||||||
|
values: values,
|
||||||
|
time: .zero,
|
||||||
|
textScale: 2.0,
|
||||||
|
completion: { [weak self] resultImage in
|
||||||
|
if let self, let resultImage {
|
||||||
|
self.willComplete(resultImage, false, { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Logger.shared.log("MediaEditor", "Completed with image \(resultImage)")
|
||||||
|
self.completion([MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), mediaAreas: mediaAreas, caption: caption, coverTimestamp: nil, options: self.state.privacy, stickers: stickers, randomId: randomId)], { [weak self] finished in
|
||||||
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
|
self?.dismiss()
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
finished()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if case let .draft(draft, id) = actualSubject, id == nil {
|
||||||
|
removeStoryDraft(engine: self.context.engine, path: draft.path, delete: true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var didComplete = false
|
||||||
|
func requestStoryCompletion(animated: Bool) {
|
||||||
|
guard let mediaEditor = self.node.mediaEditor, !self.didComplete else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.didComplete = true
|
||||||
|
|
||||||
|
self.dismissAllTooltips()
|
||||||
|
|
||||||
|
mediaEditor.stop()
|
||||||
|
mediaEditor.invalidate()
|
||||||
|
self.node.entitiesView.invalidate()
|
||||||
|
|
||||||
|
if let navigationController = self.navigationController as? NavigationController {
|
||||||
|
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.node.items.count(where: { $0.isEnabled }) > 1 {
|
||||||
|
self.processMultipleItems()
|
||||||
|
} else {
|
||||||
|
self.processSingleItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7852,7 +8197,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
self.completion(MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size))), { [weak self] finished in
|
self.completion([MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)))], { [weak self] finished in
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
@ -7955,7 +8300,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
if isVideo {
|
if isVideo {
|
||||||
self.uploadSticker(file, action: .send)
|
self.uploadSticker(file, action: .send)
|
||||||
} else {
|
} else {
|
||||||
self.completion(MediaEditorScreenImpl.Result(
|
self.completion([MediaEditorScreenImpl.Result(
|
||||||
media: .sticker(file: file, emoji: self.effectiveStickerEmoji()),
|
media: .sticker(file: file, emoji: self.effectiveStickerEmoji()),
|
||||||
mediaAreas: [],
|
mediaAreas: [],
|
||||||
caption: NSAttributedString(),
|
caption: NSAttributedString(),
|
||||||
@ -7963,7 +8308,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
|
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false),
|
||||||
stickers: [],
|
stickers: [],
|
||||||
randomId: 0
|
randomId: 0
|
||||||
), { [weak self] finished in
|
)], { [weak self] finished in
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
@ -8376,7 +8721,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||||||
result = MediaEditorScreenImpl.Result()
|
result = MediaEditorScreenImpl.Result()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.completion(result, { [weak self] finished in
|
self.completion([result], { [weak self] finished in
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
@ -145,23 +145,11 @@ final class SelectionPanelComponent: Component {
|
|||||||
selectionLayer.lineWidth = lineWidth
|
selectionLayer.lineWidth = lineWidth
|
||||||
selectionLayer.frame = selectionFrame
|
selectionLayer.frame = selectionFrame
|
||||||
selectionLayer.path = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
selectionLayer.path = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||||
|
|
||||||
// if !transition.animation.isImmediate {
|
|
||||||
// let initialPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
|
||||||
// selectionLayer.animate(from: initialPath, to: selectionLayer.path as AnyObject, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
|
||||||
// selectionLayer.animateShapeLineWidth(from: 0.0, to: lineWidth, duration: 0.2)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if let selectionLayer = self.selectionLayer {
|
} else if let selectionLayer = self.selectionLayer {
|
||||||
self.selectionLayer = nil
|
self.selectionLayer = nil
|
||||||
selectionLayer.removeFromSuperlayer()
|
selectionLayer.removeFromSuperlayer()
|
||||||
|
|
||||||
// let targetPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
|
||||||
// selectionLayer.animate(from: selectionLayer.path, to: targetPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
|
||||||
// selectionLayer.animateShapeLineWidth(from: selectionLayer.lineWidth, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
|
||||||
// selectionLayer.removeFromSuperlayer()
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,11 +361,96 @@ final class SelectionPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateIn(from buttonView: SelectionPanelButtonContentComponent.View) {
|
func animateIn(from buttonView: SelectionPanelButtonContentComponent.View) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scrollView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
|
||||||
|
let buttonFrame = buttonView.convert(buttonView.bounds, to: self)
|
||||||
|
let fromPoint = CGPoint(x: buttonFrame.center.x - self.scrollView.center.x, y: buttonFrame.center.y - self.scrollView.center.y)
|
||||||
|
|
||||||
|
self.scrollView.layer.animatePosition(from: fromPoint, to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
|
||||||
|
self.scrollView.layer.animateBounds(from: CGRect(origin: CGPoint(x: buttonFrame.minX - self.scrollView.frame.minX, y: buttonFrame.minY - self.scrollView.frame.minY), size: buttonFrame.size), to: self.scrollView.bounds, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
|
self.backgroundMaskPanelView.layer.animatePosition(from: fromPoint, to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
self.backgroundMaskPanelView.layer.animate(from: NSNumber(value: Float(16.5)), to: NSNumber(value: Float(self.backgroundMaskPanelView.layer.cornerRadius)), keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4)
|
||||||
|
self.backgroundMaskPanelView.layer.animateBounds(from: CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0)), to: self.backgroundMaskPanelView.bounds, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
|
let mainCircleDelay: Double = 0.02
|
||||||
|
let backgroundWidth = self.backgroundMaskPanelView.frame.width
|
||||||
|
for item in component.items {
|
||||||
|
guard let itemView = self.itemViews[item.asset.localIdentifier] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let distance = abs(itemView.frame.center.x - backgroundWidth)
|
||||||
|
let distanceNorm = distance / backgroundWidth
|
||||||
|
let adjustedDistanceNorm = distanceNorm
|
||||||
|
let itemDelay = mainCircleDelay + adjustedDistanceNorm * 0.14
|
||||||
|
|
||||||
|
itemView.isHidden = true
|
||||||
|
Queue.mainQueue().after(itemDelay * UIView.animationDurationFactor()) { [weak itemView] in
|
||||||
|
guard let itemView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
itemView.isHidden = false
|
||||||
|
itemView.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(to buttonView: SelectionPanelButtonContentComponent.View, completion: @escaping () -> Void) {
|
func animateOut(to buttonView: SelectionPanelButtonContentComponent.View, completion: @escaping () -> Void) {
|
||||||
completion()
|
guard let component = self.component else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scrollView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||||
|
|
||||||
|
let buttonFrame = buttonView.convert(buttonView.bounds, to: self)
|
||||||
|
let scrollButtonFrame = buttonView.convert(buttonView.bounds, to: self.scrollView)
|
||||||
|
let toPoint = CGPoint(x: buttonFrame.center.x - self.scrollView.center.x, y: buttonFrame.center.y - self.scrollView.center.y)
|
||||||
|
|
||||||
|
self.scrollView.layer.animatePosition(from: .zero, to: toPoint, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||||
|
|
||||||
|
self.scrollView.layer.animateBounds(from: self.scrollView.bounds, to: CGRect(origin: CGPoint(x: (buttonFrame.minX - self.scrollView.frame.minX) / 2.0, y: (buttonFrame.minY - self.scrollView.frame.minY) / 2.0), size: buttonFrame.size), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
|
self.backgroundMaskPanelView.layer.animatePosition(from: .zero, to: toPoint, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||||
|
self.backgroundMaskPanelView.layer.animate(from: NSNumber(value: Float(self.backgroundMaskPanelView.layer.cornerRadius)), to: NSNumber(value: Float(16.5)), keyPath: "cornerRadius", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.4, removeOnCompletion: false)
|
||||||
|
self.backgroundMaskPanelView.layer.animateBounds(from: self.backgroundMaskPanelView.bounds, to: CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0)), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { finished in
|
||||||
|
if finished {
|
||||||
|
completion()
|
||||||
|
self.backgroundMaskPanelView.layer.removeAllAnimations()
|
||||||
|
for (_, itemView) in self.itemViews {
|
||||||
|
itemView.layer.removeAllAnimations()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let mainCircleDelay: Double = 0.0
|
||||||
|
let backgroundWidth = self.backgroundMaskPanelView.frame.width
|
||||||
|
|
||||||
|
for item in component.items {
|
||||||
|
guard let itemView = self.itemViews[item.asset.localIdentifier] else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let distance = abs(itemView.frame.center.x - backgroundWidth)
|
||||||
|
let distanceNorm = distance / backgroundWidth
|
||||||
|
let adjustedDistanceNorm = distanceNorm
|
||||||
|
|
||||||
|
let itemDelay = mainCircleDelay + adjustedDistanceNorm * 0.05
|
||||||
|
|
||||||
|
Queue.mainQueue().after(itemDelay * UIView.animationDurationFactor()) { [weak itemView] in
|
||||||
|
guard let itemView else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
itemView.layer.animatePosition(from: itemView.center, to: scrollButtonFrame.center, duration: 0.4)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(component: SelectionPanelComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize {
|
func update(component: SelectionPanelComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize {
|
||||||
|
@ -201,7 +201,10 @@ extension PeerInfoScreenImpl {
|
|||||||
commit()
|
commit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
completion: { [weak self] result, commit in
|
completion: { [weak self] results, commit in
|
||||||
|
guard let result = results.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
switch result.media {
|
switch result.media {
|
||||||
case let .image(image, _):
|
case let .image(image, _):
|
||||||
resultImage = image
|
resultImage = image
|
||||||
@ -217,7 +220,7 @@ extension PeerInfoScreenImpl {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
} as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
editorController.cancelled = { _ in
|
editorController.cancelled = { _ in
|
||||||
cancelled()
|
cancelled()
|
||||||
|
@ -607,10 +607,10 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||||||
return self.profileGifts.buyStarGift(slug: slug, peerId: peerId)
|
return self.profileGifts.buyStarGift(slug: slug, peerId: peerId)
|
||||||
},
|
},
|
||||||
updateResellStars: { [weak self] price in
|
updateResellStars: { [weak self] price in
|
||||||
guard let self, case let .unique(uniqueGift) = product.gift else {
|
guard let self, let reference = product.reference else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.profileGifts.updateStarGiftResellPrice(slug: uniqueGift.slug, price: price)
|
self.profileGifts.updateStarGiftResellPrice(reference: reference, price: price)
|
||||||
},
|
},
|
||||||
togglePinnedToTop: { [weak self] pinnedToTop in
|
togglePinnedToTop: { [weak self] pinnedToTop in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1479,6 +1479,8 @@ private extension StarGiftReference {
|
|||||||
return "m_\(messageId.id)"
|
return "m_\(messageId.id)"
|
||||||
case let .peer(peerId, id):
|
case let .peer(peerId, id):
|
||||||
return "p_\(peerId.toInt64())_\(id)"
|
return "p_\(peerId.toInt64())_\(id)"
|
||||||
|
case let .slug(slug):
|
||||||
|
return "s_\(slug)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1315,13 +1315,13 @@ extension ChatControllerImpl {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, completion: { result, commit in
|
}, completion: { results, commit in
|
||||||
if case let .image(image, _) = result.media {
|
if case let .image(image, _) = results.first?.media {
|
||||||
completion(image)
|
completion(image)
|
||||||
commit({})
|
commit({})
|
||||||
}
|
}
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
} as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
editorController.cancelled = { _ in
|
editorController.cancelled = { _ in
|
||||||
cancelled()
|
cancelled()
|
||||||
@ -1930,17 +1930,17 @@ extension ChatControllerImpl {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, completion: { [weak self] result, commit in
|
}, completion: { [weak self] results, commit in
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
self?.chatDisplayNode.dismissInput()
|
self?.chatDisplayNode.dismissInput()
|
||||||
|
|
||||||
Queue.mainQueue().after(0.1) {
|
Queue.mainQueue().after(0.1) {
|
||||||
commit({})
|
commit({})
|
||||||
if case let .sticker(file, _) = result.media {
|
if case let .sticker(file, _) = results.first?.media {
|
||||||
self?.enqueueStickerFile(file)
|
self?.enqueueStickerFile(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
} as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
editorController.cancelled = { _ in
|
editorController.cancelled = { _ in
|
||||||
cancelled()
|
cancelled()
|
||||||
|
@ -3461,9 +3461,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, completion: { result, commit in
|
}, completion: { results, commit in
|
||||||
completion(result, commit)
|
completion(results.first!, commit)
|
||||||
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
} as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
editorController.cancelled = { _ in
|
editorController.cancelled = { _ in
|
||||||
cancelled()
|
cancelled()
|
||||||
@ -3525,13 +3525,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, completion: { result, commit in
|
}, completion: { results, commit in
|
||||||
if case let .sticker(file, emoji) = result.media {
|
if case let .sticker(file, emoji) = results.first?.media {
|
||||||
completion(file, emoji, {
|
completion(file, emoji, {
|
||||||
commit({})
|
commit({})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
} as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
editorController.cancelled = { _ in
|
editorController.cancelled = { _ in
|
||||||
cancelled()
|
cancelled()
|
||||||
@ -3558,13 +3558,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
transitionIn: nil,
|
transitionIn: nil,
|
||||||
transitionOut: { finished, isNew in
|
transitionOut: { finished, isNew in
|
||||||
return nil
|
return nil
|
||||||
}, completion: { result, commit in
|
}, completion: { results, commit in
|
||||||
completion(result, commit)
|
completion(results.first!, commit)
|
||||||
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
} as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
// editorController.cancelled = { _ in
|
|
||||||
// cancelled()
|
|
||||||
// }
|
|
||||||
return editorController
|
return editorController
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3724,7 +3721,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
transitionOut: { _, _ in
|
transitionOut: { _, _ in
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
completion: { [weak parentController] result, commit in
|
completion: { [weak parentController] results, commit in
|
||||||
|
guard let result = results.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let targetPeerId: EnginePeer.Id
|
let targetPeerId: EnginePeer.Id
|
||||||
let target: Stories.PendingTarget
|
let target: Stories.PendingTarget
|
||||||
if let sendAsPeerId = result.options.sendAsPeerId {
|
if let sendAsPeerId = result.options.sendAsPeerId {
|
||||||
|
@ -444,7 +444,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}, completion: { [weak self] result, commit in
|
}, completion: { [weak self] results, commit in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
dismissCameraImpl?()
|
dismissCameraImpl?()
|
||||||
commit({})
|
commit({})
|
||||||
@ -453,7 +453,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
|
|
||||||
if let customTarget, case .botPreview = customTarget {
|
if let customTarget, case .botPreview = customTarget {
|
||||||
externalState.storyTarget = customTarget
|
externalState.storyTarget = customTarget
|
||||||
self.proceedWithStoryUpload(target: customTarget, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
self.proceedWithStoryUpload(target: customTarget, results: results, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||||
|
|
||||||
dismissCameraImpl?()
|
dismissCameraImpl?()
|
||||||
return
|
return
|
||||||
@ -464,7 +464,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
target = .peer(id)
|
target = .peer(id)
|
||||||
targetPeerId = id
|
targetPeerId = id
|
||||||
} else {
|
} else {
|
||||||
if let sendAsPeerId = result.options.sendAsPeerId {
|
if let sendAsPeerId = results.first?.options.sendAsPeerId {
|
||||||
target = .peer(sendAsPeerId)
|
target = .peer(sendAsPeerId)
|
||||||
targetPeerId = sendAsPeerId
|
targetPeerId = sendAsPeerId
|
||||||
} else {
|
} else {
|
||||||
@ -486,12 +486,12 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
externalState.isPeerArchived = channel.storiesHidden ?? false
|
externalState.isPeerArchived = channel.storiesHidden ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
self.proceedWithStoryUpload(target: target, results: results, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||||
|
|
||||||
dismissCameraImpl?()
|
dismissCameraImpl?()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
} as ([MediaEditorScreenImpl.Result], @escaping (@escaping () -> Void) -> Void) -> Void
|
||||||
)
|
)
|
||||||
controller.cancelled = { showDraftTooltip in
|
controller.cancelled = { showDraftTooltip in
|
||||||
if showDraftTooltip {
|
if showDraftTooltip {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user