Various improvements

This commit is contained in:
Ilya Laktyushin 2026-05-05 14:57:06 +02:00
parent ab7fc69e16
commit 7a7333edab
118 changed files with 4380 additions and 7431 deletions

View file

@ -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 }

View file

@ -1420,7 +1420,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog
})
})
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, unarchivePeer: {
}, scrollToTop: {

View file

@ -48,6 +48,7 @@ swift_library(
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/Utils/DeviceModel",
"//submodules/PresentationDataUtils",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
],
visibility = [
"//visibility:public",

View file

@ -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 {

View file

@ -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

View file

@ -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: {

View file

@ -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))
}
}

View file

@ -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)

View file

@ -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)?

View file

@ -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",
],
)

View file

@ -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)))
}
}

View file

@ -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",

View file

@ -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
),

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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",

View file

@ -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:

View file

@ -84,6 +84,7 @@ swift_library(
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/CounterControllerTitleView",
"//submodules/TelegramUI/Components/ChatTimerScreen",
],
visibility = [
"//visibility:public",

View file

@ -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

View file

@ -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)))
}
}

View file

@ -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",
],
)

View file

@ -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)
}
}

View file

@ -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",
],
)

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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",

View file

@ -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(

View file

@ -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)
}
}
}))
}
})
}
}

View file

@ -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

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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))
}
}

View file

@ -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

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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)

View file

@ -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 {

View file

@ -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 {

View file

@ -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

View file

@ -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")
}
}

View file

@ -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)
}
}
}

View file

@ -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)
}()

View file

@ -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
}
}
}

View file

@ -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",

View file

@ -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)

View file

@ -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 {

View file

@ -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

View file

@ -64,8 +64,6 @@ public enum PresentationResourceKey: Int32 {
case itemListCloudFetchIcon
case itemListCloseIconImage
case itemListRemoveIconImage
case itemListMakeVisibleIcon
case itemListMakeInvisibleIcon
case itemListEditThemeIcon
case itemListCornersTop
case itemListCornersBottom

View file

@ -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)

View file

@ -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",

View file

@ -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 = [

View file

@ -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",

View file

@ -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
),

View file

@ -1552,6 +1552,7 @@ final class AvatarEditorScreenComponent: Component {
videoVolume: nil,
additionalVideoPath: nil,
additionalVideoIsDual: false,
additionalVideoMirroringChanges: [],
additionalVideoPosition: nil,
additionalVideoScale: nil,
additionalVideoRotation: nil,

View file

@ -139,6 +139,7 @@ final class LiveStreamMediaSource {
videoVolume: nil,
additionalVideoPath: nil,
additionalVideoIsDual: true,
additionalVideoMirroringChanges: [],
additionalVideoPosition: nil,
additionalVideoScale: 1.625,
additionalVideoRotation: 0.0,

View file

@ -139,7 +139,6 @@ public final class ChatRecentActionsController: TelegramBaseController {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { _, _ in
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, unarchivePeer: {
}, scrollToTop: {

View file

@ -664,8 +664,6 @@ public final class ChatTextInputPanelComponent: Component {
},
openScheduledMessages: {
},
openPeersNearby: {
},
displaySearchResultsTooltip: { _, _ in
},
unarchivePeer: {

View file

@ -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)

View file

@ -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)

View file

@ -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",

View file

@ -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()
}
}
}

View file

@ -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 {

View file

@ -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?

View file

@ -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,

View file

@ -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

View file

@ -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
}

View file

@ -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",

View file

@ -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

View file

@ -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)) {

View file

@ -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) {

View file

@ -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()
})
}
}))
}

View file

@ -132,7 +132,6 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, displaySlowmodeTooltip: { _, _ in
}, displaySendMessageOptions: { _, _ in
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, unarchivePeer: {
}, scrollToTop: {

View file

@ -792,7 +792,6 @@ final class PeerSelectionControllerNode: ASDisplayNode {
strongSelf.presentInGlobalOverlay(controller, nil)
})
}, openScheduledMessages: {
}, openPeersNearby: {
}, displaySearchResultsTooltip: { _, _ in
}, unarchivePeer: {
}, scrollToTop: {

View file

@ -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")
}

View file

@ -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,

View file

@ -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)

View file

@ -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",

View file

@ -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",

View file

@ -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))
}
}

View file

@ -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",

View file

@ -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))
}

View file

@ -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,

View file

@ -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,

View file

@ -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",
],
)

View file

@ -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))
}
}

View file

@ -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) {

View file

@ -1,9 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View file

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "peoplenearbyon_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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

View file

@ -1,12 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_stopshowme.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Some files were not shown because too many files have changed in this diff Show more