mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
ab7fc69e16
commit
7a7333edab
118 changed files with 4380 additions and 7431 deletions
|
|
@ -736,7 +736,6 @@ public enum PeerInfoControllerMode {
|
|||
|
||||
case generic
|
||||
case calls(messages: [EngineMessage])
|
||||
case nearbyPeer(distance: Int32)
|
||||
case group(sourceMessageId: MessageId)
|
||||
case reaction(MessageId)
|
||||
case forumTopic(thread: ChatReplyThreadMessage)
|
||||
|
|
@ -1371,7 +1370,6 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makePeerInfoController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peer: EnginePeer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool, requestsContext: PeerInvitationImportersContext?) -> ViewController?
|
||||
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController?
|
||||
func makeDeviceContactInfoController(context: ShareControllerAccountContext, environment: ShareControllerEnvironment, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
|
||||
func makePeersNearbyController(context: AccountContext) -> ViewController
|
||||
func makeComposeController(context: AccountContext) -> ViewController
|
||||
func makeChatListController(context: AccountContext, location: ChatListControllerLocation, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
|
||||
func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode, params: ChatControllerParams?) -> ChatController
|
||||
|
|
@ -1469,7 +1467,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeGiftStoreController(context: AccountContext, peerId: EnginePeer.Id, gift: StarGift.Gift) -> ViewController
|
||||
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
|
||||
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
|
||||
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: ((Bool) -> Void)?) -> ViewController
|
||||
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], actionTitle: String?, isEditing: Bool, expandIfNeeded: Bool, parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView?, CGRect?) -> Bool)?, actionPerformed: (([StickerPackScreenActionResult]) -> Void)?) -> ViewController
|
||||
func makeCameraScreen(context: AccountContext, mode: CameraScreenMode, cameraHolder: Any?, transitionIn: CameraScreenTransitionIn?, transitionOut: @escaping (Bool) -> CameraScreenTransitionOut?, completion: @escaping (Any, @escaping () -> Void) -> Void, transitionedOut: (() -> Void)?) -> ViewController
|
||||
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
|
||||
func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: (url: String, name: String?)?, remainingCount: Int32, completion: @escaping ([MediaEditorScreenResult], MediaEditorTransitionOutExternalState, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController
|
||||
|
|
@ -1638,6 +1636,23 @@ public protocol ChatSendMessageActionSheetController: ViewController {
|
|||
typealias SendParameters = ChatSendMessageActionSheetControllerSendParameters
|
||||
}
|
||||
|
||||
public enum StickerPackScreenActionKind {
|
||||
case add
|
||||
case remove(positionInList: Int)
|
||||
}
|
||||
|
||||
public struct StickerPackScreenActionResult {
|
||||
public let info: StickerPackCollectionInfo
|
||||
public let items: [StickerPackItem]
|
||||
public let action: StickerPackScreenActionKind
|
||||
|
||||
public init(info: StickerPackCollectionInfo, items: [StickerPackItem], action: StickerPackScreenActionKind) {
|
||||
self.info = info
|
||||
self.items = items
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
public protocol AccountContext: AnyObject {
|
||||
var sharedContext: SharedAccountContext { get }
|
||||
var account: Account { get }
|
||||
|
|
|
|||
|
|
@ -1420,7 +1420,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
|
|||
})
|
||||
})
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, unarchivePeer: {
|
||||
}, scrollToTop: {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/Utils/DeviceModel",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
|||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
|
||||
let buttonHeight: CGFloat = 50.0
|
||||
let buttonHeight: CGFloat = 52.0
|
||||
let bottomPanelPadding: CGFloat = 12.0
|
||||
let titleSpacing: CGFloat = -24.0
|
||||
let listSpacing: CGFloat = 12.0
|
||||
|
|
@ -416,7 +416,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
|||
listView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - listSize.width) / 2.0), y: originY), size: listSize)
|
||||
}
|
||||
|
||||
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
|
||||
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 10.0 : bottomPanelPadding
|
||||
let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset
|
||||
|
||||
let priceString: String
|
||||
|
|
@ -463,7 +463,7 @@ final class AuthorizationSequencePaymentScreenComponent: Component {
|
|||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight)
|
||||
containerSize: CGSize(width: availableSize.width - 30.0 * 2.0, height: buttonHeight)
|
||||
)
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
|
|
|
|||
|
|
@ -5376,21 +5376,18 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
if case .broadcast = channel.info {
|
||||
canClear = false
|
||||
deleteTitle = strongSelf.presentationData.strings.Channel_LeaveChannel
|
||||
if channel.addressName == nil && channel.flags.contains(.isCreator) {
|
||||
canRemoveGlobally = true
|
||||
}
|
||||
} else {
|
||||
deleteTitle = strongSelf.presentationData.strings.Group_DeleteGroup
|
||||
if channel.addressName == nil && channel.flags.contains(.isCreator) {
|
||||
canRemoveGlobally = true
|
||||
}
|
||||
}
|
||||
if strongSelf.canDeletePeerGloballyAsCreator(mainPeer) {
|
||||
canRemoveGlobally = true
|
||||
}
|
||||
if let addressName = channel.addressName, !addressName.isEmpty {
|
||||
canClear = false
|
||||
}
|
||||
}
|
||||
} else if case let .legacyGroup(group) = chatPeer {
|
||||
if case .creator = group.role {
|
||||
} else if case .legacyGroup = chatPeer {
|
||||
if strongSelf.canDeletePeerGloballyAsCreator(mainPeer) {
|
||||
canRemoveGlobally = true
|
||||
}
|
||||
} else if case let .user(user) = chatPeer, user.botInfo != nil {
|
||||
|
|
@ -5843,6 +5840,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
canRemoveGlobally = true
|
||||
}
|
||||
|
||||
if deleteGloballyIfPossible && self.canDeletePeerGloballyAsCreator(mainPeer) {
|
||||
self.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: true, completion: {
|
||||
removed()
|
||||
})
|
||||
completion(true)
|
||||
return
|
||||
}
|
||||
|
||||
if canRemoveGlobally {
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
|
|
@ -5946,6 +5951,16 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
}
|
||||
}
|
||||
|
||||
private func canDeletePeerGloballyAsCreator(_ peer: EnginePeer) -> Bool {
|
||||
if case let .channel(channel) = peer {
|
||||
return !channel.isMonoForum && channel.flags.contains(.isCreator) && channel.addressName == nil
|
||||
} else if case let .legacyGroup(group) = peer, case .creator = group.role {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func archiveChats(peerIds: [PeerId]) {
|
||||
guard !peerIds.isEmpty else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ public final class ChatPanelInterfaceInteraction {
|
|||
public let displaySendMessageOptions: (ASDisplayNode, ContextGesture) -> Void
|
||||
public let openScheduledMessages: () -> Void
|
||||
public let displaySearchResultsTooltip: (ASDisplayNode, CGRect) -> Void
|
||||
public let openPeersNearby: () -> Void
|
||||
public let unarchivePeer: () -> Void
|
||||
public let scrollToTop: () -> Void
|
||||
public let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void
|
||||
|
|
@ -285,7 +284,6 @@ public final class ChatPanelInterfaceInteraction {
|
|||
displaySlowmodeTooltip: @escaping (UIView, CGRect) -> Void,
|
||||
displaySendMessageOptions: @escaping (ASDisplayNode, ContextGesture) -> Void,
|
||||
openScheduledMessages: @escaping () -> Void,
|
||||
openPeersNearby: @escaping () -> Void,
|
||||
displaySearchResultsTooltip: @escaping (ASDisplayNode, CGRect) -> Void,
|
||||
unarchivePeer: @escaping () -> Void,
|
||||
scrollToTop: @escaping () -> Void,
|
||||
|
|
@ -417,7 +415,6 @@ public final class ChatPanelInterfaceInteraction {
|
|||
self.displaySlowmodeTooltip = displaySlowmodeTooltip
|
||||
self.displaySendMessageOptions = displaySendMessageOptions
|
||||
self.openScheduledMessages = openScheduledMessages
|
||||
self.openPeersNearby = openPeersNearby
|
||||
self.displaySearchResultsTooltip = displaySearchResultsTooltip
|
||||
self.unarchivePeer = unarchivePeer
|
||||
self.scrollToTop = scrollToTop
|
||||
|
|
@ -557,7 +554,6 @@ public final class ChatPanelInterfaceInteraction {
|
|||
}, displaySlowmodeTooltip: { _, _ in
|
||||
}, displaySendMessageOptions: { _, _ in
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, unarchivePeer: {
|
||||
}, scrollToTop: {
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
private let dimView: UIView
|
||||
private let scrollView: ScrollView
|
||||
private let backgroundView: SheetBackgroundView
|
||||
private var effectView: UIVisualEffectView?
|
||||
private var effectView: SheetBackgroundBlurView?
|
||||
private let clipView: SheetBackgroundView
|
||||
private let contentView: ComponentView<ChildEnvironmentType>
|
||||
private var headerView: ComponentView<Empty>?
|
||||
|
|
@ -411,13 +411,13 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
}
|
||||
|
||||
var backgroundColor: UIColor = .clear
|
||||
var blurEffectStyle: UIBlurEffect.Style?
|
||||
switch component.backgroundColor {
|
||||
case let .blur(style):
|
||||
blurEffectStyle = style == .dark ? .dark : .light
|
||||
self.backgroundView.isHidden = true
|
||||
if self.effectView == nil {
|
||||
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: style == .dark ? .dark : .light))
|
||||
effectView.layer.cornerRadius = self.backgroundView.layer.cornerRadius
|
||||
effectView.layer.masksToBounds = true
|
||||
let effectView = SheetBackgroundBlurView()
|
||||
self.backgroundView.superview?.insertSubview(effectView, aboveSubview: self.backgroundView)
|
||||
self.effectView = effectView
|
||||
}
|
||||
|
|
@ -471,8 +471,9 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
transition.setFrame(view: self.clipView, frame: clipFrame, completion: nil)
|
||||
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: clipFrame.size), completion: nil)
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil)
|
||||
if let effectView = self.effectView {
|
||||
if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle {
|
||||
transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - contentSize.width) / 2.0), y: -y), size: contentSize), completion: nil)
|
||||
effectView.update(style: blurEffectStyle, size: contentSize, topCornerRadius: topCornerRadius, bottomCornerRadius: topCornerRadius, transition: transition)
|
||||
}
|
||||
self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius, bottomCornerRadius: topCornerRadius, transition: transition)
|
||||
} else {
|
||||
|
|
@ -483,14 +484,19 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
transition.setFrame(view: self.clipView, frame: clipFrame)
|
||||
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil)
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil)
|
||||
if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle {
|
||||
transition.setFrame(view: effectView, frame: CGRect(origin: CGPoint(x: glassInset, y: -glassInset), size: CGSize(width: contentSize.width, height: contentSize.height)), completion: nil)
|
||||
effectView.update(style: blurEffectStyle, size: contentSize, topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition)
|
||||
}
|
||||
case .legacy:
|
||||
let clipFrame = CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 100.0))
|
||||
self.clipView.update(size: clipFrame.size, color: .clear, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition)
|
||||
transition.setFrame(view: self.clipView, frame: clipFrame)
|
||||
transition.setFrame(view: contentView, frame: CGRect(origin: .zero, size: clipFrame.size), completion: nil)
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil)
|
||||
if let effectView = self.effectView {
|
||||
if let effectView = self.effectView, let blurEffectStyle = blurEffectStyle {
|
||||
transition.setFrame(view: effectView, frame: CGRect(origin: .zero, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0)), completion: nil)
|
||||
effectView.update(style: blurEffectStyle, size: CGSize(width: contentSize.width, height: contentSize.height + 1000.0), topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition)
|
||||
}
|
||||
}
|
||||
self.backgroundView.update(size: contentSize, color: backgroundColor, topCornerRadius: topCornerRadius + 1.5, bottomCornerRadius: bottomCornerRadius, transition: transition)
|
||||
|
|
@ -614,3 +620,31 @@ public final class SheetBackgroundView: UIView {
|
|||
transition.setBackgroundColor(view: self.bottomCornersView, color: color)
|
||||
}
|
||||
}
|
||||
|
||||
private final class SheetBackgroundBlurView: UIView {
|
||||
private let clipView = SheetBackgroundView()
|
||||
private let effectView = UIVisualEffectView()
|
||||
private var currentStyle: UIBlurEffect.Style?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.clipView)
|
||||
self.clipView.bottomCornersView.addSubview(self.effectView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(style: UIBlurEffect.Style, size: CGSize, topCornerRadius: CGFloat, bottomCornerRadius: CGFloat, transition: ComponentTransition) {
|
||||
if self.currentStyle != style {
|
||||
self.currentStyle = style
|
||||
self.effectView.effect = UIBlurEffect(style: style)
|
||||
}
|
||||
|
||||
self.clipView.update(size: size, color: .clear, topCornerRadius: topCornerRadius, bottomCornerRadius: bottomCornerRadius, transition: transition)
|
||||
transition.setFrame(view: self.clipView, frame: CGRect(origin: .zero, size: size))
|
||||
transition.setFrame(view: self.effectView, frame: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -375,49 +375,6 @@ public class ContactsController: ViewController {
|
|||
}
|
||||
}
|
||||
|
||||
self.contactsNode.openPeopleNearby = { [weak self] in
|
||||
let _ = (DeviceAccess.authorizationStatus(subject: .location(.tracking))
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let presentPeersNearby = {
|
||||
let controller = strongSelf.context.sharedContext.makePeersNearbyController(context: strongSelf.context)
|
||||
controller.navigationPresentation = .master
|
||||
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
var controllers = navigationController.viewControllers.filter { !($0 is PermissionController) }
|
||||
controllers.append(controller)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||
}
|
||||
}
|
||||
|
||||
switch status {
|
||||
case .allowed:
|
||||
presentPeersNearby()
|
||||
default:
|
||||
let controller = PermissionController(context: strongSelf.context, splashScreen: false)
|
||||
controller.setState(.permission(.nearbyLocation(status: PermissionRequestStatus(accessType: status))), animated: false)
|
||||
controller.navigationPresentation = .master
|
||||
controller.proceed = { result in
|
||||
if result {
|
||||
presentPeersNearby()
|
||||
} else {
|
||||
let _ = (strongSelf.navigationController as? NavigationController)?.popViewController(animated: true)
|
||||
}
|
||||
}
|
||||
if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
navigationController.pushViewController(controller, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.contactsNode.openInvite = { [weak self] in
|
||||
let _ = (DeviceAccess.authorizationStatus(subject: .contacts)
|
||||
|> take(1)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
|
||||
var requestOpenDisabledPeerFromSearch: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
|
||||
var requestAddContact: ((String) -> Void)?
|
||||
var openPeopleNearby: (() -> Void)?
|
||||
var openInvite: (() -> Void)?
|
||||
var openQrScan: (() -> Void)?
|
||||
var openStories: ((EnginePeer, ASDisplayNode) -> Void)?
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "DateSelectionUI",
|
||||
module_name = "DateSelectionUI",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/UIKitRuntimeUtils:UIKitRuntimeUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
public final class DateSelectionActionSheetController: ActionSheetController {
|
||||
private var presentationDisposable: Disposable?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext, title: String?, currentValue: Int32, minimumDate: Date? = nil, maximumDate: Date? = nil, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
|
||||
|
||||
self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
}
|
||||
}).strict()
|
||||
|
||||
self._ready.set(.single(true))
|
||||
|
||||
var updatedValue = currentValue
|
||||
var items: [ActionSheetItem] = []
|
||||
if let title = title {
|
||||
items.append(ActionSheetTextItem(title: title))
|
||||
}
|
||||
items.append(DateSelectionActionSheetItem(strings: strings, currentValue: currentValue, minimumDate: minimumDate, maximumDate: maximumDate, valueChanged: { value in
|
||||
updatedValue = value
|
||||
}))
|
||||
if let emptyTitle = emptyTitle {
|
||||
items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
applyValue(nil)
|
||||
}))
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
applyValue(updatedValue)
|
||||
}))
|
||||
self.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
}),
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class DateSelectionActionSheetItem: ActionSheetItem {
|
||||
let strings: PresentationStrings
|
||||
|
||||
let currentValue: Int32
|
||||
let minimumDate: Date?
|
||||
let maximumDate: Date?
|
||||
let valueChanged: (Int32) -> Void
|
||||
|
||||
init(strings: PresentationStrings, currentValue: Int32, minimumDate: Date?, maximumDate: Date?, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.strings = strings
|
||||
self.currentValue = roundDateToDays(currentValue)
|
||||
self.minimumDate = minimumDate
|
||||
self.maximumDate = maximumDate
|
||||
self.valueChanged = valueChanged
|
||||
}
|
||||
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return DateSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, minimumDate: self.minimumDate, maximumDate: self.maximumDate, valueChanged: self.valueChanged)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ActionSheetItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class DateSelectionActionSheetItemNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let valueChanged: (Int32) -> Void
|
||||
private let pickerView: UIDatePicker
|
||||
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, minimumDate: Date?, maximumDate: Date?, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.valueChanged = valueChanged
|
||||
|
||||
UILabel.setDateLabel(theme.primaryTextColor)
|
||||
|
||||
self.pickerView = UIDatePicker()
|
||||
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
self.pickerView.datePickerMode = .countDownTimer
|
||||
self.pickerView.datePickerMode = .date
|
||||
self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
|
||||
self.pickerView.locale = localeWithStrings(strings)
|
||||
if #available(iOS 13.4, *) {
|
||||
self.pickerView.preferredDatePickerStyle = .wheels
|
||||
}
|
||||
if let minimumDate = minimumDate {
|
||||
self.pickerView.minimumDate = minimumDate
|
||||
}
|
||||
if let maximumDate = maximumDate {
|
||||
self.pickerView.maximumDate = maximumDate
|
||||
} else {
|
||||
self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
|
||||
}
|
||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.view.addSubview(self.pickerView)
|
||||
self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let size = CGSize(width: constrainedSize.width, height: 216.0)
|
||||
|
||||
self.pickerView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
|
||||
@objc private func datePickerUpdated() {
|
||||
self.valueChanged(roundDateToDays(Int32(self.pickerView.date.timeIntervalSince1970)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +107,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/DynamicCornerRadiusView",
|
||||
"//submodules/TelegramUI/Components/StickerPickerScreen",
|
||||
"//submodules/TelegramUI/Components/MediaEditor/ImageObjectSeparation",
|
||||
"//submodules/TelegramUI/Components/SegmentControlComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import TelegramPresentationData
|
|||
import SheetComponent
|
||||
import ViewControllerComponent
|
||||
import BlurredBackgroundComponent
|
||||
import SegmentedControlNode
|
||||
import SegmentControlComponent
|
||||
import MultilineTextComponent
|
||||
import HexColor
|
||||
import MediaEditor
|
||||
|
|
@ -1494,73 +1494,6 @@ private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor:
|
|||
})
|
||||
}
|
||||
|
||||
private class SegmentedControlComponent: Component {
|
||||
let values: [String]
|
||||
let selectedIndex: Int
|
||||
let selectionChanged: (Int) -> Void
|
||||
|
||||
init(values: [String], selectedIndex: Int, selectionChanged: @escaping (Int) -> Void) {
|
||||
self.values = values
|
||||
self.selectedIndex = selectedIndex
|
||||
self.selectionChanged = selectionChanged
|
||||
}
|
||||
|
||||
static func ==(lhs: SegmentedControlComponent, rhs: SegmentedControlComponent) -> Bool {
|
||||
if lhs.values != rhs.values {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedIndex != rhs.selectedIndex {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let node: SegmentedControlNode
|
||||
|
||||
init() {
|
||||
self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x888888, alpha: 0.1))
|
||||
self.node = SegmentedControlNode(theme: SegmentedControlTheme(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0x6f7075, alpha: 0.6), shadowColor: .black, textColor: UIColor(rgb: 0xffffff), dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)), items: [], selectedIndex: 0)
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.backgroundNode.view)
|
||||
self.addSubview(self.node.view)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: SegmentedControlComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
self.node.items = component.values.map { SegmentedControlItem(title: $0) }
|
||||
self.node.selectedIndex = component.selectedIndex
|
||||
let selectionChanged = component.selectionChanged
|
||||
self.node.selectedIndexChanged = { [weak self] index in
|
||||
self?.window?.endEditing(true)
|
||||
selectionChanged(index)
|
||||
}
|
||||
|
||||
let size = self.node.updateLayout(.stretchToFill(width: availableSize.width), transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(view: self.node.view, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
transition.setFrame(view: self.backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundNode.update(size: size, cornerRadius: 10.0, transition: .immediate)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class ColorSwatchComponent: Component {
|
||||
enum SwatchType: Equatable {
|
||||
case main
|
||||
|
|
@ -1904,7 +1837,7 @@ private final class ColorPickerContent: CombinedComponent {
|
|||
if let image = self.cachedCloseImage {
|
||||
closeImage = image
|
||||
} else {
|
||||
closeImage = generateCloseButtonImage(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xa8aab1))!
|
||||
closeImage = generateCloseButtonImage(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff))!
|
||||
self.cachedCloseImage = closeImage
|
||||
}
|
||||
return closeImage
|
||||
|
|
@ -1954,7 +1887,7 @@ private final class ColorPickerContent: CombinedComponent {
|
|||
let eyedropperButton = Child(Button.self)
|
||||
let closeButton = Child(Button.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let modeControl = Child(SegmentedControlComponent.self)
|
||||
let modeControl = Child(SegmentControlComponent.self)
|
||||
|
||||
let colorGrid = Child(ColorGridComponent.self)
|
||||
let colorSpectrum = Child(ColorSpectrumComponent.self)
|
||||
|
|
@ -2051,12 +1984,20 @@ private final class ColorPickerContent: CombinedComponent {
|
|||
|
||||
var contentHeight: CGFloat = 58.0
|
||||
|
||||
//backgroundColor: .clear, foregroundColor: UIColor(rgb: 0x6f7075, alpha: 0.6), shadowColor: .black, textColor: UIColor(rgb: 0xffffff), dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)
|
||||
let modeControl = modeControl.update(
|
||||
component: SegmentedControlComponent(
|
||||
values: [strings.Paint_ColorGrid, strings.Paint_ColorSpectrum, strings.Paint_ColorSliders],
|
||||
selectedIndex: 0,
|
||||
selectionChanged: { [weak state] index in
|
||||
state?.updateSelectedMode(index)
|
||||
component: SegmentControlComponent(
|
||||
theme: SegmentControlComponent.Theme(backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.07), legacyBackgroundColor: .clear, foregroundColor: UIColor(rgb: 0x6f7075, alpha: 0.6), textColor: .white, dividerColor: UIColor(rgb: 0x505155, alpha: 0.6)),
|
||||
items: [
|
||||
.init(id: 0, title: strings.Paint_ColorGrid),
|
||||
.init(id: 1, title: strings.Paint_ColorSpectrum),
|
||||
.init(id: 2, title: strings.Paint_ColorSliders),
|
||||
],
|
||||
selectedId: state.selectedMode,
|
||||
action: { [weak state] index in
|
||||
if let value = index.base as? Int {
|
||||
state?.updateSelectedMode(value)
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: context.availableSize.height),
|
||||
|
|
@ -2379,6 +2320,7 @@ private final class ColorPickerSheetComponent: CombinedComponent {
|
|||
})
|
||||
}
|
||||
)),
|
||||
style: .glass,
|
||||
backgroundColor: .blur(.dark),
|
||||
animateOut: animateOut
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import AVFoundation
|
|||
public final class EntityVideoRecorder {
|
||||
private weak var mediaEditor: MediaEditor?
|
||||
private weak var entitiesView: DrawingEntitiesView?
|
||||
|
||||
|
||||
private let maxDuration: Double
|
||||
|
||||
private let camera: Camera
|
||||
|
|
@ -21,6 +21,9 @@ public final class EntityVideoRecorder {
|
|||
private let micLevelPromise = Promise<Float>()
|
||||
|
||||
private var changingPositionDisposable: Disposable?
|
||||
private var positionDisposable: Disposable?
|
||||
private var currentCameraPosition: Camera.Position = .front
|
||||
private var recordingInitialPosition: Camera.Position = .front
|
||||
|
||||
public var duration: Signal<Double, NoError> {
|
||||
return self.durationPromise.get()
|
||||
|
|
@ -35,7 +38,7 @@ public final class EntityVideoRecorder {
|
|||
public init(mediaEditor: MediaEditor, entitiesView: DrawingEntitiesView) {
|
||||
self.mediaEditor = mediaEditor
|
||||
self.entitiesView = entitiesView
|
||||
|
||||
|
||||
self.maxDuration = min(60.0, mediaEditor.duration ?? 60.0)
|
||||
self.previewView = CameraSimplePreviewView(frame: .zero, main: true)
|
||||
|
||||
|
|
@ -86,6 +89,10 @@ public final class EntityVideoRecorder {
|
|||
}
|
||||
|
||||
self.micLevelPromise.set(camera.audioLevel)
|
||||
self.positionDisposable = (camera.position
|
||||
|> deliverOnMainQueue).start(next: { [weak self] position in
|
||||
self?.currentCameraPosition = position
|
||||
})
|
||||
|
||||
let start = mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0
|
||||
mediaEditor.stop()
|
||||
|
|
@ -107,6 +114,7 @@ public final class EntityVideoRecorder {
|
|||
deinit {
|
||||
self.recordingDisposable.dispose()
|
||||
self.changingPositionDisposable?.dispose()
|
||||
self.positionDisposable?.dispose()
|
||||
}
|
||||
|
||||
public func setup(
|
||||
|
|
@ -145,6 +153,7 @@ public final class EntityVideoRecorder {
|
|||
mediaEditor.maybeMuteVideo()
|
||||
mediaEditor.play()
|
||||
|
||||
self.recordingInitialPosition = self.currentCameraPosition
|
||||
self.start = CACurrentMediaTime()
|
||||
self.recordingDisposable.set((self.camera.startRecording()
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] recordingData in
|
||||
|
|
@ -169,16 +178,20 @@ public final class EntityVideoRecorder {
|
|||
}
|
||||
self.recordingDisposable.set((self.camera.stopRecording()
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
||||
guard let self, let mediaEditor = self.mediaEditor, let entitiesView = self.entitiesView, case let .finished(mainResult, _, _, _, _) = result else {
|
||||
guard let self, let mediaEditor = self.mediaEditor, let entitiesView = self.entitiesView, case let .finished(mainResult, _, _, positionChangeTimestamps, _) = result else {
|
||||
return
|
||||
}
|
||||
if save {
|
||||
let duration = AVURLAsset(url: URL(fileURLWithPath: mainResult.path)).duration
|
||||
let mirroringChanges = self.additionalVideoMirroringChanges(
|
||||
initialPosition: self.recordingInitialPosition,
|
||||
positionChangeTimestamps: positionChangeTimestamps
|
||||
)
|
||||
|
||||
let start = mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0
|
||||
mediaEditor.setAdditionalVideoOffset(-start, apply: false)
|
||||
mediaEditor.setAdditionalVideoTrimRange(0 ..< duration.seconds, apply: true)
|
||||
mediaEditor.setAdditionalVideo(mainResult.path, positionChanges: [])
|
||||
mediaEditor.setAdditionalVideo(mainResult.path, mirroringChanges: mirroringChanges, positionChanges: [])
|
||||
|
||||
mediaEditor.stop()
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
|
@ -217,7 +230,7 @@ public final class EntityVideoRecorder {
|
|||
self.mediaEditor?.maybeUnmuteVideo()
|
||||
|
||||
self.entitiesView?.remove(uuid: self.entity.uuid, animated: true)
|
||||
self.mediaEditor?.setAdditionalVideo(nil, positionChanges: [])
|
||||
self.mediaEditor?.setAdditionalVideo(nil, mirroringChanges: [], positionChanges: [])
|
||||
|
||||
completion()
|
||||
}
|
||||
|
|
@ -226,4 +239,22 @@ public final class EntityVideoRecorder {
|
|||
public func togglePosition() {
|
||||
self.camera.togglePosition()
|
||||
}
|
||||
|
||||
private func additionalVideoMirroringChanges(initialPosition: Camera.Position, positionChangeTimestamps: [(Bool, Double)]) -> [VideoMirroringChange] {
|
||||
var result: [VideoMirroringChange] = [
|
||||
VideoMirroringChange(isMirrored: initialPosition == .front, timestamp: 0.0)
|
||||
]
|
||||
for (isMirrored, timestamp) in positionChangeTimestamps.sorted(by: { $0.1 < $1.1 }) {
|
||||
result.append(VideoMirroringChange(isMirrored: isMirrored, timestamp: timestamp))
|
||||
}
|
||||
|
||||
var deduplicated: [VideoMirroringChange] = []
|
||||
for change in result {
|
||||
if deduplicated.last?.isMirrored == change.isMirrored {
|
||||
continue
|
||||
}
|
||||
deduplicated.append(change)
|
||||
}
|
||||
return deduplicated
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,6 +255,70 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane {
|
|||
self.disposable?.dispose()
|
||||
self.installDisposable.dispose()
|
||||
}
|
||||
|
||||
private func presentStickerPackActionOverlay(_ actions: [StickerPackScreenActionResult]) {
|
||||
guard let action = actions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = self.interaction.getNavigationController() {
|
||||
for controller in navigationController.overlayControllers {
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitActionAndReplacementAnimation()
|
||||
animateInAsReplacement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if let forceTheme = self.forceTheme {
|
||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
|
||||
let controller: UndoOverlayController
|
||||
switch action.action {
|
||||
case .add:
|
||||
controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .stickersModified(
|
||||
title: presentationData.strings.StickerPackActionInfo_AddedTitle,
|
||||
text: presentationData.strings.StickerPackActionInfo_AddedText(action.info.title).string,
|
||||
undo: false,
|
||||
info: action.info,
|
||||
topItem: action.items.first,
|
||||
context: self.context
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: animateInAsReplacement,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
case let .remove(positionInList):
|
||||
controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .stickersModified(
|
||||
title: presentationData.strings.StickerPackActionInfo_RemovedTitle,
|
||||
text: presentationData.strings.StickerPackActionInfo_RemovedText(action.info.title).string,
|
||||
undo: true,
|
||||
info: action.info,
|
||||
topItem: action.items.first,
|
||||
context: self.context
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: animateInAsReplacement,
|
||||
action: { [weak self] overlayAction in
|
||||
if case .undo = overlayAction {
|
||||
let _ = self?.context.engine.stickers.addStickerPackInteractively(info: action.info, items: action.items, positionInList: positionInList).start()
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
self.interaction.getNavigationController()?.presentOverlay(controller: controller)
|
||||
}
|
||||
|
||||
public func activate() {
|
||||
if self.isActivated {
|
||||
|
|
@ -374,7 +438,9 @@ public final class ChatMediaInputTrendingPane: ChatMediaInputPane {
|
|||
return false
|
||||
}
|
||||
},
|
||||
actionPerformed: nil
|
||||
actionPerformed: { [weak self] actions in
|
||||
self?.presentStickerPackActionOverlay(actions)
|
||||
}
|
||||
)
|
||||
strongSelf.interaction.presentController(controller, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ swift_library(
|
|||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/LegacyUI:LegacyUI",
|
||||
"//submodules/ImageCompression:ImageCompression",
|
||||
"//submodules/DateSelectionUI:DateSelectionUI",
|
||||
"//submodules/PasswordSetupUI:PasswordSetupUI",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import TelegramStringFormatting
|
|||
import AccountContext
|
||||
import GalleryUI
|
||||
import CountrySelectionUI
|
||||
import DateSelectionUI
|
||||
import AppBundle
|
||||
import ChatTimerScreen
|
||||
|
||||
private enum SecureIdDocumentFormTextField {
|
||||
case identifier
|
||||
|
|
@ -2368,49 +2368,76 @@ final class SecureIdDocumentFormControllerNode: FormControllerNode<SecureIdDocum
|
|||
}
|
||||
}
|
||||
|
||||
let controller = DateSelectionActionSheetController(context: strongSelf.context, title: title, currentValue: current ?? Int32(Date().timeIntervalSince1970), minimumDate: minimumDate, maximumDate: maximumDate, emptyTitle: emptyTitle, applyValue: { value in
|
||||
if let strongSelf = self, var innerState = strongSelf.innerState {
|
||||
innerState.documentState.updateDateField(type: field, value: value.flatMap(SecureIdDate.init))
|
||||
var valueKey: SecureIdValueKey?
|
||||
var errorKey: SecureIdValueContentErrorKey?
|
||||
|
||||
switch innerState.documentState {
|
||||
case let .identity(identity):
|
||||
switch field {
|
||||
case .birthdate:
|
||||
valueKey = .personalDetails
|
||||
errorKey = .field(.personalDetails(.birthdate))
|
||||
case .expiry:
|
||||
if let document = identity.document {
|
||||
switch document.type {
|
||||
case .passport:
|
||||
valueKey = .passport
|
||||
errorKey = .field(.passport(.expiryDate))
|
||||
case .internalPassport:
|
||||
valueKey = .internalPassport
|
||||
errorKey = .field(.internalPassport(.expiryDate))
|
||||
case .driversLicense:
|
||||
valueKey = .driversLicense
|
||||
errorKey = .field(.driversLicense(.expiryDate))
|
||||
case .idCard:
|
||||
valueKey = .idCard
|
||||
errorKey = .field(.idCard(.expiryDate))
|
||||
}
|
||||
}
|
||||
let controller = ChatTimerScreen(
|
||||
context: strongSelf.context,
|
||||
configuration: ChatTimerScreen.Configuration(
|
||||
style: .default,
|
||||
title: { _ in
|
||||
title
|
||||
},
|
||||
picker: .date,
|
||||
currentValue: current ?? Int32(Date().timeIntervalSince1970),
|
||||
minimumDate: minimumDate,
|
||||
maximumDate: maximumDate,
|
||||
pickerValueMapping: .roundDateToDaysUTC,
|
||||
primaryActionTitle: { strings, _, _ in
|
||||
strings.Wallpaper_Set
|
||||
},
|
||||
secondaryAction: emptyTitle.flatMap { emptyTitle in
|
||||
ChatTimerScreen.Configuration.SecondaryAction(
|
||||
title: { _ in
|
||||
emptyTitle
|
||||
},
|
||||
style: .accent,
|
||||
value: {
|
||||
nil
|
||||
}
|
||||
case .address:
|
||||
break
|
||||
)
|
||||
}
|
||||
|
||||
if let valueKey = valueKey, let errorKey = errorKey {
|
||||
let valueErrorKey: SecureIdValueContentErrorKey = .value(valueKey)
|
||||
if let previousValue = innerState.previousValues[valueKey] {
|
||||
innerState.previousValues[valueKey] = previousValue.withRemovedErrors([errorKey, valueErrorKey])
|
||||
),
|
||||
completion: { value in
|
||||
if let strongSelf = self, var innerState = strongSelf.innerState {
|
||||
innerState.documentState.updateDateField(type: field, value: value.flatMap(SecureIdDate.init))
|
||||
var valueKey: SecureIdValueKey?
|
||||
var errorKey: SecureIdValueContentErrorKey?
|
||||
|
||||
switch innerState.documentState {
|
||||
case let .identity(identity):
|
||||
switch field {
|
||||
case .birthdate:
|
||||
valueKey = .personalDetails
|
||||
errorKey = .field(.personalDetails(.birthdate))
|
||||
case .expiry:
|
||||
if let document = identity.document {
|
||||
switch document.type {
|
||||
case .passport:
|
||||
valueKey = .passport
|
||||
errorKey = .field(.passport(.expiryDate))
|
||||
case .internalPassport:
|
||||
valueKey = .internalPassport
|
||||
errorKey = .field(.internalPassport(.expiryDate))
|
||||
case .driversLicense:
|
||||
valueKey = .driversLicense
|
||||
errorKey = .field(.driversLicense(.expiryDate))
|
||||
case .idCard:
|
||||
valueKey = .idCard
|
||||
errorKey = .field(.idCard(.expiryDate))
|
||||
}
|
||||
}
|
||||
}
|
||||
case .address:
|
||||
break
|
||||
}
|
||||
|
||||
if let valueKey = valueKey, let errorKey = errorKey {
|
||||
let valueErrorKey: SecureIdValueContentErrorKey = .value(valueKey)
|
||||
if let previousValue = innerState.previousValues[valueKey] {
|
||||
innerState.previousValues[valueKey] = previousValue.withRemovedErrors([errorKey, valueErrorKey])
|
||||
}
|
||||
}
|
||||
strongSelf.updateInnerState(transition: .immediate, with: innerState)
|
||||
}
|
||||
strongSelf.updateInnerState(transition: .immediate, with: innerState)
|
||||
}
|
||||
})
|
||||
})
|
||||
strongSelf.view.endEditing(true)
|
||||
strongSelf.present(controller, nil)
|
||||
case .gender:
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ swift_library(
|
|||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/CounterControllerTitleView",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import AlertUI
|
|||
import PresentationDataUtils
|
||||
import ItemListAvatarAndNameInfoItem
|
||||
import OldChannelsController
|
||||
import ChatTimerScreen
|
||||
|
||||
private let rankMaxLength: Int32 = 16
|
||||
|
||||
|
|
@ -741,9 +742,28 @@ public func channelBannedMemberController(context: AccountContext, updatedPresen
|
|||
}))
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.MessageTimer_Custom, color: .accent, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
presentControllerImpl?(PeerBanTimeoutController(context: context, updatedPresentationData: updatedPresentationData, currentValue: Int32(Date().timeIntervalSince1970), applyValue: { value in
|
||||
applyValue(value)
|
||||
}), nil)
|
||||
let controller = ChatTimerScreen(
|
||||
context: context,
|
||||
updatedPresentationData: updatedPresentationData,
|
||||
configuration: ChatTimerScreen.Configuration(
|
||||
style: .default,
|
||||
picker: .date,
|
||||
currentValue: Int32(Date().timeIntervalSince1970),
|
||||
minimumDate: Date(),
|
||||
maximumDate: Date(timeIntervalSince1970: Double(Int32.max - 1)),
|
||||
pickerValueMapping: .roundDateToDaysUTC,
|
||||
primaryActionTitle: { strings, _, _ in
|
||||
strings.Wallpaper_Set
|
||||
}
|
||||
),
|
||||
completion: { value in
|
||||
guard let value else {
|
||||
return
|
||||
}
|
||||
applyValue(value)
|
||||
}
|
||||
)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}))
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
final class PeerBanTimeoutController: ActionSheetController {
|
||||
private var presentationDisposable: Disposable?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, currentValue: Int32, applyValue: @escaping (Int32?) -> Void) {
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
|
||||
|
||||
self._ready.set(.single(true))
|
||||
|
||||
self.presentationDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
}
|
||||
})
|
||||
|
||||
var updatedValue = currentValue
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(PeerBanTimeoutActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in
|
||||
updatedValue = value
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
applyValue(updatedValue)
|
||||
}))
|
||||
self.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
}),
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerBanTimeoutActionSheetItem: ActionSheetItem {
|
||||
let strings: PresentationStrings
|
||||
|
||||
let currentValue: Int32
|
||||
let valueChanged: (Int32) -> Void
|
||||
|
||||
init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.strings = strings
|
||||
self.currentValue = roundDateToDays(currentValue)
|
||||
self.valueChanged = valueChanged
|
||||
}
|
||||
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return PeerBanTimeoutActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ActionSheetItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerBanTimeoutActionSheetItemNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let valueChanged: (Int32) -> Void
|
||||
private let pickerView: UIDatePicker
|
||||
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.valueChanged = valueChanged
|
||||
|
||||
UILabel.setDateLabel(theme.primaryTextColor)
|
||||
|
||||
self.pickerView = UIDatePicker()
|
||||
self.pickerView.datePickerMode = .countDownTimer
|
||||
self.pickerView.datePickerMode = .date
|
||||
self.pickerView.date = Date(timeIntervalSince1970: Double(roundDateToDays(currentValue)))
|
||||
self.pickerView.locale = localeWithStrings(strings)
|
||||
self.pickerView.minimumDate = Date()
|
||||
self.pickerView.maximumDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
|
||||
if #available(iOS 13.4, *) {
|
||||
self.pickerView.preferredDatePickerStyle = .wheels
|
||||
}
|
||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.view.addSubview(self.pickerView)
|
||||
self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let size = CGSize(width: constrainedSize.width, height: 216.0)
|
||||
|
||||
self.pickerView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
|
||||
@objc private func datePickerUpdated() {
|
||||
self.valueChanged(roundDateToDays(Int32(self.pickerView.date.timeIntervalSince1970)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PeersNearbyIconNode",
|
||||
module_name = "PeersNearbyIconNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import LegacyComponents
|
||||
|
||||
private final class PeersNearbyIconWavesNodeParams: NSObject {
|
||||
let color: UIColor
|
||||
let progress: CGFloat
|
||||
|
||||
init(color: UIColor, progress: CGFloat) {
|
||||
self.color = color
|
||||
self.progress = progress
|
||||
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
|
||||
private func degToRad(_ degrees: CGFloat) -> CGFloat {
|
||||
return degrees * CGFloat.pi / 180.0
|
||||
}
|
||||
|
||||
public final class PeersNearbyIconWavesNode: ASDisplayNode {
|
||||
public var color: UIColor {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
private var effectiveProgress: CGFloat = 0.0 {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
public init(color: UIColor) {
|
||||
self.color = color
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.isOpaque = false
|
||||
}
|
||||
|
||||
override public func willEnterHierarchy() {
|
||||
super.willEnterHierarchy()
|
||||
|
||||
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
||||
|
||||
let animation = POPBasicAnimation()
|
||||
animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
||||
property?.readBlock = { node, values in
|
||||
values?.pointee = (node as! PeersNearbyIconWavesNode).effectiveProgress
|
||||
}
|
||||
property?.writeBlock = { node, values in
|
||||
(node as! PeersNearbyIconWavesNode).effectiveProgress = values!.pointee
|
||||
}
|
||||
property?.threshold = 0.01
|
||||
}) as! POPAnimatableProperty)
|
||||
animation.fromValue = CGFloat(0.0) as NSNumber
|
||||
animation.toValue = CGFloat(1.0) as NSNumber
|
||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||
animation.duration = 3.5
|
||||
animation.repeatForever = true
|
||||
self.pop_add(animation, forKey: "indefiniteProgress")
|
||||
}
|
||||
|
||||
override public func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
||||
}
|
||||
|
||||
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
let t = CACurrentMediaTime()
|
||||
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
|
||||
return PeersNearbyIconWavesNodeParams(color: self.color, progress: value)
|
||||
}
|
||||
|
||||
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||
let context = UIGraphicsGetCurrentContext()!
|
||||
|
||||
if !isRasterizing {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fill(bounds)
|
||||
}
|
||||
|
||||
if let parameters = parameters as? PeersNearbyIconWavesNodeParams {
|
||||
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
||||
let radius: CGFloat = bounds.width * 0.3333
|
||||
let range: CGFloat = (bounds.width - radius * 2.0) / 2.0
|
||||
|
||||
context.setFillColor(parameters.color.cgColor)
|
||||
|
||||
let draw: (CGContext, CGFloat) -> Void = { context, pos in
|
||||
let path = CGMutablePath()
|
||||
|
||||
let pathRadius: CGFloat = bounds.width * 0.3333 + range * pos
|
||||
path.addEllipse(in: CGRect(x: center.x - pathRadius, y: center.y - pathRadius, width: pathRadius * 2.0, height: pathRadius * 2.0))
|
||||
|
||||
let strokedPath = path.copy(strokingWithWidth: 1.0, lineCap: .round, lineJoin: .miter, miterLimit: 10.0)
|
||||
context.addPath(strokedPath)
|
||||
context.fillPath()
|
||||
}
|
||||
|
||||
let position = parameters.progress
|
||||
var alpha = position / 0.5
|
||||
if alpha > 1.0 {
|
||||
alpha = 2.0 - alpha
|
||||
}
|
||||
context.setAlpha(alpha * 0.7)
|
||||
|
||||
draw(context, position)
|
||||
|
||||
var progress = parameters.progress + 0.3333
|
||||
if progress > 1.0 {
|
||||
progress = progress - 1.0
|
||||
}
|
||||
|
||||
var largerPos = progress
|
||||
var largerAlpha = largerPos / 0.5
|
||||
if largerAlpha > 1.0 {
|
||||
largerAlpha = 2.0 - largerAlpha
|
||||
}
|
||||
context.setAlpha(largerAlpha * 0.7)
|
||||
|
||||
draw(context, largerPos)
|
||||
|
||||
progress = parameters.progress + 0.6666
|
||||
if progress > 1.0 {
|
||||
progress = progress - 1.0
|
||||
}
|
||||
|
||||
largerPos = progress
|
||||
largerAlpha = largerPos / 0.5
|
||||
if largerAlpha > 1.0 {
|
||||
largerAlpha = 2.0 - largerAlpha
|
||||
}
|
||||
context.setAlpha(largerAlpha * 0.7)
|
||||
|
||||
draw(context, largerPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateIcon(size: CGSize, color: UIColor, contentColor: UIColor) -> UIImage {
|
||||
return generateImage(size, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: size.width / 120.0, y: size.height / 120.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
context.translateBy(x: 0.0, y: 6.0)
|
||||
context.setFillColor(contentColor.cgColor)
|
||||
|
||||
if size.width == 120.0 {
|
||||
context.translateBy(x: 30.0, y: 30.0)
|
||||
}
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M27.8628211,52.2347452 L27.8628211,27.1373017 L2.76505663,27.1373017 C1.55217431,27.1373017 0.568938916,26.1540663 0.568938916,24.941184 C0.568938916,24.0832172 1.06857435,23.3038117 1.84819149,22.9456161 L51.2643819,0.241311309 C52.586928,-0.366333451 54.1516568,0.213208572 54.7593016,1.53575465 C55.0801868,2.23416513 55.080181,3.03785964 54.7592857,3.7362655 L32.0544935,53.1516391 C31.548107,54.2537536 30.2441593,54.7366865 29.1420449,54.2302999 C28.3624433,53.8720978 27.8628211,53.0927006 27.8628211,52.2347452 Z ")
|
||||
})!
|
||||
}
|
||||
|
||||
public final class PeersNearbyIconNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
|
||||
private var iconNode: ASImageNode
|
||||
private var wavesNode: PeersNearbyIconWavesNode
|
||||
|
||||
public init(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.isOpaque = false
|
||||
self.wavesNode = PeersNearbyIconWavesNode(color: theme.list.itemAccentColor)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.wavesNode)
|
||||
}
|
||||
|
||||
public func updateTheme(_ theme: PresentationTheme) {
|
||||
guard self.theme !== theme else {
|
||||
return
|
||||
}
|
||||
self.theme = theme
|
||||
|
||||
self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor)
|
||||
self.wavesNode.color = theme.list.itemAccentColor
|
||||
}
|
||||
|
||||
override public func layout() {
|
||||
super.layout()
|
||||
|
||||
if let image = self.iconNode.image, image.size.width == self.bounds.width {
|
||||
} else {
|
||||
self.iconNode.image = generateIcon(size: self.bounds.size, color: self.theme.list.itemAccentColor, contentColor: self.theme.list.itemCheckColors.foregroundColor)
|
||||
}
|
||||
self.iconNode.frame = self.bounds
|
||||
self.wavesNode.frame = self.bounds.insetBy(dx: -self.bounds.width * 0.3, dy: -self.bounds.height * 0.3)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PeersNearbyUI",
|
||||
module_name = "PeersNearbyUI",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/ItemListUI:ItemListUI",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/DeviceLocationManager:DeviceLocationManager",
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
"//submodules/TelegramPermissionsUI:TelegramPermissionsUI",
|
||||
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
|
||||
"//submodules/PeersNearbyIconNode:PeersNearbyIconNode",
|
||||
"//submodules/Geocoding:Geocoding",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/TelegramNotices:TelegramNotices",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -1,659 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import MapKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
import DeviceLocationManager
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import ItemListPeerItem
|
||||
import TelegramPermissionsUI
|
||||
import ItemListPeerActionItem
|
||||
import Geocoding
|
||||
import AppBundle
|
||||
import ContextUI
|
||||
import TelegramNotices
|
||||
import TelegramStringFormatting
|
||||
|
||||
private let maxUsersDisplayedLimit: Int32 = 5
|
||||
|
||||
private struct PeerNearbyEntry {
|
||||
let peer: EnginePeer
|
||||
let memberCount: Int32?
|
||||
let expires: Int32
|
||||
let distance: Int32
|
||||
}
|
||||
|
||||
private func arePeersNearbyEqual(_ lhs: PeerNearbyEntry?, _ rhs: PeerNearbyEntry?) -> Bool {
|
||||
if let lhs = lhs, let rhs = rhs {
|
||||
return lhs.peer == rhs.peer && lhs.expires == rhs.expires && lhs.distance == rhs.distance
|
||||
} else {
|
||||
return (lhs != nil) == (rhs != nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func arePeerNearbyArraysEqual(_ lhs: [PeerNearbyEntry], _ rhs: [PeerNearbyEntry]) -> Bool {
|
||||
if lhs.count != rhs.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.count {
|
||||
if lhs[i].peer != rhs[i].peer || lhs[i].expires != rhs[i].expires || lhs[i].distance != rhs[i].distance {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class PeersNearbyControllerArguments {
|
||||
let context: AccountContext
|
||||
let toggleVisibility: (Bool) -> Void
|
||||
let openProfile: (EnginePeer, Int32) -> Void
|
||||
let openChat: (EnginePeer) -> Void
|
||||
let openCreateGroup: (Double, Double, String?) -> Void
|
||||
let contextAction: (EnginePeer, ASDisplayNode, ContextGesture?) -> Void
|
||||
let expandUsers: () -> Void
|
||||
|
||||
init(context: AccountContext, toggleVisibility: @escaping (Bool) -> Void, openProfile: @escaping (EnginePeer, Int32) -> Void, openChat: @escaping (EnginePeer) -> Void, openCreateGroup: @escaping (Double, Double, String?) -> Void, contextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?) -> Void, expandUsers: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.toggleVisibility = toggleVisibility
|
||||
self.openProfile = openProfile
|
||||
self.openChat = openChat
|
||||
self.openCreateGroup = openCreateGroup
|
||||
self.contextAction = contextAction
|
||||
self.expandUsers = expandUsers
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeersNearbySection: Int32 {
|
||||
case header
|
||||
case users
|
||||
case groups
|
||||
case channels
|
||||
}
|
||||
|
||||
private enum PeersNearbyEntry: ItemListNodeEntry {
|
||||
case header(PresentationTheme, String)
|
||||
|
||||
case usersHeader(PresentationTheme, String, Bool)
|
||||
case empty(PresentationTheme, String)
|
||||
case visibility(PresentationTheme, String, Bool)
|
||||
case user(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry)
|
||||
case expand(PresentationTheme, String)
|
||||
|
||||
case groupsHeader(PresentationTheme, String, Bool)
|
||||
case createGroup(PresentationTheme, String, Double?, Double?, String?)
|
||||
case group(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry, Bool)
|
||||
|
||||
case channelsHeader(PresentationTheme, String)
|
||||
case channel(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PeerNearbyEntry, Bool)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .header:
|
||||
return PeersNearbySection.header.rawValue
|
||||
case .usersHeader, .empty, .visibility, .user, .expand:
|
||||
return PeersNearbySection.users.rawValue
|
||||
case .groupsHeader, .createGroup, .group:
|
||||
return PeersNearbySection.groups.rawValue
|
||||
case .channelsHeader, .channel:
|
||||
return PeersNearbySection.channels.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .header:
|
||||
return 0
|
||||
case .usersHeader:
|
||||
return 1
|
||||
case .empty:
|
||||
return 2
|
||||
case .visibility:
|
||||
return 3
|
||||
case let .user(index, _, _, _, _, _):
|
||||
return 4 + index
|
||||
case .expand:
|
||||
return 1000
|
||||
case .groupsHeader:
|
||||
return 1001
|
||||
case .createGroup:
|
||||
return 1002
|
||||
case let .group(index, _, _, _, _, _, _):
|
||||
return 1003 + index
|
||||
case .channelsHeader:
|
||||
return 2000
|
||||
case let .channel(index, _, _, _, _, _, _):
|
||||
return 2001 + index
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .header(lhsTheme, lhsText):
|
||||
if case let .header(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .usersHeader(lhsTheme, lhsText, lhsLoading):
|
||||
if case let .usersHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .empty(lhsTheme, lhsText):
|
||||
if case let .empty(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .visibility(lhsTheme, lhsText, lhsStop):
|
||||
if case let .visibility(rhsTheme, rhsText, rhsStop) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStop == rhsStop {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
case let .user(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer):
|
||||
if case let .user(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .expand(lhsTheme, lhsText):
|
||||
if case let .expand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .groupsHeader(lhsTheme, lhsText, lhsLoading):
|
||||
if case let .groupsHeader(rhsTheme, rhsText, rhsLoading) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLoading == rhsLoading {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .createGroup(lhsTheme, lhsText, lhsLatitude, lhsLongitude, lhsAddress):
|
||||
if case let .createGroup(rhsTheme, rhsText, rhsLatitude, rhsLongitude, rhsAddress) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsLatitude == rhsLatitude && lhsLongitude == rhsLongitude && lhsAddress == rhsAddress {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .group(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer, lhsHighlighted):
|
||||
if case let .group(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer, rhsHighlighted) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer), lhsHighlighted == rhsHighlighted {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .channelsHeader(lhsTheme, lhsText):
|
||||
if case let .channelsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .channel(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayOrder, lhsPeer, lhsHighlighted):
|
||||
if case let .channel(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayOrder, rhsPeer, rhsHighlighted) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayOrder == rhsDisplayOrder, arePeersNearbyEqual(lhsPeer, rhsPeer), lhsHighlighted == rhsHighlighted {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: PeersNearbyEntry, rhs: PeersNearbyEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! PeersNearbyControllerArguments
|
||||
switch self {
|
||||
case let .header(theme, text):
|
||||
return PeersNearbyHeaderItem(context: arguments.context, theme: theme, text: text, sectionId: self.section)
|
||||
case let .usersHeader(_, text, loading):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, activityIndicator: loading ? .left : .none, sectionId: self.section)
|
||||
case let .empty(theme, text):
|
||||
return ItemListPlaceholderItem(theme: theme, text: text, sectionId: self.section, style: .blocks)
|
||||
case let .visibility(theme, title, stop):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: stop ? PresentationResourcesItemList.makeInvisibleIcon(theme) : PresentationResourcesItemList.makeVisibleIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, color: stop ? .destructive : .accent, editing: false, action: {
|
||||
arguments.toggleVisibility(!stop)
|
||||
})
|
||||
case let .user(_, _, strings, dateTimeFormat, nameDisplayOrder, peer):
|
||||
var text = strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string
|
||||
let isSelfPeer = peer.peer.id == arguments.context.account.peerId
|
||||
if isSelfPeer {
|
||||
text = strings.PeopleNearby_VisibleUntil(humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: peer.expires).string).string
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: !isSelfPeer, sectionId: self.section, action: {
|
||||
if !isSelfPeer {
|
||||
arguments.openProfile(peer.peer, peer.distance)
|
||||
}
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil, hasTopGroupInset: false, tag: nil)
|
||||
case let .expand(theme, title):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
arguments.expandUsers()
|
||||
})
|
||||
case let .groupsHeader(_, text, loading):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, activityIndicator: loading ? .left : .none, sectionId: self.section)
|
||||
case let .createGroup(theme, title, latitude, longitude, address):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.createGroupIcon(theme), title: title, alwaysPlain: false, sectionId: self.section, editing: false, action: {
|
||||
if let latitude = latitude, let longitude = longitude {
|
||||
arguments.openCreateGroup(latitude, longitude, address)
|
||||
}
|
||||
})
|
||||
case let .group(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted):
|
||||
var text: ItemListPeerItemText
|
||||
if let memberCount = peer.memberCount {
|
||||
text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(memberCount > 0 ? strings.Conversation_StatusMembers(memberCount) : strings.PeopleNearby_NoMembers)", .secondary)
|
||||
} else {
|
||||
text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary)
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openChat(peer.peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in
|
||||
arguments.contextAction(peer.peer, node, gesture)
|
||||
}, hasTopGroupInset: false, tag: nil)
|
||||
case let .channelsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .channel(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted):
|
||||
var text: ItemListPeerItemText
|
||||
if let memberCount = peer.memberCount {
|
||||
text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string), \(strings.Conversation_StatusSubscribers(memberCount))", .secondary)
|
||||
} else {
|
||||
text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).string, .secondary)
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: {
|
||||
arguments.openChat(peer.peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: { node, gesture in
|
||||
arguments.contextAction(peer.peer, node, gesture)
|
||||
}, hasTopGroupInset: false, tag: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PeersNearbyData: Equatable {
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
let address: String?
|
||||
let visible: Bool
|
||||
let accountPeerId: EnginePeer.Id
|
||||
let users: [PeerNearbyEntry]
|
||||
let groups: [PeerNearbyEntry]
|
||||
let channels: [PeerNearbyEntry]
|
||||
|
||||
init(latitude: Double, longitude: Double, address: String?, visible: Bool, accountPeerId: EnginePeer.Id, users: [PeerNearbyEntry], groups: [PeerNearbyEntry], channels: [PeerNearbyEntry]) {
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.address = address
|
||||
self.visible = visible
|
||||
self.accountPeerId = accountPeerId
|
||||
self.users = users
|
||||
self.groups = groups
|
||||
self.channels = channels
|
||||
}
|
||||
|
||||
static func ==(lhs: PeersNearbyData, rhs: PeersNearbyData) -> Bool {
|
||||
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude && lhs.address == rhs.address && lhs.visible == rhs.visible && lhs.accountPeerId == rhs.accountPeerId && arePeerNearbyArraysEqual(lhs.users, rhs.users) && arePeerNearbyArraysEqual(lhs.groups, rhs.groups) && arePeerNearbyArraysEqual(lhs.channels, rhs.channels)
|
||||
}
|
||||
}
|
||||
|
||||
private func peersNearbyControllerEntries(data: PeersNearbyData?, state: PeersNearbyState, presentationData: PresentationData, displayLoading: Bool, expanded: Bool, chatLocation: ChatLocation?) -> [PeersNearbyEntry] {
|
||||
var entries: [PeersNearbyEntry] = []
|
||||
|
||||
entries.append(.header(presentationData.theme, presentationData.strings.PeopleNearby_DiscoverDescription))
|
||||
entries.append(.usersHeader(presentationData.theme, presentationData.strings.PeopleNearby_Users.uppercased(), displayLoading && data == nil))
|
||||
|
||||
let visible = state.visibilityExpires != nil
|
||||
entries.append(.visibility(presentationData.theme, visible ? presentationData.strings.PeopleNearby_MakeInvisible : presentationData.strings.PeopleNearby_MakeVisible, visible))
|
||||
|
||||
if let data = data, !data.users.isEmpty {
|
||||
var index: Int32 = 0
|
||||
var users = data.users.filter { $0.peer.id != data.accountPeerId }
|
||||
var effectiveExpanded = expanded
|
||||
if users.count > maxUsersDisplayedLimit && !expanded {
|
||||
users = Array(users.prefix(Int(maxUsersDisplayedLimit)))
|
||||
} else {
|
||||
effectiveExpanded = true
|
||||
}
|
||||
|
||||
for user in users {
|
||||
entries.append(.user(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, user))
|
||||
index += 1
|
||||
}
|
||||
|
||||
if !effectiveExpanded {
|
||||
entries.append(.expand(presentationData.theme, presentationData.strings.PeopleNearby_ShowMorePeople(Int32(data.users.count) - maxUsersDisplayedLimit)))
|
||||
}
|
||||
}
|
||||
|
||||
var highlightedPeerId: EnginePeer.Id?
|
||||
if let chatLocation = chatLocation, case let .peer(peerId) = chatLocation {
|
||||
highlightedPeerId = peerId
|
||||
}
|
||||
|
||||
entries.append(.groupsHeader(presentationData.theme, presentationData.strings.PeopleNearby_Groups.uppercased(), displayLoading && data == nil))
|
||||
entries.append(.createGroup(presentationData.theme, presentationData.strings.PeopleNearby_CreateGroup, data?.latitude, data?.longitude, data?.address))
|
||||
if let data = data, !data.groups.isEmpty {
|
||||
var i: Int32 = 0
|
||||
for group in data.groups {
|
||||
entries.append(.group(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, group, highlightedPeerId == group.peer.id))
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
if let data = data, !data.channels.isEmpty {
|
||||
var i: Int32 = 0
|
||||
for channel in data.channels {
|
||||
entries.append(.channel(i, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, channel, highlightedPeerId == channel.peer.id))
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
weak var sourceNode: ASDisplayNode?
|
||||
|
||||
let navigationController: NavigationController? = nil
|
||||
|
||||
let passthroughTouches: Bool = true
|
||||
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||
let sourceNode = self.sourceNode
|
||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||
if let sourceNode = sourceNode {
|
||||
return (sourceNode.view, sourceNode.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func animatedIn() {
|
||||
}
|
||||
}
|
||||
|
||||
private func peerNearbyContextMenuItems(context: AccountContext, peerId: EnginePeer.Id, present: @escaping (ViewController) -> Void) -> Signal<[ContextMenuItem], NoError> {
|
||||
return .single([])
|
||||
}
|
||||
|
||||
private class PeersNearbyControllerImpl: ItemListController {
|
||||
fileprivate let chatLocation = Promise<ChatLocation?>(nil)
|
||||
|
||||
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if self.isNodeLoaded {
|
||||
self.chatLocation.set(.single(data as? ChatLocation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func peersNearbyController(context: AccountContext) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var replaceTopControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
var navigateToProfileImpl: ((EnginePeer, Int32) -> Void)?
|
||||
var navigateToChatImpl: ((EnginePeer) -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
let checkCreationAvailabilityDisposable = MetaDisposable()
|
||||
actionsDisposable.add(checkCreationAvailabilityDisposable)
|
||||
|
||||
let dataPromise = Promise<PeersNearbyData?>(nil)
|
||||
let addressPromise = Promise<String?>(nil)
|
||||
let expandedPromise = ValuePromise<Bool>(false)
|
||||
|
||||
let chatLocationPromise = Promise<ChatLocation?>(nil)
|
||||
|
||||
let coordinatePromise = Promise<CLLocationCoordinate2D?>(nil)
|
||||
coordinatePromise.set(.single(nil) |> then(currentLocationManagerCoordinate(manager: context.sharedContext.locationManager!, timeout: 5.0)))
|
||||
|
||||
let arguments = PeersNearbyControllerArguments(context: context, toggleVisibility: { visible in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
if visible {
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.PeopleNearby_MakeVisibleTitle, text: presentationData.strings.PeopleNearby_MakeVisibleDescription, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
|
||||
let _ = (coordinatePromise.get()
|
||||
|> deliverOnMainQueue).start(next: { coordinate in
|
||||
if let coordinate = coordinate {
|
||||
let _ = context.engine.peersNearby.updatePeersNearbyVisibility(update: .visible(latitude: coordinate.latitude, longitude: coordinate.longitude), background: false).start()
|
||||
}
|
||||
})
|
||||
})]), nil)
|
||||
|
||||
|
||||
} else {
|
||||
let _ = context.engine.peersNearby.updatePeersNearbyVisibility(update: .invisible, background: false).start()
|
||||
}
|
||||
}, openProfile: { peer, distance in
|
||||
navigateToProfileImpl?(peer, distance)
|
||||
}, openChat: { peer in
|
||||
navigateToChatImpl?(peer)
|
||||
}, openCreateGroup: { latitude, longitude, address in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
presentControllerImpl?(controller, nil)
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.5, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
cancelImpl = {
|
||||
checkCreationAvailabilityDisposable.set(nil)
|
||||
}
|
||||
checkCreationAvailabilityDisposable.set((context.engine.peers.checkPublicChannelCreationAvailability(location: true)
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { available in
|
||||
if available {
|
||||
let controller = PermissionController(context: context, splashScreen: true)
|
||||
controller.navigationPresentation = .modalInLargeLayout
|
||||
controller.setState(.custom(icon: .icon(PermissionControllerCustomIcon(light: UIImage(bundleImageName: "Location/LocalGroupLightIcon"), dark: UIImage(bundleImageName: "Location/LocalGroupDarkIcon"))), title: presentationData.strings.LocalGroup_Title, subtitle: address, text: presentationData.strings.LocalGroup_Text, buttonTitle: presentationData.strings.LocalGroup_ButtonTitle, secondaryButtonTitle: nil, footerText: presentationData.strings.LocalGroup_IrrelevantWarning), animated: false)
|
||||
controller.proceed = { result in
|
||||
let controller = context.sharedContext.makeCreateGroupController(context: context, peerIds: [], initialTitle: nil, mode: .locatedGroup(latitude: latitude, longitude: longitude, address: address), completion: nil)
|
||||
controller.navigationPresentation = .modalInLargeLayout
|
||||
replaceTopControllerImpl?(controller)
|
||||
}
|
||||
pushControllerImpl?(controller)
|
||||
} else {
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.CreateGroup_ErrorLocatedGroupsTooMuch, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
}))
|
||||
}, contextAction: { peer, node, gesture in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.previewing), params: nil)
|
||||
chatController.canReadHistory.set(false)
|
||||
let contextController = makeContextController(presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: peerNearbyContextMenuItems(context: context, peerId: peer.id, present: { c in
|
||||
presentControllerImpl?(c, nil)
|
||||
}) |> map { ContextController.Items(content: .list($0), animationCache: nil) }, gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
}, expandUsers: {
|
||||
expandedPromise.set(true)
|
||||
})
|
||||
|
||||
let dataSignal: Signal<PeersNearbyData?, NoError> = coordinatePromise.get()
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
return lhs?.latitude == rhs?.latitude && lhs?.longitude == rhs?.longitude
|
||||
})
|
||||
|> mapToSignal { coordinate -> Signal<PeersNearbyData?, NoError> in
|
||||
guard let coordinate = coordinate else {
|
||||
return .single(nil)
|
||||
/*let peersNearbyContext = PeersNearbyContext(network: context.account.network, stateManager: context.account.stateManager, coordinate: nil)
|
||||
return peersNearbyContext.get()
|
||||
|> map { peersNearby -> PeersNearbyData in
|
||||
var isVisible = false
|
||||
if let peersNearby {
|
||||
for peer in peersNearby {
|
||||
if case .selfPeer = peer {
|
||||
isVisible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return PeersNearbyData(latitude: 0.0, longitude: 0.0, address: nil, visible: isVisible, accountPeerId: context.account.peerId, users: [], groups: [], channels: [])
|
||||
}*/
|
||||
}
|
||||
|
||||
return Signal { subscriber in
|
||||
let peersNearbyContext = PeersNearbyContext(network: context.account.network, stateManager: context.account.stateManager, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude))
|
||||
|
||||
let peersNearby: Signal<PeersNearbyData?, NoError> = combineLatest(peersNearbyContext.get(), addressPromise.get())
|
||||
|> mapToSignal { peersNearby, address -> Signal<([PeerNearby]?, String?), NoError> in
|
||||
if let address = address {
|
||||
return .single((peersNearby, address))
|
||||
} else {
|
||||
return reverseGeocodeLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
|
||||
|> map { placemark in
|
||||
return (peersNearby, placemark?.fullAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mapToSignal { peersNearby, address -> Signal<PeersNearbyData?, NoError> in
|
||||
guard let peersNearby = peersNearby else {
|
||||
return .single(nil)
|
||||
}
|
||||
let peerIds = peersNearby.map { entry -> EnginePeer.Id in
|
||||
switch entry {
|
||||
case let .peer(id, _, _):
|
||||
return id
|
||||
case .selfPeer:
|
||||
return context.account.peerId
|
||||
}
|
||||
}
|
||||
return context.engine.data.get(
|
||||
EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)),
|
||||
EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init))
|
||||
)
|
||||
|> map { peerMap, participantCountMap -> PeersNearbyData? in
|
||||
var users: [PeerNearbyEntry] = []
|
||||
var groups: [PeerNearbyEntry] = []
|
||||
var visible = false
|
||||
for peerNearby in peersNearby {
|
||||
switch peerNearby {
|
||||
case let .peer(id, expires, distance):
|
||||
if let maybePeer = peerMap[id], let peer = maybePeer {
|
||||
if id.namespace == Namespaces.Peer.CloudUser {
|
||||
users.append(PeerNearbyEntry(peer: peer, memberCount: nil, expires: expires, distance: distance))
|
||||
} else {
|
||||
var participantCount: Int32?
|
||||
if let maybeParticipantCount = participantCountMap[id] {
|
||||
participantCount = maybeParticipantCount.flatMap(Int32.init)
|
||||
}
|
||||
groups.append(PeerNearbyEntry(peer: peer, memberCount: participantCount, expires: expires, distance: distance))
|
||||
}
|
||||
}
|
||||
case let .selfPeer(expires):
|
||||
visible = true
|
||||
if let maybePeer = peerMap[context.account.peerId], let peer = maybePeer {
|
||||
users.append(PeerNearbyEntry(peer: peer, memberCount: nil, expires: expires, distance: 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
return PeersNearbyData(latitude: coordinate.latitude, longitude: coordinate.longitude, address: address, visible: visible, accountPeerId: context.account.peerId, users: users, groups: groups, channels: [])
|
||||
}
|
||||
}
|
||||
|
||||
let disposable = peersNearby.start(next: { data in
|
||||
subscriber.putNext(data)
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
disposable.dispose()
|
||||
let _ = peersNearbyContext.get()
|
||||
}
|
||||
}
|
||||
}
|
||||
dataPromise.set(.single(nil) |> then(dataSignal))
|
||||
|
||||
let previousData = Atomic<PeersNearbyData?>(value: nil)
|
||||
let displayLoading: Signal<Bool, NoError> = .single(false)
|
||||
|> then(
|
||||
.single(true)
|
||||
|> delay(1.0, queue: Queue.mainQueue())
|
||||
)
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), chatLocationPromise.get(), displayLoading, expandedPromise.get(), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.peersNearby)))
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, data, chatLocation, displayLoading, expanded, view -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let previous = previousData.swap(data)
|
||||
let state = view?.get(PeersNearbyState.self) ?? .default
|
||||
|
||||
var crossfade = false
|
||||
if (data?.users.isEmpty ?? true) != (previous?.users.isEmpty ?? true) {
|
||||
crossfade = true
|
||||
}
|
||||
if (data?.groups.isEmpty ?? true) != (previous?.groups.isEmpty ?? true) {
|
||||
crossfade = true
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.PeopleNearby_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: peersNearbyControllerEntries(data: data, state: state, presentationData: presentationData, displayLoading: displayLoading, expanded: expanded, chatLocation: chatLocation), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: !crossfade)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = PeersNearbyControllerImpl(context: context, state: signal)
|
||||
chatLocationPromise.set(controller.chatLocation.get())
|
||||
controller.didDisappear = { [weak controller] _ in
|
||||
controller?.clearItemNodesHighlight(animated: true)
|
||||
}
|
||||
navigateToProfileImpl = { [weak controller] peer, distance in
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .nearbyPeer(distance: distance), avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
navigateToChatImpl = { [weak controller] peer in
|
||||
if let navigationController = controller?.navigationController as? NavigationController {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always, purposefulAction: {}, peekData: nil))
|
||||
}
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
if let controller = controller {
|
||||
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
||||
}
|
||||
}
|
||||
replaceTopControllerImpl = { [weak controller] c in
|
||||
if let controller = controller {
|
||||
(controller.navigationController as? NavigationController)?.replaceTopController(c, animated: true)
|
||||
}
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, p in
|
||||
if let controller = controller {
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
}
|
||||
}
|
||||
presentInGlobalOverlayImpl = { [weak controller] c in
|
||||
if let controller = controller {
|
||||
controller.presentInGlobalOverlay(c)
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import AccountContext
|
||||
|
||||
class PeersNearbyHeaderItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let text: String
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, text: String, sectionId: ItemListSectionId) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.text = text
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = PeersNearbyHeaderItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
guard let nodeValue = node() as? PeersNearbyHeaderItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.regular(13.0)
|
||||
|
||||
class PeersNearbyHeaderItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
private var animationNode: AnimatedStickerNode
|
||||
|
||||
private var item: PeersNearbyHeaderItem?
|
||||
|
||||
init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
|
||||
super.init(layerBacked: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.animationNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: PeersNearbyHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
let leftInset: CGFloat = 32.0 + params.leftInset
|
||||
let topInset: CGFloat = 92.0
|
||||
|
||||
let attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: item.theme.list.freeTextColor)
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.item == nil {
|
||||
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "Compass"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||
strongSelf.animationNode.visibility = true
|
||||
}
|
||||
strongSelf.item = item
|
||||
strongSelf.accessibilityLabel = attributedText.string
|
||||
|
||||
let iconSize = CGSize(width: 96.0, height: 96.0)
|
||||
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
|
||||
strongSelf.animationNode.updateLayout(size: iconSize)
|
||||
|
||||
let _ = titleApply()
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: topInset + 8.0), size: titleLayout.size)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
|
@ -3645,8 +3645,16 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||
|
||||
let controller = context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: loadedPack.flatMap { [$0] } ?? [], actionTitle: nil, isEditing: false, expandIfNeeded: false, parentNavigationController: navigationController, sendSticker: { _, _, _ in
|
||||
return false
|
||||
}, actionPerformed: { added in
|
||||
updatedInstalled = added
|
||||
}, actionPerformed: { actions in
|
||||
guard let action = actions.first?.action else {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case .add:
|
||||
updatedInstalled = true
|
||||
case .remove:
|
||||
updatedInstalled = false
|
||||
}
|
||||
})
|
||||
presentController(controller)
|
||||
break
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ swift_library(
|
|||
],
|
||||
deps = [
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/MtProtoKit:MtProtoKit",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
|
|
@ -40,6 +41,8 @@ swift_library(
|
|||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/SegmentControlComponent",
|
||||
"//submodules/UrlEscaping:UrlEscaping",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -18,47 +18,56 @@ import Markdown
|
|||
import TextFormat
|
||||
import QrCode
|
||||
import LottieComponent
|
||||
import MtProtoKit
|
||||
import SegmentControlComponent
|
||||
import UrlEscaping
|
||||
|
||||
private func shareQrCode(context: AccountContext, link: String, ecl: String, view: UIView) {
|
||||
let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")), ecl: ecl)
|
||||
|> map { _, generator -> UIImage? in
|
||||
let imageSize = CGSize(width: 768.0, height: 768.0)
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||
return context?.generateImage()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { image in
|
||||
guard let image = image else {
|
||||
return
|
||||
}
|
||||
|
||||
let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil)
|
||||
private func shareQrCode(sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject, asImage: Bool, view: UIView) {
|
||||
let shareImpl: (Any) -> Void = { item in
|
||||
let activityController = UIActivityViewController(activityItems: [item], applicationActivities: nil)
|
||||
if let window = view.window {
|
||||
activityController.popoverPresentationController?.sourceView = window
|
||||
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||
}
|
||||
context.sharedContext.applicationBindings.presentNativeController(activityController)
|
||||
})
|
||||
sharedContext.applicationBindings.presentNativeController(activityController)
|
||||
}
|
||||
if asImage {
|
||||
let _ = (qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: subject.icon, ecl: subject.ecl)
|
||||
|> map { _, generator -> UIImage? in
|
||||
let imageSize = CGSize(width: 768.0, height: 768.0)
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||
return context?.generateImage()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { image in
|
||||
guard let image else {
|
||||
return
|
||||
}
|
||||
shareImpl(image)
|
||||
})
|
||||
} else {
|
||||
shareImpl(subject.link)
|
||||
}
|
||||
}
|
||||
|
||||
private final class SheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let sharedContext: SharedAccountContext
|
||||
let subject: QrCodeScreen.Subject
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
sharedContext: SharedAccountContext,
|
||||
subject: QrCodeScreen.Subject,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.sharedContext = sharedContext
|
||||
self.subject = subject
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
if lhs.sharedContext !== rhs.sharedContext {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -70,11 +79,19 @@ private final class SheetContent: CombinedComponent {
|
|||
private var initialBrightness: CGFloat?
|
||||
private var brightnessArguments: (Double, Double, CGFloat, CGFloat)?
|
||||
private var animator: ConstantDisplayLinkAnimator?
|
||||
|
||||
init(context: AccountContext) {
|
||||
|
||||
var selectedProxyExternalLink: Bool
|
||||
|
||||
init(sharedContext: SharedAccountContext, subject: QrCodeScreen.Subject) {
|
||||
if case let .proxy(_, externalLink) = subject {
|
||||
self.selectedProxyExternalLink = externalLink
|
||||
} else {
|
||||
self.selectedProxyExternalLink = false
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.idleTimerExtensionDisposable.set(context.sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||
self.idleTimerExtensionDisposable.set(sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||
|
||||
self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
self?.updateBrightness()
|
||||
|
|
@ -116,16 +133,18 @@ private final class SheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context)
|
||||
return State(sharedContext: self.sharedContext, subject: self.subject)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let qrCode = Child(PlainButtonComponent.self)
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let title = Child(Text.self)
|
||||
let segmentControl = Child(SegmentControlComponent.self)
|
||||
let text = Child(BalancedTextComponent.self)
|
||||
|
||||
let button = Child(ButtonComponent.self)
|
||||
let secondaryButton = Child(ButtonComponent.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
|
|
@ -134,10 +153,15 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
|
||||
let link = component.subject.link
|
||||
let ecl = component.subject.ecl
|
||||
|
||||
let state = context.state
|
||||
|
||||
let effectiveSubject: QrCodeScreen.Subject
|
||||
if case let .proxy(server, _) = component.subject {
|
||||
effectiveSubject = .proxy(server: server, externalLink: state.selectedProxyExternalLink)
|
||||
} else {
|
||||
effectiveSubject = component.subject
|
||||
}
|
||||
|
||||
let titleString: String
|
||||
let textString: String
|
||||
switch component.subject {
|
||||
|
|
@ -154,6 +178,9 @@ private final class SheetContent: CombinedComponent {
|
|||
case .chatFolder:
|
||||
titleString = strings.InviteLink_QRCodeFolder_Title
|
||||
textString = strings.InviteLink_QRCodeFolder_Text
|
||||
case .proxy:
|
||||
titleString = ""
|
||||
textString = strings.SocksProxySetup_ShareQRCodeInfo
|
||||
default:
|
||||
titleString = ""
|
||||
textString = ""
|
||||
|
|
@ -185,24 +212,67 @@ private final class SheetContent: CombinedComponent {
|
|||
)
|
||||
|
||||
let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0
|
||||
|
||||
let title = title.update(
|
||||
component: Text(text: titleString, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
|
||||
if case .proxy = component.subject {
|
||||
let constrainedSegmentWidth = min(constrainedTitleWidth, max(200.0, context.availableSize.width - 144.0))
|
||||
|
||||
let theme = SegmentControlComponent.Theme(
|
||||
backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor,
|
||||
legacyBackgroundColor: theme.overallDarkAppearance ? theme.list.itemBlocksBackgroundColor : theme.rootController.navigationBar.segmentedBackgroundColor,
|
||||
foregroundColor: theme.actionSheet.opaqueItemBackgroundColor,
|
||||
textColor: theme.rootController.navigationBar.segmentedTextColor,
|
||||
dividerColor: theme.rootController.navigationBar.segmentedDividerColor
|
||||
)
|
||||
|
||||
//TODO:localize
|
||||
let segmentControl = segmentControl.update(
|
||||
component: SegmentControlComponent(
|
||||
theme: theme,
|
||||
items: [
|
||||
SegmentControlComponent.Item(id: AnyHashable(false), title: "tg:// link"),
|
||||
SegmentControlComponent.Item(id: AnyHashable(true), title: "t.me link")
|
||||
],
|
||||
selectedId: AnyHashable(state.selectedProxyExternalLink),
|
||||
action: { id in
|
||||
guard let externalLink = id.base as? Bool else {
|
||||
return
|
||||
}
|
||||
if state.selectedProxyExternalLink != externalLink {
|
||||
state.selectedProxyExternalLink = externalLink
|
||||
state.updated(transition: ComponentTransition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: constrainedSegmentWidth, height: 36.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(segmentControl
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height))
|
||||
)
|
||||
contentSize.height += segmentControl.size.height
|
||||
} else {
|
||||
let title = title.update(
|
||||
component: Text(text: titleString, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
}
|
||||
contentSize.height += 13.0
|
||||
|
||||
let qrCode = qrCode.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(QrCodeComponent(context: component.context, link: link, ecl: ecl)),
|
||||
content: AnyComponent(
|
||||
QrCodeComponent(
|
||||
subject: effectiveSubject
|
||||
)
|
||||
),
|
||||
action: { [weak controller] in
|
||||
if let view = controller?.view {
|
||||
shareQrCode(context: component.context, link: link, ecl: ecl, view: view)
|
||||
shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: true, view: view)
|
||||
}
|
||||
},
|
||||
animateScale: false
|
||||
|
|
@ -250,8 +320,7 @@ private final class SheetContent: CombinedComponent {
|
|||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
|
||||
cornerRadius: 10.0,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
|
|
@ -261,7 +330,7 @@ private final class SheetContent: CombinedComponent {
|
|||
displaysProgress: false,
|
||||
action: { [weak controller] in
|
||||
if let view = controller?.view {
|
||||
shareQrCode(context: component.context, link: link, ecl: ecl, view: view)
|
||||
shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: true, view: view)
|
||||
}
|
||||
}
|
||||
),
|
||||
|
|
@ -272,8 +341,42 @@ private final class SheetContent: CombinedComponent {
|
|||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += button.size.height
|
||||
|
||||
if case .proxy = component.subject {
|
||||
contentSize.height += 8.0
|
||||
|
||||
let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let secondaryButton = secondaryButton.update(
|
||||
component: ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemAccentColor.withMultipliedAlpha(0.1),
|
||||
foreground: theme.list.itemAccentColor,
|
||||
pressedColor: theme.list.itemAccentColor.withMultipliedAlpha(0.8)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.SocksProxySetup_ShareLink, font: Font.semibold(17.0), textColor: theme.list.itemAccentColor, paragraphAlignment: .center))))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak controller] in
|
||||
if let view = controller?.view {
|
||||
shareQrCode(sharedContext: component.sharedContext, subject: effectiveSubject, asImage: false, view: view)
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(secondaryButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + secondaryButton.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += secondaryButton.size.height
|
||||
}
|
||||
|
||||
contentSize.height += buttonInsets.bottom
|
||||
|
||||
|
||||
return contentSize
|
||||
}
|
||||
}
|
||||
|
|
@ -282,19 +385,19 @@ private final class SheetContent: CombinedComponent {
|
|||
private final class QrCodeSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
private let context: AccountContext
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let subject: QrCodeScreen.Subject
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
sharedContext: SharedAccountContext,
|
||||
subject: QrCodeScreen.Subject
|
||||
) {
|
||||
self.context = context
|
||||
self.sharedContext = sharedContext
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
static func ==(lhs: QrCodeSheetComponent, rhs: QrCodeSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
if lhs.sharedContext !== rhs.sharedContext {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -312,7 +415,7 @@ private final class QrCodeSheetComponent: CombinedComponent {
|
|||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
context: context.component.context,
|
||||
sharedContext: context.component.sharedContext,
|
||||
subject: context.component.subject,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
|
|
@ -372,10 +475,11 @@ public final class QrCodeScreen: ViewControllerComponentContainer {
|
|||
case groupCall
|
||||
}
|
||||
|
||||
public enum Subject {
|
||||
public enum Subject: Equatable {
|
||||
case peer(peer: EnginePeer)
|
||||
case invite(invite: ExportedInvitation, type: SubjectType)
|
||||
case chatFolder(slug: String)
|
||||
case proxy(server: ProxyServerSettings, externalLink: Bool)
|
||||
|
||||
var link: String {
|
||||
switch self {
|
||||
|
|
@ -389,39 +493,79 @@ public final class QrCodeScreen: ViewControllerComponentContainer {
|
|||
} else {
|
||||
return "https://t.me/addlist/\(slug)"
|
||||
}
|
||||
case let .proxy(server, externalLink):
|
||||
var link: String
|
||||
let serverHost = server.host.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? ""
|
||||
switch server.connection {
|
||||
case let .mtp(secret):
|
||||
let secret = MTProxySecret.parseData(secret)?.serializeToString() ?? ""
|
||||
link = "\(externalLink ? "https://t.me/proxy" : "tg://proxy")?server=\(serverHost)&port=\(server.port)"
|
||||
link += "&secret=\(secret.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
|
||||
case let .socks5(username, password):
|
||||
link = "\(externalLink ? "https://t.me/socks" : "tg://socks")?server=\(serverHost)&port=\(server.port)"
|
||||
if let username, !username.isEmpty {
|
||||
link += "&user=\(username.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
|
||||
}
|
||||
if let password, !password.isEmpty {
|
||||
link += "&pass=\(password.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
|
||||
}
|
||||
}
|
||||
return link
|
||||
}
|
||||
}
|
||||
|
||||
var ecl: String {
|
||||
switch self {
|
||||
case .peer:
|
||||
return "Q"
|
||||
case .invite:
|
||||
return "Q"
|
||||
case .chatFolder:
|
||||
case .peer, .invite, .chatFolder, .proxy:
|
||||
return "Q"
|
||||
}
|
||||
}
|
||||
|
||||
var icon: QrCodeIcon {
|
||||
switch self {
|
||||
case .peer, .invite, .chatFolder:
|
||||
return .custom(UIImage(bundleImageName: "Chat/Links/QrLogo"))
|
||||
case .proxy:
|
||||
return .proxy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||
subject: QrCodeScreen.Subject
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: QrCodeSheetComponent(
|
||||
context: context,
|
||||
sharedContext: context.sharedContext,
|
||||
subject: subject
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default //
|
||||
theme: .default,
|
||||
updatedPresentationData: updatedPresentationData
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
public init(
|
||||
sharedContext: SharedAccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>),
|
||||
subject: QrCodeScreen.Subject
|
||||
) {
|
||||
super.init(
|
||||
component: QrCodeSheetComponent(
|
||||
sharedContext: sharedContext,
|
||||
subject: subject
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
presentationMode: .default,
|
||||
theme: .default,
|
||||
updatedPresentationData: updatedPresentationData
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
|
|
@ -439,28 +583,16 @@ public final class QrCodeScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
|
||||
private final class QrCodeComponent: Component {
|
||||
let context: AccountContext
|
||||
let link: String
|
||||
let ecl: String
|
||||
let subject: QrCodeScreen.Subject
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
link: String,
|
||||
ecl: String
|
||||
subject: QrCodeScreen.Subject
|
||||
) {
|
||||
self.context = context
|
||||
self.link = link
|
||||
self.ecl = ecl
|
||||
self.subject = subject
|
||||
}
|
||||
|
||||
static func ==(lhs: QrCodeComponent, rhs: QrCodeComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.link != rhs.link {
|
||||
return false
|
||||
}
|
||||
if lhs.ecl != rhs.ecl {
|
||||
if lhs.subject != rhs.subject {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -502,9 +634,14 @@ private final class QrCodeComponent: Component {
|
|||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
if previousComponent?.link != component.link {
|
||||
self.imageNode.setSignal(qrCode(string: component.link, color: .black, backgroundColor: .white, icon: .cutout, ecl: component.ecl) |> beforeNext { [weak self] size, _ in
|
||||
|
||||
var isProxy = false
|
||||
if case .proxy = component.subject {
|
||||
isProxy = true
|
||||
}
|
||||
|
||||
if previousComponent?.subject != component.subject {
|
||||
self.imageNode.setSignal(qrCode(string: component.subject.link, color: .black, backgroundColor: .white, icon: isProxy ? .proxy : .cutout, ecl: component.subject.ecl) |> beforeNext { [weak self] size, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -524,7 +661,7 @@ private final class QrCodeComponent: Component {
|
|||
let imageFrame = CGRect(origin: CGPoint(x: (size.width - imageSize.width) / 2.0, y: (size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
self.imageNode.frame = imageFrame
|
||||
|
||||
if let qrCodeSize = self.qrCodeSize {
|
||||
if !isProxy, let qrCodeSize = self.qrCodeSize {
|
||||
let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil)
|
||||
|
||||
let _ = self.icon.update(
|
||||
|
|
|
|||
|
|
@ -1,446 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import AsyncDisplayKit
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ActivityIndicator
|
||||
import OverlayStatusController
|
||||
import AccountContext
|
||||
import PresentationDataUtils
|
||||
import UrlEscaping
|
||||
|
||||
public final class ProxyServerActionSheetController: ActionSheetController {
|
||||
private let sharedContext: SharedAccountContext
|
||||
private var presentationDisposable: Disposable?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
convenience public init(context: AccountContext, server: ProxyServerSettings) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.init(sharedContext: context.sharedContext, presentationData: presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, server: server, updatedPresentationData: context.sharedContext.presentationData)
|
||||
}
|
||||
|
||||
public init(sharedContext: SharedAccountContext, presentationData: PresentationData, accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox, network: Network, server: ProxyServerSettings, updatedPresentationData: Signal<PresentationData, NoError>?) {
|
||||
self.sharedContext = sharedContext
|
||||
let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
super.init(theme: sheetTheme)
|
||||
|
||||
self._ready.set(.single(true))
|
||||
|
||||
var items: [ActionSheetItem] = []
|
||||
if case .mtp = server.connection {
|
||||
items.append(ActionSheetTextItem(title: presentationData.strings.SocksProxySetup_AdNoticeHelp))
|
||||
}
|
||||
items.append(ProxyServerInfoItem(strings: presentationData.strings, network: network, server: server))
|
||||
items.append(ProxyServerActionItem(sharedContext: sharedContext, accountManager:accountManager, postbox: postbox, network: network, presentationData: presentationData, server: server, dismiss: { [weak self] success in
|
||||
guard let strongSelf = self, !strongSelf.isDismissed else {
|
||||
return
|
||||
}
|
||||
strongSelf.isDismissed = true
|
||||
if success {
|
||||
strongSelf.present(OverlayStatusController(theme: presentationData.theme, type: .shieldSuccess(presentationData.strings.SocksProxySetup_ProxyEnabled, false)), in: .window(.root))
|
||||
}
|
||||
strongSelf.dismissAnimated()
|
||||
}, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}))
|
||||
self.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
|
||||
if let updatedPresentationData = updatedPresentationData {
|
||||
self.presentationDisposable = updatedPresentationData.start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProxyServerInfoItem: ActionSheetItem {
|
||||
private let strings: PresentationStrings
|
||||
private let network: Network
|
||||
private let server: ProxyServerSettings
|
||||
|
||||
init(strings: PresentationStrings, network: Network, server: ProxyServerSettings) {
|
||||
self.strings = strings
|
||||
self.network = network
|
||||
self.server = server
|
||||
}
|
||||
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return ProxyServerInfoItemNode(theme: theme, strings: self.strings, network: self.network, server: self.server)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ActionSheetItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
private enum ProxyServerInfoStatusType {
|
||||
case generic(String)
|
||||
case failed(String)
|
||||
}
|
||||
|
||||
private final class ProxyServerInfoItemNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
private let textFont: UIFont
|
||||
|
||||
private let network: Network
|
||||
private let server: ProxyServerSettings
|
||||
|
||||
private let fieldNodes: [(ImmediateTextNode, ImmediateTextNode)]
|
||||
private let statusTextNode: ImmediateTextNode
|
||||
|
||||
private let statusDisposable = MetaDisposable()
|
||||
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, network: Network, server: ProxyServerSettings) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.network = network
|
||||
self.server = server
|
||||
|
||||
self.textFont = Font.regular(floor(theme.baseFontSize * 16.0 / 17.0))
|
||||
|
||||
var fieldNodes: [(ImmediateTextNode, ImmediateTextNode)] = []
|
||||
let serverTitleNode = ImmediateTextNode()
|
||||
serverTitleNode.isUserInteractionEnabled = false
|
||||
serverTitleNode.displaysAsynchronously = false
|
||||
serverTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Hostname, font: textFont, textColor: theme.secondaryTextColor)
|
||||
let serverTextNode = ImmediateTextNode()
|
||||
serverTextNode.isUserInteractionEnabled = false
|
||||
serverTextNode.displaysAsynchronously = false
|
||||
serverTextNode.attributedText = NSAttributedString(string: urlEncodedStringFromString(server.host), font: textFont, textColor: theme.primaryTextColor)
|
||||
fieldNodes.append((serverTitleNode, serverTextNode))
|
||||
|
||||
let portTitleNode = ImmediateTextNode()
|
||||
portTitleNode.isUserInteractionEnabled = false
|
||||
portTitleNode.displaysAsynchronously = false
|
||||
portTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Port, font: textFont, textColor: theme.secondaryTextColor)
|
||||
let portTextNode = ImmediateTextNode()
|
||||
portTextNode.isUserInteractionEnabled = false
|
||||
portTextNode.displaysAsynchronously = false
|
||||
portTextNode.attributedText = NSAttributedString(string: "\(server.port)", font: textFont, textColor: theme.primaryTextColor)
|
||||
fieldNodes.append((portTitleNode, portTextNode))
|
||||
|
||||
switch server.connection {
|
||||
case let .socks5(username, password):
|
||||
if let username = username {
|
||||
let usernameTitleNode = ImmediateTextNode()
|
||||
usernameTitleNode.isUserInteractionEnabled = false
|
||||
usernameTitleNode.displaysAsynchronously = false
|
||||
usernameTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Username, font: textFont, textColor: theme.secondaryTextColor)
|
||||
let usernameTextNode = ImmediateTextNode()
|
||||
usernameTextNode.isUserInteractionEnabled = false
|
||||
usernameTextNode.displaysAsynchronously = false
|
||||
usernameTextNode.attributedText = NSAttributedString(string: username, font: textFont, textColor: theme.primaryTextColor)
|
||||
fieldNodes.append((usernameTitleNode, usernameTextNode))
|
||||
}
|
||||
|
||||
if let password = password {
|
||||
let passwordTitleNode = ImmediateTextNode()
|
||||
passwordTitleNode.isUserInteractionEnabled = false
|
||||
passwordTitleNode.displaysAsynchronously = false
|
||||
passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Password, font: textFont, textColor: theme.secondaryTextColor)
|
||||
let passwordTextNode = ImmediateTextNode()
|
||||
passwordTextNode.isUserInteractionEnabled = false
|
||||
passwordTextNode.displaysAsynchronously = false
|
||||
passwordTextNode.attributedText = NSAttributedString(string: password, font: textFont, textColor: theme.primaryTextColor)
|
||||
fieldNodes.append((passwordTitleNode, passwordTextNode))
|
||||
}
|
||||
case .mtp:
|
||||
let passwordTitleNode = ImmediateTextNode()
|
||||
passwordTitleNode.isUserInteractionEnabled = false
|
||||
passwordTitleNode.displaysAsynchronously = false
|
||||
passwordTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Secret, font: textFont, textColor: theme.secondaryTextColor)
|
||||
let passwordTextNode = ImmediateTextNode()
|
||||
passwordTextNode.isUserInteractionEnabled = false
|
||||
passwordTextNode.displaysAsynchronously = false
|
||||
passwordTextNode.attributedText = NSAttributedString(string: "•••••", font: textFont, textColor: theme.primaryTextColor)
|
||||
fieldNodes.append((passwordTitleNode, passwordTextNode))
|
||||
}
|
||||
|
||||
let statusTitleNode = ImmediateTextNode()
|
||||
statusTitleNode.isUserInteractionEnabled = false
|
||||
statusTitleNode.displaysAsynchronously = false
|
||||
statusTitleNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_Status, font: textFont, textColor: theme.secondaryTextColor)
|
||||
let statusTextNode = ImmediateTextNode()
|
||||
statusTextNode.isUserInteractionEnabled = false
|
||||
statusTextNode.displaysAsynchronously = false
|
||||
statusTextNode.attributedText = NSAttributedString(string: strings.SocksProxySetup_ProxyStatusChecking, font: textFont, textColor: theme.primaryTextColor)
|
||||
fieldNodes.append((statusTitleNode, statusTextNode))
|
||||
|
||||
self.fieldNodes = fieldNodes
|
||||
self.statusTextNode = statusTextNode
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
for (lhs, rhs) in fieldNodes {
|
||||
self.addSubnode(lhs)
|
||||
self.addSubnode(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let statusesContext = ProxyServersStatuses(network: network, servers: .single([self.server]))
|
||||
self.statusDisposable.set((statusesContext.statuses()
|
||||
|> map { return $0.first?.value }
|
||||
|> distinctUntilChanged
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self, let status = status {
|
||||
let statusType: ProxyServerInfoStatusType
|
||||
switch status {
|
||||
case .checking:
|
||||
statusType = .generic(strongSelf.strings.SocksProxySetup_ProxyStatusChecking)
|
||||
case let .available(rtt):
|
||||
let pingTime = Int(rtt * 1000.0)
|
||||
statusType = .generic(strongSelf.strings.SocksProxySetup_ProxyStatusPing("\(pingTime)").string)
|
||||
case .notAvailable:
|
||||
statusType = .failed(strongSelf.strings.SocksProxySetup_ProxyStatusUnavailable)
|
||||
}
|
||||
strongSelf.setStatus(statusType)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func setStatus(_ status: ProxyServerInfoStatusType) {
|
||||
let attributedString: NSAttributedString
|
||||
switch status {
|
||||
case let .generic(text):
|
||||
attributedString = NSAttributedString(string: text, font: textFont, textColor: theme.primaryTextColor)
|
||||
case let .failed(text):
|
||||
attributedString = NSAttributedString(string: text, font: textFont, textColor: theme.destructiveActionTextColor)
|
||||
}
|
||||
self.statusTextNode.attributedText = attributedString
|
||||
self.requestLayoutUpdate()
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let size = CGSize(width: constrainedSize.width, height: 36.0 * CGFloat(self.fieldNodes.count) + 12.0)
|
||||
|
||||
var offset: CGFloat = 15.0
|
||||
for (lhs, rhs) in self.fieldNodes {
|
||||
let lhsSize = lhs.updateLayout(CGSize(width: size.width - 18.0 * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
lhs.frame = CGRect(origin: CGPoint(x: 18, y: offset), size: lhsSize)
|
||||
|
||||
let rhsSize = rhs.updateLayout(CGSize(width: max(1.0, size.width - 18 * 2.0 - lhsSize.width - 4.0), height: CGFloat.greatestFiniteMagnitude))
|
||||
rhs.frame = CGRect(origin: CGPoint(x: size.width - 18 - rhsSize.width, y: offset), size: rhsSize)
|
||||
|
||||
offset += 36.0
|
||||
}
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProxyServerActionItem: ActionSheetItem {
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let accountManager: AccountManager<TelegramAccountManagerTypes>
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let presentationData: PresentationData
|
||||
private let server: ProxyServerSettings
|
||||
private let dismiss: (Bool) -> Void
|
||||
private let present: (ViewController, Any?) -> Void
|
||||
|
||||
init(sharedContext: SharedAccountContext, accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox, network: Network, presentationData: PresentationData, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.sharedContext = sharedContext
|
||||
self.accountManager = accountManager
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.presentationData = presentationData
|
||||
self.server = server
|
||||
self.dismiss = dismiss
|
||||
self.present = present
|
||||
}
|
||||
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return ProxyServerActionItemNode(sharedContext: self.sharedContext, accountManager: self.accountManager, postbox: self.postbox, network: self.network, presentationData: self.presentationData, theme: theme, server: self.server, dismiss: self.dismiss, present: self.present)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ActionSheetItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProxyServerActionItemNode: ActionSheetItemNode {
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let accountManager: AccountManager<TelegramAccountManagerTypes>
|
||||
private let postbox: Postbox
|
||||
private let network: Network
|
||||
private let presentationData: PresentationData
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let server: ProxyServerSettings
|
||||
private let dismiss: (Bool) -> Void
|
||||
private let present: (ViewController, Any?) -> Void
|
||||
|
||||
private let buttonNode: HighlightableButtonNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let activityIndicator: ActivityIndicator
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
private var revertSettings: ProxySettings?
|
||||
|
||||
init(sharedContext: SharedAccountContext, accountManager: AccountManager<TelegramAccountManagerTypes>, postbox: Postbox, network: Network, presentationData: PresentationData, theme: ActionSheetControllerTheme, server: ProxyServerSettings, dismiss: @escaping (Bool) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.sharedContext = sharedContext
|
||||
self.accountManager = accountManager
|
||||
self.postbox = postbox
|
||||
self.network = network
|
||||
self.theme = theme
|
||||
self.presentationData = presentationData
|
||||
self.server = server
|
||||
self.dismiss = dismiss
|
||||
self.present = present
|
||||
|
||||
let titleFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.SocksProxySetup_ConnectAndSave, font: titleFont, textColor: theme.controlAccentColor)
|
||||
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(theme.controlAccentColor, 22.0, 1.5, false))
|
||||
self.activityIndicator.isHidden = true
|
||||
|
||||
self.buttonNode = HighlightableButtonNode()
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.activityIndicator)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
if let revertSettings = self.revertSettings {
|
||||
let _ = updateProxySettingsInteractively(accountManager: self.accountManager, { _ in
|
||||
return revertSettings
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let size = CGSize(width: constrainedSize.width, height: 57.0)
|
||||
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
let labelSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - 10.0), height: size.height))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
|
||||
let activitySize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
||||
self.titleNode.frame = titleFrame
|
||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: 14.0, y: titleFrame.minY - 0.0), size: activitySize)
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
let proxyServerSettings = self.server
|
||||
let _ = (self.accountManager.transaction { transaction -> ProxySettings in
|
||||
var currentSettings: ProxySettings?
|
||||
let _ = updateProxySettingsInteractively(transaction: transaction, { settings in
|
||||
currentSettings = settings
|
||||
var settings = settings
|
||||
if let index = settings.servers.firstIndex(of: proxyServerSettings) {
|
||||
settings.servers[index] = proxyServerSettings
|
||||
settings.activeServer = proxyServerSettings
|
||||
} else {
|
||||
settings.servers.insert(proxyServerSettings, at: 0)
|
||||
settings.activeServer = proxyServerSettings
|
||||
}
|
||||
settings.enabled = true
|
||||
return settings
|
||||
})
|
||||
return currentSettings ?? ProxySettings.defaultSettings
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] previousSettings in
|
||||
if let strongSelf = self {
|
||||
strongSelf.revertSettings = previousSettings
|
||||
strongSelf.buttonNode.isUserInteractionEnabled = false
|
||||
strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.SocksProxySetup_Connecting, font: Font.regular(20.0), textColor: strongSelf.theme.primaryTextColor)
|
||||
strongSelf.activityIndicator.isHidden = false
|
||||
strongSelf.requestLayoutUpdate()
|
||||
|
||||
let signal = strongSelf.network.connectionStatus
|
||||
|> filter { status in
|
||||
switch status {
|
||||
case let .online(proxyAddress):
|
||||
if proxyAddress == proxyServerSettings.host {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> map { _ -> Bool in
|
||||
return true
|
||||
}
|
||||
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .single(false))
|
||||
|> deliverOnMainQueue
|
||||
strongSelf.disposable.set(signal.start(next: { value in
|
||||
if let strongSelf = self {
|
||||
strongSelf.activityIndicator.isHidden = true
|
||||
strongSelf.revertSettings = nil
|
||||
if value {
|
||||
strongSelf.dismiss(true)
|
||||
} else {
|
||||
let _ = updateProxySettingsInteractively(accountManager: strongSelf.accountManager, { _ in
|
||||
return previousSettings
|
||||
})
|
||||
strongSelf.titleNode.attributedText = NSAttributedString(string: strongSelf.presentationData.strings.SocksProxySetup_ConnectAndSave, font: Font.regular(20.0), textColor: strongSelf.theme.controlAccentColor)
|
||||
strongSelf.buttonNode.isUserInteractionEnabled = true
|
||||
strongSelf.requestLayoutUpdate()
|
||||
|
||||
strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -10,20 +10,7 @@ import PresentationDataUtils
|
|||
import AccountContext
|
||||
import UrlEscaping
|
||||
import UrlHandling
|
||||
|
||||
private func shareLink(for server: ProxyServerSettings) -> String {
|
||||
var link: String
|
||||
switch server.connection {
|
||||
case let .mtp(secret):
|
||||
let secret = MTProxySecret.parseData(secret)?.serializeToString() ?? ""
|
||||
link = "tg://proxy?server=\(server.host)&port=\(server.port)"
|
||||
link += "&secret=\(secret.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
|
||||
case let .socks5(username, password):
|
||||
link = "https://t.me/socks?server=\(server.host)&port=\(server.port)"
|
||||
link += "&user=\(username?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")&pass=\(password?.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryValueAllowed) ?? "")"
|
||||
}
|
||||
return link
|
||||
}
|
||||
import QrCodeUI
|
||||
|
||||
private final class ProxyServerSettingsControllerArguments {
|
||||
let updateState: ((ProxyServerSettingsControllerState) -> ProxyServerSettingsControllerState) -> Void
|
||||
|
|
@ -300,7 +287,7 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
|
|||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
var shareImpl: (() -> Void)?
|
||||
|
|
@ -370,8 +357,8 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
|
|||
|
||||
let controller = ItemListController(presentationData: ItemListPresentationData(presentationData), updatedPresentationData: updatedPresentationData |> map(ItemListPresentationData.init(_:)), state: signal, tabBarItem: nil)
|
||||
controller.navigationPresentation = .modal
|
||||
presentControllerImpl = { [weak controller] c, d in
|
||||
controller?.present(c, in: .window(.root), with: d)
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
let _ = controller?.dismiss()
|
||||
|
|
@ -381,12 +368,14 @@ func proxyServerSettingsController(sharedContext: SharedAccountContext, context:
|
|||
guard let server = proxyServerSettings(with: state) else {
|
||||
return
|
||||
}
|
||||
|
||||
let link = shareLink(for: server)
|
||||
controller?.view.endEditing(true)
|
||||
|
||||
let controller = ShareProxyServerActionSheetController(presentationData: presentationData, updatedPresentationData: updatedPresentationData, link: link)
|
||||
presentControllerImpl?(controller, nil)
|
||||
let controller = QrCodeScreen(
|
||||
sharedContext: sharedContext,
|
||||
updatedPresentationData: (presentationData, updatedPresentationData),
|
||||
subject: .proxy(server: server, externalLink: false)
|
||||
)
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
|
||||
return controller
|
||||
|
|
|
|||
|
|
@ -1,184 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import AsyncDisplayKit
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import QrCode
|
||||
|
||||
public final class ShareProxyServerActionSheetController: ActionSheetController {
|
||||
private var presentationDisposable: Disposable?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
public init(presentationData: PresentationData, updatedPresentationData: Signal<PresentationData, NoError>, link: String) {
|
||||
let sheetTheme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
super.init(theme: sheetTheme)
|
||||
|
||||
let presentActivityController: (Any) -> Void = { [weak self] item in
|
||||
let activityController = UIActivityViewController(activityItems: [item], applicationActivities: nil)
|
||||
if let window = self?.view.window, let rootViewController = window.rootViewController {
|
||||
activityController.popoverPresentationController?.sourceView = window
|
||||
activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||
rootViewController.present(activityController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ProxyServerQRCodeItem(strings: presentationData.strings, link: link, ready: { [weak self] in
|
||||
self?._ready.set(.single(true))
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.SocksProxySetup_ShareQRCode, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy)
|
||||
|> map { _, generator -> UIImage? in
|
||||
let imageSize = CGSize(width: 768.0, height: 768.0)
|
||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||
return context?.generateImage()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { image in
|
||||
if let image = image {
|
||||
presentActivityController(image)
|
||||
}
|
||||
})
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.SocksProxySetup_ShareLink, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
presentActivityController(link)
|
||||
}))
|
||||
self.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
|
||||
self.presentationDisposable = updatedPresentationData.start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProxyServerQRCodeItem: ActionSheetItem {
|
||||
private let strings: PresentationStrings
|
||||
private let link: String
|
||||
private let ready: () -> Void
|
||||
|
||||
init(strings: PresentationStrings, link: String, ready: @escaping () -> Void = {}) {
|
||||
self.strings = strings
|
||||
self.link = link
|
||||
self.ready = ready
|
||||
}
|
||||
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return ProxyServerQRCodeItemNode(theme: theme, strings: self.strings, link: self.link, ready: self.ready)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ActionSheetItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProxyServerQRCodeItemNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
private let link: String
|
||||
|
||||
private let label: ASTextNode
|
||||
private let imageNode: TransformImageNode
|
||||
|
||||
private let ready: () -> Void
|
||||
|
||||
private var cachedHasLabel = true
|
||||
private var cachedHasImage = true
|
||||
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, link: String, ready: @escaping () -> Void = {}) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.link = link
|
||||
self.ready = ready
|
||||
|
||||
let textFont = Font.regular(floor(theme.baseFontSize * 13.0 / 17.0))
|
||||
|
||||
self.label = ASTextNode()
|
||||
self.label.isUserInteractionEnabled = false
|
||||
self.label.maximumNumberOfLines = 0
|
||||
self.label.displaysAsynchronously = false
|
||||
self.label.truncationMode = .byTruncatingTail
|
||||
self.label.isUserInteractionEnabled = false
|
||||
self.label.attributedText = NSAttributedString(string: strings.SocksProxySetup_ShareQRCodeInfo, font: textFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center)
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.clipsToBounds = true
|
||||
self.imageNode.setSignal(qrCode(string: link, color: .black, backgroundColor: .white, icon: .proxy) |> map { $0.1 }, attemptSynchronously: true)
|
||||
self.imageNode.cornerRadius = 14.0
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.addSubnode(self.label)
|
||||
self.addSubnode(self.imageNode)
|
||||
}
|
||||
|
||||
override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let imageInset: CGFloat = 44.0
|
||||
let side = constrainedSize.width - imageInset * 2.0
|
||||
var imageSize = CGSize(width: side, height: side)
|
||||
|
||||
let makeLayout = self.imageNode.asyncLayout()
|
||||
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil))
|
||||
apply()
|
||||
|
||||
var labelSize = self.label.measure(CGSize(width: max(1.0, constrainedSize.width - 64.0), height: constrainedSize.height))
|
||||
|
||||
self.cachedHasImage = constrainedSize.width < constrainedSize.height
|
||||
if !self.cachedHasImage {
|
||||
imageSize = CGSize()
|
||||
}
|
||||
|
||||
self.ready()
|
||||
|
||||
self.cachedHasLabel = constrainedSize.height > 480 || !self.cachedHasImage
|
||||
if !self.cachedHasLabel {
|
||||
labelSize = CGSize()
|
||||
}
|
||||
let size = CGSize(width: constrainedSize.width, height: 14.0 + (labelSize.height > 0.0 ? labelSize.height + 14.0 : 0.0) + (imageSize.height > 0.0 ? imageSize.height + 14.0 : 8.0))
|
||||
|
||||
let inset: CGFloat = 32.0
|
||||
let spacing: CGFloat = 18.0
|
||||
if self.cachedHasLabel {
|
||||
labelSize = self.label.measure(CGSize(width: max(1.0, size.width - inset * 2.0), height: size.height))
|
||||
self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: spacing), size: labelSize)
|
||||
} else {
|
||||
labelSize = CGSize()
|
||||
}
|
||||
|
||||
if !self.cachedHasImage {
|
||||
imageSize = CGSize()
|
||||
} else {
|
||||
imageSize = CGSize(width: size.width - imageInset * 2.0, height: size.width - imageInset * 2.0)
|
||||
}
|
||||
let imageOrigin = CGPoint(x: imageInset, y: self.label.frame.maxY + spacing - 4.0)
|
||||
self.imageNode.frame = CGRect(origin: imageOrigin, size: imageSize)
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import Geocoding
|
|||
import WallpaperResources
|
||||
import Sunrise
|
||||
import ThemeSettingsThemeItem
|
||||
import ChatTimerScreen
|
||||
|
||||
private enum TriggerMode {
|
||||
case system
|
||||
|
|
@ -487,32 +488,44 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
|||
break
|
||||
}
|
||||
|
||||
presentControllerImpl?(ThemeAutoNightTimeSelectionActionSheet(context: context, currentValue: currentValue, applyValue: { value in
|
||||
guard let value = value else {
|
||||
return
|
||||
}
|
||||
updateSettings { settings in
|
||||
var settings = settings
|
||||
switch settings.trigger {
|
||||
case let .timeBased(setting):
|
||||
switch setting {
|
||||
case var .manual(fromSeconds, toSeconds):
|
||||
switch field {
|
||||
case .from:
|
||||
fromSeconds = value
|
||||
case .to:
|
||||
toSeconds = value
|
||||
let controller = ChatTimerScreen(
|
||||
context: context,
|
||||
configuration: ChatTimerScreen.Configuration(
|
||||
style: .default,
|
||||
picker: .timeOfDay,
|
||||
currentValue: currentValue,
|
||||
pickerValueMapping: .secondsFromMidnightGMT,
|
||||
primaryActionTitle: { strings, _, _ in
|
||||
strings.Wallpaper_Set
|
||||
}
|
||||
),
|
||||
completion: { value in
|
||||
guard let value = value else {
|
||||
return
|
||||
}
|
||||
updateSettings { settings in
|
||||
var settings = settings
|
||||
switch settings.trigger {
|
||||
case let .timeBased(setting):
|
||||
switch setting {
|
||||
case var .manual(fromSeconds, toSeconds):
|
||||
switch field {
|
||||
case .from:
|
||||
fromSeconds = value
|
||||
case .to:
|
||||
toSeconds = value
|
||||
}
|
||||
settings.trigger = .timeBased(setting: .manual(fromSeconds: fromSeconds, toSeconds: toSeconds))
|
||||
default:
|
||||
break
|
||||
}
|
||||
settings.trigger = .timeBased(setting: .manual(fromSeconds: fromSeconds, toSeconds: toSeconds))
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return settings
|
||||
}
|
||||
return settings
|
||||
}
|
||||
}))
|
||||
})
|
||||
presentControllerImpl?(controller)
|
||||
|
||||
return settings
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
final class ThemeAutoNightTimeSelectionActionSheet: ActionSheetController {
|
||||
private var presentationDisposable: Disposable?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
init(context: AccountContext, currentValue: Int32, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
|
||||
|
||||
self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
}
|
||||
})
|
||||
|
||||
self._ready.set(.single(true))
|
||||
|
||||
var updatedValue = currentValue
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ThemeAutoNightTimeSelectionActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in
|
||||
updatedValue = value
|
||||
}))
|
||||
if let emptyTitle = emptyTitle {
|
||||
items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
applyValue(nil)
|
||||
}))
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
applyValue(updatedValue)
|
||||
}))
|
||||
self.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
}),
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class ThemeAutoNightTimeSelectionActionSheetItem: ActionSheetItem {
|
||||
let strings: PresentationStrings
|
||||
|
||||
let currentValue: Int32
|
||||
let valueChanged: (Int32) -> Void
|
||||
|
||||
init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.strings = strings
|
||||
self.currentValue = currentValue
|
||||
self.valueChanged = valueChanged
|
||||
}
|
||||
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return ThemeAutoNightTimeSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ActionSheetItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ThemeAutoNightTimeSelectionActionSheetItemNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let valueChanged: (Int32) -> Void
|
||||
private let pickerView: UIDatePicker
|
||||
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.valueChanged = valueChanged
|
||||
|
||||
UILabel.setDateLabel(theme.primaryTextColor)
|
||||
|
||||
self.pickerView = UIDatePicker()
|
||||
self.pickerView.datePickerMode = .countDownTimer
|
||||
self.pickerView.datePickerMode = .time
|
||||
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
self.pickerView.date = Date(timeIntervalSince1970: Double(currentValue))
|
||||
self.pickerView.locale = Locale.current
|
||||
if #available(iOS 13.4, *) {
|
||||
self.pickerView.preferredDatePickerStyle = .wheels
|
||||
}
|
||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.view.addSubview(self.pickerView)
|
||||
self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let size = CGSize(width: constrainedSize.width, height: 216.0)
|
||||
|
||||
self.pickerView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
|
||||
@objc private func datePickerUpdated() {
|
||||
self.valueChanged(Int32(self.pickerView.date.timeIntervalSince1970))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,6 @@ enum AccountStateMutationOperation {
|
|||
case UpdateLangPack(String, Api.LangPackDifference?)
|
||||
case UpdateMinAvailableMessage(MessageId)
|
||||
case UpdatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool)
|
||||
case UpdatePeersNearby([PeerNearby])
|
||||
case UpdateTheme(TelegramTheme)
|
||||
case SyncChatListFilters
|
||||
case UpdateChatListFilterOrder(order: [Int32])
|
||||
|
|
@ -554,11 +553,7 @@ struct AccountMutableState {
|
|||
mutating func updatePeerChatInclusion(peerId: PeerId, groupId: PeerGroupId, changedGroup: Bool) {
|
||||
self.addOperation(.UpdatePeerChatInclusion(peerId: peerId, groupId: groupId, changedGroup: changedGroup))
|
||||
}
|
||||
|
||||
mutating func updatePeersNearby(_ peersNearby: [PeerNearby]) {
|
||||
self.addOperation(.UpdatePeersNearby(peersNearby))
|
||||
}
|
||||
|
||||
|
||||
mutating func updateTheme(_ theme: TelegramTheme) {
|
||||
self.addOperation(.UpdateTheme(theme))
|
||||
}
|
||||
|
|
@ -746,7 +741,7 @@ struct AccountMutableState {
|
|||
|
||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||
switch operation {
|
||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
|
||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
|
||||
break
|
||||
case let .AddMessages(messages, location):
|
||||
for message in messages {
|
||||
|
|
@ -884,7 +879,6 @@ struct AccountReplayedFinalState {
|
|||
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)]
|
||||
let groupCallMessageUpdates: [GroupCallMessageUpdate]
|
||||
let storyUpdates: [InternalStoryUpdate]
|
||||
let updatedPeersNearby: [PeerNearby]?
|
||||
let isContactUpdates: [(PeerId, Bool)]
|
||||
let delayNotificatonsUntil: Int32?
|
||||
let updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id]
|
||||
|
|
@ -915,7 +909,6 @@ struct AccountFinalStateEvents {
|
|||
let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)]
|
||||
let groupCallMessageUpdates: [GroupCallMessageUpdate]
|
||||
let storyUpdates: [InternalStoryUpdate]
|
||||
let updatedPeersNearby: [PeerNearby]?
|
||||
let isContactUpdates: [(PeerId, Bool)]
|
||||
let displayAlerts: [(text: String, isDropAuth: Bool)]
|
||||
let dismissBotWebViews: [Int64]
|
||||
|
|
@ -938,10 +931,10 @@ struct AccountFinalStateEvents {
|
|||
let updatedEmojiGameInfo: EmojiGameInfo?
|
||||
|
||||
var isEmpty: Bool {
|
||||
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty && self.updatedEmojiGameInfo == nil
|
||||
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty && self.updatedEmojiGameInfo == nil
|
||||
}
|
||||
|
||||
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:], updatedEmojiGameInfo: EmojiGameInfo? = nil) {
|
||||
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:], updatedEmojiGameInfo: EmojiGameInfo? = nil) {
|
||||
self.addedIncomingMessageIds = addedIncomingMessageIds
|
||||
self.addedReactionEvents = addedReactionEvents
|
||||
self.wasScheduledMessageIds = wasScheduledMessageIds
|
||||
|
|
@ -953,7 +946,6 @@ struct AccountFinalStateEvents {
|
|||
self.updatedGroupCallParticipants = updatedGroupCallParticipants
|
||||
self.groupCallMessageUpdates = groupCallMessageUpdates
|
||||
self.storyUpdates = storyUpdates
|
||||
self.updatedPeersNearby = updatedPeersNearby
|
||||
self.isContactUpdates = isContactUpdates
|
||||
self.displayAlerts = displayAlerts
|
||||
self.dismissBotWebViews = dismissBotWebViews
|
||||
|
|
@ -989,7 +981,6 @@ struct AccountFinalStateEvents {
|
|||
self.updatedGroupCallParticipants = state.updatedGroupCallParticipants
|
||||
self.groupCallMessageUpdates = state.groupCallMessageUpdates
|
||||
self.storyUpdates = state.storyUpdates
|
||||
self.updatedPeersNearby = state.updatedPeersNearby
|
||||
self.isContactUpdates = state.isContactUpdates
|
||||
self.displayAlerts = state.state.state.displayAlerts
|
||||
self.dismissBotWebViews = state.state.state.dismissBotWebViews
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ extension PeerStatusSettings {
|
|||
init(apiSettings: Api.PeerSettings) {
|
||||
switch apiSettings {
|
||||
case let .peerSettings(peerSettingsData):
|
||||
let (flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl, chargePaidMessageStars, registrationMonth, phoneCountry, nameChangeDate, photoChangeDate) = (peerSettingsData.flags, peerSettingsData.geoDistance, peerSettingsData.requestChatTitle, peerSettingsData.requestChatDate, peerSettingsData.businessBotId, peerSettingsData.businessBotManageUrl, peerSettingsData.chargePaidMessageStars, peerSettingsData.registrationMonth, peerSettingsData.phoneCountry, peerSettingsData.nameChangeDate, peerSettingsData.photoChangeDate)
|
||||
let (flags, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl, chargePaidMessageStars, registrationMonth, phoneCountry, nameChangeDate, photoChangeDate) = (peerSettingsData.flags, peerSettingsData.requestChatTitle, peerSettingsData.requestChatDate, peerSettingsData.businessBotId, peerSettingsData.businessBotManageUrl, peerSettingsData.chargePaidMessageStars, peerSettingsData.registrationMonth, peerSettingsData.phoneCountry, peerSettingsData.nameChangeDate, peerSettingsData.photoChangeDate)
|
||||
var result = PeerStatusSettings.Flags()
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
result.insert(.canAddContact)
|
||||
|
|
@ -44,7 +44,6 @@ extension PeerStatusSettings {
|
|||
}
|
||||
self = PeerStatusSettings(
|
||||
flags: result,
|
||||
geoDistance: geoDistance,
|
||||
requestChatTitle: requestChatTitle,
|
||||
requestChatDate: requestChatDate,
|
||||
requestChatIsChannel: (flags & (1 << 10)) != 0,
|
||||
|
|
|
|||
|
|
@ -1819,19 +1819,6 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||
updatedState.updatePeerChatInclusion(peerId: peer.peerId, groupId: PeerGroupId(rawValue: folderId), changedGroup: true)
|
||||
}
|
||||
}
|
||||
case let .updatePeerLocated(updatePeerLocatedData):
|
||||
var peersNearby: [PeerNearby] = []
|
||||
for peer in updatePeerLocatedData.peers {
|
||||
switch peer {
|
||||
case let .peerLocated(peerLocatedData):
|
||||
let (peer, expires, distance) = (peerLocatedData.peer, peerLocatedData.expires, peerLocatedData.distance)
|
||||
peersNearby.append(.peer(id: peer.peerId, expires: expires, distance: distance))
|
||||
case let .peerSelfLocated(peerSelfLocatedData):
|
||||
let expires = peerSelfLocatedData.expires
|
||||
peersNearby.append(.selfPeer(expires: expires))
|
||||
}
|
||||
}
|
||||
updatedState.updatePeersNearby(peersNearby)
|
||||
case let .updateNewScheduledMessage(updateNewScheduledMessageData):
|
||||
var peerIsForum = false
|
||||
if let peerId = updateNewScheduledMessageData.message.peerId {
|
||||
|
|
@ -3808,7 +3795,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
|||
var currentAddQuickReplyMessages: OptimizeAddMessagesState?
|
||||
for operation in operations {
|
||||
switch operation {
|
||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
|
||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
|
||||
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||
}
|
||||
|
|
@ -3927,7 +3914,6 @@ func replayFinalState(
|
|||
var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = []
|
||||
var groupCallMessageUpdates: [GroupCallMessageUpdate] = []
|
||||
var storyUpdates: [InternalStoryUpdate] = []
|
||||
var updatedPeersNearby: [PeerNearby]?
|
||||
var isContactUpdates: [(PeerId, Bool)] = []
|
||||
var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = []
|
||||
var recentlyUsedStickers: [MediaId: (MessageIndex, TelegramMediaFile)] = [:]
|
||||
|
|
@ -5220,8 +5206,6 @@ func replayFinalState(
|
|||
}
|
||||
case let .UpdateIsContact(peerId, value):
|
||||
isContactUpdates.append((peerId, value))
|
||||
case let .UpdatePeersNearby(peersNearby):
|
||||
updatedPeersNearby = peersNearby
|
||||
case let .UpdateTheme(theme):
|
||||
updatedThemes[theme.id] = theme
|
||||
case let .UpdateWallpaper(peerId, wallpaper):
|
||||
|
|
@ -6159,7 +6143,6 @@ func replayFinalState(
|
|||
updatedGroupCallParticipants: updatedGroupCallParticipants,
|
||||
groupCallMessageUpdates: groupCallMessageUpdates,
|
||||
storyUpdates: storyUpdates,
|
||||
updatedPeersNearby: updatedPeersNearby,
|
||||
isContactUpdates: isContactUpdates,
|
||||
delayNotificatonsUntil: delayNotificatonsUntil,
|
||||
updatedIncomingThreadReadStates: updatedIncomingThreadReadStates,
|
||||
|
|
|
|||
|
|
@ -40,10 +40,6 @@ private final class UpdatedWebpageSubscriberContext {
|
|||
let subscribers = Bag<(TelegramMediaWebpage) -> Void>()
|
||||
}
|
||||
|
||||
private final class UpdatedPeersNearbySubscriberContext {
|
||||
let subscribers = Bag<([PeerNearby]) -> Void>()
|
||||
}
|
||||
|
||||
private final class UpdatedStarsBalanceSubscriberContext {
|
||||
let subscribers = Bag<([PeerId: StarsAmount]) -> Void>()
|
||||
}
|
||||
|
|
@ -366,7 +362,6 @@ public final class AccountStateManager {
|
|||
}
|
||||
|
||||
private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
|
||||
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
|
||||
private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext()
|
||||
private var updatedTonBalanceContext = UpdatedStarsBalanceSubscriberContext()
|
||||
private var updatedStarsRevenueStatusContext = UpdatedStarsRevenueStatusSubscriberContext()
|
||||
|
|
@ -1127,9 +1122,6 @@ public final class AccountStateManager {
|
|||
if !events.updatedWebpages.isEmpty {
|
||||
strongSelf.notifyUpdatedWebpages(events.updatedWebpages)
|
||||
}
|
||||
if let updatedPeersNearby = events.updatedPeersNearby {
|
||||
strongSelf.notifyUpdatedPeersNearby(updatedPeersNearby)
|
||||
}
|
||||
if !events.updatedStarsBalance.isEmpty {
|
||||
strongSelf.notifyUpdatedStarsBalance(events.updatedStarsBalance)
|
||||
}
|
||||
|
|
@ -1709,34 +1701,7 @@ public final class AccountStateManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updatedPeersNearby() -> Signal<[PeerNearby], NoError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
queue.async {
|
||||
if let strongSelf = self {
|
||||
let index = strongSelf.updatedPeersNearbyContext.subscribers.add({ peersNearby in
|
||||
subscriber.putNext(peersNearby)
|
||||
})
|
||||
|
||||
disposable.set(ActionDisposable {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updatedPeersNearbyContext.subscribers.remove(index)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
private func notifyUpdatedPeersNearby(_ updatedPeersNearby: [PeerNearby]) {
|
||||
for subscriber in self.updatedPeersNearbyContext.subscribers.copyItems() {
|
||||
subscriber(updatedPeersNearby)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func updatedStarsBalance() -> Signal<[PeerId: StarsAmount], NoError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
|
|
@ -2235,12 +2200,6 @@ public final class AccountStateManager {
|
|||
}
|
||||
}
|
||||
|
||||
public func updatedPeersNearby() -> Signal<[PeerNearby], NoError> {
|
||||
return self.impl.signalWith { impl, subscriber in
|
||||
return impl.updatedPeersNearby().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
}
|
||||
}
|
||||
|
||||
public func updatedStarsBalance() -> Signal<[PeerId: StarsAmount], NoError> {
|
||||
return self.impl.signalWith { impl, subscriber in
|
||||
return impl.updatedStarsBalance().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
|
|
|
|||
|
|
@ -589,7 +589,7 @@ public final class CachedChannelData: CachedPeerData {
|
|||
var peerIds = Set<PeerId>()
|
||||
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ public final class CachedGroupData: CachedPeerData {
|
|||
self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i")
|
||||
self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo]
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1418,7 +1418,7 @@ public final class CachedUserData: CachedPeerData {
|
|||
self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo
|
||||
self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -433,12 +433,6 @@ public struct PreferencesKeys {
|
|||
return key
|
||||
}()
|
||||
|
||||
public static let peersNearby: ValueBoxKey = {
|
||||
let key = ValueBoxKey(length: 4)
|
||||
key.setInt32(0, value: PreferencesKeyValues.peersNearby.rawValue)
|
||||
return key
|
||||
}()
|
||||
|
||||
public static let chatListFiltersFeaturedState: ValueBoxKey = {
|
||||
let key = ValueBoxKey(length: 4)
|
||||
key.setInt32(0, value: PreferencesKeyValues.chatListFiltersFeaturedState.rawValue)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
}
|
||||
|
||||
public var flags: PeerStatusSettings.Flags
|
||||
public var geoDistance: Int32?
|
||||
public var requestChatTitle: String?
|
||||
public var requestChatDate: Int32?
|
||||
public var requestChatIsChannel: Bool?
|
||||
|
|
@ -46,7 +45,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
|
||||
public init() {
|
||||
self.flags = PeerStatusSettings.Flags()
|
||||
self.geoDistance = nil
|
||||
self.requestChatTitle = nil
|
||||
self.requestChatDate = nil
|
||||
self.managingBot = nil
|
||||
|
|
@ -59,7 +57,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
|
||||
public init(
|
||||
flags: PeerStatusSettings.Flags,
|
||||
geoDistance: Int32? = nil,
|
||||
requestChatTitle: String? = nil,
|
||||
requestChatDate: Int32? = nil,
|
||||
requestChatIsChannel: Bool? = nil,
|
||||
|
|
@ -71,7 +68,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
photoChangeDate: Int32? = nil
|
||||
) {
|
||||
self.flags = flags
|
||||
self.geoDistance = geoDistance
|
||||
self.requestChatTitle = requestChatTitle
|
||||
self.requestChatDate = requestChatDate
|
||||
self.requestChatIsChannel = requestChatIsChannel
|
||||
|
|
@ -85,7 +81,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.flags = Flags(rawValue: decoder.decodeInt32ForKey("flags", orElse: 0))
|
||||
self.geoDistance = decoder.decodeOptionalInt32ForKey("geoDistance")
|
||||
self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle")
|
||||
self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate")
|
||||
self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel")
|
||||
|
|
@ -99,11 +94,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.flags.rawValue, forKey: "flags")
|
||||
if let geoDistance = self.geoDistance {
|
||||
encoder.encodeInt32(geoDistance, forKey: "geoDistance")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "geoDistance")
|
||||
}
|
||||
if let requestChatTitle = self.requestChatTitle {
|
||||
encoder.encodeString(requestChatTitle, forKey: "requestChatTitle")
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public final class CachedSecretChatData: CachedPeerData {
|
|||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), managingBot: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -118,20 +118,17 @@ public enum EnginePeer: Equatable {
|
|||
}
|
||||
|
||||
public var flags: Flags
|
||||
public var geoDistance: Int32?
|
||||
public var requestChatTitle: String?
|
||||
public var requestChatDate: Int32?
|
||||
public var requestChatIsChannel: Bool?
|
||||
|
||||
public init(
|
||||
flags: Flags,
|
||||
geoDistance: Int32?,
|
||||
requestChatTitle: String?,
|
||||
requestChatDate: Int32?,
|
||||
requestChatIsChannel: Bool?
|
||||
) {
|
||||
self.flags = flags
|
||||
self.geoDistance = geoDistance
|
||||
self.requestChatTitle = requestChatTitle
|
||||
self.requestChatDate = requestChatDate
|
||||
self.requestChatIsChannel = requestChatIsChannel
|
||||
|
|
@ -363,7 +360,6 @@ public extension EnginePeer.StatusSettings {
|
|||
init(_ statusSettings: PeerStatusSettings) {
|
||||
self.init(
|
||||
flags: Flags(rawValue: statusSettings.flags.rawValue),
|
||||
geoDistance: statusSettings.geoDistance,
|
||||
requestChatTitle: statusSettings.requestChatTitle,
|
||||
requestChatDate: statusSettings.requestChatDate,
|
||||
requestChatIsChannel: statusSettings.requestChatIsChannel
|
||||
|
|
|
|||
|
|
@ -1,309 +0,0 @@
|
|||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
|
||||
private typealias SignalKitTimer = SwiftSignalKit.Timer
|
||||
|
||||
public enum PeerNearby {
|
||||
case selfPeer(expires: Int32)
|
||||
case peer(id: PeerId, expires: Int32, distance: Int32)
|
||||
|
||||
var expires: Int32 {
|
||||
switch self {
|
||||
case let .selfPeer(expires), let .peer(_, expires, _):
|
||||
return expires
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum PeerNearbyVisibilityUpdate {
|
||||
case visible(latitude: Double, longitude: Double)
|
||||
case location(latitude: Double, longitude: Double)
|
||||
case invisible
|
||||
}
|
||||
|
||||
func _internal_updatePeersNearbyVisibility(account: Account, update: PeerNearbyVisibilityUpdate, background: Bool) -> Signal<Void, NoError> {
|
||||
var flags: Int32 = 0
|
||||
var geoPoint: Api.InputGeoPoint
|
||||
var selfExpires: Int32?
|
||||
|
||||
switch update {
|
||||
case let .visible(latitude, longitude):
|
||||
flags |= (1 << 0)
|
||||
geoPoint = .inputGeoPoint(.init(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil))
|
||||
selfExpires = 10800
|
||||
case let .location(latitude, longitude):
|
||||
geoPoint = .inputGeoPoint(.init(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil))
|
||||
case .invisible:
|
||||
flags |= (1 << 0)
|
||||
geoPoint = .inputGeoPointEmpty
|
||||
selfExpires = 0
|
||||
}
|
||||
|
||||
let _ = (account.postbox.transaction { transaction in
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.peersNearby, { entry in
|
||||
var settings = entry?.get(PeersNearbyState.self) ?? PeersNearbyState.default
|
||||
if case .invisible = update {
|
||||
settings.visibilityExpires = nil
|
||||
} else if let expires = selfExpires {
|
||||
settings.visibilityExpires = expires
|
||||
}
|
||||
return PreferencesEntry(settings)
|
||||
})
|
||||
}).start()
|
||||
|
||||
if background {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.contacts.getLocated(flags: flags, geoPoint: geoPoint, selfExpires: selfExpires))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<Api.Updates?, NoError> in
|
||||
if error.errorCode == 406 {
|
||||
if error.errorDescription == "USERPIC_PRIVACY_REQUIRED" {
|
||||
let _ = (account.postbox.transaction { transaction in
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.peersNearby, { entry in
|
||||
var settings = entry?.get(PeersNearbyState.self) ?? PeersNearbyState.default
|
||||
settings.visibilityExpires = nil
|
||||
return PreferencesEntry(settings)
|
||||
})
|
||||
}).start()
|
||||
}
|
||||
return .single(nil)
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
if let updates = updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
public final class PeersNearbyContext {
|
||||
private let queue: Queue = Queue.mainQueue()
|
||||
private var subscribers = Bag<([PeerNearby]?) -> Void>()
|
||||
private let disposable = MetaDisposable()
|
||||
private var timer: SignalKitTimer?
|
||||
|
||||
private var entries: [PeerNearby]?
|
||||
|
||||
public init(network: Network, stateManager: AccountStateManager, coordinate: (latitude: Double, longitude: Double)) {
|
||||
let expiryExtension: Double = 10.0
|
||||
|
||||
let poll = network.request(Api.functions.contacts.getLocated(flags: 0, geoPoint: .inputGeoPoint(.init(flags: 0, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: nil)), selfExpires: nil))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> castError(Void.self)
|
||||
|> mapToSignal { updates -> Signal<[PeerNearby], Void> in
|
||||
var peersNearby: [PeerNearby] = []
|
||||
if let updates = updates {
|
||||
switch updates {
|
||||
case let .updates(updatesData):
|
||||
let updates = updatesData.updates
|
||||
for update in updates {
|
||||
if case let .updatePeerLocated(updatePeerLocatedData) = update {
|
||||
let peers = updatePeerLocatedData.peers
|
||||
for peer in peers {
|
||||
switch peer {
|
||||
case let .peerLocated(peerLocatedData):
|
||||
let (peer, expires, distance) = (peerLocatedData.peer, peerLocatedData.expires, peerLocatedData.distance)
|
||||
peersNearby.append(.peer(id: peer.peerId, expires: expires, distance: distance))
|
||||
case let .peerSelfLocated(peerSelfLocatedData):
|
||||
let expires = peerSelfLocatedData.expires
|
||||
peersNearby.append(.selfPeer(expires: expires))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
stateManager.addUpdates(updates)
|
||||
}
|
||||
return .single(peersNearby)
|
||||
|> then(
|
||||
stateManager.updatedPeersNearby()
|
||||
|> castError(Void.self)
|
||||
)
|
||||
}
|
||||
|
||||
let error: Signal<Void, Void> = .single(Void()) |> then(Signal.fail(Void()) |> suspendAwareDelay(25.0, queue: self.queue))
|
||||
let combined = combineLatest(poll, error)
|
||||
|> map { data, _ -> [PeerNearby] in
|
||||
return data
|
||||
}
|
||||
|> restartIfError
|
||||
|> `catch` { _ -> Signal<[PeerNearby], NoError> in
|
||||
}
|
||||
|
||||
self.disposable.set((combined
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] updatedEntries in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
var entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp } ?? []
|
||||
let updatedEntries = updatedEntries.filter { Double($0.expires) + expiryExtension > timestamp }
|
||||
|
||||
var existingPeerIds: [PeerId: Int] = [:]
|
||||
var existingSelfPeer: Int?
|
||||
for i in 0 ..< entries.count {
|
||||
if case let .peer(id, _, _) = entries[i] {
|
||||
existingPeerIds[id] = i
|
||||
} else if case .selfPeer = entries[i] {
|
||||
existingSelfPeer = i
|
||||
}
|
||||
}
|
||||
|
||||
var selfPeer: PeerNearby?
|
||||
for entry in updatedEntries {
|
||||
switch entry {
|
||||
case .selfPeer:
|
||||
if let index = existingSelfPeer {
|
||||
entries[index] = entry
|
||||
} else {
|
||||
selfPeer = entry
|
||||
}
|
||||
case let .peer(id, _, _):
|
||||
if let index = existingPeerIds[id] {
|
||||
entries[index] = entry
|
||||
} else {
|
||||
entries.append(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let peer = selfPeer {
|
||||
entries.insert(peer, at: 0)
|
||||
}
|
||||
|
||||
strongSelf.entries = entries
|
||||
for subscriber in strongSelf.subscribers.copyItems() {
|
||||
subscriber(strongSelf.entries)
|
||||
}
|
||||
}))
|
||||
|
||||
self.timer = SignalKitTimer(timeout: 2.0, repeat: true, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
strongSelf.entries = strongSelf.entries?.filter { Double($0.expires) + expiryExtension > timestamp }
|
||||
for subscriber in strongSelf.subscribers.copyItems() {
|
||||
subscriber(strongSelf.entries)
|
||||
}
|
||||
}, queue: self.queue)
|
||||
self.timer?.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.timer?.invalidate()
|
||||
}
|
||||
|
||||
public func get() -> Signal<[PeerNearby]?, NoError> {
|
||||
let queue = self.queue
|
||||
return Signal { [weak self] subscriber in
|
||||
if let strongSelf = self {
|
||||
subscriber.putNext(strongSelf.entries)
|
||||
|
||||
let index = strongSelf.subscribers.add({ entries in
|
||||
subscriber.putNext(entries)
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
queue.async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.subscribers.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return EmptyDisposable
|
||||
}
|
||||
} |> runOn(queue)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateChannelGeoLocation(postbox: Postbox, network: Network, channelId: PeerId, coordinate: (latitude: Double, longitude: Double)?, address: String?) -> Signal<Bool, NoError> {
|
||||
return postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(channelId)
|
||||
}
|
||||
|> mapToSignal { channel -> Signal<Bool, NoError> in
|
||||
guard let channel = channel, let apiChannel = apiInputChannel(channel) else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
let geoPoint: Api.InputGeoPoint
|
||||
if let (latitude, longitude) = coordinate, let _ = address {
|
||||
geoPoint = .inputGeoPoint(.init(flags: 0, lat: latitude, long: longitude, accuracyRadius: nil))
|
||||
} else {
|
||||
geoPoint = .inputGeoPointEmpty
|
||||
}
|
||||
|
||||
return network.request(Api.functions.channels.editLocation(channel: apiChannel, geoPoint: geoPoint, address: address ?? ""))
|
||||
|> map { result -> Bool in
|
||||
switch result {
|
||||
case .boolTrue:
|
||||
return true
|
||||
case .boolFalse:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> `catch` { error -> Signal<Bool, NoError> in
|
||||
return .single(false)
|
||||
}
|
||||
|> mapToSignal { result in
|
||||
if result {
|
||||
return postbox.transaction { transaction in
|
||||
transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { (_, current) -> CachedPeerData? in
|
||||
let current: CachedChannelData = current as? CachedChannelData ?? CachedChannelData()
|
||||
let peerGeoLocation: PeerGeoLocation?
|
||||
if let (latitude, longitude) = coordinate, let address = address {
|
||||
peerGeoLocation = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address)
|
||||
} else {
|
||||
peerGeoLocation = nil
|
||||
}
|
||||
return current.withUpdatedPeerGeoLocation(peerGeoLocation)
|
||||
})
|
||||
}
|
||||
|> map { _ in
|
||||
return result
|
||||
}
|
||||
} else {
|
||||
return .single(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct PeersNearbyState: Codable, Equatable {
|
||||
public var visibilityExpires: Int32?
|
||||
|
||||
public static var `default` = PeersNearbyState(visibilityExpires: nil)
|
||||
|
||||
public init(visibilityExpires: Int32?) {
|
||||
self.visibilityExpires = visibilityExpires
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.visibilityExpires = try container.decodeIfPresent(Int32.self, forKey: "expires")
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encodeIfPresent(self.visibilityExpires, forKey: "expires")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import SwiftSignalKit
|
||||
|
||||
public extension TelegramEngine {
|
||||
final class PeersNearby {
|
||||
private let account: Account
|
||||
|
||||
init(account: Account) {
|
||||
self.account = account
|
||||
}
|
||||
|
||||
public func updatePeersNearbyVisibility(update: PeerNearbyVisibilityUpdate, background: Bool) -> Signal<Void, NoError> {
|
||||
return _internal_updatePeersNearbyVisibility(account: self.account, update: update, background: background)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,10 +13,6 @@ public final class TelegramEngine {
|
|||
return SecureId(account: self.account)
|
||||
}()
|
||||
|
||||
public lazy var peersNearby: PeersNearby = {
|
||||
return PeersNearby(account: self.account)
|
||||
}()
|
||||
|
||||
public lazy var payments: Payments = {
|
||||
return Payments(account: self.account)
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ public enum PermissionKind: Int32 {
|
|||
case notifications
|
||||
case siri
|
||||
case cellularData
|
||||
case nearbyLocation
|
||||
}
|
||||
|
||||
public enum PermissionRequestStatus {
|
||||
|
|
@ -39,7 +38,6 @@ public enum PermissionState: Equatable {
|
|||
case notifications(status: PermissionRequestStatus)
|
||||
case siri(status: PermissionRequestStatus)
|
||||
case cellularData(status: PermissionRequestStatus)
|
||||
case nearbyLocation(status: PermissionRequestStatus)
|
||||
|
||||
public var kind: PermissionKind {
|
||||
switch self {
|
||||
|
|
@ -51,8 +49,6 @@ public enum PermissionState: Equatable {
|
|||
return .siri
|
||||
case .cellularData:
|
||||
return .cellularData
|
||||
case .nearbyLocation:
|
||||
return .nearbyLocation
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -66,8 +62,6 @@ public enum PermissionState: Equatable {
|
|||
return status
|
||||
case let .cellularData(status):
|
||||
return status
|
||||
case let .nearbyLocation(status):
|
||||
return status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ swift_library(
|
|||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/TelegramPermissions:TelegramPermissions",
|
||||
"//submodules/DeviceAccess:DeviceAccess",
|
||||
"//submodules/PeersNearbyIconNode:PeersNearbyIconNode",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import TelegramCore
|
|||
import TelegramPresentationData
|
||||
import TextFormat
|
||||
import TelegramPermissions
|
||||
import PeersNearbyIconNode
|
||||
import SolidRoundedButtonNode
|
||||
import PresentationDataUtils
|
||||
import Markdown
|
||||
|
|
@ -38,7 +37,6 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||
private let filterHitTest: Bool
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let nearbyIconNode: PeersNearbyIconNode?
|
||||
private let animationNode: AnimatedStickerNode?
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let subtitleNode: ImmediateTextNode
|
||||
|
|
@ -78,13 +76,7 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||
|
||||
self.animationNode?.setup(source: AnimatedStickerNodeLocalFileSource(name: animation), width: 320, height: 320, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||
self.animationNode?.visibility = true
|
||||
|
||||
self.nearbyIconNode = nil
|
||||
} else if kind == PermissionKind.nearbyLocation.rawValue {
|
||||
self.nearbyIconNode = PeersNearbyIconNode(theme: theme)
|
||||
self.animationNode = nil
|
||||
} else {
|
||||
self.nearbyIconNode = nil
|
||||
self.animationNode = nil
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +141,6 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.nearbyIconNode.flatMap { self.addSubnode($0) }
|
||||
self.animationNode.flatMap { self.addSubnode($0) }
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
|
|
@ -255,11 +246,6 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||
imageSize = icon.size
|
||||
contentHeight += imageSize.height + imageSpacing
|
||||
}
|
||||
if let _ = self.nearbyIconNode, size.width < size.height {
|
||||
imageSpacing = floor(availableHeight * 0.12)
|
||||
imageSize = CGSize(width: 120.0, height: 120.0)
|
||||
contentHeight += imageSize.height + imageSpacing
|
||||
}
|
||||
if let _ = self.animationNode, size.width < size.height {
|
||||
imageSpacing = floor(availableHeight * 0.12)
|
||||
imageSize = CGSize(width: 240.0, height: 240.0)
|
||||
|
|
@ -275,7 +261,6 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||
|
||||
let contentOrigin = insets.top + floor((size.height - insets.top - insets.bottom - contentHeight) / 2.0) - verticalOffset
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize)
|
||||
let nearbyIconFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize)
|
||||
let animationFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: contentOrigin), size: imageSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + imageSpacing), size: titleSize)
|
||||
|
||||
|
|
@ -300,9 +285,6 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
||||
if let nearbyIconNode = self.nearbyIconNode {
|
||||
transition.updateFrame(node: nearbyIconNode, frame: nearbyIconFrame)
|
||||
}
|
||||
if let animationNode = self.animationNode {
|
||||
transition.updateFrame(node: animationNode, frame: animationFrame)
|
||||
animationNode.updateLayout(size: animationFrame.size)
|
||||
|
|
|
|||
|
|
@ -114,10 +114,7 @@ public final class PermissionController: ViewController {
|
|||
|
||||
self.state = state
|
||||
if case let .permission(permission) = state, let state = permission {
|
||||
if case .nearbyLocation = state {
|
||||
} else {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed))
|
||||
}
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed))
|
||||
|
||||
switch state {
|
||||
case let .contacts(status):
|
||||
|
|
@ -186,28 +183,6 @@ public final class PermissionController: ViewController {
|
|||
strongSelf.proceed?(true)
|
||||
}
|
||||
}
|
||||
case let .nearbyLocation(status):
|
||||
self.title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0
|
||||
|
||||
if self.locationManager == nil {
|
||||
self.locationManager = LocationManager()
|
||||
}
|
||||
|
||||
self.allow = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
switch status {
|
||||
case .requestable:
|
||||
DeviceAccess.authorizeAccess(to: .location(.tracking), locationManager: strongSelf.locationManager, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, { [weak self] result in
|
||||
self?.proceed?(result)
|
||||
})
|
||||
case .denied, .unreachable:
|
||||
strongSelf.openAppSettings()
|
||||
strongSelf.proceed?(false)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if case let .custom(icon, _, _, _, _, _, _) = state {
|
||||
if case .animation = icon, case .modal = self.navigationPresentation {
|
||||
|
|
|
|||
|
|
@ -195,16 +195,6 @@ final class PermissionControllerNode: ASDisplayNode {
|
|||
text = self.presentationData.strings.Permissions_CellularDataText_v0
|
||||
buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings_v0
|
||||
hasPrivacyPolicy = false
|
||||
case let .nearbyLocation(status):
|
||||
icon = nil
|
||||
title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0
|
||||
text = self.presentationData.strings.Permissions_PeopleNearbyText_v0
|
||||
if status == .denied {
|
||||
buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllowInSettings_v0
|
||||
} else {
|
||||
buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllow_v0
|
||||
}
|
||||
hasPrivacyPolicy = false
|
||||
}
|
||||
|
||||
let contentNode = PermissionContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind.rawValue, icon: .image(icon), title: title, text: text, buttonTitle: buttonTitle, secondaryButtonTitle: nil, buttonAction: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -64,8 +64,6 @@ public enum PresentationResourceKey: Int32 {
|
|||
case itemListCloudFetchIcon
|
||||
case itemListCloseIconImage
|
||||
case itemListRemoveIconImage
|
||||
case itemListMakeVisibleIcon
|
||||
case itemListMakeInvisibleIcon
|
||||
case itemListEditThemeIcon
|
||||
case itemListCornersTop
|
||||
case itemListCornersBottom
|
||||
|
|
|
|||
|
|
@ -269,19 +269,7 @@ public struct PresentationResourcesItemList {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func makeVisibleIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListMakeVisibleIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/MakeVisibleIcon"), color: theme.list.itemAccentColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func makeInvisibleIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListMakeInvisibleIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/MakeInvisibleIcon"), color: theme.list.itemDestructiveColor)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
public static func editThemeIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListEditThemeIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Settings/EditTheme"), color: theme.list.itemAccentColor)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,6 @@ swift_library(
|
|||
"//submodules/OpenInExternalAppUI:OpenInExternalAppUI",
|
||||
"//submodules/LegacyUI:LegacyUI",
|
||||
"//submodules/ImageCompression:ImageCompression",
|
||||
"//submodules/DateSelectionUI:DateSelectionUI",
|
||||
"//submodules/PasswordSetupUI:PasswordSetupUI",
|
||||
"//submodules/Pdf:Pdf",
|
||||
"//submodules/InstantPageUI:InstantPageUI",
|
||||
|
|
@ -154,7 +153,6 @@ swift_library(
|
|||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
"//submodules/ContactsPeerItem:ContactsPeerItem",
|
||||
"//submodules/TelegramPermissionsUI:TelegramPermissionsUI",
|
||||
"//submodules/PeersNearbyIconNode:PeersNearbyIconNode",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/PasscodeUI:PasscodeUI",
|
||||
"//submodules/CallListUI:CallListUI",
|
||||
|
|
@ -175,7 +173,6 @@ swift_library(
|
|||
"//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI",
|
||||
"//submodules/MimeTypes:MimeTypes",
|
||||
"//submodules/LocalMediaResources:LocalMediaResources",
|
||||
"//submodules/PeersNearbyUI:PeersNearbyUI",
|
||||
"//submodules/Geocoding:Geocoding",
|
||||
"//submodules/PeerInfoUI:PeerInfoUI",
|
||||
"//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI",
|
||||
|
|
@ -244,6 +241,7 @@ swift_library(
|
|||
"//submodules/GradientBackground:GradientBackground",
|
||||
"//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
"//submodules/AdUI:AdUI",
|
||||
"//submodules/SparseItemGrid:SparseItemGrid",
|
||||
"//submodules/CalendarMessageScreen:CalendarMessageScreen",
|
||||
|
|
|
|||
|
|
@ -10,19 +10,15 @@ swift_library(
|
|||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/ResizableSheetComponent",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/ScrollComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
|
|
@ -30,6 +26,8 @@ swift_library(
|
|||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/TelegramUI/Components/Ads/AdsReportScreen",
|
||||
],
|
||||
visibility = [
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -19,6 +19,7 @@ swift_library(
|
|||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
|
|
@ -30,6 +31,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/TelegramUI/Components/NavigationStackComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import NavigationStackComponent
|
|||
import ItemListUI
|
||||
import UndoUI
|
||||
import AccountContext
|
||||
import BundleIconComponent
|
||||
import GlassBarButtonComponent
|
||||
|
||||
private enum ReportResult {
|
||||
case reported
|
||||
|
|
@ -37,22 +39,19 @@ private final class SheetPageContent: CombinedComponent {
|
|||
let subtitle: String
|
||||
let items: [Item]
|
||||
let action: (Item) -> Void
|
||||
let pop: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
title: String?,
|
||||
subtitle: String,
|
||||
items: [Item],
|
||||
action: @escaping (Item) -> Void,
|
||||
pop: @escaping () -> Void
|
||||
action: @escaping (Item) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.title = title
|
||||
self.subtitle = subtitle
|
||||
self.items = items
|
||||
self.action = action
|
||||
self.pop = pop
|
||||
}
|
||||
|
||||
static func ==(lhs: SheetPageContent, rhs: SheetPageContent) -> Bool {
|
||||
|
|
@ -72,7 +71,6 @@ private final class SheetPageContent: CombinedComponent {
|
|||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
var backArrowImage: (UIImage, PresentationTheme)?
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
|
|
@ -80,8 +78,7 @@ private final class SheetPageContent: CombinedComponent {
|
|||
}
|
||||
|
||||
static var body: Body {
|
||||
let background = Child(RoundedRectangle.self)
|
||||
let back = Child(Button.self)
|
||||
let background = Child(Rectangle.self)
|
||||
let title = Child(Text.self)
|
||||
let subtitle = Child(MultilineTextComponent.self)
|
||||
let section = Child(ListSectionComponent.self)
|
||||
|
|
@ -89,7 +86,6 @@ private final class SheetPageContent: CombinedComponent {
|
|||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let component = context.component
|
||||
let state = context.state
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let theme = environment.theme
|
||||
|
|
@ -97,10 +93,10 @@ private final class SheetPageContent: CombinedComponent {
|
|||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 26.0)
|
||||
|
||||
let background = background.update(
|
||||
component: RoundedRectangle(color: theme.list.modalBlocksBackgroundColor, cornerRadius: 8.0),
|
||||
component: Rectangle(color: theme.list.modalBlocksBackgroundColor),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 1000.0),
|
||||
transition: .immediate
|
||||
)
|
||||
|
|
@ -108,73 +104,38 @@ private final class SheetPageContent: CombinedComponent {
|
|||
.position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
|
||||
)
|
||||
|
||||
let backArrowImage: UIImage
|
||||
if let (cached, cachedTheme) = state.backArrowImage, cachedTheme === theme {
|
||||
backArrowImage = cached
|
||||
} else {
|
||||
backArrowImage = NavigationBarTheme.generateBackArrowImage(color: theme.list.itemAccentColor)!
|
||||
state.backArrowImage = (backArrowImage, theme)
|
||||
}
|
||||
|
||||
let backContents: AnyComponent<Empty>
|
||||
if component.title == nil {
|
||||
backContents = AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.list.itemAccentColor))
|
||||
} else {
|
||||
backContents = AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(Image(image: backArrowImage, contentMode: .center))),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: strings.Common_Back, font: Font.regular(17.0), color: theme.list.itemAccentColor)))
|
||||
], spacing: 6.0)
|
||||
)
|
||||
}
|
||||
let back = back.update(
|
||||
component: Button(
|
||||
content: backContents,
|
||||
action: {
|
||||
component.pop()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(back
|
||||
.position(CGPoint(x: sideInset + back.size.width / 2.0 - (component.title != nil ? 8.0 : 0.0), y: contentSize.height + back.size.height / 2.0))
|
||||
)
|
||||
|
||||
let constrainedTitleWidth = context.availableSize.width - (back.size.width + 16.0) * 2.0
|
||||
let constrainedTitleWidth = context.availableSize.width - 60.0 * 2.0
|
||||
|
||||
let title = title.update(
|
||||
component: Text(text: strings.ReportAd_Title, font: Font.semibold(17.0), color: theme.list.itemPrimaryTextColor),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
|
||||
if let subtitleText = component.title {
|
||||
let subtitle = subtitle.update(
|
||||
component: MultilineTextComponent(text: .plain(NSAttributedString(string: subtitleText, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor)), truncationType: .end, maximumNumberOfLines: 1),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0 - 8.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
context.add(subtitle
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + subtitle.size.height / 2.0 - 9.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + 4.0 + subtitle.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += subtitle.size.height
|
||||
contentSize.height += 8.0
|
||||
} else {
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + title.size.height / 2.0))
|
||||
)
|
||||
contentSize.height += title.size.height
|
||||
contentSize.height += 4.0 + subtitle.size.height
|
||||
contentSize.height += 24.0
|
||||
} else {
|
||||
contentSize.height += 40.0
|
||||
}
|
||||
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
for item in component.items {
|
||||
items.append(AnyComponentWithIdentity(id: item.title, component: AnyComponent(ListActionItemComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
|
|
@ -195,6 +156,7 @@ private final class SheetPageContent: CombinedComponent {
|
|||
let section = section.update(
|
||||
component: ListSectionComponent(
|
||||
theme: theme,
|
||||
style: .glass,
|
||||
header: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.subtitle.uppercased(),
|
||||
|
|
@ -315,6 +277,7 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
static var body: Body {
|
||||
let navigation = Child(NavigationStackComponent<EnvironmentType>.self)
|
||||
let backButton = Child(GlassBarButtonComponent.self)
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
|
|
@ -360,9 +323,6 @@ private final class SheetContent: CombinedComponent {
|
|||
},
|
||||
action: { item in
|
||||
action(item)
|
||||
},
|
||||
pop: {
|
||||
component.dismiss()
|
||||
}
|
||||
)
|
||||
)))
|
||||
|
|
@ -377,10 +337,6 @@ private final class SheetContent: CombinedComponent {
|
|||
},
|
||||
action: { item in
|
||||
action(item)
|
||||
},
|
||||
pop: { [weak state] in
|
||||
state?.pushedOptions.removeLast()
|
||||
update(.spring(duration: 0.45))
|
||||
}
|
||||
)
|
||||
)))
|
||||
|
|
@ -402,10 +358,39 @@ private final class SheetContent: CombinedComponent {
|
|||
)
|
||||
context.add(navigation
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: navigation.size.height / 2.0))
|
||||
.clipsToBounds(true)
|
||||
.cornerRadius(8.0)
|
||||
)
|
||||
contentSize.height += navigation.size.height
|
||||
|
||||
let isBack = items.count > 1
|
||||
let barButtonSize = CGSize(width: 44.0, height: 44.0)
|
||||
let backButton = backButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: barButtonSize,
|
||||
backgroundColor: nil,
|
||||
isDark: environment.theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: isBack ? "Navigation/Back" : "Navigation/Close",
|
||||
tintColor: environment.theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
action: { [weak state] _ in
|
||||
if isBack {
|
||||
state?.pushedOptions.removeLast()
|
||||
update(.spring(duration: 0.45))
|
||||
} else {
|
||||
component.dismiss()
|
||||
}
|
||||
}
|
||||
),
|
||||
environment: {},
|
||||
availableSize: barButtonSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(backButton
|
||||
.position(CGPoint(x: 16.0 + backButton.size.width / 2.0, y: 16.0 + backButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
return contentSize
|
||||
}
|
||||
|
|
@ -495,8 +480,10 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
state?.updated(transition: transition)
|
||||
}
|
||||
)),
|
||||
style: .glass,
|
||||
backgroundColor: .color(environment.theme.list.modalBlocksBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
clipsContent: true,
|
||||
externalState: sheetExternalState,
|
||||
animateOut: animateOut
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1552,6 +1552,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||
videoVolume: nil,
|
||||
additionalVideoPath: nil,
|
||||
additionalVideoIsDual: false,
|
||||
additionalVideoMirroringChanges: [],
|
||||
additionalVideoPosition: nil,
|
||||
additionalVideoScale: nil,
|
||||
additionalVideoRotation: nil,
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ final class LiveStreamMediaSource {
|
|||
videoVolume: nil,
|
||||
additionalVideoPath: nil,
|
||||
additionalVideoIsDual: true,
|
||||
additionalVideoMirroringChanges: [],
|
||||
additionalVideoPosition: nil,
|
||||
additionalVideoScale: 1.625,
|
||||
additionalVideoRotation: 0.0,
|
||||
|
|
|
|||
|
|
@ -139,7 +139,6 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
|||
}, displaySlowmodeTooltip: { _, _ in
|
||||
}, displaySendMessageOptions: { _, _ in
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, unarchivePeer: {
|
||||
}, scrollToTop: {
|
||||
|
|
|
|||
|
|
@ -664,8 +664,6 @@ public final class ChatTextInputPanelComponent: Component {
|
|||
},
|
||||
openScheduledMessages: {
|
||||
},
|
||||
openPeersNearby: {
|
||||
},
|
||||
displaySearchResultsTooltip: { _, _ in
|
||||
},
|
||||
unarchivePeer: {
|
||||
|
|
|
|||
|
|
@ -2792,6 +2792,70 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
|
|||
self.present = present
|
||||
}
|
||||
|
||||
private func presentStickerPackActionOverlay(_ actions: [StickerPackScreenActionResult], interaction: Interaction) {
|
||||
guard let action = actions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = interaction.navigationController() {
|
||||
for controller in navigationController.overlayControllers {
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitActionAndReplacementAnimation()
|
||||
animateInAsReplacement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if let forceTheme = self.forceTheme {
|
||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||
}
|
||||
|
||||
let controller: UndoOverlayController
|
||||
switch action.action {
|
||||
case .add:
|
||||
controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .stickersModified(
|
||||
title: presentationData.strings.StickerPackActionInfo_AddedTitle,
|
||||
text: presentationData.strings.StickerPackActionInfo_AddedText(action.info.title).string,
|
||||
undo: false,
|
||||
info: action.info,
|
||||
topItem: action.items.first,
|
||||
context: self.context
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: animateInAsReplacement,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
case let .remove(positionInList):
|
||||
controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .stickersModified(
|
||||
title: presentationData.strings.StickerPackActionInfo_RemovedTitle,
|
||||
text: presentationData.strings.StickerPackActionInfo_RemovedText(action.info.title).string,
|
||||
undo: true,
|
||||
info: action.info,
|
||||
topItem: action.items.first,
|
||||
context: self.context
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: animateInAsReplacement,
|
||||
action: { [weak self] overlayAction in
|
||||
if case .undo = overlayAction {
|
||||
let _ = self?.context.engine.stickers.addStickerPackInteractively(info: action.info, items: action.items, positionInList: positionInList).start()
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
interaction.presentGlobalOverlayController(controller, nil)
|
||||
}
|
||||
|
||||
public func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, CALayer, TelegramMediaFile)?) {
|
||||
self.viewRecords = self.viewRecords.filter({ $0.view != nil })
|
||||
|
||||
|
|
@ -3062,7 +3126,12 @@ public final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
|
|||
let controller = strongSelf.context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], actionTitle: nil, isEditing: false, expandIfNeeded: false, parentNavigationController: interaction.navigationController(), sendSticker: { file, sourceView, sourceRect in
|
||||
sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil)
|
||||
return true
|
||||
}, actionPerformed: nil)
|
||||
}, actionPerformed: { [weak self] actions in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.presentStickerPackActionOverlay(actions, interaction: interaction)
|
||||
})
|
||||
|
||||
interaction.navigationController()?.view.window?.endEditing(true)
|
||||
interaction.presentController(controller, nil)
|
||||
|
|
|
|||
|
|
@ -428,7 +428,9 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
return false
|
||||
}
|
||||
},
|
||||
actionPerformed: nil
|
||||
actionPerformed: { [weak self] actions in
|
||||
self?.presentStickerPackActionOverlay(actions)
|
||||
}
|
||||
)
|
||||
strongSelf.interaction.presentController(controller, nil)
|
||||
}
|
||||
|
|
@ -529,6 +531,73 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||
self.installDisposable.dispose()
|
||||
}
|
||||
|
||||
private func presentStickerPackActionOverlay(_ actions: [StickerPackScreenActionResult]) {
|
||||
guard let action = actions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = self.interaction.getNavigationController() {
|
||||
for controller in navigationController.overlayControllers {
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitActionAndReplacementAnimation()
|
||||
animateInAsReplacement = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: self.theme)
|
||||
let controller: UndoOverlayController
|
||||
switch action.action {
|
||||
case .add:
|
||||
self.setPackInstalledState(id: action.info.id, installed: true)
|
||||
controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .stickersModified(
|
||||
title: presentationData.strings.StickerPackActionInfo_AddedTitle,
|
||||
text: presentationData.strings.StickerPackActionInfo_AddedText(action.info.title).string,
|
||||
undo: false,
|
||||
info: action.info,
|
||||
topItem: action.items.first,
|
||||
context: self.context
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: animateInAsReplacement,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
case let .remove(positionInList):
|
||||
self.setPackInstalledState(id: action.info.id, installed: false)
|
||||
controller = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .stickersModified(
|
||||
title: presentationData.strings.StickerPackActionInfo_RemovedTitle,
|
||||
text: presentationData.strings.StickerPackActionInfo_RemovedText(action.info.title).string,
|
||||
undo: true,
|
||||
info: action.info,
|
||||
topItem: action.items.first,
|
||||
context: self.context
|
||||
),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: animateInAsReplacement,
|
||||
action: { [weak self] overlayAction in
|
||||
if case .undo = overlayAction {
|
||||
let _ = self?.context.engine.stickers.addStickerPackInteractively(info: action.info, items: action.items, positionInList: positionInList).start()
|
||||
self?.setPackInstalledState(id: action.info.id, installed: true)
|
||||
}
|
||||
return true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if let navigationController = self.interaction.getNavigationController() {
|
||||
navigationController.presentOverlay(controller: controller)
|
||||
} else {
|
||||
self.interaction.presentController(controller, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func updateText(_ text: String, languageCode: String?) {
|
||||
if self.selectedPack != nil {
|
||||
self.clearSelectedPack(applySearchResults: false)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ swift_library(
|
|||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/Components/ResizableSheetComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedCounterComponent",
|
||||
"//submodules/AvatarNode",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -416,11 +416,11 @@ public final class MediaEditor {
|
|||
return (artist: artist, title: title)
|
||||
}
|
||||
|
||||
func playerAndThumbnails(_ signal: Signal<AVPlayer?, NoError>, mirror: Bool = false) -> Signal<(AVPlayer, [UIImage], Double)?, NoError> {
|
||||
func playerAndThumbnails(_ signal: Signal<AVPlayer?, NoError>, mirroringChanges: [VideoMirroringChange] = []) -> Signal<(AVPlayer, [UIImage], Double)?, NoError> {
|
||||
return signal
|
||||
|> mapToSignal { player -> Signal<(AVPlayer, [UIImage], Double)?, NoError> in
|
||||
if let player, let asset = player.currentItem?.asset {
|
||||
return videoFrames(asset: asset, count: framesCount, mirror: mirror)
|
||||
return videoFrames(asset: asset, count: framesCount, mirroringChanges: mirroringChanges)
|
||||
|> map { framesAndUpdateTimestamp in
|
||||
return (player, framesAndUpdateTimestamp.0, framesAndUpdateTimestamp.1)
|
||||
}
|
||||
|
|
@ -434,7 +434,8 @@ public final class MediaEditor {
|
|||
playerAndThumbnails(self.playerPromise.get()),
|
||||
self.additionalPlayersPromise.get()
|
||||
|> mapToSignal { players in
|
||||
return combineLatest(players.compactMap { playerAndThumbnails(.single($0), mirror: true) })
|
||||
let mirroringChanges = self.values.additionalVideoMirroringChanges
|
||||
return combineLatest(players.compactMap { playerAndThumbnails(.single($0), mirroringChanges: mirroringChanges) })
|
||||
},
|
||||
self.audioPlayerPromise.get(),
|
||||
self.valuesPromise.get(),
|
||||
|
|
@ -566,6 +567,7 @@ public final class MediaEditor {
|
|||
videoVolume: 1.0,
|
||||
additionalVideoPath: nil,
|
||||
additionalVideoIsDual: false,
|
||||
additionalVideoMirroringChanges: [],
|
||||
additionalVideoPosition: nil,
|
||||
additionalVideoScale: nil,
|
||||
additionalVideoRotation: nil,
|
||||
|
|
@ -629,9 +631,7 @@ public final class MediaEditor {
|
|||
return
|
||||
}
|
||||
let additionalTexture = additionalImage.flatMap { loadTexture(image: $0, device: device) }
|
||||
if mirror {
|
||||
self.renderer.videoFinishPass.additionalTextureRotation = .rotate0DegreesMirrored
|
||||
}
|
||||
self.renderer.videoFinishPass.additionalTextureRotation = mirror ? .rotate0DegreesMirrored : .rotate0Degrees
|
||||
let hasTransparency = imageHasTransparency(image)
|
||||
self.renderer.consume(main: .texture(texture, time, hasTransparency, nil, 1.0, .zero), additionals: additionalTexture.flatMap { [.texture($0, time, false, nil, 1.0, .zero)] } ?? [], render: true, displayEnabled: false)
|
||||
}
|
||||
|
|
@ -1821,9 +1821,9 @@ public final class MediaEditor {
|
|||
self.updateAdditionalVideoPlaybackRange()
|
||||
}
|
||||
|
||||
public func setAdditionalVideo(_ path: String?, isDual: Bool = false, positionChanges: [VideoPositionChange]) {
|
||||
public func setAdditionalVideo(_ path: String?, isDual: Bool = false, mirroringChanges: [VideoMirroringChange] = [], positionChanges: [VideoPositionChange]) {
|
||||
self.updateValues(mode: .skipRendering) { values in
|
||||
var values = values.withUpdatedAdditionalVideo(path: path, isDual: isDual, positionChanges: positionChanges)
|
||||
var values = values.withUpdatedAdditionalVideo(path: path, isDual: isDual, mirroringChanges: mirroringChanges, positionChanges: positionChanges)
|
||||
if path == nil {
|
||||
values = values.withUpdatedAdditionalVideoOffset(nil).withUpdatedAdditionalVideoTrimRange(nil).withUpdatedAdditionalVideoVolume(nil)
|
||||
}
|
||||
|
|
@ -2414,7 +2414,29 @@ public final class MediaEditor {
|
|||
|
||||
}
|
||||
|
||||
public func videoFrames(asset: AVAsset?, count: Int, initialPlaceholder: UIImage? = nil, initialTimestamp: Double? = nil, mirror: Bool = false) -> Signal<([UIImage], Double), NoError> {
|
||||
private func videoFrameMirroring(at timestamp: Double, changes: [VideoMirroringChange]) -> Bool {
|
||||
guard let firstChange = changes.first else {
|
||||
return false
|
||||
}
|
||||
var isMirrored = firstChange.isMirrored
|
||||
for change in changes {
|
||||
if timestamp >= change.timestamp {
|
||||
isMirrored = change.isMirrored
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return isMirrored
|
||||
}
|
||||
|
||||
private func mirroredVideoFrame(_ image: UIImage, mirrored: Bool) -> UIImage {
|
||||
guard mirrored, let cgImage = image.cgImage else {
|
||||
return image
|
||||
}
|
||||
return UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored)
|
||||
}
|
||||
|
||||
public func videoFrames(asset: AVAsset?, count: Int, initialPlaceholder: UIImage? = nil, initialTimestamp: Double? = nil, mirroringChanges: [VideoMirroringChange] = []) -> Signal<([UIImage], Double), NoError> {
|
||||
func blurredImage(_ image: UIImage) -> UIImage? {
|
||||
guard let image = image.cgImage else {
|
||||
return nil
|
||||
|
|
@ -2481,35 +2503,46 @@ public func videoFrames(asset: AVAsset?, count: Int, initialPlaceholder: UIImage
|
|||
} else {
|
||||
firstFrame = generateSingleColorImage(size: CGSize(width: 24.0, height: 36.0), color: .black)!
|
||||
}
|
||||
firstFrame = mirroredVideoFrame(firstFrame, mirrored: videoFrameMirroring(at: 0.0, changes: mirroringChanges))
|
||||
|
||||
if let asset {
|
||||
return Signal { subscriber in
|
||||
subscriber.putNext((Array(repeating: firstFrame, count: count), initialTimestamp ?? CACurrentMediaTime()))
|
||||
|
||||
var timestamps: [NSValue] = []
|
||||
var requestedTimes: [CMTime] = []
|
||||
let duration = asset.duration.seconds
|
||||
let interval = duration / Double(count)
|
||||
for i in 0 ..< count {
|
||||
timestamps.append(NSValue(time: CMTime(seconds: Double(i) * interval, preferredTimescale: CMTimeScale(1000))))
|
||||
let requestedTime = CMTime(seconds: Double(i) * interval, preferredTimescale: CMTimeScale(1000))
|
||||
requestedTimes.append(requestedTime)
|
||||
timestamps.append(NSValue(time: requestedTime))
|
||||
}
|
||||
|
||||
var updatedFrames: [UIImage] = []
|
||||
imageGenerator?.generateCGImagesAsynchronously(forTimes: timestamps) { _, image, _, _, _ in
|
||||
var updatedFrames = Array(repeating: firstFrame, count: count)
|
||||
var remainingFrames = count
|
||||
imageGenerator?.generateCGImagesAsynchronously(forTimes: timestamps) { requestedTime, image, actualTime, _, _ in
|
||||
guard let frameIndex = requestedTimes.firstIndex(where: { CMTimeCompare($0, requestedTime) == 0 }) else {
|
||||
return
|
||||
}
|
||||
if let image {
|
||||
updatedFrames.append(UIImage(cgImage: image, scale: 1.0, orientation: mirror ? .upMirrored : .up))
|
||||
if updatedFrames.count == count {
|
||||
let frameTimestamp = actualTime.seconds.isFinite ? actualTime.seconds : requestedTime.seconds
|
||||
updatedFrames[frameIndex] = mirroredVideoFrame(
|
||||
UIImage(cgImage: image),
|
||||
mirrored: videoFrameMirroring(at: frameTimestamp, changes: mirroringChanges)
|
||||
)
|
||||
remainingFrames -= 1
|
||||
if remainingFrames == 0 {
|
||||
subscriber.putNext((updatedFrames, CACurrentMediaTime()))
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
var tempFrames = updatedFrames
|
||||
for _ in 0 ..< count - updatedFrames.count {
|
||||
tempFrames.append(firstFrame)
|
||||
}
|
||||
subscriber.putNext((tempFrames, CACurrentMediaTime()))
|
||||
subscriber.putNext((updatedFrames, CACurrentMediaTime()))
|
||||
}
|
||||
} else {
|
||||
if let previous = updatedFrames.last {
|
||||
updatedFrames.append(previous)
|
||||
} else if remainingFrames > 0 {
|
||||
remainingFrames -= 1
|
||||
if remainingFrames == 0 {
|
||||
subscriber.putNext((updatedFrames, CACurrentMediaTime()))
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public enum EditorToolKey: Int32, CaseIterable {
|
|||
case blur
|
||||
case curves
|
||||
case stickerOutline
|
||||
|
||||
|
||||
static let adjustmentToolsKeys: [EditorToolKey] = [
|
||||
.enhance,
|
||||
.brightness,
|
||||
|
|
@ -44,7 +44,7 @@ public struct VideoPositionChange: Codable, Equatable {
|
|||
case translationFrom
|
||||
case timestamp
|
||||
}
|
||||
|
||||
|
||||
public let additional: Bool
|
||||
public let translationFrom: CGPoint?
|
||||
public let timestamp: Double
|
||||
|
|
@ -60,6 +60,24 @@ public struct VideoPositionChange: Codable, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
public struct VideoMirroringChange: Codable, Equatable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case isMirrored
|
||||
case timestamp
|
||||
}
|
||||
|
||||
public let isMirrored: Bool
|
||||
public let timestamp: Double
|
||||
|
||||
public init(
|
||||
isMirrored: Bool,
|
||||
timestamp: Double
|
||||
) {
|
||||
self.isMirrored = isMirrored
|
||||
self.timestamp = timestamp
|
||||
}
|
||||
}
|
||||
|
||||
public struct MediaAudioTrack: Codable, Equatable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case path
|
||||
|
|
@ -291,6 +309,9 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
if lhs.additionalVideoIsDual != rhs.additionalVideoIsDual {
|
||||
return false
|
||||
}
|
||||
if lhs.additionalVideoMirroringChanges != rhs.additionalVideoMirroringChanges {
|
||||
return false
|
||||
}
|
||||
if lhs.additionalVideoPosition != rhs.additionalVideoPosition {
|
||||
return false
|
||||
}
|
||||
|
|
@ -402,6 +423,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
case videoVolume
|
||||
case additionalVideoPath
|
||||
case additionalVideoIsDual
|
||||
case additionalVideoMirroringChanges
|
||||
case additionalVideoPosition
|
||||
case additionalVideoScale
|
||||
case additionalVideoRotation
|
||||
|
|
@ -591,6 +613,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
|
||||
public let additionalVideoPath: String?
|
||||
public let additionalVideoIsDual: Bool
|
||||
public let additionalVideoMirroringChanges: [VideoMirroringChange]
|
||||
public let additionalVideoPosition: CGPoint?
|
||||
public let additionalVideoScale: CGFloat?
|
||||
public let additionalVideoRotation: CGFloat?
|
||||
|
|
@ -660,6 +683,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
videoVolume: CGFloat?,
|
||||
additionalVideoPath: String?,
|
||||
additionalVideoIsDual: Bool,
|
||||
additionalVideoMirroringChanges: [VideoMirroringChange],
|
||||
additionalVideoPosition: CGPoint?,
|
||||
additionalVideoScale: CGFloat?,
|
||||
additionalVideoRotation: CGFloat?,
|
||||
|
|
@ -700,6 +724,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
self.videoVolume = videoVolume
|
||||
self.additionalVideoPath = additionalVideoPath
|
||||
self.additionalVideoIsDual = additionalVideoIsDual
|
||||
self.additionalVideoMirroringChanges = additionalVideoMirroringChanges
|
||||
self.additionalVideoPosition = additionalVideoPosition
|
||||
self.additionalVideoScale = additionalVideoScale
|
||||
self.additionalVideoRotation = additionalVideoRotation
|
||||
|
|
@ -755,6 +780,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
|
||||
self.additionalVideoPath = try container.decodeIfPresent(String.self, forKey: .additionalVideoPath)
|
||||
self.additionalVideoIsDual = try container.decodeIfPresent(Bool.self, forKey: .additionalVideoIsDual) ?? false
|
||||
self.additionalVideoMirroringChanges = try container.decodeIfPresent([VideoMirroringChange].self, forKey: .additionalVideoMirroringChanges) ?? []
|
||||
self.additionalVideoPosition = try container.decodeIfPresent(CGPoint.self, forKey: .additionalVideoPosition)
|
||||
self.additionalVideoScale = try container.decodeIfPresent(CGFloat.self, forKey: .additionalVideoScale)
|
||||
self.additionalVideoRotation = try container.decodeIfPresent(CGFloat.self, forKey: .additionalVideoRotation)
|
||||
|
|
@ -829,6 +855,7 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
|
||||
try container.encodeIfPresent(self.additionalVideoPath, forKey: .additionalVideoPath)
|
||||
try container.encodeIfPresent(self.additionalVideoIsDual, forKey: .additionalVideoIsDual)
|
||||
try container.encodeIfPresent(self.additionalVideoMirroringChanges, forKey: .additionalVideoMirroringChanges)
|
||||
try container.encodeIfPresent(self.additionalVideoPosition, forKey: .additionalVideoPosition)
|
||||
try container.encodeIfPresent(self.additionalVideoScale, forKey: .additionalVideoScale)
|
||||
try container.encodeIfPresent(self.additionalVideoRotation, forKey: .additionalVideoRotation)
|
||||
|
|
@ -869,125 +896,125 @@ public final class MediaEditorValues: Codable, Equatable, CustomStringConvertibl
|
|||
}
|
||||
|
||||
public func makeCopy() -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedCropRect(cropRect: CGRect, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
|
||||
func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedVideoVolume(_ videoVolume: CGFloat?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAdditionalVideo(path: String?, isDual: Bool, positionChanges: [VideoPositionChange]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
func withUpdatedAdditionalVideo(path: String?, isDual: Bool, mirroringChanges: [VideoMirroringChange], positionChanges: [VideoPositionChange]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoMirroringChanges: mirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedAdditionalVideoPositionChanges(additionalVideoPositionChanges: [VideoPositionChange]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAdditionalVideoTrimRange(_ additionalVideoTrimRange: Range<Double>?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
|
||||
func withUpdatedAdditionalVideoOffset(_ additionalVideoOffset: Double?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAdditionalVideoVolume(_ additionalVideoVolume: CGFloat?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedCollage(_ collage: [VideoCollageItem]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedVideoTrimRange(_ videoTrimRange: Range<Double>) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedMaskDrawing(maskDrawing: UIImage?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAudioTrack(_ audioTrack: MediaAudioTrack?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAudioTrackTrimRange(_ audioTrackTrimRange: Range<Double>?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAudioTrackOffset(_ audioTrackOffset: Double?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAudioTrackVolume(_ audioTrackVolume: CGFloat?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedAudioTrackSamples(_ audioTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedCollageTrackSamples(_ collageTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
func withUpdatedNightTheme(_ nightTheme: Bool) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedEntities(_ entities: [CodableDrawingEntity]) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedCoverImageTimestamp(_ coverImageTimestamp: Double?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedCoverDimensions(_ coverDimensions: CGSize?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: coverDimensions, qualityPreset: self.qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: coverDimensions, qualityPreset: self.qualityPreset)
|
||||
}
|
||||
|
||||
public func withUpdatedQualityPreset(_ qualityPreset: MediaQualityPreset?) -> MediaEditorValues {
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: qualityPreset)
|
||||
return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoBounce: self.videoBounce, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoMirroringChanges: self.additionalVideoMirroringChanges, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, collage: self.collage, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, collageTrackSamples: self.collageTrackSamples, coverImageTimestamp: self.coverImageTimestamp, coverDimensions: self.coverDimensions, qualityPreset: qualityPreset)
|
||||
}
|
||||
|
||||
public var resultDimensions: PixelDimensions {
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ public final class MediaEditorVideoExport {
|
|||
} else if let additionalPath = self.configuration.values.additionalVideoPath {
|
||||
let asset = AVURLAsset(url: URL(fileURLWithPath: additionalPath))
|
||||
additionalAsset = asset
|
||||
signals = [.single(.video(asset: asset, rect: nil, scale: 1.0, offset: .zero, rotation: textureRotatonForAVAsset(asset, mirror: true), duration: asset.duration.seconds, trimRange: nil, trimOffset: nil, volume: nil))]
|
||||
signals = [.single(.video(asset: asset, rect: nil, scale: 1.0, offset: .zero, rotation: textureRotatonForAVAsset(asset, mirror: false), duration: asset.duration.seconds, trimRange: nil, trimOffset: nil, volume: nil))]
|
||||
}
|
||||
|
||||
var audioAsset: AVAsset?
|
||||
|
|
|
|||
|
|
@ -264,8 +264,7 @@ private class VideoInputContext: NSObject, InputContext, AVPlayerItemOutputPullD
|
|||
|
||||
super.init()
|
||||
|
||||
//TODO: mirror if self.additionalPlayer == nil && self.mirror
|
||||
self.textureRotation = textureRotatonForAVAsset(self.playerItem.asset, mirror: rect == nil ? additional : false)
|
||||
self.textureRotation = textureRotatonForAVAsset(self.playerItem.asset, mirror: false)
|
||||
|
||||
let colorProperties: [String: Any] = [
|
||||
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ private func lookupSpringValue(_ t: CGFloat) -> CGFloat {
|
|||
for i in 0 ..< table.count - 2 {
|
||||
let lhs = table[i]
|
||||
let rhs = table[i + 1]
|
||||
|
||||
|
||||
if t >= lhs.0 && t <= rhs.0 {
|
||||
let fraction = (t - lhs.0) / (rhs.0 - lhs.0)
|
||||
let value = lhs.1 + fraction * (rhs.1 - lhs.1)
|
||||
|
|
@ -289,7 +289,7 @@ struct VideoEncodeParameters {
|
|||
|
||||
final class VideoFinishPass: RenderPass {
|
||||
private var cachedTexture: MTLTexture?
|
||||
|
||||
|
||||
var gradientPipelineState: MTLRenderPipelineState?
|
||||
|
||||
var mainPipelineState: MTLRenderPipelineState?
|
||||
|
|
@ -444,9 +444,8 @@ final class VideoFinishPass: RenderPass {
|
|||
if let position = values.additionalVideoPosition, let scale = values.additionalVideoScale, let rotation = values.additionalVideoRotation {
|
||||
self.additionalPosition = VideoFinishPass.VideoPosition(position: position, size: CGSize(width: 1080.0 / 4.0, height: 1440.0 / 4.0), scale: scale, rotation: rotation, mirroring: false, baseScale: self.additionalPosition.baseScale)
|
||||
}
|
||||
if !values.additionalVideoPositionChanges.isEmpty {
|
||||
self.videoPositionChanges = values.additionalVideoPositionChanges
|
||||
}
|
||||
self.videoPositionChanges = values.additionalVideoPositionChanges
|
||||
self.additionalVideoMirroringChanges = values.additionalVideoMirroringChanges
|
||||
self.videoDuration = videoDuration
|
||||
self.additionalVideoDuration = additionalVideoDuration
|
||||
self.videoRange = values.videoTrimRange
|
||||
|
|
@ -486,12 +485,30 @@ final class VideoFinishPass: RenderPass {
|
|||
private var isSticker = true
|
||||
private var coverDimensions: CGSize?
|
||||
private var videoPositionChanges: [VideoPositionChange] = []
|
||||
private var additionalVideoMirroringChanges: [VideoMirroringChange] = []
|
||||
private var videoDuration: Double?
|
||||
private var additionalVideoDuration: Double?
|
||||
private var videoRange: Range<Double>?
|
||||
private var additionalVideoRange: Range<Double>?
|
||||
private var additionalVideoOffset: Double?
|
||||
|
||||
private func additionalVideoMirroring(at timestamp: Double) -> Bool {
|
||||
guard let firstChange = self.additionalVideoMirroringChanges.first else {
|
||||
return false
|
||||
}
|
||||
let assetTimestamp = timestamp + (self.additionalVideoOffset ?? 0.0)
|
||||
|
||||
var isMirrored = firstChange.isMirrored
|
||||
for change in self.additionalVideoMirroringChanges {
|
||||
if assetTimestamp >= change.timestamp {
|
||||
isMirrored = change.isMirrored
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return isMirrored
|
||||
}
|
||||
|
||||
enum VideoType {
|
||||
case main
|
||||
case additional
|
||||
|
|
@ -566,6 +583,7 @@ final class VideoFinishPass: RenderPass {
|
|||
|
||||
var mainPosition = self.mainPosition
|
||||
var additionalPosition = self.additionalPosition
|
||||
additionalPosition = VideoPosition(position: additionalPosition.position, size: additionalPosition.size, scale: additionalPosition.scale, rotation: additionalPosition.rotation, mirroring: self.additionalVideoMirroring(at: timestamp), baseScale: additionalPosition.baseScale)
|
||||
var disappearingPosition = self.mainPosition
|
||||
|
||||
var transitionFraction = 1.0
|
||||
|
|
@ -588,7 +606,7 @@ final class VideoFinishPass: RenderPass {
|
|||
backgroundTexture = additionalInput
|
||||
backgroundTextureRotation = self.additionalTextureRotation
|
||||
|
||||
mainPosition = VideoPosition(position: mainPosition.position, size: CGSize(width: 1440.0, height: 1920.0), scale: mainPosition.scale, rotation: mainPosition.rotation, mirroring: mainPosition.mirroring, baseScale: mainPosition.baseScale)
|
||||
mainPosition = VideoPosition(position: mainPosition.position, size: CGSize(width: 1440.0, height: 1920.0), scale: mainPosition.scale, rotation: mainPosition.rotation, mirroring: additionalPosition.mirroring, baseScale: mainPosition.baseScale)
|
||||
additionalPosition = VideoPosition(position: additionalPosition.position, size: CGSize(width: 1080.0 / 4.0, height: 1920.0 / 4.0), scale: additionalPosition.scale, rotation: additionalPosition.rotation, mirroring: additionalPosition.mirroring, baseScale: additionalPosition.baseScale)
|
||||
|
||||
foregroundTexture = mainInput
|
||||
|
|
|
|||
|
|
@ -9,6 +9,21 @@ import Photos
|
|||
import MediaEditor
|
||||
import DrawingUI
|
||||
|
||||
private func additionalVideoMirroring(at timestamp: Double, changes: [VideoMirroringChange]) -> Bool {
|
||||
guard let firstChange = changes.first else {
|
||||
return false
|
||||
}
|
||||
var isMirrored = firstChange.isMirrored
|
||||
for change in changes {
|
||||
if timestamp >= change.timestamp {
|
||||
isMirrored = change.isMirrored
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return isMirrored
|
||||
}
|
||||
|
||||
extension MediaEditorScreenImpl {
|
||||
func requestStoryCompletion(animated: Bool) {
|
||||
guard let mediaEditor = self.node.mediaEditor, !self.didComplete else {
|
||||
|
|
@ -161,6 +176,10 @@ extension MediaEditorScreenImpl {
|
|||
} else {
|
||||
firstFrameTime = CMTime(seconds: mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, preferredTimescale: CMTimeScale(60))
|
||||
}
|
||||
let additionalFirstFrameTime = CMTime(
|
||||
seconds: max(0.0, firstFrameTime.seconds + (mediaEditor.values.additionalVideoOffset ?? 0.0)),
|
||||
preferredTimescale: firstFrameTime.timescale
|
||||
)
|
||||
let videoResult: Signal<MediaResult.VideoResult, NoError>
|
||||
var videoIsMirrored = false
|
||||
let duration: Double
|
||||
|
|
@ -210,7 +229,7 @@ extension MediaEditorScreenImpl {
|
|||
let avAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath))
|
||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: additionalFirstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in
|
||||
if let additionalCGImage {
|
||||
subscriber.putNext((UIImage(cgImage: cgImage), UIImage(cgImage: additionalCGImage)))
|
||||
subscriber.putCompletion()
|
||||
|
|
@ -302,7 +321,7 @@ extension MediaEditorScreenImpl {
|
|||
let avAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath))
|
||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: additionalFirstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in
|
||||
if let additionalCGImage {
|
||||
subscriber.putNext((UIImage(cgImage: cgImage), UIImage(cgImage: additionalCGImage)))
|
||||
subscriber.putCompletion()
|
||||
|
|
@ -328,7 +347,7 @@ extension MediaEditorScreenImpl {
|
|||
let avAsset = AVURLAsset(url: URL(fileURLWithPath: additionalPath))
|
||||
let avAssetGenerator = AVAssetImageGenerator(asset: avAsset)
|
||||
avAssetGenerator.appliesPreferredTrackTransform = true
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: firstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in
|
||||
avAssetGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: additionalFirstFrameTime)], completionHandler: { _, additionalCGImage, _, _, _ in
|
||||
if let additionalCGImage {
|
||||
subscriber.putNext((image, UIImage(cgImage: additionalCGImage)))
|
||||
subscriber.putCompletion()
|
||||
|
|
@ -437,7 +456,15 @@ extension MediaEditorScreenImpl {
|
|||
let (image, additionalImage) = images
|
||||
var currentImage = mediaEditor.resultImage
|
||||
if let image {
|
||||
mediaEditor.replaceSource(image, additionalImage: additionalImage, time: firstFrameTime, mirror: true)
|
||||
mediaEditor.replaceSource(
|
||||
image,
|
||||
additionalImage: additionalImage,
|
||||
time: firstFrameTime,
|
||||
mirror: additionalVideoMirroring(
|
||||
at: additionalFirstFrameTime.seconds,
|
||||
changes: mediaEditor.values.additionalVideoMirroringChanges
|
||||
)
|
||||
)
|
||||
if let updatedImage = mediaEditor.getResultImage(mirror: videoIsMirrored) {
|
||||
currentImage = updatedImage
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ swift_library(
|
|||
"//submodules/ChatListUI",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemView",
|
||||
"//submodules/ChatPresentationInterfaceState",
|
||||
"//submodules/TelegramUI/Components/ChatScheduleTimeController",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
"//submodules/TelegramUI/Components/ChatTitleView",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ final class PeerInfoInteraction {
|
|||
let editingOpenAutoremoveMesages: () -> Void
|
||||
let openPermissions: () -> Void
|
||||
let openLocation: () -> Void
|
||||
let editingOpenSetupLocation: () -> Void
|
||||
let openPeerInfo: (EnginePeer, Bool) -> Void
|
||||
let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void
|
||||
let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void
|
||||
|
|
@ -120,7 +119,6 @@ final class PeerInfoInteraction {
|
|||
editingOpenAutoremoveMesages: @escaping () -> Void,
|
||||
openPermissions: @escaping () -> Void,
|
||||
openLocation: @escaping () -> Void,
|
||||
editingOpenSetupLocation: @escaping () -> Void,
|
||||
openPeerInfo: @escaping (EnginePeer, Bool) -> Void,
|
||||
performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void,
|
||||
openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode, CGRect?) -> Void,
|
||||
|
|
@ -198,7 +196,6 @@ final class PeerInfoInteraction {
|
|||
self.editingOpenAutoremoveMesages = editingOpenAutoremoveMesages
|
||||
self.openPermissions = openPermissions
|
||||
self.openLocation = openLocation
|
||||
self.editingOpenSetupLocation = editingOpenSetupLocation
|
||||
self.openPeerInfo = openPeerInfo
|
||||
self.performMemberAction = performMemberAction
|
||||
self.openPeerInfoContextMenu = openPeerInfoContextMenu
|
||||
|
|
|
|||
|
|
@ -1332,7 +1332,6 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s
|
|||
let ItemRecentActions = 111
|
||||
let ItemLocationHeader = 112
|
||||
let ItemLocation = 113
|
||||
let ItemLocationSetup = 114
|
||||
let ItemDeleteGroup = 115
|
||||
let ItemReactions = 116
|
||||
let ItemTopics = 117
|
||||
|
|
@ -1356,11 +1355,6 @@ func editingItems(data: PeerInfoScreenData?, boostStatus: ChannelBoostStatus?, s
|
|||
interaction.openLocation()
|
||||
}
|
||||
))
|
||||
if cachedData.flags.contains(.canChangePeerGeoLocation) {
|
||||
items[.groupLocation]!.append(PeerInfoScreenActionItem(id: ItemLocationSetup, text: presentationData.strings.Group_Location_ChangeLocation, action: {
|
||||
interaction.editingOpenSetupLocation()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if isCreator || (channel.adminRights != nil && channel.hasPermission(.pinMessages)) {
|
||||
|
|
|
|||
|
|
@ -518,9 +518,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
openLocation: { [weak self] in
|
||||
self?.openLocation()
|
||||
},
|
||||
editingOpenSetupLocation: { [weak self] in
|
||||
self?.editingOpenSetupLocation()
|
||||
},
|
||||
openPeerInfo: { [weak self] peer, isMember in
|
||||
self?.openPeerInfo(peer: peer, isMember: isMember)
|
||||
},
|
||||
|
|
@ -4299,40 +4296,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
let controller = LocationViewController(context: context, updatedPresentationData: self.controller?.updatedPresentationData, subject: EngineMessage(message), params: controllerParams)
|
||||
self.controller?.push(controller)
|
||||
}
|
||||
|
||||
private func editingOpenSetupLocation() {
|
||||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = LocationPickerController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, mode: .pick, completion: { [weak self] location, _, _, address, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let addressSignal: Signal<String, NoError>
|
||||
if let address = address {
|
||||
addressSignal = .single(address)
|
||||
} else {
|
||||
addressSignal = reverseGeocodeLocation(latitude: location.latitude, longitude: location.longitude)
|
||||
|> map { placemark in
|
||||
if let placemark = placemark {
|
||||
return placemark.fullAddress
|
||||
} else {
|
||||
return "\(location.latitude), \(location.longitude)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let context = strongSelf.context
|
||||
let _ = (addressSignal
|
||||
|> mapToSignal { address -> Signal<Bool, NoError> in
|
||||
return updateChannelGeoLocation(postbox: context.account.postbox, network: context.account.network, channelId: peer.id, coordinate: (location.latitude, location.longitude), address: address)
|
||||
}
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
})
|
||||
self.controller?.push(controller)
|
||||
}
|
||||
|
||||
private func openPeerInfo(peer: EnginePeer, isMember: Bool) {
|
||||
let mode: PeerInfoControllerMode = .generic
|
||||
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer, mode: mode, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,119 @@ import SwiftSignalKit
|
|||
import TelegramCore
|
||||
import LegacyMediaPickerUI
|
||||
import ChatHistorySearchContainerNode
|
||||
import ChatScheduleTimeController
|
||||
import MediaResources
|
||||
import TelegramUIPreferences
|
||||
|
||||
extension PeerInfoScreenNode {
|
||||
private func presentMediaScheduleTimePicker(completion: @escaping (Int32, Bool) -> Void) {
|
||||
guard let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.account.viewTracker.peerView(peerId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in
|
||||
guard let self, let controller = self.controller, let peer = peerViewMainPeer(peerView) else {
|
||||
return
|
||||
}
|
||||
|
||||
var sendWhenOnlineAvailable = false
|
||||
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case .present = presence.status {
|
||||
sendWhenOnlineAvailable = true
|
||||
}
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
|
||||
sendWhenOnlineAvailable = false
|
||||
}
|
||||
|
||||
let mode: ChatScheduleTimeScreen.Mode
|
||||
if peerId == self.context.account.peerId {
|
||||
mode = .reminders
|
||||
} else {
|
||||
mode = .scheduledMessages(peerId: peer.id, sendWhenOnlineAvailable: sendWhenOnlineAvailable)
|
||||
}
|
||||
|
||||
let scheduleController = ChatScheduleTimeScreen(
|
||||
context: self.context,
|
||||
mode: mode,
|
||||
currentTime: nil,
|
||||
currentRepeatPeriod: nil,
|
||||
minimalTime: nil,
|
||||
silentPosting: false,
|
||||
isDark: true,
|
||||
completion: { result in
|
||||
completion(result.time, result.silentPosting)
|
||||
}
|
||||
)
|
||||
self.view.endEditing(true)
|
||||
controller.present(scheduleController, in: .window(.root))
|
||||
})
|
||||
}
|
||||
|
||||
private func openScheduledMessages() {
|
||||
guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
var mappedChatLocation = self.chatLocation
|
||||
if case let .replyThread(message) = self.chatLocation, message.peerId == self.context.account.peerId {
|
||||
mappedChatLocation = .peer(id: self.context.account.peerId)
|
||||
}
|
||||
|
||||
let scheduledController = self.context.sharedContext.makeChatController(
|
||||
context: self.context,
|
||||
chatLocation: mappedChatLocation,
|
||||
subject: .scheduledMessages,
|
||||
botStart: nil,
|
||||
mode: .standard(.default),
|
||||
params: nil
|
||||
)
|
||||
scheduledController.navigationPresentation = .modal
|
||||
navigationController.pushViewController(scheduledController)
|
||||
}
|
||||
|
||||
private func transformEditedMediaMessages(_ messages: [EnqueueMessage], replyToMessageId: EngineMessage.Id, silentPosting: Bool, scheduleTime: Int32?) -> [EnqueueMessage] {
|
||||
let replySubject = EngineMessageReplySubject(messageId: replyToMessageId, quote: nil, innerSubject: nil)
|
||||
let defaultThreadId: Int64?
|
||||
if case let .replyThread(replyThreadMessage) = self.chatLocation, replyThreadMessage.peerId == self.context.account.peerId {
|
||||
defaultThreadId = replyThreadMessage.threadId
|
||||
} else {
|
||||
defaultThreadId = nil
|
||||
}
|
||||
|
||||
return messages.map { message in
|
||||
var message = message.withUpdatedReplyToMessageId(replySubject)
|
||||
|
||||
if let defaultThreadId {
|
||||
var updateThreadId = false
|
||||
switch message {
|
||||
case let .message(_, _, _, _, threadId, _, _, _, _, _):
|
||||
updateThreadId = threadId == nil
|
||||
case let .forward(_, threadId, _, _, _):
|
||||
updateThreadId = threadId == nil
|
||||
}
|
||||
if updateThreadId {
|
||||
message = message.withUpdatedThreadId(defaultThreadId)
|
||||
}
|
||||
}
|
||||
|
||||
return message.withUpdatedAttributes { attributes in
|
||||
var attributes = attributes
|
||||
for i in (0 ..< attributes.count).reversed() {
|
||||
if attributes[i] is NotificationInfoMessageAttribute || attributes[i] is OutgoingScheduleInfoMessageAttribute {
|
||||
attributes.remove(at: i)
|
||||
}
|
||||
}
|
||||
if silentPosting {
|
||||
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
|
||||
}
|
||||
if let scheduleTime {
|
||||
attributes.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil))
|
||||
}
|
||||
return attributes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openMessage(id: EngineMessage.Id) -> Bool {
|
||||
guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return false
|
||||
|
|
@ -113,17 +222,31 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
|
||||
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
|
||||
let hasSilentPosting = peer.id != strongSelf.context.account.peerId
|
||||
let hasSchedule = peer.id.namespace != Namespaces.Peer.SecretChat
|
||||
legacyMediaEditor(context: strongSelf.context, peer: EnginePeer(peer), threadTitle: message.associatedThreadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
|
||||
|
||||
transitionCompletion()
|
||||
}, getCaptionPanelView: {
|
||||
return nil
|
||||
}, sendMessagesWithSignals: { [weak self] signals, _, _, _ in
|
||||
}, hasSilentPosting: hasSilentPosting, hasSchedule: hasSchedule, reminder: peer.id == strongSelf.context.account.peerId, presentSchedulePicker: { [weak self] _, done in
|
||||
self?.presentMediaScheduleTimePicker(completion: { time, silentPosting in
|
||||
done(time, silentPosting)
|
||||
})
|
||||
}, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime, _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.enqueueMediaMessageDisposable.set((legacyAssetPickerEnqueueMessages(context: strongSelf.context, account: strongSelf.context.account, signals: signals!)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] messages in
|
||||
if let strongSelf = self {
|
||||
let _ = enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: messages.map { $0.message.withUpdatedReplyToMessageId(.init(messageId: message.id, quote: nil, innerSubject: nil)) }).startStandalone()
|
||||
let effectiveScheduleTime = scheduleTime == 0 ? nil : scheduleTime
|
||||
let mappedMessages = strongSelf.transformEditedMediaMessages(messages.map(\.message), replyToMessageId: message.id, silentPosting: silentPosting, scheduleTime: effectiveScheduleTime)
|
||||
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: strongSelf.peerId, messages: mappedMessages)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] _ in
|
||||
guard let self, let effectiveScheduleTime, effectiveScheduleTime != scheduleWhenOnlineTimestamp else {
|
||||
return
|
||||
}
|
||||
self.openScheduledMessages()
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,6 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||
}, displaySlowmodeTooltip: { _, _ in
|
||||
}, displaySendMessageOptions: { _, _ in
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, unarchivePeer: {
|
||||
}, scrollToTop: {
|
||||
|
|
|
|||
|
|
@ -792,7 +792,6 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||
strongSelf.presentInGlobalOverlay(controller, nil)
|
||||
})
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
}, unarchivePeer: {
|
||||
}, scrollToTop: {
|
||||
|
|
|
|||
|
|
@ -20,22 +20,25 @@ import OverlayStatusController
|
|||
private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let sharedContext: SharedAccountContext
|
||||
let network: Network
|
||||
let server: ProxyServerSettings
|
||||
let cancel: (Bool) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
sharedContext: SharedAccountContext,
|
||||
network: Network,
|
||||
server: ProxyServerSettings,
|
||||
cancel: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.sharedContext = sharedContext
|
||||
self.network = network
|
||||
self.server = server
|
||||
self.cancel = cancel
|
||||
}
|
||||
|
||||
static func ==(lhs: ProxyServerPreviewSheetContent, rhs: ProxyServerPreviewSheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
if lhs.sharedContext !== rhs.sharedContext {
|
||||
return false
|
||||
}
|
||||
if lhs.server != rhs.server {
|
||||
|
|
@ -45,7 +48,8 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
private let context: AccountContext
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let network: Network
|
||||
private let server: ProxyServerSettings
|
||||
|
||||
private var disposable = MetaDisposable()
|
||||
|
|
@ -59,8 +63,9 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
|
||||
private var revertSettings: ProxySettings?
|
||||
|
||||
init(context: AccountContext, server: ProxyServerSettings) {
|
||||
self.context = context
|
||||
init(sharedContext: SharedAccountContext, network: Network, server: ProxyServerSettings) {
|
||||
self.sharedContext = sharedContext
|
||||
self.network = network
|
||||
self.server = server
|
||||
|
||||
super.init()
|
||||
|
|
@ -71,7 +76,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
self.statusDisposable.dispose()
|
||||
|
||||
if let revertSettings = self.revertSettings {
|
||||
let _ = updateProxySettingsInteractively(accountManager: self.context.sharedContext.accountManager, { _ in
|
||||
let _ = updateProxySettingsInteractively(accountManager: self.sharedContext.accountManager, { _ in
|
||||
return revertSettings
|
||||
})
|
||||
}
|
||||
|
|
@ -91,7 +96,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
return
|
||||
}
|
||||
|
||||
let statusesContext = ProxyServersStatuses(network: self.context.account.network, servers: .single([self.server]))
|
||||
let statusesContext = ProxyServersStatuses(network: self.network, servers: .single([self.server]))
|
||||
self.statusesContext = statusesContext
|
||||
|
||||
self.status = .checking
|
||||
|
|
@ -114,13 +119,13 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
return
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = self.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.displayWarningIfNeeded { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let accountManager = self.context.sharedContext.accountManager
|
||||
let accountManager = self.sharedContext.accountManager
|
||||
let proxyServerSettings = self.server
|
||||
let _ = (accountManager.transaction { transaction -> ProxySettings in
|
||||
var currentSettings: ProxySettings?
|
||||
|
|
@ -145,7 +150,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
self.inProgress = true
|
||||
self.updated()
|
||||
|
||||
let signal = self.context.account.network.connectionStatus
|
||||
let signal = self.network.connectionStatus
|
||||
|> filter { status in
|
||||
switch status {
|
||||
case let .online(proxyAddress):
|
||||
|
|
@ -181,7 +186,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
let _ = updateProxySettingsInteractively(accountManager: accountManager, { _ in
|
||||
return previousSettings
|
||||
}).start()
|
||||
self.controller?.present(textAlertController(sharedContext: self.context.sharedContext, title: nil, text: presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
self.controller?.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: presentationData.strings.SocksProxySetup_FailedToConnect, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -195,9 +200,9 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
commit()
|
||||
return
|
||||
}
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = self.sharedContext.currentPresentationData.with { $0 }
|
||||
let alertController = textAlertController(
|
||||
context: context,
|
||||
sharedContext: self.sharedContext,
|
||||
title: presentationData.strings.SocksProxySetup_Warning_Title,
|
||||
text: presentationData.strings.SocksProxySetup_Warning_Text,
|
||||
actions: [
|
||||
|
|
@ -212,7 +217,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, server: self.server)
|
||||
return State(sharedContext: self.sharedContext, network: self.network, server: self.server)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
|
|
@ -414,19 +419,22 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
private final class ProxyServerPreviewSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let sharedContext: SharedAccountContext
|
||||
let network: Network
|
||||
let server: ProxyServerSettings
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
sharedContext: SharedAccountContext,
|
||||
network: Network,
|
||||
server: ProxyServerSettings
|
||||
) {
|
||||
self.context = context
|
||||
self.sharedContext = sharedContext
|
||||
self.network = network
|
||||
self.server = server
|
||||
}
|
||||
|
||||
static func ==(lhs: ProxyServerPreviewSheetComponent, rhs: ProxyServerPreviewSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
if lhs.sharedContext !== rhs.sharedContext {
|
||||
return false
|
||||
}
|
||||
if lhs.server != rhs.server {
|
||||
|
|
@ -446,7 +454,8 @@ private final class ProxyServerPreviewSheetComponent: CombinedComponent {
|
|||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(ProxyServerPreviewSheetContent(
|
||||
context: context.component.context,
|
||||
sharedContext: context.component.sharedContext,
|
||||
network: context.component.network,
|
||||
server: context.component.server,
|
||||
cancel: { animate in
|
||||
if animate {
|
||||
|
|
@ -504,18 +513,15 @@ private final class ProxyServerPreviewSheetComponent: CombinedComponent {
|
|||
}
|
||||
|
||||
public class ProxyServerPreviewScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
server: ProxyServerSettings
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: ProxyServerPreviewSheetComponent(
|
||||
context: context,
|
||||
sharedContext: context.sharedContext,
|
||||
network: context.account.network,
|
||||
server: server
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
|
|
@ -526,6 +532,28 @@ public class ProxyServerPreviewScreen: ViewControllerComponentContainer {
|
|||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
public init(
|
||||
sharedContext: SharedAccountContext,
|
||||
network: Network,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>),
|
||||
server: ProxyServerSettings
|
||||
) {
|
||||
super.init(
|
||||
component: ProxyServerPreviewSheetComponent(
|
||||
sharedContext: sharedContext,
|
||||
network: network,
|
||||
server: server
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
presentationMode: .default,
|
||||
theme: .default,
|
||||
updatedPresentationData: updatedPresentationData
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -892,6 +892,7 @@ private extension MediaEditorValues {
|
|||
videoVolume: 1.0,
|
||||
additionalVideoPath: nil,
|
||||
additionalVideoIsDual: false,
|
||||
additionalVideoMirroringChanges: [],
|
||||
additionalVideoPosition: nil,
|
||||
additionalVideoScale: nil,
|
||||
additionalVideoRotation: nil,
|
||||
|
|
@ -1041,6 +1042,7 @@ private extension MediaEditorValues {
|
|||
videoVolume: 1.0,
|
||||
additionalVideoPath: nil,
|
||||
additionalVideoIsDual: false,
|
||||
additionalVideoMirroringChanges: [],
|
||||
additionalVideoPosition: nil,
|
||||
additionalVideoScale: nil,
|
||||
additionalVideoRotation: nil,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,57 @@ import TelegramPresentationData
|
|||
import SegmentedControlNode
|
||||
|
||||
public final class SegmentControlComponent: Component {
|
||||
public final class Theme: Equatable {
|
||||
public let backgroundColor: UIColor
|
||||
public let legacyBackgroundColor: UIColor
|
||||
public let foregroundColor: UIColor
|
||||
public let textColor: UIColor
|
||||
public let dividerColor: UIColor
|
||||
|
||||
public init(
|
||||
backgroundColor: UIColor,
|
||||
legacyBackgroundColor: UIColor,
|
||||
foregroundColor: UIColor,
|
||||
textColor: UIColor,
|
||||
dividerColor: UIColor
|
||||
) {
|
||||
self.backgroundColor = backgroundColor
|
||||
self.legacyBackgroundColor = legacyBackgroundColor
|
||||
self.foregroundColor = foregroundColor
|
||||
self.textColor = textColor
|
||||
self.dividerColor = dividerColor
|
||||
}
|
||||
|
||||
public convenience init(theme: PresentationTheme) {
|
||||
self.init(
|
||||
backgroundColor: theme.rootController.navigationBar.segmentedBackgroundColor,
|
||||
legacyBackgroundColor: theme.overallDarkAppearance ? theme.list.itemBlocksBackgroundColor : theme.rootController.navigationBar.segmentedBackgroundColor,
|
||||
foregroundColor: theme.rootController.navigationBar.segmentedForegroundColor,
|
||||
textColor: theme.rootController.navigationBar.segmentedTextColor,
|
||||
dividerColor: theme.rootController.navigationBar.segmentedDividerColor
|
||||
)
|
||||
}
|
||||
|
||||
public static func ==(lhs: Theme, rhs: Theme) -> Bool {
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.legacyBackgroundColor != rhs.legacyBackgroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.foregroundColor != rhs.foregroundColor {
|
||||
return false
|
||||
}
|
||||
if lhs.textColor != rhs.textColor {
|
||||
return false
|
||||
}
|
||||
if lhs.dividerColor != rhs.dividerColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public struct Item: Equatable {
|
||||
var id: AnyHashable
|
||||
var title: String
|
||||
|
|
@ -18,13 +69,13 @@ public final class SegmentControlComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
let theme: PresentationTheme
|
||||
let theme: Theme
|
||||
let items: [Item]
|
||||
let selectedId: AnyHashable?
|
||||
let action: (AnyHashable) -> Void
|
||||
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
theme: Theme,
|
||||
items: [Item],
|
||||
selectedId: AnyHashable?,
|
||||
action: @escaping (AnyHashable) -> Void
|
||||
|
|
@ -34,9 +85,23 @@ public final class SegmentControlComponent: Component {
|
|||
self.selectedId = selectedId
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public convenience init(
|
||||
theme: PresentationTheme,
|
||||
items: [Item],
|
||||
selectedId: AnyHashable?,
|
||||
action: @escaping (AnyHashable) -> Void
|
||||
) {
|
||||
self.init(
|
||||
theme: Theme(theme: theme),
|
||||
items: items,
|
||||
selectedId: selectedId,
|
||||
action: action
|
||||
)
|
||||
}
|
||||
|
||||
public static func ==(lhs: SegmentControlComponent, rhs: SegmentControlComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
if lhs.theme != rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
|
|
@ -102,7 +167,7 @@ public final class SegmentControlComponent: Component {
|
|||
}
|
||||
|
||||
func update(component: SegmentControlComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let themeUpdated = self.component?.theme !== component.theme
|
||||
let themeUpdated = self.component?.theme != component.theme
|
||||
|
||||
self.component = component
|
||||
|
||||
|
|
@ -127,13 +192,12 @@ public final class SegmentControlComponent: Component {
|
|||
}
|
||||
|
||||
if themeUpdated {
|
||||
let backgroundColor = component.theme.rootController.navigationBar.segmentedBackgroundColor
|
||||
segmentedView.setTitleTextAttributes([
|
||||
.font: Font.semibold(14.0),
|
||||
.foregroundColor: component.theme.rootController.navigationBar.segmentedTextColor
|
||||
.foregroundColor: component.theme.textColor
|
||||
], for: .normal)
|
||||
segmentedView.foregroundColor = component.theme.rootController.navigationBar.segmentedForegroundColor
|
||||
segmentedView.backgroundColor = backgroundColor
|
||||
segmentedView.foregroundColor = component.theme.foregroundColor
|
||||
segmentedView.backgroundColor = component.theme.backgroundColor
|
||||
}
|
||||
|
||||
controlSize = segmentedView.sizeThatFits(availableSize)
|
||||
|
|
@ -146,16 +210,14 @@ public final class SegmentControlComponent: Component {
|
|||
segmentedNode = current
|
||||
|
||||
if themeUpdated {
|
||||
let backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.itemBlocksBackgroundColor : component.theme.rootController.navigationBar.segmentedBackgroundColor
|
||||
let controlTheme = SegmentedControlTheme(backgroundColor: backgroundColor, foregroundColor: component.theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .clear, textColor: component.theme.rootController.navigationBar.segmentedTextColor, dividerColor: component.theme.rootController.navigationBar.segmentedDividerColor)
|
||||
let controlTheme = SegmentedControlTheme(backgroundColor: component.theme.legacyBackgroundColor, foregroundColor: component.theme.foregroundColor, shadowColor: .clear, textColor: component.theme.textColor, dividerColor: component.theme.dividerColor)
|
||||
segmentedNode.updateTheme(controlTheme)
|
||||
}
|
||||
} else {
|
||||
let mappedItems: [SegmentedControlItem] = component.items.map { item -> SegmentedControlItem in
|
||||
return SegmentedControlItem(title: item.title)
|
||||
}
|
||||
let backgroundColor = component.theme.overallDarkAppearance ? component.theme.list.itemBlocksBackgroundColor : component.theme.rootController.navigationBar.segmentedBackgroundColor
|
||||
let controlTheme = SegmentedControlTheme(backgroundColor: backgroundColor, foregroundColor: component.theme.rootController.navigationBar.segmentedForegroundColor, shadowColor: .clear, textColor: component.theme.rootController.navigationBar.segmentedTextColor, dividerColor: component.theme.rootController.navigationBar.segmentedDividerColor)
|
||||
let controlTheme = SegmentedControlTheme(backgroundColor: component.theme.legacyBackgroundColor, foregroundColor: component.theme.foregroundColor, shadowColor: .clear, textColor: component.theme.textColor, dividerColor: component.theme.dividerColor)
|
||||
segmentedNode = SegmentedControlNode(theme: controlTheme, items: mappedItems, selectedIndex: component.items.firstIndex(where: { $0.id == component.selectedId }) ?? 0, cornerRadius: 18.0)
|
||||
self.legacySegmentedNode = segmentedNode
|
||||
self.addSubnode(segmentedNode)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ swift_library(
|
|||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/Components/ResizableSheetComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/Markdown",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -42,9 +42,8 @@ swift_library(
|
|||
"//submodules/ItemListPeerActionItem",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController",
|
||||
"//submodules/DateSelectionUI",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/TelegramUI/Components/TimeSelectionActionSheet",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
"//submodules/TelegramUI/Components/ChatListHeaderComponent",
|
||||
"//submodules/AttachmentUI",
|
||||
"//submodules/SearchBarNode",
|
||||
|
|
|
|||
|
|
@ -23,11 +23,10 @@ import Markdown
|
|||
import PeerListItemComponent
|
||||
import AvatarNode
|
||||
import ListItemSliderSelectorComponent
|
||||
import DateSelectionUI
|
||||
import PlainButtonComponent
|
||||
import TelegramStringFormatting
|
||||
import TextFormat
|
||||
import TimeSelectionActionSheet
|
||||
import ChatTimerScreen
|
||||
|
||||
private let checkIcon: UIImage = {
|
||||
return generateImage(CGSize(width: 12.0, height: 10.0), rotatedContext: { size, context in
|
||||
|
|
@ -505,14 +504,19 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
|||
return
|
||||
}
|
||||
|
||||
let controller = DateSelectionActionSheetController(
|
||||
let controller = ChatTimerScreen(
|
||||
context: component.context,
|
||||
title: nil,
|
||||
currentValue: Int32(clippedDate.timeIntervalSince1970),
|
||||
minimumDate: nil,
|
||||
maximumDate: nil,
|
||||
emptyTitle: nil,
|
||||
applyValue: { [weak self] value in
|
||||
configuration: ChatTimerScreen.Configuration(
|
||||
style: .default,
|
||||
title: { strings in return isStartTime ? strings.BusinessMessageSetup_ScheduleStartTime : strings.BusinessMessageSetup_ScheduleEndTime },
|
||||
picker: .date,
|
||||
currentValue: Int32(clippedDate.timeIntervalSince1970),
|
||||
pickerValueMapping: .roundDateToDaysUTC,
|
||||
primaryActionTitle: { strings, _, _ in
|
||||
strings.Wallpaper_Set
|
||||
}
|
||||
),
|
||||
completion: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -544,33 +548,44 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component {
|
|||
let hour = components.hour ?? 0
|
||||
let minute = components.minute ?? 0
|
||||
|
||||
let controller = TimeSelectionActionSheet(context: component.context, currentValue: Int32(hour * 60 * 60 + minute * 60), applyValue: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let value else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedHour = value / (60 * 60)
|
||||
let updatedMinute = (value % (60 * 60)) / 60
|
||||
|
||||
let calendar = Calendar.current
|
||||
var updatedComponents = calendar.dateComponents([.year, .month, .day], from: currentValue)
|
||||
updatedComponents.hour = Int(updatedHour)
|
||||
updatedComponents.minute = Int(updatedMinute)
|
||||
|
||||
guard let updatedClippedDate = calendar.date(from: updatedComponents) else {
|
||||
return
|
||||
}
|
||||
|
||||
if isStartTime {
|
||||
self.customScheduleStart = updatedClippedDate
|
||||
} else {
|
||||
self.customScheduleEnd = updatedClippedDate
|
||||
}
|
||||
self.state?.updated(transition: .immediate)
|
||||
})
|
||||
let controller = ChatTimerScreen(
|
||||
context: component.context,
|
||||
configuration: ChatTimerScreen.Configuration(
|
||||
style: .default,
|
||||
picker: .timeOfDay,
|
||||
currentValue: Int32(hour * 60 * 60 + minute * 60),
|
||||
pickerValueMapping: .secondsFromMidnightGMT,
|
||||
primaryActionTitle: { strings, _, _ in
|
||||
strings.Wallpaper_Set
|
||||
}
|
||||
),
|
||||
completion: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let value else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedHour = value / (60 * 60)
|
||||
let updatedMinute = (value % (60 * 60)) / 60
|
||||
|
||||
let calendar = Calendar.current
|
||||
var updatedComponents = calendar.dateComponents([.year, .month, .day], from: currentValue)
|
||||
updatedComponents.hour = Int(updatedHour)
|
||||
updatedComponents.minute = Int(updatedMinute)
|
||||
|
||||
guard let updatedClippedDate = calendar.date(from: updatedComponents) else {
|
||||
return
|
||||
}
|
||||
|
||||
if isStartTime {
|
||||
self.customScheduleStart = updatedClippedDate
|
||||
} else {
|
||||
self.customScheduleEnd = updatedClippedDate
|
||||
}
|
||||
self.state?.updated(transition: .immediate)
|
||||
})
|
||||
self.environment?.controller()?.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ swift_library(
|
|||
"//submodules/TextFormat",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
"//submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen",
|
||||
"//submodules/TelegramUI/Components/TimeSelectionActionSheet",
|
||||
"//submodules/TelegramUI/Components/ChatTimerScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import LocationUI
|
|||
import TelegramStringFormatting
|
||||
import TextFormat
|
||||
import PlainButtonComponent
|
||||
import TimeSelectionActionSheet
|
||||
import ChatTimerScreen
|
||||
|
||||
func clipMinutes(_ value: Int) -> Int {
|
||||
return value % (24 * 60)
|
||||
|
|
@ -180,31 +180,42 @@ final class BusinessDaySetupScreenComponent: Component {
|
|||
return
|
||||
}
|
||||
|
||||
let controller = TimeSelectionActionSheet(context: component.context, currentValue: Int32(isStartTime ? clipMinutes(range.startMinute) : clipMinutes(range.endMinute)) * 60, applyValue: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let value else {
|
||||
return
|
||||
}
|
||||
if let index = self.ranges.firstIndex(where: { $0.id == rangeId }) {
|
||||
var startMinute = range.startMinute
|
||||
var endMinute = range.endMinute
|
||||
if isStartTime {
|
||||
startMinute = Int(value) / 60
|
||||
} else {
|
||||
endMinute = Int(value) / 60
|
||||
let controller = ChatTimerScreen(
|
||||
context: component.context,
|
||||
configuration: ChatTimerScreen.Configuration(
|
||||
style: .default,
|
||||
picker: .timeOfDay,
|
||||
currentValue: Int32(isStartTime ? clipMinutes(range.startMinute) : clipMinutes(range.endMinute)) * 60,
|
||||
pickerValueMapping: .secondsFromMidnightGMT,
|
||||
primaryActionTitle: { strings, _, _ in
|
||||
strings.Wallpaper_Set
|
||||
}
|
||||
if endMinute < startMinute {
|
||||
endMinute = endMinute + 24 * 60
|
||||
),
|
||||
completion: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.ranges[index].startMinute = startMinute
|
||||
self.ranges[index].endMinute = endMinute
|
||||
self.validateRanges()
|
||||
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
})
|
||||
guard let value else {
|
||||
return
|
||||
}
|
||||
if let index = self.ranges.firstIndex(where: { $0.id == rangeId }) {
|
||||
var startMinute = range.startMinute
|
||||
var endMinute = range.endMinute
|
||||
if isStartTime {
|
||||
startMinute = Int(value) / 60
|
||||
} else {
|
||||
endMinute = Int(value) / 60
|
||||
}
|
||||
if endMinute < startMinute {
|
||||
endMinute = endMinute + 24 * 60
|
||||
}
|
||||
self.ranges[index].startMinute = startMinute
|
||||
self.ranges[index].endMinute = endMinute
|
||||
self.validateRanges()
|
||||
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
})
|
||||
self.environment?.controller()?.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -833,7 +833,7 @@ final class ChatbotSetupScreenComponent: Component {
|
|||
}
|
||||
controller.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .invitedToVoiceChat(context: component.context, peer: peer, title: nil, text: environment.strings.ChatbotSetup_BotInstalled(peer.compactDisplayTitle).string, action: nil, duration: 2.0),
|
||||
content: .actionSucceeded(title: nil, text: environment.strings.ChatbotSetup_BotInstalled(peer.compactDisplayTitle).string, cancel: nil, destructive: false),
|
||||
elevatedLayout: false,
|
||||
position: .bottom,
|
||||
animateInAsReplacement: false,
|
||||
|
|
|
|||
|
|
@ -633,7 +633,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||
starTransition.setFrame(view: topBalanceIconView, frame: topBalanceIconFrame)
|
||||
}
|
||||
|
||||
contentHeight += 181.0
|
||||
contentHeight += 197.0
|
||||
|
||||
let descriptionSize = self.descriptionView.update(
|
||||
transition: .immediate,
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "TimeSelectionActionSheet",
|
||||
module_name = "TimeSelectionActionSheet",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
public final class TimeSelectionActionSheet: ActionSheetController {
|
||||
private var presentationDisposable: Disposable?
|
||||
|
||||
private let _ready = Promise<Bool>()
|
||||
override public var ready: Promise<Bool> {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext, currentValue: Int32, emptyTitle: String? = nil, applyValue: @escaping (Int32?) -> Void) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
super.init(theme: ActionSheetControllerTheme(presentationData: presentationData))
|
||||
|
||||
self.presentationDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.theme = ActionSheetControllerTheme(presentationData: presentationData)
|
||||
}
|
||||
})
|
||||
|
||||
self._ready.set(.single(true))
|
||||
|
||||
var updatedValue = currentValue
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(TimeSelectionActionSheetItem(strings: strings, currentValue: currentValue, valueChanged: { value in
|
||||
updatedValue = value
|
||||
}))
|
||||
if let emptyTitle = emptyTitle {
|
||||
items.append(ActionSheetButtonItem(title: emptyTitle, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
applyValue(nil)
|
||||
}))
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: strings.Wallpaper_Set, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
applyValue(updatedValue)
|
||||
}))
|
||||
self.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in
|
||||
self?.dismissAnimated()
|
||||
}),
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class TimeSelectionActionSheetItem: ActionSheetItem {
|
||||
let strings: PresentationStrings
|
||||
|
||||
let currentValue: Int32
|
||||
let valueChanged: (Int32) -> Void
|
||||
|
||||
init(strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.strings = strings
|
||||
self.currentValue = currentValue
|
||||
self.valueChanged = valueChanged
|
||||
}
|
||||
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return TimeSelectionActionSheetItemNode(theme: theme, strings: self.strings, currentValue: self.currentValue, valueChanged: self.valueChanged)
|
||||
}
|
||||
|
||||
func updateNode(_ node: ActionSheetItemNode) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class TimeSelectionActionSheetItemNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
|
||||
private let valueChanged: (Int32) -> Void
|
||||
private let pickerView: UIDatePicker
|
||||
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, currentValue: Int32, valueChanged: @escaping (Int32) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.valueChanged = valueChanged
|
||||
|
||||
UILabel.setDateLabel(theme.primaryTextColor)
|
||||
|
||||
self.pickerView = UIDatePicker()
|
||||
self.pickerView.datePickerMode = .countDownTimer
|
||||
self.pickerView.datePickerMode = .time
|
||||
self.pickerView.timeZone = TimeZone(secondsFromGMT: 0)
|
||||
self.pickerView.date = Date(timeIntervalSince1970: Double(currentValue))
|
||||
self.pickerView.locale = Locale.current
|
||||
if #available(iOS 13.4, *) {
|
||||
self.pickerView.preferredDatePickerStyle = .wheels
|
||||
}
|
||||
self.pickerView.setValue(theme.primaryTextColor, forKey: "textColor")
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.view.addSubview(self.pickerView)
|
||||
self.pickerView.addTarget(self, action: #selector(self.datePickerUpdated), for: .valueChanged)
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let size = CGSize(width: constrainedSize.width, height: 216.0)
|
||||
|
||||
self.pickerView.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.updateInternalLayout(size, constrainedSize: constrainedSize)
|
||||
return size
|
||||
}
|
||||
|
||||
@objc private func datePickerUpdated() {
|
||||
self.valueChanged(Int32(self.pickerView.date.timeIntervalSince1970))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1940,7 +1940,48 @@ public class VideoMessageCameraScreen: ViewController {
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoBounce: false, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, collage: [], nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, collageTrackSamples: nil, coverImageTimestamp: nil, coverDimensions: nil, qualityPreset: .videoMessage)
|
||||
let values = MediaEditorValues(
|
||||
peerId: self.context.account.peerId,
|
||||
originalDimensions: dimensions,
|
||||
cropOffset: .zero,
|
||||
cropRect: CGRect(origin: .zero, size: dimensions.cgSize),
|
||||
cropScale: 1.0,
|
||||
cropRotation: 0.0,
|
||||
cropMirroring: false,
|
||||
cropOrientation: nil,
|
||||
gradientColors: nil,
|
||||
videoTrimRange: self.node.previewState?.trimRange,
|
||||
videoBounce: false,
|
||||
videoIsMuted: false,
|
||||
videoIsFullHd: false,
|
||||
videoIsMirrored: false,
|
||||
videoVolume: nil,
|
||||
additionalVideoPath: nil,
|
||||
additionalVideoIsDual: false,
|
||||
additionalVideoMirroringChanges: [],
|
||||
additionalVideoPosition: nil,
|
||||
additionalVideoScale: nil,
|
||||
additionalVideoRotation: nil,
|
||||
additionalVideoPositionChanges: [],
|
||||
additionalVideoTrimRange: nil,
|
||||
additionalVideoOffset: nil,
|
||||
additionalVideoVolume: nil,
|
||||
collage: [],
|
||||
nightTheme: false,
|
||||
drawing: nil,
|
||||
maskDrawing: nil,
|
||||
entities: [],
|
||||
toolValues: [:],
|
||||
audioTrack: nil,
|
||||
audioTrackTrimRange: nil,
|
||||
audioTrackOffset: nil,
|
||||
audioTrackVolume: nil,
|
||||
audioTrackSamples: nil,
|
||||
collageTrackSamples: nil,
|
||||
coverImageTimestamp: nil,
|
||||
coverDimensions: nil,
|
||||
qualityPreset: .videoMessage
|
||||
)
|
||||
|
||||
var resourceAdjustments: VideoMediaResourceAdjustments? = nil
|
||||
if let valuesData = try? JSONEncoder().encode(values) {
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "peoplenearbyon_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.934998 1.735001 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
7.330000 17.464998 m
|
||||
7.330000 18.423212 8.106786 19.199999 9.065001 19.199999 c
|
||||
10.023214 19.199999 10.800000 18.423212 10.800000 17.464998 c
|
||||
10.800000 16.506784 10.023214 15.729999 9.065001 15.729999 c
|
||||
8.106786 15.729999 7.330000 16.506784 7.330000 17.464998 c
|
||||
h
|
||||
9.065001 20.529999 m
|
||||
7.372247 20.529999 6.000000 19.157751 6.000000 17.464998 c
|
||||
6.000000 15.772245 7.372247 14.399999 9.065001 14.399999 c
|
||||
10.757753 14.399999 12.130000 15.772245 12.130000 17.464998 c
|
||||
12.130000 19.157751 10.757753 20.529999 9.065001 20.529999 c
|
||||
h
|
||||
1.330000 3.865000 m
|
||||
1.330000 4.026144 1.400150 4.232668 1.634496 4.483692 c
|
||||
1.872207 4.738321 2.249572 5.004240 2.774642 5.255713 c
|
||||
3.677844 5.688284 4.928888 6.035789 6.400000 6.230032 c
|
||||
6.400001 4.564999 l
|
||||
6.400001 3.755901 7.055903 3.099998 7.865001 3.099998 c
|
||||
10.265000 3.099998 l
|
||||
11.074098 3.099998 11.730000 3.755901 11.730000 4.564999 c
|
||||
11.730000 6.230032 l
|
||||
13.201113 6.035789 14.452158 5.688284 15.355359 5.255713 c
|
||||
15.880429 5.004240 16.257793 4.738321 16.495506 4.483692 c
|
||||
16.729851 4.232668 16.800003 4.026144 16.800003 3.865000 c
|
||||
16.800003 3.677490 16.702744 3.422382 16.359842 3.113541 c
|
||||
16.017097 2.804838 15.483717 2.496361 14.767962 2.223692 c
|
||||
13.341063 1.680111 11.324945 1.330000 9.065001 1.330000 c
|
||||
6.805056 1.330000 4.788938 1.680111 3.362040 2.223692 c
|
||||
2.646284 2.496361 2.112905 2.804838 1.770159 3.113541 c
|
||||
1.427257 3.422382 1.330000 3.677490 1.330000 3.865000 c
|
||||
h
|
||||
2.200152 6.455237 m
|
||||
3.300211 6.982090 4.758119 7.368791 6.400000 7.570784 c
|
||||
6.400000 8.099998 l
|
||||
5.859936 8.099998 l
|
||||
5.076447 8.099998 4.338713 8.757976 4.479389 9.657219 c
|
||||
4.588190 10.352705 4.856467 11.405879 5.540887 12.298394 c
|
||||
6.249626 13.222622 7.373408 13.929998 9.065001 13.929998 c
|
||||
10.756596 13.929998 11.880377 13.222621 12.589115 12.298393 c
|
||||
13.273535 11.405878 13.541812 10.352703 13.650612 9.657217 c
|
||||
13.791287 8.757974 13.053553 8.099998 12.270063 8.099998 c
|
||||
11.730000 8.099998 l
|
||||
11.730000 7.570784 l
|
||||
13.371881 7.368791 14.829790 6.982090 15.929850 6.455237 c
|
||||
16.545578 6.160346 17.079411 5.807216 17.467701 5.391293 c
|
||||
17.859352 4.971766 18.130001 4.456234 18.130001 3.865000 c
|
||||
18.130001 3.168854 17.757156 2.582132 17.249931 2.125290 c
|
||||
16.742554 1.668306 16.045780 1.287239 15.241435 0.980824 c
|
||||
13.628130 0.366230 11.444248 0.000000 9.065001 0.000000 c
|
||||
6.685753 0.000000 4.501871 0.366230 2.888566 0.980824 c
|
||||
2.084221 1.287239 1.387449 1.668306 0.880069 2.125290 c
|
||||
0.372844 2.582132 0.000000 3.168854 0.000000 3.865000 c
|
||||
0.000000 4.456234 0.270649 4.971766 0.662301 5.391293 c
|
||||
1.050590 5.807216 1.584423 6.160346 2.200152 6.455237 c
|
||||
h
|
||||
6.596293 11.489061 m
|
||||
6.105442 10.848969 5.886520 10.046863 5.793407 9.451655 c
|
||||
5.793330 9.451145 l
|
||||
5.800257 9.444448 5.821226 9.429998 5.859936 9.429998 c
|
||||
7.065000 9.429998 l
|
||||
7.432269 9.429998 7.730000 9.132269 7.730000 8.764999 c
|
||||
7.730000 4.564999 l
|
||||
7.730000 4.490440 7.790442 4.429998 7.865001 4.429998 c
|
||||
8.533002 4.429998 l
|
||||
8.533002 6.764999 l
|
||||
8.533002 7.058815 8.771186 7.296999 9.065001 7.296999 c
|
||||
9.358817 7.296999 9.597001 7.058815 9.597001 6.764999 c
|
||||
9.597001 4.429998 l
|
||||
10.265000 4.429998 l
|
||||
10.339560 4.429998 10.400000 4.490440 10.400000 4.564999 c
|
||||
10.400000 8.764999 l
|
||||
10.400000 8.941368 10.470061 9.110514 10.594772 9.235225 c
|
||||
10.719484 9.359937 10.888630 9.429998 11.064999 9.429998 c
|
||||
12.270063 9.429998 l
|
||||
12.308775 9.429998 12.329742 9.444448 12.336671 9.451145 c
|
||||
12.336594 9.451655 l
|
||||
12.243481 10.046862 12.024561 10.848969 11.533709 11.489061 c
|
||||
11.067177 12.097442 10.327614 12.599998 9.065001 12.599998 c
|
||||
7.802389 12.599998 7.062826 12.097442 6.596293 11.489061 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
3674
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000003764 00000 n
|
||||
0000003787 00000 n
|
||||
0000003960 00000 n
|
||||
0000004034 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4093
|
||||
%%EOF
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_stopshowme.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue