From c1f39634c615bea92656171d43a6c972fec857e8 Mon Sep 17 00:00:00 2001 From: Mike Renoir <> Date: Tue, 18 Apr 2023 17:15:22 +0300 Subject: [PATCH 1/7] bug fixes --- submodules/Emoji/Sources/EmojiUtils.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/Emoji/Sources/EmojiUtils.swift b/submodules/Emoji/Sources/EmojiUtils.swift index 50924083bf..e30a0e09c3 100644 --- a/submodules/Emoji/Sources/EmojiUtils.swift +++ b/submodules/Emoji/Sources/EmojiUtils.swift @@ -104,7 +104,7 @@ public extension String { var emojis: [String] { var emojis: [String] = [] self.enumerateSubstrings(in: self.startIndex ..< self.endIndex, options: .byComposedCharacterSequences) { substring, _, _, _ in - if let substring = substring { + if let substring = substring, substring.isSingleEmoji { emojis.append(substring) } } From 7d6b9c95e7a6827b20cf754d4086450249918f09 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 18 Apr 2023 18:33:35 +0400 Subject: [PATCH 2/7] [WIP] Stories UI --- .../Sources/DebugController.swift | 14 +- submodules/GalleryData/BUILD | 2 + .../GalleryData/Sources/GalleryData.swift | 18 + .../Sources/PhotoResources.swift | 4 +- submodules/TelegramUI/BUILD | 2 + .../MessageInputPanelComponent/BUILD | 20 + .../Sources/MessageInputPanelComponent.swift | 112 ++++ .../Stories/StoryContainerScreen/BUILD | 24 + .../MediaNavigationStripComponent.swift | 139 +++++ .../Sources/StoryContainerScreen.swift | 544 ++++++++++++++++++ .../Sources/StoryContent.swift | 46 ++ .../Stories/StoryContentComponent/BUILD | 28 + .../Sources/StoryAuthorInfoComponent.swift | 96 ++++ .../Sources/StoryAvatarInfoComponent.swift | 71 +++ .../Sources/StoryChatContent.swift | 72 +++ .../StoryMessageContentComponent.swift | 213 +++++++ .../Sources/StoryVideoDecoration.swift | 122 ++++ .../Close.imageset/Contents.json | 12 + .../Media Gallery/Close.imageset/icon.svg | 3 + .../TelegramUI/Sources/OpenChatMessage.swift | 7 + .../Sources/ExperimentalUISettings.swift | 10 +- 21 files changed, 1548 insertions(+), 11 deletions(-) create mode 100644 submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD create mode 100644 submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD create mode 100644 submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD create mode 100644 submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAuthorInfoComponent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAvatarInfoComponent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryVideoDecoration.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/icon.svg diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 9aa30f9383..2620357f47 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -92,7 +92,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case inlineForums(Bool) case localTranscription(Bool) case enableReactionOverrides(Bool) - case playerEmbedding(Bool) + case storiesExperiment(Bool) case playlistPlayback(Bool) case enableQuickReactionSwitch(Bool) case voiceConference @@ -118,7 +118,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases: + case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases: return DebugControllerSection.experiments.rawValue case .logTranslationRecognition, .resetTranslationStates: return DebugControllerSection.translation.rawValue @@ -213,7 +213,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 41 case .resetTranslationStates: return 42 - case .playerEmbedding: + case .storiesExperiment: return 43 case .playlistPlayback: return 44 @@ -1220,12 +1220,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .playerEmbedding(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in + case let .storiesExperiment(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Gallery X", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - settings.playerEmbedding = value + settings.storiesExperiment = value return PreferencesEntry(settings) }) }).start() @@ -1384,7 +1384,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.logTranslationRecognition(experimentalSettings.logLanguageRecognition)) entries.append(.resetTranslationStates) - entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) + entries.append(.storiesExperiment(experimentalSettings.storiesExperiment)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction)) } diff --git a/submodules/GalleryData/BUILD b/submodules/GalleryData/BUILD index 5e3be4d4e2..6c8d7399cc 100644 --- a/submodules/GalleryData/BUILD +++ b/submodules/GalleryData/BUILD @@ -24,6 +24,8 @@ swift_library( "//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI", "//submodules/MediaResources:MediaResources", "//submodules/WebsiteType:WebsiteType", + "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", + "//submodules/TelegramUI/Components/Stories/StoryContentComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 931fecacc6..10acbaaa3e 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -14,6 +14,8 @@ import PeerAvatarGalleryUI import GalleryUI import MediaResources import WebsiteType +import StoryContainerScreen +import StoryContentComponent public enum ChatMessageGalleryControllerData { case url(String) @@ -28,6 +30,7 @@ public enum ChatMessageGalleryControllerData { case chatAvatars(AvatarGalleryController, Media) case theme(TelegramMediaFile) case other(Media) + case story(Signal) } private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] { @@ -267,6 +270,21 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati openChatLocationContextHolder = Atomic(value: nil) } + if context.sharedContext.immediateExperimentalUISettings.storiesExperiment { + return .story(StoryChatContent.messages( + context: context, + messageId: message.id + ) + |> take(1) + |> deliverOnMainQueue + |> map { initialContent in + return StoryContainerScreen( + context: context, + initialContent: initialContent + ) + }) + } + return .gallery(startState |> deliverOnMainQueue |> map { startState in diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index c830fe9deb..2bc7c2294d 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -2057,8 +2057,8 @@ public func chatWebpageSnippetPhoto(account: Account, userLocation: MediaResourc } } -public func chatMessageVideo(postbox: Postbox, userLocation: MediaResourceUserLocation, videoReference: FileMediaReference) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - return mediaGridMessageVideo(postbox: postbox, userLocation: userLocation, videoReference: videoReference) +public func chatMessageVideo(postbox: Postbox, userLocation: MediaResourceUserLocation, videoReference: FileMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + return mediaGridMessageVideo(postbox: postbox, userLocation: userLocation, videoReference: videoReference, synchronousLoad: synchronousLoad) } private func chatSecretMessageVideoData(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal { diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 2125a4eca0..aedf979642 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -360,6 +360,8 @@ swift_library( "//submodules/TelegramUI/Components/SendInviteLinkScreen", "//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen", "//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem", + "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", + "//submodules/TelegramUI/Components/Stories/StoryContentComponent", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD new file mode 100644 index 0000000000..1f614ccdb6 --- /dev/null +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MessageInputPanelComponent", + module_name = "MessageInputPanelComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift new file mode 100644 index 0000000000..9a24a16301 --- /dev/null +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -0,0 +1,112 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AppBundle + +public final class MessageInputPanelComponent: Component { + public init() { + + } + + public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool { + return true + } + + public final class View: UIView { + private let fieldBackgroundView: UIImageView + private let fieldPlaceholder = ComponentView() + + private let attachmentIconView: UIImageView + private let recordingIconView: UIImageView + private let stickerIconView: UIImageView + + private var component: MessageInputPanelComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.fieldBackgroundView = UIImageView() + self.attachmentIconView = UIImageView() + self.recordingIconView = UIImageView() + self.stickerIconView = UIImageView() + + super.init(frame: frame) + + self.addSubview(self.fieldBackgroundView) + + self.addSubview(self.fieldBackgroundView) + self.addSubview(self.attachmentIconView) + self.addSubview(self.recordingIconView) + self.addSubview(self.stickerIconView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: MessageInputPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let insets = UIEdgeInsets(top: 5.0, left: 41.0, bottom: 5.0, right: 41.0) + let fieldCornerRadius: CGFloat = 16.0 + + self.component = component + self.state = state + + if self.fieldBackgroundView.image == nil { + self.fieldBackgroundView.image = generateStretchableFilledCircleImage(diameter: fieldCornerRadius * 2.0, color: nil, strokeColor: UIColor(white: 1.0, alpha: 0.16), strokeWidth: 1.0, backgroundColor: nil) + } + if self.attachmentIconView.image == nil { + self.attachmentIconView.image = UIImage(bundleImageName: "Chat/Input/Text/IconAttachment")?.withRenderingMode(.alwaysTemplate) + self.attachmentIconView.tintColor = .white + } + if self.recordingIconView.image == nil { + self.recordingIconView.image = UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone")?.withRenderingMode(.alwaysTemplate) + self.recordingIconView.tintColor = .white + } + if self.stickerIconView.image == nil { + self.stickerIconView.image = UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconStickers")?.withRenderingMode(.alwaysTemplate) + self.stickerIconView.tintColor = .white + } + + let fieldFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: availableSize.width - insets.left - insets.right, height: availableSize.height - insets.top - insets.bottom)) + transition.setFrame(view: self.fieldBackgroundView, frame: fieldFrame) + + let rightFieldInset: CGFloat = 34.0 + + let placeholderSize = self.fieldPlaceholder.update( + transition: .immediate, + component: AnyComponent(Text(text: "Reply Privately...", font: Font.regular(17.0), color: UIColor(white: 1.0, alpha: 0.16))), + environment: {}, + containerSize: fieldFrame.size + ) + if let fieldPlaceholderView = self.fieldPlaceholder.view { + if fieldPlaceholderView.superview == nil { + fieldPlaceholderView.layer.anchorPoint = CGPoint() + fieldPlaceholderView.isUserInteractionEnabled = false + self.addSubview(fieldPlaceholderView) + } + fieldPlaceholderView.bounds = CGRect(origin: CGPoint(), size: placeholderSize) + transition.setPosition(view: fieldPlaceholderView, position: CGPoint(x: fieldFrame.minX + 12.0, y: fieldFrame.minY + floor((fieldFrame.height - placeholderSize.height) * 0.5))) + } + + if let image = self.attachmentIconView.image { + transition.setFrame(view: self.attachmentIconView, frame: CGRect(origin: CGPoint(x: floor((insets.left - image.size.width) * 0.5), y: floor((availableSize.height - image.size.height) * 0.5)), size: image.size)) + } + if let image = self.recordingIconView.image { + transition.setFrame(view: self.recordingIconView, frame: CGRect(origin: CGPoint(x: availableSize.width - insets.right + floor((insets.right - image.size.width) * 0.5), y: floor((availableSize.height - image.size.height) * 0.5)), size: image.size)) + } + if let image = self.stickerIconView.image { + transition.setFrame(view: self.stickerIconView, frame: CGRect(origin: CGPoint(x: fieldFrame.maxX - rightFieldInset + floor((rightFieldInset - image.size.width) * 0.5), y: fieldFrame.minY + floor((fieldFrame.height - image.size.height) * 0.5)), size: image.size)) + } + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD new file mode 100644 index 0000000000..7c3d66be69 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StoryContainerScreen", + module_name = "StoryContainerScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/Components/ViewControllerComponent", + "//submodules/TelegramUI/Components/MessageInputPanelComponent", + "//submodules/AccountContext", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AppBundle", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift new file mode 100644 index 0000000000..8de025765b --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift @@ -0,0 +1,139 @@ +import Foundation +import UIKit +import Display +import ComponentFlow + +final class MediaNavigationStripComponent: Component { + let index: Int + let count: Int + + init(index: Int, count: Int) { + self.index = index + self.count = count + } + + static func ==(lhs: MediaNavigationStripComponent, rhs: MediaNavigationStripComponent) -> Bool { + if lhs.index != rhs.index { + return false + } + if lhs.count != rhs.count { + return false + } + return true + } + + private final class ItemLayer: SimpleLayer { + override init() { + super.init() + + self.cornerRadius = 1.5 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override init(layer: Any) { + super.init(layer: layer) + } + } + + final class View: UIView { + private var visibleItems: [Int: ItemLayer] = [:] + + override init(frame: CGRect) { + super.init(frame: frame) + + self.clipsToBounds = true + self.layer.cornerRadius = 1.0 + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: MediaNavigationStripComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let spacing: CGFloat = 3.0 + let itemHeight: CGFloat = 2.0 + let minItemWidth: CGFloat = 3.0 + + var validIndices: [Int] = [] + if component.count != 0 { + var idealItemWidth: CGFloat = (availableSize.width - CGFloat(component.count - 1) * spacing) / CGFloat(component.count) + idealItemWidth = round(idealItemWidth) + + let itemWidth: CGFloat + if idealItemWidth < minItemWidth { + itemWidth = minItemWidth + } else { + itemWidth = idealItemWidth + } + + let globalWidth: CGFloat = CGFloat(component.count) * itemWidth + CGFloat(component.count - 1) * spacing + let globalFocusedFrame = CGRect(origin: CGPoint(x: CGFloat(component.index) * (itemWidth + spacing), y: 0.0), size: CGSize(width: itemWidth, height: itemHeight)) + var globalOffset: CGFloat = floor(globalFocusedFrame.midX - availableSize.width * 0.5) + if globalOffset > globalWidth - availableSize.width { + globalOffset = globalWidth - availableSize.width + } + if globalOffset < 0.0 { + globalOffset = 0.0 + } + + //itemWidth * itemCount + (itemCount - 1) * spacing = width + //itemWidth * itemCount + itemCount * spacing - spacing = width + //itemCount * (itemWidth + spacing) = width + spacing + //itemCount = (width + spacing) / (itemWidth + spacing) + let potentiallyVisibleCount = Int(ceil((availableSize.width + spacing) / (itemWidth + spacing))) + for i in (component.index - potentiallyVisibleCount) ... (component.index + potentiallyVisibleCount) { + if i < 0 { + continue + } + if i >= component.count { + continue + } + let itemFrame = CGRect(origin: CGPoint(x: -globalOffset + CGFloat(i) * (itemWidth + spacing), y: 0.0), size: CGSize(width: itemWidth, height: itemHeight)) + if itemFrame.maxY < 0.0 || itemFrame.minY >= availableSize.width { + continue + } + + validIndices.append(i) + + let itemLayer: ItemLayer + if let current = self.visibleItems[i] { + itemLayer = current + } else { + itemLayer = ItemLayer() + self.layer.addSublayer(itemLayer) + self.visibleItems[i] = itemLayer + itemLayer.cornerRadius = itemHeight * 0.5 + } + + transition.setFrame(layer: itemLayer, frame: itemFrame) + + itemLayer.backgroundColor = UIColor(white: 1.0, alpha: i == component.index ? 1.0 : 0.5).cgColor + } + } + + var removedIndices: [Int] = [] + for (index, itemLayer) in self.visibleItems { + if !validIndices.contains(index) { + removedIndices.append(index) + itemLayer.removeFromSuperlayer() + } + } + for index in removedIndices { + self.visibleItems.removeValue(forKey: index) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift new file mode 100644 index 0000000000..eb08a95dc0 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -0,0 +1,544 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import ViewControllerComponent +import AccountContext +import SwiftSignalKit +import AppBundle +import MessageInputPanelComponent + +private final class StoryContainerScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let initialContent: StoryContentItemSlice + + init( + initialContent: StoryContentItemSlice + ) { + self.initialContent = initialContent + } + + static func ==(lhs: StoryContainerScreenComponent, rhs: StoryContainerScreenComponent) -> Bool { + if lhs.initialContent !== rhs.initialContent { + return false + } + return true + } + + private final class ScrollView: UIScrollView { + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + private struct ItemLayout { + var size: CGSize + + init(size: CGSize) { + self.size = size + } + } + + private final class VisibleItem { + let view = ComponentView() + + init() { + } + } + + private final class InfoItem { + let component: AnyComponent + let view = ComponentView() + + init(component: AnyComponent) { + self.component = component + } + } + + final class View: UIView, UIScrollViewDelegate { + private let scrollView: ScrollView + + private let contentContainerView: UIView + private let topContentGradientLayer: SimpleGradientLayer + + private let closeButton: HighlightableButton + private let closeButtonIconView: UIImageView + + private let navigationStrip = ComponentView() + + private var centerInfoItem: InfoItem? + private var rightInfoItem: InfoItem? + + private let inputPanel = ComponentView() + + private var component: StoryContainerScreenComponent? + private weak var state: EmptyComponentState? + private var environment: ViewControllerComponentContainer.Environment? + + private var itemLayout: ItemLayout? + private var ignoreScrolling: Bool = false + + private var focusedItemId: AnyHashable? + private var currentSlice: StoryContentItemSlice? + private var currentSliceDisposable: Disposable? + + private var visibleItems: [AnyHashable: VisibleItem] = [:] + + override init(frame: CGRect) { + self.scrollView = ScrollView() + + self.contentContainerView = UIView() + self.contentContainerView.clipsToBounds = true + self.contentContainerView.isUserInteractionEnabled = false + + self.topContentGradientLayer = SimpleGradientLayer() + + self.closeButton = HighlightableButton() + self.closeButtonIconView = UIImageView() + + super.init(frame: frame) + + self.backgroundColor = .black + + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.clipsToBounds = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.alwaysBounceHorizontal = false + self.scrollView.alwaysBounceVertical = true + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + self.scrollView.clipsToBounds = true + + self.addSubview(self.contentContainerView) + self.layer.addSublayer(self.topContentGradientLayer) + + self.closeButton.addSubview(self.closeButtonIconView) + self.addSubview(self.closeButton) + self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside) + + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.currentSliceDisposable?.dispose() + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state, let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }), let itemLayout = self.itemLayout { + let point = recognizer.location(in: self) + + var nextIndex: Int + if point.x < itemLayout.size.width * 0.5 { + nextIndex = currentIndex + 1 + } else { + nextIndex = currentIndex - 1 + } + nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1)) + if nextIndex != currentIndex { + let focusedItemId = currentSlice.items[nextIndex].id + self.focusedItemId = focusedItemId + self.state?.updated(transition: .immediate) + + self.currentSliceDisposable?.dispose() + self.currentSliceDisposable = (currentSlice.update( + currentSlice, + focusedItemId + ) + |> deliverOnMainQueue).start(next: { [weak self] contentSlice in + guard let self else { + return + } + self.currentSlice = contentSlice + self.state?.updated(transition: .immediate) + }) + } + } + } + + @objc private func closePressed() { + guard let environment = self.environment, let controller = environment.controller() else { + return + } + controller.dismiss() + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateScrolling(transition: .immediate) + } + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + + private func updateScrolling(transition: Transition) { + guard let itemLayout = self.itemLayout else { + return + } + + var validIds: [AnyHashable] = [] + if let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) { + validIds.append(focusedItemId) + + var itemTransition = transition + let visibleItem: VisibleItem + if let current = self.visibleItems[focusedItemId] { + visibleItem = current + } else { + itemTransition = .immediate + visibleItem = VisibleItem() + self.visibleItems[focusedItemId] = visibleItem + } + + let _ = visibleItem.view.update( + transition: itemTransition, + component: focusedItem.component, + environment: {}, + containerSize: itemLayout.size + ) + if let view = visibleItem.view.view { + if view.superview == nil { + self.contentContainerView.addSubview(view) + } + itemTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(), size: itemLayout.size)) + } + } + + var removeIds: [AnyHashable] = [] + for (id, visibleItem) in self.visibleItems { + if !validIds.contains(id) { + removeIds.append(id) + if let view = visibleItem.view.view { + view.removeFromSuperview() + } + } + } + for id in removeIds { + self.visibleItems.removeValue(forKey: id) + } + } + + func animateIn() { + self.layer.allowsGroupOpacity = true + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, completion: { [weak self] _ in + guard let self else { + return + } + self.layer.allowsGroupOpacity = false + }) + } + + func animateOut(completion: @escaping () -> Void) { + self.layer.allowsGroupOpacity = true + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + completion() + }) + } + + func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let isFirstTime = self.component == nil + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + + if self.component == nil { + self.focusedItemId = component.initialContent.focusedItemId + self.currentSlice = component.initialContent + + self.currentSliceDisposable?.dispose() + self.currentSliceDisposable = (component.initialContent.update( + component.initialContent, + component.initialContent.focusedItemId + ) + |> deliverOnMainQueue).start(next: { [weak self] contentSlice in + guard let self else { + return + } + self.currentSlice = contentSlice + self.state?.updated(transition: .immediate) + }) + } + + if self.topContentGradientLayer.colors == nil { + var locations: [NSNumber] = [] + var colors: [CGColor] = [] + let numStops = 4 + let baseAlpha: CGFloat = 0.5 + for i in 0 ..< numStops { + let step = 1.0 - CGFloat(i) / CGFloat(numStops - 1) + locations.append((1.0 - step) as NSNumber) + let alphaStep: CGFloat = pow(step, 1.5) + colors.append(UIColor.black.withAlphaComponent(alphaStep * baseAlpha).cgColor) + } + + self.topContentGradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0) + self.topContentGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) + + self.topContentGradientLayer.locations = locations + self.topContentGradientLayer.colors = colors + self.topContentGradientLayer.type = .axial + } + + if let focusedItemId = self.focusedItemId { + if let currentSlice = self.currentSlice { + if !currentSlice.items.contains(where: { $0.id == focusedItemId }) { + self.focusedItemId = currentSlice.items.first?.id + } + } else { + self.focusedItemId = nil + } + } + + self.component = component + self.state = state + self.environment = environment + + let bottomContentInset: CGFloat + if !environment.safeInsets.bottom.isZero { + bottomContentInset = environment.safeInsets.bottom + 5.0 + 44.0 + } else { + bottomContentInset = 44.0 + } + + let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: environment.statusBarHeight), size: CGSize(width: availableSize.width, height: availableSize.height - environment.statusBarHeight - bottomContentInset)) + transition.setFrame(view: self.contentContainerView, frame: contentFrame) + transition.setCornerRadius(layer: self.contentContainerView.layer, cornerRadius: 14.0) + + if self.closeButtonIconView.image == nil { + self.closeButtonIconView.image = UIImage(bundleImageName: "Media Gallery/Close")?.withRenderingMode(.alwaysTemplate) + self.closeButtonIconView.tintColor = .white + } + if let image = self.closeButtonIconView.image { + let closeButtonFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: 50.0, height: 64.0)) + transition.setFrame(view: self.closeButton, frame: closeButtonFrame) + transition.setFrame(view: self.closeButtonIconView, frame: CGRect(origin: CGPoint(x: floor((closeButtonFrame.width - image.size.width) * 0.5), y: floor((closeButtonFrame.height - image.size.height) * 0.5)), size: image.size)) + } + + var currentRightInfoItem: InfoItem? + if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { + if let rightInfoComponent = item.rightInfoComponent { + if let rightInfoItem = self.rightInfoItem, rightInfoItem.component == item.rightInfoComponent { + currentRightInfoItem = rightInfoItem + } else { + currentRightInfoItem = InfoItem(component: rightInfoComponent) + } + } + } + + if let rightInfoItem = self.rightInfoItem, currentRightInfoItem?.component != rightInfoItem.component { + self.rightInfoItem = nil + if let view = rightInfoItem.view.view { + view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in + view?.removeFromSuperview() + }) + } + } + + var currentCenterInfoItem: InfoItem? + if let currentSlice = self.currentSlice, let item = currentSlice.items.first(where: { $0.id == self.focusedItemId }) { + if let centerInfoComponent = item.centerInfoComponent { + if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == item.centerInfoComponent { + currentCenterInfoItem = centerInfoItem + } else { + currentCenterInfoItem = InfoItem(component: centerInfoComponent) + } + } + } + + if let centerInfoItem = self.centerInfoItem, currentCenterInfoItem?.component != centerInfoItem.component { + self.centerInfoItem = nil + if let view = centerInfoItem.view.view { + view.removeFromSuperview() + /*view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in + view?.removeFromSuperview() + })*/ + } + } + + if let currentRightInfoItem { + self.rightInfoItem = currentRightInfoItem + + let rightInfoItemSize = currentRightInfoItem.view.update( + transition: .immediate, + component: currentRightInfoItem.component, + environment: {}, + containerSize: CGSize(width: 36.0, height: 36.0) + ) + if let view = currentRightInfoItem.view.view { + var animateIn = false + if view.superview == nil { + self.addSubview(view) + animateIn = true + } + transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: contentFrame.maxX - 6.0 - rightInfoItemSize.width, y: contentFrame.minY + 14.0), size: rightInfoItemSize)) + + if animateIn, !isFirstTime { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } + } + } + + if let currentCenterInfoItem { + self.centerInfoItem = currentCenterInfoItem + + let centerInfoItemSize = currentCenterInfoItem.view.update( + transition: .immediate, + component: currentCenterInfoItem.component, + environment: {}, + containerSize: CGSize(width: contentFrame.width, height: 44.0) + ) + if let view = currentCenterInfoItem.view.view { + var animateIn = false + if view.superview == nil { + view.isUserInteractionEnabled = false + self.addSubview(view) + animateIn = true + } + transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY + 10.0), size: centerInfoItemSize)) + + if animateIn, !isFirstTime { + //view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + } + + if let currentSlice = self.currentSlice { + let navigationStripSideInset: CGFloat = 8.0 + let navigationStripTopInset: CGFloat = 8.0 + + let index = currentSlice.items.first(where: { $0.id == self.focusedItemId })?.position ?? 0 + + let _ = self.navigationStrip.update( + transition: transition, + component: AnyComponent(MediaNavigationStripComponent( + index: max(0, min(currentSlice.totalCount - 1 - index, currentSlice.totalCount - 1)), + count: currentSlice.totalCount + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0) + ) + if let navigationStripView = self.navigationStrip.view { + if navigationStripView.superview == nil { + self.addSubview(navigationStripView) + } + transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: contentFrame.minX + navigationStripSideInset, y: contentFrame.minY + navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0))) + } + } + + let gradientHeight: CGFloat = 74.0 + transition.setFrame(layer: self.topContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: gradientHeight))) + + let itemLayout = ItemLayout(size: contentFrame.size) + self.itemLayout = itemLayout + + let inputPanelSize = self.inputPanel.update( + transition: transition, + component: AnyComponent(MessageInputPanelComponent( + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 44.0) + ) + if let inputPanelView = self.inputPanel.view { + if inputPanelView.superview == nil { + self.addSubview(inputPanelView) + } + transition.setFrame(view: inputPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentFrame.maxY), size: inputPanelSize)) + } + + self.ignoreScrolling = true + transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) + let contentSize = availableSize + if contentSize != self.scrollView.contentSize { + self.scrollView.contentSize = contentSize + } + self.ignoreScrolling = false + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public class StoryContainerScreen: ViewControllerComponentContainer { + private var isDismissed: Bool = false + + public init( + context: AccountContext, + initialContent: StoryContentItemSlice + ) { + super.init(context: context, component: StoryContainerScreenComponent( + initialContent: initialContent + ), navigationBarAppearance: .none) + + self.statusBar.statusBarStyle = .White + self.navigationPresentation = .flatModal + self.blocksBackgroundWhenInOverlay = true + //self.automaticallyControlPresentationContextLayout = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.view.disablesInteractiveModalDismiss = true + + if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View { + componentView.animateIn() + } + } + + override public func dismiss(completion: (() -> Void)? = nil) { + if !self.isDismissed { + self.isDismissed = true + + if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View { + componentView.animateOut(completion: { [weak self] in + completion?() + self?.dismiss(animated: false) + }) + } else { + self.dismiss(animated: false) + } + } + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift new file mode 100644 index 0000000000..c5756dc148 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -0,0 +1,46 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit + +public final class StoryContentItem { + public let id: AnyHashable + public let position: Int + public let component: AnyComponent + public let centerInfoComponent: AnyComponent? + public let rightInfoComponent: AnyComponent? + + public init( + id: AnyHashable, + position: Int, + component: AnyComponent, + centerInfoComponent: AnyComponent?, + rightInfoComponent: AnyComponent? + ) { + self.id = id + self.position = position + self.component = component + self.centerInfoComponent = centerInfoComponent + self.rightInfoComponent = rightInfoComponent + } +} + +public final class StoryContentItemSlice { + public let focusedItemId: AnyHashable + public let items: [StoryContentItem] + public let totalCount: Int + public let update: (StoryContentItemSlice, AnyHashable) -> Signal + + public init( + focusedItemId: AnyHashable, + items: [StoryContentItem], + totalCount: Int, + update: @escaping (StoryContentItemSlice, AnyHashable) -> Signal + ) { + self.focusedItemId = focusedItemId + self.items = items + self.totalCount = totalCount + self.update = update + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD b/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD new file mode 100644 index 0000000000..5b7d77123e --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "StoryContentComponent", + module_name = "StoryContentComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/Stories/StoryContainerScreen", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AccountContext", + "//submodules/TelegramCore", + "//submodules/PhotoResources", + "//submodules/MediaPlayer:UniversalMediaPlayer", + "//submodules/TelegramUniversalVideoContent", + "//submodules/AvatarNode", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAuthorInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAuthorInfoComponent.swift new file mode 100644 index 0000000000..914b7f2375 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAuthorInfoComponent.swift @@ -0,0 +1,96 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore +import TelegramStringFormatting + +final class StoryAuthorInfoComponent: Component { + let context: AccountContext + let message: EngineMessage + + init(context: AccountContext, message: EngineMessage) { + self.context = context + self.message = message + } + + static func ==(lhs: StoryAuthorInfoComponent, rhs: StoryAuthorInfoComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.message != rhs.message { + return false + } + return true + } + + final class View: UIView { + private let title = ComponentView() + private let subtitle = ComponentView() + + private var component: StoryAuthorInfoComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: StoryAuthorInfoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + let size = availableSize + let spacing: CGFloat = 0.0 + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + + let title = component.message.author?.debugDisplayTitle ?? "" + let subtitle = humanReadableStringForTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: component.message.timestamp).string + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(Text(text: title, font: Font.semibold(17.0), color: .white)), + environment: {}, + containerSize: availableSize + ) + let subtitleSize = self.subtitle.update( + transition: .immediate, + component: AnyComponent(Text(text: subtitle, font: Font.regular(12.0), color: UIColor(white: 1.0, alpha: 0.8))), + environment: {}, + containerSize: availableSize + ) + + let contentHeight: CGFloat = titleSize.height + spacing + subtitleSize.height + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((availableSize.height - contentHeight) * 0.5)), size: titleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: titleFrame.maxY + spacing), size: subtitleSize) + + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + transition.setFrame(view: titleView, frame: titleFrame) + } + if let subtitleView = self.subtitle.view { + if subtitleView.superview == nil { + self.addSubview(subtitleView) + } + transition.setFrame(view: subtitleView, frame: subtitleFrame) + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAvatarInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAvatarInfoComponent.swift new file mode 100644 index 0000000000..fd75f34511 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryAvatarInfoComponent.swift @@ -0,0 +1,71 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore +import AsyncDisplayKit +import AvatarNode + +final class StoryAvatarInfoComponent: Component { + let context: AccountContext + let peer: EnginePeer + + init(context: AccountContext, peer: EnginePeer) { + self.context = context + self.peer = peer + } + + static func ==(lhs: StoryAvatarInfoComponent, rhs: StoryAvatarInfoComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.peer != rhs.peer { + return false + } + return true + } + + final class View: UIView { + private let avatarNode: AvatarNode + + private var component: StoryAvatarInfoComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0)) + + super.init(frame: frame) + + self.addSubnode(self.avatarNode) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: StoryAvatarInfoComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + let size = CGSize(width: 36.0, height: 36.0) + + self.avatarNode.frame = CGRect(origin: CGPoint(), size: size) + self.avatarNode.setPeer( + context: component.context, + theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme, + peer: component.peer + ) + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift new file mode 100644 index 0000000000..598f9926cd --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift @@ -0,0 +1,72 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import SwiftSignalKit +import AccountContext +import TelegramCore +import StoryContainerScreen + +public enum StoryChatContent { + public static func messages( + context: AccountContext, + messageId: EngineMessage.Id + ) -> Signal { + return context.account.postbox.aroundIdMessageHistoryViewForLocation( + .peer(peerId: messageId.peerId, threadId: nil), + ignoreMessagesInTimestampRange: nil, + count: 10, + messageId: messageId, + topTaggedMessageIdNamespaces: Set(), + tagMask: .photoOrVideo, + appendMessagesFromTheSameGroup: false, + namespaces: .not(Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal])), + orderStatistics: .combinedLocation + ) + |> map { view -> StoryContentItemSlice in + var items: [StoryContentItem] = [] + var totalCount = 0 + for entry in view.0.entries { + if let location = entry.location { + totalCount = location.count + } + items.append(StoryContentItem( + id: AnyHashable(entry.message.id), + position: entry.location?.index ?? 0, + component: AnyComponent(StoryMessageContentComponent( + context: context, + message: EngineMessage(entry.message) + )), + centerInfoComponent: AnyComponent(StoryAuthorInfoComponent( + context: context, + message: EngineMessage(entry.message) + )), + rightInfoComponent: entry.message.author.flatMap { author -> AnyComponent in + return AnyComponent(StoryAvatarInfoComponent( + context: context, + peer: EnginePeer(author) + )) + } + )) + } + return StoryContentItemSlice( + focusedItemId: AnyHashable(messageId), + items: items, + totalCount: totalCount, + update: { _, itemId in + if let id = itemId.base as? EngineMessage.Id { + return StoryChatContent.messages( + context: context, + messageId: id + ) + } else { + return StoryChatContent.messages( + context: context, + messageId: messageId + ) + } + } + ) + } + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift new file mode 100644 index 0000000000..173773ca5a --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift @@ -0,0 +1,213 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore +import AsyncDisplayKit +import PhotoResources +import SwiftSignalKit +import UniversalMediaPlayer +import TelegramUniversalVideoContent + +final class StoryMessageContentComponent: Component { + let context: AccountContext + let message: EngineMessage + + init(context: AccountContext, message: EngineMessage) { + self.context = context + self.message = message + } + + static func ==(lhs: StoryMessageContentComponent, rhs: StoryMessageContentComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.message != rhs.message { + return false + } + return true + } + + final class View: UIView { + private let imageNode: TransformImageNode + private var videoNode: UniversalVideoNode? + + private var currentMessageMedia: EngineMedia? + private var fetchDisposable: Disposable? + + private var component: StoryMessageContentComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.imageNode = TransformImageNode() + + super.init(frame: frame) + + self.addSubnode(self.imageNode) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.fetchDisposable?.dispose() + } + + private func performActionAfterImageContentLoaded(update: Bool) { + guard let component = self.component, let currentMessageMedia = self.currentMessageMedia else { + return + } + + if case let .file(file) = currentMessageMedia { + if self.videoNode == nil { + let videoNode = UniversalVideoNode( + postbox: component.context.account.postbox, + audioSession: component.context.sharedContext.mediaManager.audioSession, + manager: component.context.sharedContext.mediaManager.universalVideoManager, + decoration: StoryVideoDecoration(), + content: NativeVideoContent( + id: .message(component.message.stableId, file.fileId), + userLocation: .peer(component.message.id.peerId), + fileReference: .message(message: MessageReference(component.message._asMessage()), media: file), + imageReference: nil, + loopVideo: true, + enableSound: true, + tempFilePath: nil, + captureProtected: component.message._asMessage().isCopyProtected(), + storeAfterDownload: nil + ), + priority: .gallery + ) + videoNode.ownsContentNodeUpdated = { [weak self] value in + guard let self else { + return + } + if value { + self.videoNode?.play() + } + } + videoNode.canAttachContent = true + self.videoNode = videoNode + self.addSubnode(videoNode) + if update { + self.state?.updated(transition: .immediate) + } + } + } + } + + func update(component: StoryMessageContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + var messageMedia: EngineMedia? + for media in component.message.media { + switch media { + case let image as TelegramMediaImage: + messageMedia = .image(image) + case let file as TelegramMediaFile: + messageMedia = .file(file) + default: + break + } + } + + var reloadMedia = false + if self.currentMessageMedia?.id != messageMedia?.id { + self.currentMessageMedia = messageMedia + reloadMedia = true + } + + if reloadMedia, let messageMedia { + var signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? + var fetchSignal: Signal? + switch messageMedia { + case let .image(image): + signal = chatMessagePhoto( + postbox: component.context.account.postbox, + userLocation: .peer(component.message.id.peerId), + photoReference: .message(message: MessageReference(component.message._asMessage()), media: image), + synchronousLoad: true, + highQuality: true + ) + if let representation = image.representations.last { + fetchSignal = messageMediaImageInteractiveFetched(context: component.context, message: component.message._asMessage(), image: image, resource: representation.resource, userInitiated: true, storeToDownloadsPeerId: component.message.id.peerId) + |> ignoreValues + } + case let .file(file): + signal = chatMessageVideo( + postbox: component.context.account.postbox, + userLocation: .peer(component.message.id.peerId), + videoReference: .message(message: MessageReference(component.message._asMessage()), media: file), + synchronousLoad: true + ) + fetchSignal = messageMediaFileInteractiveFetched(context: component.context, message: component.message._asMessage(), file: file, userInitiated: true, storeToDownloadsPeerId: component.message.id.peerId) + |> ignoreValues + default: + break + } + + if let signal { + var wasSynchronous = true + self.imageNode.setSignal(signal |> afterCompleted { [weak self] in + Queue.mainQueue().async { + guard let self else { + return + } + + self.performActionAfterImageContentLoaded(update: !wasSynchronous) + } + }, attemptSynchronously: true) + wasSynchronous = false + } + + self.fetchDisposable?.dispose() + self.fetchDisposable = nil + if let fetchSignal { + self.fetchDisposable = fetchSignal.start() + } + } + + if let messageMedia { + var dimensions: CGSize? + switch messageMedia { + case let .image(image): + dimensions = image.representations.last?.dimensions.cgSize + case let .file(file): + dimensions = file.dimensions?.cgSize + default: + break + } + + if let dimensions { + let apply = self.imageNode.asyncLayout()(TransformImageArguments( + corners: ImageCorners(), + imageSize: dimensions.aspectFilled(availableSize), + boundingSize: availableSize, + intrinsicInsets: UIEdgeInsets() + )) + apply() + + if let videoNode = self.videoNode { + let videoSize = dimensions.aspectFilled(availableSize) + videoNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) * 0.5), y: floor((availableSize.height - videoSize.height) * 0.5)), size: videoSize) + videoNode.updateLayout(size: videoSize, transition: .immediate) + } + } + self.imageNode.frame = CGRect(origin: CGPoint(), size: availableSize) + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryVideoDecoration.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryVideoDecoration.swift new file mode 100644 index 0000000000..bed9c3d450 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryVideoDecoration.swift @@ -0,0 +1,122 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import UniversalMediaPlayer +import AccountContext +import PhotoResources + +public final class StoryVideoDecoration: UniversalVideoDecoration { + public let backgroundNode: ASDisplayNode? = nil + public let contentContainerNode: ASDisplayNode + public let foregroundNode: ASDisplayNode? = nil + + private var contentNode: (ASDisplayNode & UniversalVideoContentNode)? + + private var validLayoutSize: CGSize? + + public init() { + self.contentContainerNode = ASDisplayNode() + } + + public func updateContentNode(_ contentNode: (UniversalVideoContentNode & ASDisplayNode)?) { + if self.contentNode !== contentNode { + let previous = self.contentNode + self.contentNode = contentNode + + if let previous = previous { + if previous.supernode === self.contentContainerNode { + previous.removeFromSupernode() + } + } + + if let contentNode = contentNode { + if contentNode.supernode !== self.contentContainerNode { + self.contentContainerNode.addSubnode(contentNode) + if let validLayoutSize = self.validLayoutSize { + contentNode.frame = CGRect(origin: CGPoint(), size: validLayoutSize) + contentNode.updateLayout(size: validLayoutSize, transition: .immediate) + } + } + } + } + } + + public func updateCorners(_ corners: ImageCorners) { + self.contentContainerNode.clipsToBounds = true + if isRoundEqualCorners(corners) { + self.contentContainerNode.cornerRadius = corners.topLeft.radius + } else { + let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius)) + let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom) + let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) + guard let context = DrawingContext(size: size, clear: true) else { + return + } + context.withContext { ctx in + ctx.setFillColor(UIColor.black.cgColor) + ctx.fill(arguments.drawingRect) + } + addCorners(context, arguments: arguments) + + if let maskImage = context.generateImage() { + let mask = CALayer() + mask.contents = maskImage.cgImage + mask.contentsScale = maskImage.scale + mask.contentsCenter = CGRect(x: max(corners.topLeft.radius, corners.bottomLeft.radius) / maskImage.size.width, y: max(corners.topLeft.radius, corners.topRight.radius) / maskImage.size.height, width: (maskImage.size.width - max(corners.topLeft.radius, corners.bottomLeft.radius) - max(corners.topRight.radius, corners.bottomRight.radius)) / maskImage.size.width, height: (maskImage.size.height - max(corners.topLeft.radius, corners.topRight.radius) - max(corners.bottomLeft.radius, corners.bottomRight.radius)) / maskImage.size.height) + + self.contentContainerNode.layer.mask = mask + self.contentContainerNode.layer.mask?.frame = self.contentContainerNode.bounds + } + } + } + + public func updateClippingFrame(_ frame: CGRect, completion: (() -> Void)?) { + self.contentContainerNode.layer.animate(from: NSValue(cgRect: self.contentContainerNode.bounds), to: NSValue(cgRect: frame), keyPath: "bounds", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + }) + + if let maskLayer = self.contentContainerNode.layer.mask { + maskLayer.animate(from: NSValue(cgRect: self.contentContainerNode.bounds), to: NSValue(cgRect: frame), keyPath: "bounds", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + }) + + maskLayer.animate(from: NSValue(cgPoint: maskLayer.position), to: NSValue(cgPoint: frame.center), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + }) + } + + if let contentNode = self.contentNode { + contentNode.layer.animate(from: NSValue(cgPoint: contentNode.layer.position), to: NSValue(cgPoint: frame.center), keyPath: "position", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in + completion?() + }) + } + } + + public func updateContentNodeSnapshot(_ snapshot: UIView?) { + } + + public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + self.validLayoutSize = size + + let bounds = CGRect(origin: CGPoint(), size: size) + if let backgroundNode = self.backgroundNode { + transition.updateFrame(node: backgroundNode, frame: bounds) + } + if let foregroundNode = self.foregroundNode { + transition.updateFrame(node: foregroundNode, frame: bounds) + } + transition.updateFrame(node: self.contentContainerNode, frame: bounds) + if let maskLayer = self.contentContainerNode.layer.mask { + transition.updateFrame(layer: maskLayer, frame: bounds) + } + if let contentNode = self.contentNode { + transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(), size: size)) + contentNode.updateLayout(size: size, transition: transition) + } + } + + public func setStatus(_ status: Signal) { + } + + public func tap() { + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/Contents.json new file mode 100644 index 0000000000..52ca28db8c --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "icon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/icon.svg b/submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/icon.svg new file mode 100644 index 0000000000..163f631c23 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/Close.imageset/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index f87528e5a0..77c96c872e 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -22,6 +22,7 @@ import ShareController import UndoUI import WebsiteType import GalleryData +import StoryContainerScreen func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) { @@ -172,6 +173,12 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { } params.context.sharedContext.mediaManager.setPlaylist((params.context.account, PeerMessagesMediaPlaylist(context: params.context, location: location, chatLocationContextHolder: params.chatLocationContextHolder)), type: playerType, control: control) return true + case let .story(storyController): + params.dismissInput() + let _ = (storyController + |> deliverOnMainQueue).start(next: { storyController in + params.navigationController?.pushViewController(storyController) + }) case let .gallery(gallery): params.dismissInput() let _ = (gallery diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index ae4ede014c..54a2987973 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -50,6 +50,7 @@ public struct ExperimentalUISettings: Codable, Equatable { public var disableImageContentAnalysis: Bool public var disableBackgroundAnimation: Bool public var logLanguageRecognition: Bool + public var storiesExperiment: Bool public static var defaultSettings: ExperimentalUISettings { return ExperimentalUISettings( @@ -77,7 +78,8 @@ public struct ExperimentalUISettings: Codable, Equatable { disableLanguageRecognition: false, disableImageContentAnalysis: false, disableBackgroundAnimation: false, - logLanguageRecognition: false + logLanguageRecognition: false, + storiesExperiment: false ) } @@ -106,7 +108,8 @@ public struct ExperimentalUISettings: Codable, Equatable { disableLanguageRecognition: Bool, disableImageContentAnalysis: Bool, disableBackgroundAnimation: Bool, - logLanguageRecognition: Bool + logLanguageRecognition: Bool, + storiesExperiment: Bool ) { self.keepChatNavigationStack = keepChatNavigationStack self.skipReadHistory = skipReadHistory @@ -133,6 +136,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.disableImageContentAnalysis = disableImageContentAnalysis self.disableBackgroundAnimation = disableBackgroundAnimation self.logLanguageRecognition = logLanguageRecognition + self.storiesExperiment = storiesExperiment } public init(from decoder: Decoder) throws { @@ -163,6 +167,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.disableImageContentAnalysis = try container.decodeIfPresent(Bool.self, forKey: "disableImageContentAnalysis") ?? false self.disableBackgroundAnimation = try container.decodeIfPresent(Bool.self, forKey: "disableBackgroundAnimation") ?? false self.logLanguageRecognition = try container.decodeIfPresent(Bool.self, forKey: "logLanguageRecognition") ?? false + self.storiesExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesExperiment") ?? false } public func encode(to encoder: Encoder) throws { @@ -193,6 +198,7 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode(self.disableImageContentAnalysis, forKey: "disableImageContentAnalysis") try container.encode(self.disableBackgroundAnimation, forKey: "disableBackgroundAnimation") try container.encode(self.logLanguageRecognition, forKey: "logLanguageRecognition") + try container.encode(self.storiesExperiment, forKey: "storiesExperiment") } } From 61b95461d59ba8a5812b1630ca3cd3431b3fa697 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 19 Apr 2023 18:21:09 +0400 Subject: [PATCH 3/7] Update localization --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 5 ----- submodules/ChatListUI/Sources/Node/ChatListNode.swift | 7 ++----- .../ChatListUI/Sources/Node/ChatListNodeEntries.swift | 1 - .../Sources/Node/ChatListStorageInfoItem.swift | 9 --------- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index e4f83b5618..86699d872e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9112,11 +9112,6 @@ Sorry for the inconvenience."; "Wallpaper.ApplyForAll" = "Apply For All Chats"; "Wallpaper.ApplyForChat" = "Apply For This Chat"; -"ChatList.ChatFolderUpdateCount_1" = "1 new chat"; -"ChatList.ChatFolderUpdateCount_any" = "%d new chats"; -"ChatList.ChatFolderUpdateHintTitle" = "You can join %@"; -"ChatList.ChatFolderUpdateHintText" = "Tap here to view them"; - "Premium.MaxSharedFolderMembershipText" = "You can only add **%1$@** shareable folders. Upgrade to **Telegram Premium** to increase this limit up to **%2$@**."; "Premium.MaxSharedFolderMembershipNoPremiumText" = "You can only add **%1$@** shareable folders. We are working to let you increase this limit in the future."; "Premium.MaxSharedFolderMembershipFinalText" = "Sorry, you can only add **%1$@** shareable folders."; diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index fd358cff29..0dd0e80308 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1841,19 +1841,16 @@ public final class ChatListNode: ListView { suggestedChatListNotice, savedMessagesPeer, chatListViewUpdate, - self.chatFolderUpdates.get() |> distinctUntilChanged, self.statePromise.get(), contacts ) - |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state, contacts) -> Signal in + |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts) -> Signal in let (update, filter) = updateAndFilter let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) let notice: ChatListNotice? - if let chatFolderUpdates, chatFolderUpdates.availableChatsToJoin != 0 { - notice = .chatFolderUpdates(count: chatFolderUpdates.availableChatsToJoin) - } else if let suggestedChatListNotice { + if let suggestedChatListNotice { notice = suggestedChatListNotice } else if let storageInfo { notice = .clearStorage(sizeFraction: storageInfo) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 43d34c20ad..595552db43 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -84,7 +84,6 @@ enum ChatListNotice: Equatable { case setupPassword case premiumUpgrade(discount: Int32) case premiumAnnualDiscount(discount: Int32) - case chatFolderUpdates(count: Int) } enum ChatListNodeEntry: Comparable, Identifiable { diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift index e33af5cb73..d9a9c13a7a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift @@ -172,15 +172,6 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { titleString = titleStringValue textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) - case let .chatFolderUpdates(count): - let rawTitleString = item.strings.ChatList_ChatFolderUpdateHintTitle(item.strings.ChatList_ChatFolderUpdateCount(Int32(count))) - let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)) - if let range = rawTitleString.ranges.first { - titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range) - } - titleString = titleStringValue - - textString = NSAttributedString(string: item.strings.ChatList_ChatFolderUpdateHintText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) } let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0))) From 6a548e11a66938eb6b12b8d9cb0aeee56a4b90a7 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 19 Apr 2023 23:47:38 +0400 Subject: [PATCH 4/7] Refactoring [skip ci] --- submodules/AccountContext/BUILD | 1 - .../Sources/AccountContext.swift | 2 - .../AccountContext/Sources/MediaManager.swift | 6 +- .../Sources/CalendarMessageScreen.swift | 25 +- .../Sources/ChatImportActivityScreen.swift | 39 +- .../ChatListFilterPresetController.swift | 25 +- .../ChatListFilterPresetListController.swift | 3 +- .../ChatListFilterPresetListItem.swift | 1 - .../ChatListFilterTabContainerNode.swift | 1 - ...ChatListFilterTabInlineContainerNode.swift | 1028 ------------- .../Sources/Node/ChatListNode.swift | 8 - .../Node/ChatListStorageInfoItem.swift | 2 - .../Sources/FileMediaResourceStatus.swift | 11 +- .../GalleryData/Sources/GalleryData.swift | 10 +- .../Sources/HashtagSearchController.swift | 1 - .../Sources/ImageContentAnalysis.swift | 11 +- .../Sources/ImportStickerPack.swift | 3 +- .../Sources/ImportStickerPackController.swift | 13 +- .../ImportStickerPackControllerNode.swift | 35 +- .../ImportStickerPackTitleController.swift | 1 - .../Sources/StickerPackPreviewGridItem.swift | 3 +- .../Sources/StickerPreviewPeekContent.swift | 3 +- .../Sources/CachedInstantPages.swift | 2 +- .../Sources/CachedInternalInstantPages.swift | 1 - .../Sources/InstantPageAnchorItem.swift | 1 - .../Sources/InstantPageArticleItem.swift | 7 +- .../Sources/InstantPageArticleNode.swift | 5 +- .../Sources/InstantPageAudioItem.swift | 1 - .../Sources/InstantPageAudioNode.swift | 5 +- .../Sources/InstantPageContentNode.swift | 1 - .../Sources/InstantPageControllerNode.swift | 27 +- .../Sources/InstantPageDetailsItem.swift | 1 - .../Sources/InstantPageDetailsNode.swift | 1 - .../Sources/InstantPageFeedbackItem.swift | 1 - .../Sources/InstantPageFeedbackNode.swift | 1 - .../InstantPageGalleryController.swift | 6 +- .../Sources/InstantPageImageItem.swift | 1 - .../Sources/InstantPageImageNode.swift | 29 +- .../Sources/InstantPageItem.swift | 1 - .../Sources/InstantPageLayout.swift | 53 +- .../Sources/InstantPageMedia.swift | 7 +- .../Sources/InstantPageMediaPlaylist.swift | 11 +- .../InstantPagePeerReferenceItem.swift | 5 +- .../InstantPagePeerReferenceNode.swift | 25 +- .../InstantPagePlayableVideoItem.swift | 1 - .../InstantPagePlayableVideoNode.swift | 17 +- .../InstantPageReferenceController.swift | 1 - .../InstantPageReferenceControllerNode.swift | 5 +- .../Sources/InstantPageScrollableNode.swift | 1 - .../Sources/InstantPageSettingsNode.swift | 1 - .../Sources/InstantPageShapeItem.swift | 1 - .../Sources/InstantPageSlideshowItem.swift | 1 - .../InstantPageSlideshowItemNode.swift | 4 +- .../Sources/InstantPageStoredState.swift | 5 +- .../Sources/InstantPageSubContentNode.swift | 1 - .../Sources/InstantPageTableItem.swift | 3 +- .../Sources/InstantPageTextItem.swift | 15 +- .../Sources/InstantPageWebEmbedItem.swift | 1 - .../Sources/ItemListInviteRequestItem.swift | 4 +- .../Sources/ListMessageFileItemNode.swift | 6 +- submodules/LottieMeshSwift/BUILD | 156 -- .../PublicHeaders/LottieMesh/LottieMesh.h | 60 - .../PublicHeaders/LottieMesh/Point.h | 47 - .../LottieMesh/Sources/LineSegment.h | 86 -- .../LottieMesh/Sources/LottieMesh.cpp | 95 -- .../LottieMesh/Sources/Polyline2D.h | 446 ------ .../LottieMesh/Sources/Triangulation.cpp | 51 - .../LottieMesh/Sources/Triangulation.h | 14 - .../LottieMeshSwift/LottieMesh/Sources/Vec2.h | 99 -- .../LottieMesh/Sources/earcut.hpp | 823 ---------- .../LottieMeshBinding/LottieMeshBinding.h | 43 - .../Sources/LottieMeshBinding.mm | 315 ---- .../LottieMeshSwift/Resources/Shapes.metal | 82 - .../LottieMeshSwift/Sources/Buffer.swift | 122 -- .../Sources/CapturedGeometry.swift | 62 - .../Sources/Layers/MyCompositionLayer.swift | 111 -- .../Layers/MyImageCompositionLayer.swift | 36 - .../Sources/Layers/MyMaskContainerLayer.swift | 150 -- .../Layers/MyNullCompositionLayer.swift | 15 - .../Layers/MyPreCompositionLayer.swift | 84 -- .../Layers/MyShapeCompositionLayer.swift | 54 - .../Layers/MySolidCompositionLayer.swift | 41 - .../Utility/InvertedMatteLayer.swift | 58 - .../Utility/LayerTransformNode.swift | 120 -- .../Lottie/Private/Model/Animation.swift | 107 -- .../Lottie/Private/Model/Assets/Asset.swift | 27 - .../Private/Model/Assets/AssetLibrary.swift | 48 - .../Private/Model/Assets/ImageAsset.swift | 48 - .../Private/Model/Assets/PrecompAsset.swift | 30 - .../Private/Model/Extensions/Bundle.swift | 21 - .../KeyedDecodingContainerExtensions.swift | 40 - .../Private/Model/Keyframes/Keyframe.swift | 128 -- .../Model/Keyframes/KeyframeGroup.swift | 108 -- .../Model/Layers/ImageLayerModel.swift | 32 - .../Private/Model/Layers/LayerModel.swift | 150 -- .../Model/Layers/PreCompLayerModel.swift | 50 - .../Model/Layers/ShapeLayerModel.swift | 32 - .../Model/Layers/SolidLayerModel.swift | 44 - .../Private/Model/Layers/TextLayerModel.swift | 44 - .../Private/Model/Objects/DashPattern.swift | 24 - .../Lottie/Private/Model/Objects/Marker.swift | 23 - .../Lottie/Private/Model/Objects/Mask.swift | 48 - .../Private/Model/Objects/Transform.swift | 105 -- .../Private/Model/ShapeItems/Ellipse.swift | 50 - .../Private/Model/ShapeItems/FillI.swift | 49 - .../Model/ShapeItems/GradientFill.swift | 86 -- .../Model/ShapeItems/GradientStroke.swift | 125 -- .../Private/Model/ShapeItems/Group.swift | 32 - .../Private/Model/ShapeItems/Merge.swift | 41 - .../Private/Model/ShapeItems/Rectangle.swift | 50 - .../Private/Model/ShapeItems/Repeater.swift | 80 - .../Private/Model/ShapeItems/Shape.swift | 37 - .../Private/Model/ShapeItems/ShapeItem.swift | 95 -- .../Model/ShapeItems/ShapeTransform.swift | 68 - .../Private/Model/ShapeItems/Star.swift | 86 -- .../Private/Model/ShapeItems/Stroke.swift | 67 - .../Private/Model/ShapeItems/Trim.swift | 53 - .../Lottie/Private/Model/Text/Font.swift | 35 - .../Lottie/Private/Model/Text/Glyph.swift | 72 - .../Private/Model/Text/TextAnimator.swift | 99 -- .../Private/Model/Text/TextDocument.swift | 70 - .../Extensions/ItemsExtension.swift | 95 -- .../NodeProperties/NodeProperty.swift | 47 - .../Protocols/AnyNodeProperty.swift | 44 - .../Protocols/AnyValueContainer.swift | 26 - .../Protocols/KeypathSearchable.swift | 1 - .../Protocols/NodePropertyMap.swift | 33 - .../NodeProperties/ValueContainer.swift | 43 - .../ValueProviders/GroupInterpolator.swift | 33 - .../ValueProviders/KeyframeInterpolator.swift | 233 --- .../ValueProviders/SingleValueProvider.swift | 38 - .../Nodes/ModifierNodes/TrimPathNode.swift | 244 --- .../Nodes/OutputNodes/GroupOutputNode.swift | 70 - .../OutputNodes/PassThroughOutputNode.swift | 43 - .../Nodes/OutputNodes/PathOutputNode.swift | 88 -- .../Renderables/FillRenderer.swift | 72 - .../Renderables/GradientFillRenderer.swift | 126 -- .../Renderables/GradientStrokeRenderer.swift | 59 - .../Renderables/StrokeRenderer.swift | 162 -- .../Nodes/PathNodes/EllipseNode.swift | 101 -- .../Nodes/PathNodes/PolygonNode.swift | 126 -- .../Nodes/PathNodes/RectNode.swift | 184 --- .../Nodes/PathNodes/ShapeNode.swift | 57 - .../Nodes/PathNodes/StarNode.swift | 179 --- .../Nodes/RenderContainers/GroupNode.swift | 122 -- .../Nodes/RenderNodes/FillNode.swift | 72 - .../Nodes/RenderNodes/GradientFillNode.swift | 85 -- .../RenderNodes/GradientStrokeNode.swift | 139 -- .../Nodes/RenderNodes/StrokeNode.swift | 124 -- .../Nodes/Text/TextAnimatorNode.swift | 245 --- .../Protocols/AnimatorNode.swift | 175 --- .../NodeRenderSystem/Protocols/PathNode.swift | 20 - .../Protocols/RenderNode.swift | 57 - .../RenderLayers/ShapeContainerLayer.swift | 89 -- .../RenderLayers/ShapeRenderLayer.swift | 204 --- .../AnimationKeypathExtension.swift | 1 - .../Extensions/CGFloatExtensions.swift | 149 -- .../Private/Utility/Extensions/MathKit.swift | 539 ------- .../Utility/Extensions/StringExtensions.swift | 32 - .../Interpolatable/Interpolatable.swift | 18 - .../InterpolatableExtensions.swift | 170 --- .../Interpolatable/KeyframeExtensions.swift | 43 - .../Utility/Primitives/BezierPath.swift | 402 ----- .../Utility/Primitives/ColorExtension.swift | 76 - .../Primitives/CompoundBezierPath.swift | 158 -- .../Utility/Primitives/CurveVertex.swift | 177 --- .../Utility/Primitives/PathElement.swift | 68 - .../Primitives/VectorsExtensions.swift | 218 --- .../DynamicProperties/AnimationKeypath.swift | 46 - .../DynamicProperties/AnyValueProvider.swift | 29 - .../ValueProviders/ColorValueProvider.swift | 66 - .../ValueProviders/FloatValueProvider.swift | 66 - .../GradientValueProvider.swift | 114 -- .../ValueProviders/PointValueProvider.swift | 64 - .../ValueProviders/SizeValueProvider.swift | 65 - .../Public/Primitives/AnimationTime.swift | 15 - .../Lottie/Public/Primitives/Color.swift | 41 - .../Lottie/Public/Primitives/Vectors.swift | 37 - .../Sources/MeshAnimation.swift | 693 --------- .../LottieMeshSwift/Sources/Render.swift | 138 -- .../libtess2/Include/tesselator.h | 242 --- .../libtess2/Sources/bucketalloc.c | 191 --- .../libtess2/Sources/bucketalloc.h | 51 - .../LottieMeshSwift/libtess2/Sources/dict.c | 109 -- .../LottieMeshSwift/libtess2/Sources/dict.h | 74 - .../LottieMeshSwift/libtess2/Sources/geom.c | 293 ---- .../LottieMeshSwift/libtess2/Sources/geom.h | 78 - .../LottieMeshSwift/libtess2/Sources/mesh.c | 917 ------------ .../LottieMeshSwift/libtess2/Sources/mesh.h | 269 ---- .../libtess2/Sources/priorityq.c | 514 ------- .../libtess2/Sources/priorityq.h | 104 -- .../LottieMeshSwift/libtess2/Sources/sweep.c | 1324 ----------------- .../LottieMeshSwift/libtess2/Sources/sweep.h | 74 - .../LottieMeshSwift/libtess2/Sources/tess.c | 1114 -------------- .../LottieMeshSwift/libtess2/Sources/tess.h | 93 -- .../Sources/MediaPlaybackStoredState.swift | 9 +- submodules/MeshAnimationCache/BUILD | 22 - .../Sources/MeshAnimationCache.swift | 182 --- .../Sources/NotificationSoundSelection.swift | 15 +- .../Sources/PasscodeEntryController.swift | 1 - .../Sources/PasscodeEntryControllerNode.swift | 1 - .../Sources/PasscodeSetupController.swift | 1 - .../Sources/ResetPasswordController.swift | 1 - .../SetupTwoStepVerificationController.swift | 1 - ...tupTwoStepVerificationControllerNode.swift | 1 - .../Sources/AvatarGalleryController.swift | 93 +- .../AvatarGalleryItemFooterContentNode.swift | 5 +- .../Sources/PeerAvatarImageGalleryItem.swift | 23 +- .../Sources/PeerInfoAvatarListNode.swift | 51 +- .../Sources/ChannelAdminController.swift | 37 +- .../ChannelMembersSearchController.swift | 2 +- .../ChannelMembersSearchControllerNode.swift | 47 +- .../Sources/DeviceContactInfoController.swift | 2 +- submodules/Postbox/Sources/MediaBox.swift | 16 +- .../Postbox/Sources/MediaResource.swift | 15 +- .../Sources/PhoneDemoComponent.swift | 3 +- .../PremiumUI/Sources/PremiumGiftScreen.swift | 2 +- .../Sources/PremiumIntroScreen.swift | 8 +- .../Sources/PremiumLimitScreen.swift | 1 - .../Sources/PremiumLimitsListScreen.swift | 2 +- ...AutodownloadConnectionTypeController.swift | 1 - .../AutodownloadMediaCategoryController.swift | 1 - .../EnergySavingSettingsScreen.swift | 1 - ...ectivePrivacySettingsPeersController.swift | 21 +- .../TermsOfServiceController.swift | 1 - .../TermsOfServiceControllerNode.swift | 1 - .../TextSizeSelectionController.swift | 2 +- .../Sources/Themes/ThemeColorPresets.swift | 1 - .../Themes/ThemeColorsGridController.swift | 1 - .../ThemeColorsGridControllerItem.swift | 1 - .../ThemeColorsGridControllerNode.swift | 1 - .../Sources/UsernameSetupController.swift | 6 +- .../Sources/ShareControllerNode.swift | 2 +- .../Sources/ShareControllerPeerGridItem.swift | 3 +- .../Sources/ShareLoadingContainerNode.swift | 29 +- .../Sources/SharePeersContainerNode.swift | 7 +- .../Sources/ShareSearchContainerNode.swift | 29 +- .../Sources/ShareTopicGridItem.swift | 1 - .../Sources/ShareTopicsContainerNode.swift | 1 - .../Sources/GroupStatsController.swift | 80 +- .../Sources/MessageStatsController.swift | 13 +- .../Sources/TelegramBaseController.swift | 2 +- .../Sources/VoiceChatController.swift | 6 +- .../VoiceChatFullscreenParticipantItem.swift | 2 +- .../Sources/VoiceChatOverlayController.swift | 1 - .../Sources/VoiceChatParticipantItem.swift | 4 +- .../VoiceChatPeerActionSheetItem.swift | 7 +- .../Sources/VoiceChatPeerProfileNode.swift | 11 +- .../VoiceChatRecordingSetupController.swift | 9 +- .../Sources/VoiceChatTileItemNode.swift | 17 +- .../VoiceChatTitleEditController.swift | 1 - .../Sources/Account/Account.swift | 2 +- .../SyncCore_TelegramMediaAction.swift | 6 +- .../SyncCore_TelegramMediaContact.swift | 8 +- .../SyncCore/SyncCore_TelegramMediaDice.swift | 6 +- ...SyncCore_TelegramMediaExpiredContent.swift | 6 +- .../SyncCore/SyncCore_TelegramMediaGame.swift | 6 +- .../SyncCore_TelegramMediaInvoice.swift | 6 +- .../SyncCore/SyncCore_TelegramMediaMap.swift | 6 +- .../SyncCore_TelegramMediaUnsupported.swift | 6 +- .../TelegramEngine/Messages/Media.swift | 2 +- .../Resources/TelegramEngineResources.swift | 19 +- .../Stickers/ImportStickers.swift | 22 + .../Resources/PresentationResourcesChat.swift | 1 - submodules/TelegramUI/BUILD | 2 - .../EntityKeyboardBottomPanelComponent.swift | 1 - .../TelegramUI/Sources/AccountContext.swift | 3 - .../TelegramUI/Sources/ChatController.swift | 5 +- .../ChatMessageAnimatedStickerItemNode.swift | 34 +- .../ChatMessageInteractiveFileNode.swift | 14 +- ...atMessageInteractiveInstantVideoNode.swift | 4 +- .../Sources/ChatOverlayNavigationBar.swift | 1 - .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 4 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 4 +- .../TelegramUI/Sources/PrefetchManager.swift | 17 +- .../Sources/PrepareSecretThumbnailData.swift | 6 +- .../TelegramAccountAuxiliaryMethods.swift | 2 +- 277 files changed, 613 insertions(+), 19657 deletions(-) delete mode 100644 submodules/ChatListUI/Sources/ChatListFilterTabInlineContainerNode.swift delete mode 100644 submodules/LottieMeshSwift/BUILD delete mode 100644 submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/LottieMesh.h delete mode 100644 submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/Point.h delete mode 100644 submodules/LottieMeshSwift/LottieMesh/Sources/LineSegment.h delete mode 100644 submodules/LottieMeshSwift/LottieMesh/Sources/LottieMesh.cpp delete mode 100644 submodules/LottieMeshSwift/LottieMesh/Sources/Polyline2D.h delete mode 100644 submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.cpp delete mode 100644 submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.h delete mode 100644 submodules/LottieMeshSwift/LottieMesh/Sources/Vec2.h delete mode 100644 submodules/LottieMeshSwift/LottieMesh/Sources/earcut.hpp delete mode 100644 submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h delete mode 100644 submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm delete mode 100644 submodules/LottieMeshSwift/Resources/Shapes.metal delete mode 100644 submodules/LottieMeshSwift/Sources/Buffer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/CapturedGeometry.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Layers/MyCompositionLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Layers/MyImageCompositionLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Layers/MyMaskContainerLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Layers/MyNullCompositionLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Layers/MyPreCompositionLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Layers/MyShapeCompositionLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Layers/MySolidCompositionLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/InvertedMatteLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/LayerTransformNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Animation.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/Asset.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/AssetLibrary.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/ImageAsset.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/PrecompAsset.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/Bundle.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/Keyframe.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ImageLayerModel.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/LayerModel.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/SolidLayerModel.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/TextLayerModel.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/DashPattern.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Marker.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Mask.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Transform.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Ellipse.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/FillI.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientFill.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Group.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Merge.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Rectangle.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Repeater.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Shape.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Star.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Stroke.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Trim.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Font.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Glyph.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextAnimator.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextDocument.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Extensions/ItemsExtension.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/RectNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderContainers/GroupNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/AnimatorNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/PathNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/RenderNode.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/AnimationKeypathExtension.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/CGFloatExtensions.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/MathKit.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/StringExtensions.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/Interpolatable.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/InterpolatableExtensions.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/KeyframeExtensions.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/BezierPath.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/ColorExtension.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CurveVertex.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/PathElement.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/VectorsExtensions.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/PointValueProvider.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/AnimationTime.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Color.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Vectors.swift delete mode 100644 submodules/LottieMeshSwift/Sources/MeshAnimation.swift delete mode 100644 submodules/LottieMeshSwift/Sources/Render.swift delete mode 100755 submodules/LottieMeshSwift/libtess2/Include/tesselator.h delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.c delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.h delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/dict.c delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/dict.h delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/geom.c delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/geom.h delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/mesh.c delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/mesh.h delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/priorityq.c delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/priorityq.h delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/sweep.c delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/sweep.h delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/tess.c delete mode 100755 submodules/LottieMeshSwift/libtess2/Sources/tess.h delete mode 100644 submodules/MeshAnimationCache/BUILD delete mode 100644 submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift diff --git a/submodules/AccountContext/BUILD b/submodules/AccountContext/BUILD index 19689e50b1..fb2ccfc448 100644 --- a/submodules/AccountContext/BUILD +++ b/submodules/AccountContext/BUILD @@ -20,7 +20,6 @@ swift_library( "//submodules/Postbox:Postbox", "//submodules/TelegramCore:TelegramCore", "//submodules/MusicAlbumArtResources:MusicAlbumArtResources", - "//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/Utils/RangeSet:RangeSet", "//submodules/InAppPurchaseManager:InAppPurchaseManager", "//submodules/TextFormat:TextFormat", diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 0869ada5a6..49f3d885c0 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -10,7 +10,6 @@ import AsyncDisplayKit import Display import DeviceLocationManager import TemporaryCachedPeerDataManager -import MeshAnimationCache import InAppPurchaseManager import AnimationCache import MultiAnimationRenderer @@ -935,7 +934,6 @@ public protocol AccountContext: AnyObject { var currentCountriesConfiguration: Atomic { get } var cachedGroupCallContexts: AccountGroupCallContextCache { get } - var meshAnimationCache: MeshAnimationCache { get } var animationCache: AnimationCache { get } var animationRenderer: MultiAnimationRenderer { get } diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index 51debe0e34..3281f18dee 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -95,8 +95,8 @@ public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation } } -public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? { - func extractFileMedia(_ message: Message) -> TelegramMediaFile? { +public func peerMessageMediaPlayerType(_ message: EngineMessage) -> MediaManagerPlayerType? { + func extractFileMedia(_ message: EngineMessage) -> TelegramMediaFile? { var file: TelegramMediaFile? for media in message.media { if let media = media as? TelegramMediaFile { @@ -120,7 +120,7 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer return nil } -public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { +public func peerMessagesMediaPlaylistAndItemId(_ message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? { if isGlobalSearch && !isDownloadList { return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index)) } else if isRecentActions && !isDownloadList { diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index c293f8802c..d07300b31f 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -371,7 +370,7 @@ private final class DayComponent: Component { private var currentSelection: DaySelection? private(set) var timestamp: Int32? - private(set) var index: MessageIndex? + private(set) var index: EngineMessage.Index? private var isHighlightingEnabled: Bool = false init() { @@ -983,12 +982,12 @@ public final class CalendarMessageScreen: ViewController { private weak var controller: CalendarMessageScreen? private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let initialTimestamp: Int32 private let enableMessageRangeDeletion: Bool private let canNavigateToEmptyDays: Bool private let navigateToOffset: (Int, Int32) -> Void - private let previewDay: (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + private let previewDay: (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void private var presentationData: PresentationData private var scrollView: Scroller @@ -1019,13 +1018,13 @@ public final class CalendarMessageScreen: ViewController { init( controller: CalendarMessageScreen, context: AccountContext, - peerId: PeerId, + peerId: EnginePeer.Id, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, enableMessageRangeDeletion: Bool, canNavigateToEmptyDays: Bool, navigateToOffset: @escaping (Int, Int32) -> Void, - previewDay: @escaping (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + previewDay: @escaping (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void ) { self.controller = controller self.context = context @@ -1783,9 +1782,9 @@ public final class CalendarMessageScreen: ViewController { guard let calendarState = self.calendarState else { return } - var messageMap: [Message] = [] + var messageMap: [EngineMessage] = [] for (_, entry) in calendarState.messagesByDay { - messageMap.append(entry.message) + messageMap.append(EngineMessage(entry.message)) } var updatedMedia: [Int: [Int: DayMedia]] = [:] @@ -1805,7 +1804,7 @@ public final class CalendarMessageScreen: ViewController { mediaLoop: for media in message.media { switch media { case _ as TelegramMediaImage, _ as TelegramMediaFile: - updatedMedia[i]![day] = DayMedia(message: EngineMessage(message), media: EngineMedia(media)) + updatedMedia[i]![day] = DayMedia(message: message, media: EngineMedia(media)) break mediaLoop default: break @@ -1830,13 +1829,13 @@ public final class CalendarMessageScreen: ViewController { } private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let calendarSource: SparseMessageCalendar private let initialTimestamp: Int32 private let enableMessageRangeDeletion: Bool private let canNavigateToEmptyDays: Bool private let navigateToDay: (CalendarMessageScreen, Int, Int32) -> Void - private let previewDay: (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + private let previewDay: (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void private var presentationData: PresentationData @@ -1844,13 +1843,13 @@ public final class CalendarMessageScreen: ViewController { public init( context: AccountContext, - peerId: PeerId, + peerId: EnginePeer.Id, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, enableMessageRangeDeletion: Bool, canNavigateToEmptyDays: Bool, navigateToDay: @escaping (CalendarMessageScreen, Int, Int32) -> Void, - previewDay: @escaping (Int32, MessageIndex?, ASDisplayNode, CGRect, ContextGesture) -> Void + previewDay: @escaping (Int32, EngineMessage.Index?, ASDisplayNode, CGRect, ContextGesture) -> Void ) { self.context = context self.peerId = peerId diff --git a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift index 1761a2d008..eaa0290715 100644 --- a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift +++ b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift @@ -3,7 +3,6 @@ import AsyncDisplayKit import Display import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext import PresentationDataUtils @@ -17,6 +16,26 @@ import ConfettiEffect import TelegramUniversalVideoContent import SolidRoundedButtonNode +private func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? { + if useTotalFileAllocatedSize { + let url = URL(fileURLWithPath: path) + if let values = (try? url.resourceValues(forKeys: Set([.isRegularFileKey, .totalFileAllocatedSizeKey]))) { + if values.isRegularFile ?? false { + if let fileSize = values.totalFileAllocatedSize { + return Int64(fileSize) + } + } + } + } + + var value = stat() + if stat(path, &value) == 0 { + return value.st_size + } else { + return nil + } +} + private final class ProgressEstimator { private var averageProgressPerSecond: Double = 0.0 private var lastMeasurement: (Double, Float)? @@ -91,7 +110,7 @@ private final class ImportManager { return self.statePromise.get() } - init(account: Account, peerId: PeerId, mainFile: TempBoxFile, archivePath: String?, entries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { + init(account: Account, peerId: EnginePeer.Id, mainFile: EngineTempBox.File, archivePath: String?, entries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { self.account = account self.archivePath = archivePath self.entries = entries @@ -234,8 +253,8 @@ private final class ImportManager { Logger.shared.log("ChatImportScreen", "updateState take pending entry \(entry.1)") - let unpackedFile = Signal { subscriber in - let tempFile = TempBox.shared.tempFile(fileName: entry.0.path) + let unpackedFile = Signal { subscriber in + let tempFile = EngineTempBox.shared.tempFile(fileName: entry.0.path) Logger.shared.log("ChatImportScreen", "Extracting \(entry.0.path) to \(tempFile.path)...") let startTime = CACurrentMediaTime() if SSZipArchive.extractFileFromArchive(atPath: archivePath, filePath: entry.0.path, toPath: tempFile.path) { @@ -440,9 +459,9 @@ public final class ChatImportActivityScreen: ViewController { if let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) - let videoContent = NativeVideoContent(id: .message(1, MediaId(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) + let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) videoNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)) @@ -724,9 +743,9 @@ public final class ChatImportActivityScreen: ViewController { private let context: AccountContext private var presentationData: PresentationData fileprivate let cancel: () -> Void - fileprivate var peerId: PeerId + fileprivate var peerId: EnginePeer.Id private let archivePath: String? - private let mainEntry: TempBoxFile + private let mainEntry: EngineTempBox.File private let totalBytes: Int64 private let totalMediaBytes: Int64 private let otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)] @@ -746,7 +765,7 @@ public final class ChatImportActivityScreen: ViewController { } } - public init(context: AccountContext, cancel: @escaping () -> Void, peerId: PeerId, archivePath: String?, mainEntry: TempBoxFile, otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { + public init(context: AccountContext, cancel: @escaping () -> Void, peerId: EnginePeer.Id, archivePath: String?, mainEntry: EngineTempBox.File, otherEntries: [(SSZipEntry, String, TelegramEngine.HistoryImport.MediaType)]) { self.context = context self.cancel = cancel self.peerId = peerId @@ -818,7 +837,7 @@ public final class ChatImportActivityScreen: ViewController { self.progressEstimator = ProgressEstimator() self.beganCompletion = false - let resolvedPeerId: Signal + let resolvedPeerId: Signal if self.peerId.namespace == Namespaces.Peer.CloudGroup { resolvedPeerId = self.context.engine.peers.convertGroupToSupergroup(peerId: self.peerId) |> mapError { _ -> ImportManager.ImportError in diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index 0b888606a1..5d69ea440b 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import PresentationDataUtils @@ -30,8 +29,8 @@ private final class ChatListFilterPresetControllerArguments { let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void let openAddIncludePeer: () -> Void let openAddExcludePeer: () -> Void - let deleteIncludePeer: (PeerId) -> Void - let deleteExcludePeer: (PeerId) -> Void + let deleteIncludePeer: (EnginePeer.Id) -> Void + let deleteExcludePeer: (EnginePeer.Id) -> Void let setItemIdWithRevealedOptions: (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void let deleteIncludeCategory: (ChatListFilterIncludeCategory) -> Void let deleteExcludeCategory: (ChatListFilterExcludeCategory) -> Void @@ -49,8 +48,8 @@ private final class ChatListFilterPresetControllerArguments { updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, openAddIncludePeer: @escaping () -> Void, openAddExcludePeer: @escaping () -> Void, - deleteIncludePeer: @escaping (PeerId) -> Void, - deleteExcludePeer: @escaping (PeerId) -> Void, + deleteIncludePeer: @escaping (EnginePeer.Id) -> Void, + deleteExcludePeer: @escaping (EnginePeer.Id) -> Void, setItemIdWithRevealedOptions: @escaping (ChatListFilterRevealedItemId?, ChatListFilterRevealedItemId?) -> Void, deleteIncludeCategory: @escaping (ChatListFilterIncludeCategory) -> Void, deleteExcludeCategory: @escaping (ChatListFilterExcludeCategory) -> Void, @@ -93,7 +92,7 @@ private enum ChatListFilterPresetControllerSection: Int32 { private enum ChatListFilterPresetEntryStableId: Hashable { case index(Int) - case peer(PeerId) + case peer(EnginePeer.Id) case includePeerInfo case excludePeerInfo case includeCategory(ChatListFilterIncludeCategory) @@ -311,7 +310,7 @@ private extension ChatListFilterCategoryIcon { } private enum ChatListFilterRevealedItemId: Equatable { - case peer(PeerId) + case peer(EnginePeer.Id) case includeCategory(ChatListFilterIncludeCategory) case excludeCategory(ChatListFilterExcludeCategory) } @@ -573,8 +572,8 @@ private struct ChatListFilterPresetControllerState: Equatable { var excludeMuted: Bool var excludeRead: Bool var excludeArchived: Bool - var additionallyIncludePeers: [PeerId] - var additionallyExcludePeers: [PeerId] + var additionallyIncludePeers: [EnginePeer.Id] + var additionallyExcludePeers: [EnginePeer.Id] var revealedItemId: ChatListFilterRevealedItemId? var expandedSections: Set @@ -825,7 +824,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f return } - var includePeers: [PeerId] = [] + var includePeers: [EnginePeer.Id] = [] for peerId in peerIds { switch peerId { case let .peer(id): @@ -838,7 +837,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f if filter.id > 1, case let .filter(_, _, _, data) = filter, data.hasSharedLinks { let newPeers = includePeers.filter({ !(filter.data?.includePeers.peers.contains($0) ?? false) }) - var removedPeers: [PeerId] = [] + var removedPeers: [EnginePeer.Id] = [] if let data = filter.data { removedPeers = data.includePeers.peers.filter({ !includePeers.contains($0) }) } @@ -951,7 +950,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex return } - var excludePeers: [PeerId] = [] + var excludePeers: [EnginePeer.Id] = [] for peerId in peerIds { switch peerId { case let .peer(id): @@ -1144,7 +1143,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi sharedLinks.set(Signal<[ExportedChatFolderLink]?, NoError>.single(nil) |> then(context.engine.peers.getExportedChatFolderLinks(id: initialPreset.id))) } - let currentPeers = Atomic<[PeerId: EngineRenderedPeer]>(value: [:]) + let currentPeers = Atomic<[EnginePeer.Id: EngineRenderedPeer]>(value: [:]) let stateWithPeers = statePromise.get() |> mapToSignal { state -> Signal<(ChatListFilterPresetControllerState, [EngineRenderedPeer], [EngineRenderedPeer]), NoError> in let currentPeersValue = currentPeers.with { $0 } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index e658314834..4a6d7ce254 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -39,7 +38,7 @@ private enum ChatListFilterPresetListSection: Int32 { case list } -private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings: PresentationStrings) -> String { +private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String { if peers.isEmpty { return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder } else { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index 2050206093..609c9ae333 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift index b0d1b6d8a6..92af4bf216 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterTabContainerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData diff --git a/submodules/ChatListUI/Sources/ChatListFilterTabInlineContainerNode.swift b/submodules/ChatListUI/Sources/ChatListFilterTabInlineContainerNode.swift deleted file mode 100644 index 38f9888047..0000000000 --- a/submodules/ChatListUI/Sources/ChatListFilterTabInlineContainerNode.swift +++ /dev/null @@ -1,1028 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import Postbox -import TelegramCore -import TelegramPresentationData - -private final class ItemNodeDeleteButtonNode: HighlightableButtonNode { - private let pressed: () -> Void - - private let contentImageNode: ASImageNode - - private var theme: PresentationTheme? - - init(pressed: @escaping () -> Void) { - self.pressed = pressed - - self.contentImageNode = ASImageNode() - - super.init() - - self.addSubnode(self.contentImageNode) - - self.addTarget(self, action: #selector(self.pressedEvent), forControlEvents: .touchUpInside) - } - - @objc private func pressedEvent() { - self.pressed() - } - - func update(theme: PresentationTheme) -> CGSize { - let size = CGSize(width: 18.0, height: 18.0) - if self.theme !== theme { - self.theme = theme - self.contentImageNode.image = generateImage(size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(UIColor(rgb: 0xbbbbbb).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(UIColor(rgb: 0xffffff).cgColor) - context.setLineWidth(1.5) - context.setLineCap(.round) - context.move(to: CGPoint(x: 6.38, y: 6.38)) - context.addLine(to: CGPoint(x: 11.63, y: 11.63)) - context.strokePath() - context.move(to: CGPoint(x: 6.38, y: 11.63)) - context.addLine(to: CGPoint(x: 11.63, y: 6.38)) - context.strokePath() - }) - } - - self.contentImageNode.frame = CGRect(origin: CGPoint(), size: size) - - return size - } -} - -private final class ItemNode: ASDisplayNode { - private let pressed: () -> Void - private let requestedDeletion: () -> Void - - private let extractedContainerNode: ContextExtractedContentContainingNode - private let containerNode: ContextControllerSourceNode - - private let extractedBackgroundNode: ASImageNode - private let titleNode: ImmediateTextNode - private let shortTitleNode: ImmediateTextNode - private let badgeContainerNode: ASDisplayNode - private let badgeTextNode: ImmediateTextNode - private let badgeBackgroundActiveNode: ASImageNode - private let badgeBackgroundInactiveNode: ASImageNode - - private var deleteButtonNode: ItemNodeDeleteButtonNode? - private let buttonNode: HighlightTrackingButtonNode - - private var isSelected: Bool = false - private(set) var unreadCount: Int = 0 - - private var isReordering: Bool = false - - private var theme: PresentationTheme? - - init(pressed: @escaping () -> Void, requestedDeletion: @escaping () -> Void, contextGesture: @escaping (ContextExtractedContentContainingNode, ContextGesture) -> Void) { - self.pressed = pressed - self.requestedDeletion = requestedDeletion - - self.extractedContainerNode = ContextExtractedContentContainingNode() - self.containerNode = ContextControllerSourceNode() - - self.extractedBackgroundNode = ASImageNode() - self.extractedBackgroundNode.alpha = 0.0 - - let titleInset: CGFloat = 4.0 - - self.titleNode = ImmediateTextNode() - self.titleNode.displaysAsynchronously = false - self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) - - self.shortTitleNode = ImmediateTextNode() - self.shortTitleNode.displaysAsynchronously = false - self.shortTitleNode.alpha = 0.0 - self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) - - self.badgeContainerNode = ASDisplayNode() - - self.badgeTextNode = ImmediateTextNode() - self.badgeTextNode.displaysAsynchronously = false - - self.badgeBackgroundActiveNode = ASImageNode() - self.badgeBackgroundActiveNode.displaysAsynchronously = false - self.badgeBackgroundActiveNode.displayWithoutProcessing = true - - self.badgeBackgroundInactiveNode = ASImageNode() - self.badgeBackgroundInactiveNode.displaysAsynchronously = false - self.badgeBackgroundInactiveNode.displayWithoutProcessing = true - self.badgeBackgroundInactiveNode.isHidden = true - - self.buttonNode = HighlightTrackingButtonNode() - - super.init() - - self.extractedContainerNode.contentNode.addSubnode(self.extractedBackgroundNode) - self.extractedContainerNode.contentNode.addSubnode(self.titleNode) - self.extractedContainerNode.contentNode.addSubnode(self.shortTitleNode) - self.badgeContainerNode.addSubnode(self.badgeBackgroundActiveNode) - self.badgeContainerNode.addSubnode(self.badgeBackgroundInactiveNode) - self.badgeContainerNode.addSubnode(self.badgeTextNode) - self.extractedContainerNode.contentNode.addSubnode(self.badgeContainerNode) - self.extractedContainerNode.contentNode.addSubnode(self.buttonNode) - - self.containerNode.addSubnode(self.extractedContainerNode) - self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode - self.addSubnode(self.containerNode) - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - - self.containerNode.activated = { [weak self] gesture, _ in - guard let strongSelf = self else { - return - } - contextGesture(strongSelf.extractedContainerNode, gesture) - } - - self.extractedContainerNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in - guard let strongSelf = self else { - return - } - - if isExtracted { - strongSelf.extractedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 32.0, color: strongSelf.isSelected ? UIColor(rgb: 0xbbbbbb) : UIColor(rgb: 0xf1f1f1)) - } - transition.updateAlpha(node: strongSelf.extractedBackgroundNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in - if !isExtracted { - self?.extractedBackgroundNode.image = nil - } - }) - } - } - - @objc private func buttonPressed() { - self.pressed() - } - - func updateText(title: String, shortTitle: String, unreadCount: Int, unreadHasUnmuted: Bool, isNoFilter: Bool, isSelected: Bool, isEditing: Bool, isAllChats: Bool, isReordering: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { - if self.theme !== presentationData.theme { - self.theme = presentationData.theme - - self.badgeBackgroundActiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeActiveBackgroundColor) - self.badgeBackgroundInactiveNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: presentationData.theme.chatList.unreadBadgeInactiveBackgroundColor) - } - - self.containerNode.isGestureEnabled = !isEditing && !isReordering - self.buttonNode.isUserInteractionEnabled = !isEditing && !isReordering - - self.isSelected = isSelected - self.unreadCount = unreadCount - - transition.updateAlpha(node: self.containerNode, alpha: isReordering && isAllChats ? 0.5 : 1.0) - - if isReordering && !isAllChats { - if self.deleteButtonNode == nil { - let deleteButtonNode = ItemNodeDeleteButtonNode(pressed: { [weak self] in - self?.requestedDeletion() - }) - self.extractedContainerNode.contentNode.addSubnode(deleteButtonNode) - self.deleteButtonNode = deleteButtonNode - if case .animated = transition { - deleteButtonNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.25) - deleteButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - } - } - } else if let deleteButtonNode = self.deleteButtonNode { - self.deleteButtonNode = nil - transition.updateTransformScale(node: deleteButtonNode, scale: 0.1) - transition.updateAlpha(node: deleteButtonNode, alpha: 0.0, completion: { [weak deleteButtonNode] _ in - deleteButtonNode?.removeFromSupernode() - }) - } - - transition.updateAlpha(node: self.badgeContainerNode, alpha: (isReordering || unreadCount == 0) ? 0.0 : 1.0) - - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: isSelected ? presentationData.theme.contextMenu.badgeForegroundColor : presentationData.theme.list.itemSecondaryTextColor) - self.shortTitleNode.attributedText = NSAttributedString(string: shortTitle, font: Font.bold(17.0), textColor: isSelected ? presentationData.theme.contextMenu.badgeForegroundColor : presentationData.theme.list.itemSecondaryTextColor) - if unreadCount != 0 { - self.badgeTextNode.attributedText = NSAttributedString(string: "\(unreadCount)", font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - self.badgeBackgroundActiveNode.isHidden = !isSelected && !unreadHasUnmuted - self.badgeBackgroundInactiveNode.isHidden = isSelected || unreadHasUnmuted - } - - if self.isReordering != isReordering { - self.isReordering = isReordering - if self.isReordering && !isAllChats { - self.startShaking() - } else { - self.layer.removeAnimation(forKey: "shaking_position") - self.layer.removeAnimation(forKey: "shaking_rotation") - } - } - } - - func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> (width: CGFloat, shortWidth: CGFloat) { - let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) - self.titleNode.frame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - - let shortTitleSize = self.shortTitleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude)) - self.shortTitleNode.frame = CGRect(origin: CGPoint(x: -self.shortTitleNode.insets.left, y: floor((height - shortTitleSize.height) / 2.0)), size: shortTitleSize) - - if let deleteButtonNode = self.deleteButtonNode { - if let theme = self.theme { - let deleteButtonSize = deleteButtonNode.update(theme: theme) - deleteButtonNode.frame = CGRect(origin: CGPoint(x: -deleteButtonSize.width + 7.0, y: 5.0), size: deleteButtonSize) - } - } - - let badgeSize = self.badgeTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) - let badgeInset: CGFloat = 4.0 - let badgeBackgroundFrame = CGRect(origin: CGPoint(x: titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + 5.0, y: floor((height - 18.0) / 2.0)), size: CGSize(width: max(18.0, badgeSize.width + badgeInset * 2.0), height: 18.0)) - self.badgeContainerNode.frame = badgeBackgroundFrame - self.badgeBackgroundActiveNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size) - self.badgeBackgroundInactiveNode.frame = CGRect(origin: CGPoint(), size: badgeBackgroundFrame.size) - self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((badgeBackgroundFrame.width - badgeSize.width) / 2.0), y: floor((badgeBackgroundFrame.height - badgeSize.height) / 2.0)), size: badgeSize) - - let width: CGFloat - if self.unreadCount == 0 || self.isReordering { - if !self.isReordering { - self.badgeContainerNode.alpha = 0.0 - } - width = titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right - } else { - if !self.isReordering { - self.badgeContainerNode.alpha = 1.0 - } - width = badgeBackgroundFrame.maxX - } - - return (width, shortTitleSize.width - self.shortTitleNode.insets.left - self.shortTitleNode.insets.right) - } - - func updateArea(size: CGSize, sideInset: CGFloat, useShortTitle: Bool, transition: ContainedViewLayoutTransition) { - transition.updateAlpha(node: self.titleNode, alpha: useShortTitle ? 0.0 : 1.0) - transition.updateAlpha(node: self.shortTitleNode, alpha: useShortTitle ? 1.0 : 0.0) - - self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height)) - - self.extractedContainerNode.frame = CGRect(origin: CGPoint(), size: size) - self.extractedContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: size) - self.extractedContainerNode.contentRect = CGRect(origin: CGPoint(x: self.extractedBackgroundNode.frame.minX, y: 0.0), size: CGSize(width: self.extractedBackgroundNode.frame.width, height: size.height)) - self.containerNode.frame = CGRect(origin: CGPoint(), size: size) - - self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset) - self.extractedContainerNode.hitTestSlop = self.hitTestSlop - self.extractedContainerNode.contentNode.hitTestSlop = self.hitTestSlop - self.containerNode.hitTestSlop = self.hitTestSlop - - let extractedBackgroundHeight: CGFloat = 32.0 - let extractedBackgroundInset: CGFloat = 14.0 - self.extractedBackgroundNode.frame = CGRect(origin: CGPoint(x: -extractedBackgroundInset, y: floor((size.height - extractedBackgroundHeight) / 2.0)), size: CGSize(width: size.width + extractedBackgroundInset * 2.0, height: extractedBackgroundHeight)) - } - - func animateBadgeIn() { - if !self.isReordering { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - self.badgeContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) - ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) - transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) - } - } - - func animateBadgeOut() { - if !self.isReordering { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - self.badgeContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) - ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 1.0) - transition.updateSublayerTransformScale(node: self.badgeContainerNode, scale: 0.1) - } - } - - private func startShaking() { - func degreesToRadians(_ x: CGFloat) -> CGFloat { - return .pi * x / 180.0 - } - - let duration: Double = 0.4 - let displacement: CGFloat = 1.0 - let degreesRotation: CGFloat = 2.0 - - let negativeDisplacement = -1.0 * displacement - let position = CAKeyframeAnimation.init(keyPath: "position") - position.beginTime = 0.8 - position.duration = duration - position.values = [ - NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)), - NSValue(cgPoint: CGPoint(x: 0, y: 0)), - NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)), - NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)), - NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)) - ] - position.calculationMode = .linear - position.isRemovedOnCompletion = false - position.repeatCount = Float.greatestFiniteMagnitude - position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) - position.isAdditive = true - - let transform = CAKeyframeAnimation.init(keyPath: "transform") - transform.beginTime = 2.6 - transform.duration = 0.3 - transform.valueFunction = CAValueFunction(name: CAValueFunctionName.rotateZ) - transform.values = [ - degreesToRadians(-1.0 * degreesRotation), - degreesToRadians(degreesRotation), - degreesToRadians(-1.0 * degreesRotation) - ] - transform.calculationMode = .linear - transform.isRemovedOnCompletion = false - transform.repeatCount = Float.greatestFiniteMagnitude - transform.isAdditive = true - transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100)) - - self.layer.add(position, forKey: "shaking_position") - self.layer.add(transform, forKey: "shaking_rotation") - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let deleteButtonNode = self.deleteButtonNode { - if deleteButtonNode.frame.insetBy(dx: -4.0, dy: -4.0).contains(point) { - return deleteButtonNode.view - } - } - return super.hitTest(point, with: event) - } -} - -private final class ItemNodePair { - let regular: ItemNode - let highlighted: ItemNode - - init(regular: ItemNode, highlighted: ItemNode) { - self.regular = regular - self.highlighted = highlighted - } -} - -final class ChatListFilterTabInlineContainerNode: ASDisplayNode { - private let scrollNode: ASScrollNode - private let itemsBackgroundView: UIVisualEffectView - private let itemsBackgroundTintNode: ASImageNode - - private let selectedBackgroundNode: ASImageNode - private var itemNodePairs: [ChatListFilterTabEntryId: ItemNodePair] = [:] - private var itemsContainer: ASDisplayNode - private var highlightedItemsClippingContainer: ASDisplayNode - private var highlightedItemsContainer: ASDisplayNode - - var tabSelected: ((ChatListFilterTabEntryId) -> Void)? - var tabRequestedDeletion: ((ChatListFilterTabEntryId) -> Void)? - var addFilter: (() -> Void)? - var contextGesture: ((Int32?, ContextExtractedContentContainingNode, ContextGesture, Bool) -> Void)? - - private var reorderingGesture: ReorderingGestureRecognizer? - private var reorderingItem: ChatListFilterTabEntryId? - private var reorderingItemPosition: (initial: CGFloat, offset: CGFloat)? - private var reorderingAutoScrollAnimator: ConstantDisplayLinkAnimator? - private var reorderedItemIds: [ChatListFilterTabEntryId]? - private lazy var hapticFeedback = { HapticFeedback() }() - - private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData)? - - var reorderedFilterIds: [Int32]? { - return self.reorderedItemIds.flatMap { - $0.compactMap { - switch $0 { - case .all: - return 0 - case let .filter(id): - return id - } - } - } - } - - override init() { - self.scrollNode = ASScrollNode() - - self.itemsBackgroundView = UIVisualEffectView() - self.itemsBackgroundView.clipsToBounds = true - self.itemsBackgroundView.layer.cornerRadius = 20.0 - - self.itemsBackgroundTintNode = ASImageNode() - self.itemsBackgroundTintNode.displaysAsynchronously = false - self.itemsBackgroundTintNode.displayWithoutProcessing = true - - self.selectedBackgroundNode = ASImageNode() - self.selectedBackgroundNode.displaysAsynchronously = false - self.selectedBackgroundNode.displayWithoutProcessing = true - - self.itemsContainer = ASDisplayNode() - - self.highlightedItemsClippingContainer = ASDisplayNode() - self.highlightedItemsClippingContainer.clipsToBounds = true - self.highlightedItemsClippingContainer.layer.cornerRadius = 16.0 - - self.highlightedItemsContainer = ASDisplayNode() - - super.init() - - self.scrollNode.view.showsHorizontalScrollIndicator = false - self.scrollNode.view.scrollsToTop = false - self.scrollNode.view.delaysContentTouches = false - self.scrollNode.view.canCancelContentTouches = true - if #available(iOS 11.0, *) { - self.scrollNode.view.contentInsetAdjustmentBehavior = .never - } - - self.addSubnode(self.scrollNode) - self.scrollNode.view.addSubview(self.itemsBackgroundView) - self.scrollNode.addSubnode(self.itemsBackgroundTintNode) - self.scrollNode.addSubnode(self.itemsContainer) - self.scrollNode.addSubnode(self.selectedBackgroundNode) - self.scrollNode.addSubnode(self.highlightedItemsClippingContainer) - self.highlightedItemsClippingContainer.addSubnode(self.highlightedItemsContainer) - - let reorderingGesture = ReorderingGestureRecognizer(shouldBegin: { [weak self] point in - guard let strongSelf = self else { - return false - } - for (id, itemNodePair) in strongSelf.itemNodePairs { - if itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.view).contains(point) { - if case .all = id { - return false - } - return true - } - } - return false - }, began: { [weak self] point in - guard let strongSelf = self, let _ = strongSelf.currentParams else { - return - } - for (id, itemNodePair) in strongSelf.itemNodePairs { - let itemFrame = itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.view) - if itemFrame.contains(point) { - strongSelf.hapticFeedback.impact() - - strongSelf.reorderingItem = id - itemNodePair.regular.frame = itemFrame - strongSelf.reorderingAutoScrollAnimator = ConstantDisplayLinkAnimator(update: { - guard let strongSelf = self, let currentLocation = strongSelf.reorderingGesture?.currentLocation else { - return - } - let edgeWidth: CGFloat = 20.0 - if currentLocation.x <= edgeWidth { - var contentOffset = strongSelf.scrollNode.view.contentOffset - contentOffset.x = max(0.0, contentOffset.x - 3.0) - strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false) - } else if currentLocation.x >= strongSelf.bounds.width - edgeWidth { - var contentOffset = strongSelf.scrollNode.view.contentOffset - contentOffset.x = max(0.0, min(strongSelf.scrollNode.view.contentSize.width - strongSelf.scrollNode.bounds.width, contentOffset.x + 3.0)) - strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false) - } - }) - strongSelf.reorderingAutoScrollAnimator?.isPaused = false - strongSelf.addSubnode(itemNodePair.regular) - - strongSelf.reorderingItemPosition = (itemNodePair.regular.frame.minX, 0.0) - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) - } - return - } - } - }, ended: { [weak self] in - guard let strongSelf = self, let reorderingItem = strongSelf.reorderingItem else { - return - } - if let itemNodePair = strongSelf.itemNodePairs[reorderingItem] { - let projectedItemFrame = itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.scrollNode.view) - itemNodePair.regular.frame = projectedItemFrame - strongSelf.itemsContainer.addSubnode(itemNodePair.regular) - } - - strongSelf.reorderingItem = nil - strongSelf.reorderingItemPosition = nil - strongSelf.reorderingAutoScrollAnimator?.invalidate() - strongSelf.reorderingAutoScrollAnimator = nil - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) - } - }, moved: { [weak self] offset in - guard let strongSelf = self, let reorderingItem = strongSelf.reorderingItem else { - return - } - if let reorderingItemNodePair = strongSelf.itemNodePairs[reorderingItem], let (initial, _) = strongSelf.reorderingItemPosition, let reorderedItemIds = strongSelf.reorderedItemIds, let currentItemIndex = reorderedItemIds.firstIndex(of: reorderingItem) { - for (id, itemNodePair) in strongSelf.itemNodePairs { - guard let itemIndex = reorderedItemIds.firstIndex(of: id) else { - continue - } - if id != reorderingItem { - let itemFrame = itemNodePair.regular.view.convert(itemNodePair.regular.bounds, to: strongSelf.view) - if reorderingItemNodePair.regular.frame.intersects(itemFrame) { - let targetIndex: Int - if reorderingItemNodePair.regular.frame.midX < itemFrame.midX { - targetIndex = max(1, itemIndex - 1) - } else { - targetIndex = max(1, min(reorderedItemIds.count - 1, itemIndex)) - } - if targetIndex != currentItemIndex { - strongSelf.hapticFeedback.tap() - - var updatedReorderedItemIds = reorderedItemIds - if targetIndex > currentItemIndex { - updatedReorderedItemIds.insert(reorderingItem, at: targetIndex + 1) - updatedReorderedItemIds.remove(at: currentItemIndex) - } else { - updatedReorderedItemIds.remove(at: currentItemIndex) - updatedReorderedItemIds.insert(reorderingItem, at: targetIndex) - } - strongSelf.reorderedItemIds = updatedReorderedItemIds - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .animated(duration: 0.25, curve: .easeInOut)) - } - } - break - } - } - } - - strongSelf.reorderingItemPosition = (initial, offset) - } - if let (size, sideInset, filters, selectedFilter, isReordering, isEditing, transitionFraction, presentationData) = strongSelf.currentParams { - strongSelf.update(size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering: isReordering, isEditing: isEditing, transitionFraction: transitionFraction, presentationData: presentationData, transition: .immediate) - } - }) - self.reorderingGesture = reorderingGesture - self.view.addGestureRecognizer(reorderingGesture) - reorderingGesture.isEnabled = false - } - - private var previousSelectedAbsFrame: CGRect? - private var previousSelectedFrame: CGRect? - - func cancelAnimations() { - self.selectedBackgroundNode.layer.removeAllAnimations() - self.scrollNode.layer.removeAllAnimations() - self.highlightedItemsContainer.layer.removeAllAnimations() - self.highlightedItemsClippingContainer.layer.removeAllAnimations() - } - - func update(size: CGSize, sideInset: CGFloat, filters: [ChatListFilterTabEntry], selectedFilter: ChatListFilterTabEntryId?, isReordering: Bool, isEditing: Bool, transitionFraction: CGFloat, presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) { - let isFirstTime = self.currentParams == nil - let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition - - var focusOnSelectedFilter = self.currentParams?.selectedFilter != selectedFilter - let previousScrollBounds = self.scrollNode.bounds - let previousContentWidth = self.scrollNode.view.contentSize.width - - if self.currentParams?.presentationData.theme !== presentationData.theme { - if presentationData.theme.rootController.keyboardColor == .dark { - self.itemsBackgroundView.effect = UIBlurEffect(style: .dark) - } else { - self.itemsBackgroundView.effect = UIBlurEffect(style: .light) - } - - self.itemsBackgroundTintNode.image = generateStretchableFilledCircleImage(diameter: 40.0, color: UIColor(rgb: 0xf1f1f1)) - - self.selectedBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 32.0, color: UIColor(rgb: 0xbbbbbb)) - } - - if isReordering { - if let reorderedItemIds = self.reorderedItemIds { - let currentIds = Set(reorderedItemIds) - if currentIds != Set(filters.map { $0.id }) { - var updatedReorderedItemIds = reorderedItemIds.filter { id in - return filters.contains(where: { $0.id == id }) - } - for filter in filters { - if !currentIds.contains(filter.id) { - updatedReorderedItemIds.append(filter.id) - } - } - self.reorderedItemIds = updatedReorderedItemIds - } - } else { - self.reorderedItemIds = filters.map { $0.id } - } - } else if self.reorderedItemIds != nil { - self.reorderedItemIds = nil - } - - self.currentParams = (size: size, sideInset: sideInset, filters: filters, selectedFilter: selectedFilter, isReordering, isEditing, transitionFraction, presentationData: presentationData) - - self.reorderingGesture?.isEnabled = isEditing || isReordering - - transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size)) - - enum BadgeAnimation { - case `in` - case out - } - - var badgeAnimations: [ChatListFilterTabEntryId: BadgeAnimation] = [:] - - var reorderedFilters: [ChatListFilterTabEntry] = filters - if let reorderedItemIds = self.reorderedItemIds { - reorderedFilters = reorderedItemIds.compactMap { id -> ChatListFilterTabEntry? in - if let index = filters.firstIndex(where: { $0.id == id }) { - return filters[index] - } else { - return nil - } - } - } - - for filter in reorderedFilters { - let itemNodePair: ItemNodePair - var itemNodeTransition = transition - var wasAdded = false - if let current = self.itemNodePairs[filter.id] { - itemNodePair = current - } else { - itemNodeTransition = .immediate - wasAdded = true - itemNodePair = ItemNodePair(regular: ItemNode(pressed: { [weak self] in - self?.tabSelected?(filter.id) - }, requestedDeletion: { [weak self] in - self?.tabRequestedDeletion?(filter.id) - }, contextGesture: { [weak self] sourceNode, gesture in - guard let strongSelf = self else { - return - } - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true - strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false) - switch filter { - case let .filter(id, _, _): - strongSelf.contextGesture?(id, sourceNode, gesture, false) - default: - strongSelf.contextGesture?(nil, sourceNode, gesture, false) - } - }), highlighted: ItemNode(pressed: { [weak self] in - self?.tabSelected?(filter.id) - }, requestedDeletion: { [weak self] in - self?.tabRequestedDeletion?(filter.id) - }, contextGesture: { [weak self] sourceNode, gesture in - guard let strongSelf = self else { - return - } - switch filter { - case let .filter(id, _, _): - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = false - strongSelf.scrollNode.view.panGestureRecognizer.isEnabled = true - strongSelf.scrollNode.view.setContentOffset(strongSelf.scrollNode.view.contentOffset, animated: false) - strongSelf.contextGesture?(id, sourceNode, gesture, false) - default: - strongSelf.contextGesture?(nil, sourceNode, gesture, false) - } - })) - self.itemNodePairs[filter.id] = itemNodePair - } - let unreadCount: Int - let unreadHasUnmuted: Bool - var isNoFilter: Bool = false - switch filter { - case let .all(count): - unreadCount = count - unreadHasUnmuted = true - isNoFilter = true - case let .filter(_, _, unread): - unreadCount = unread.value - unreadHasUnmuted = unread.hasUnmuted - } - if !wasAdded && (itemNodePair.regular.unreadCount != 0) != (unreadCount != 0) { - badgeAnimations[filter.id] = (unreadCount != 0) ? .in : .out - } - itemNodePair.regular.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, isSelected: false, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition) - itemNodePair.highlighted.updateText(title: filter.title(strings: presentationData.strings), shortTitle: filter.shortTitle(strings: presentationData.strings), unreadCount: unreadCount, unreadHasUnmuted: unreadHasUnmuted, isNoFilter: isNoFilter, isSelected: true, isEditing: false, isAllChats: isNoFilter, isReordering: isEditing || isReordering, presentationData: presentationData, transition: itemNodeTransition) - } - var removeKeys: [ChatListFilterTabEntryId] = [] - for (id, _) in self.itemNodePairs { - if !filters.contains(where: { $0.id == id }) { - removeKeys.append(id) - } - } - for id in removeKeys { - if let itemNodePair = self.itemNodePairs.removeValue(forKey: id) { - let regular = itemNodePair.regular - let highlighted = itemNodePair.highlighted - transition.updateAlpha(node: regular, alpha: 0.0, completion: { [weak regular] _ in - regular?.removeFromSupernode() - }) - transition.updateTransformScale(node: regular, scale: 0.1) - transition.updateAlpha(node: highlighted, alpha: 0.0, completion: { [weak highlighted] _ in - highlighted?.removeFromSupernode() - }) - transition.updateTransformScale(node: highlighted, scale: 0.1) - } - } - - var tabSizes: [(ChatListFilterTabEntryId, CGSize, CGSize, ItemNodePair, Bool)] = [] - var totalRawTabSize: CGFloat = 0.0 - var selectionFrames: [CGRect] = [] - - for filter in reorderedFilters { - guard let itemNodePair = self.itemNodePairs[filter.id] else { - continue - } - let wasAdded = itemNodePair.regular.supernode == nil - var itemNodeTransition = transition - if wasAdded { - itemNodeTransition = .immediate - self.itemsContainer.addSubnode(itemNodePair.regular) - self.highlightedItemsContainer.addSubnode(itemNodePair.highlighted) - } - let (paneNodeWidth, paneNodeShortWidth) = itemNodePair.regular.updateLayout(height: size.height, transition: itemNodeTransition) - let _ = itemNodePair.highlighted.updateLayout(height: size.height, transition: itemNodeTransition) - let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height) - let paneNodeShortSize = CGSize(width: paneNodeShortWidth, height: size.height) - tabSizes.append((filter.id, paneNodeSize, paneNodeShortSize, itemNodePair, wasAdded)) - totalRawTabSize += paneNodeSize.width - - if case .animated = transition, let badgeAnimation = badgeAnimations[filter.id] { - switch badgeAnimation { - case .in: - itemNodePair.regular.animateBadgeIn() - itemNodePair.highlighted.animateBadgeIn() - case .out: - itemNodePair.regular.animateBadgeOut() - itemNodePair.highlighted.animateBadgeOut() - } - } - } - - let minSpacing: CGFloat = 30.0 - - let resolvedInitialSideInset: CGFloat = 8.0 + 14.0 + 4.0 + sideInset - - var longTitlesWidth: CGFloat = 0.0 - var shortTitlesWidth: CGFloat = 0.0 - for i in 0 ..< tabSizes.count { - let (_, paneNodeSize, paneNodeShortSize, _, _) = tabSizes[i] - longTitlesWidth += paneNodeSize.width - shortTitlesWidth += paneNodeShortSize.width - } - let totalSpacing = CGFloat(tabSizes.count - 1) * minSpacing - let useShortTitles = (longTitlesWidth + totalSpacing + resolvedInitialSideInset * 2.0) > size.width - - var rawContentWidth = useShortTitles ? shortTitlesWidth : longTitlesWidth - rawContentWidth += totalSpacing - - let resolvedSideInset = max(resolvedInitialSideInset, floor((size.width - rawContentWidth) / 2.0)) - - var leftOffset: CGFloat = resolvedSideInset - - let itemsBackgroundLeftX = leftOffset - 14.0 - 4.0 - - for i in 0 ..< tabSizes.count { - let (itemId, paneNodeLongSize, paneNodeShortSize, itemNodePair, wasAdded) = tabSizes[i] - var itemNodeTransition = transition - if wasAdded { - itemNodeTransition = .immediate - } - - let useShortTitle = itemId == .all && useShortTitles - let paneNodeSize = useShortTitle ? paneNodeShortSize : paneNodeLongSize - - let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0)), size: paneNodeSize) - - if itemId == self.reorderingItem, let (initial, offset) = self.reorderingItemPosition { - itemNodeTransition.updateSublayerTransformScale(node: itemNodePair.regular, scale: 1.2) - itemNodeTransition.updateAlpha(node: itemNodePair.regular, alpha: 0.9) - let offsetFrame = CGRect(origin: CGPoint(x: initial + offset, y: paneFrame.minY), size: paneFrame.size) - itemNodeTransition.updateFrameAdditive(node: itemNodePair.regular, frame: offsetFrame) - selectionFrames.append(offsetFrame) - } else { - itemNodeTransition.updateSublayerTransformScale(node: itemNodePair.regular, scale: 1.0) - itemNodeTransition.updateAlpha(node: itemNodePair.regular, alpha: 1.0) - if wasAdded { - itemNodePair.regular.frame = paneFrame - itemNodePair.regular.alpha = 0.0 - itemNodeTransition.updateAlpha(node: itemNodePair.regular, alpha: 1.0) - } else { - itemNodeTransition.updateFrameAdditive(node: itemNodePair.regular, frame: paneFrame) - } - selectionFrames.append(paneFrame) - } - - if wasAdded { - itemNodePair.highlighted.frame = paneFrame - itemNodePair.highlighted.alpha = 0.0 - itemNodeTransition.updateAlpha(node: itemNodePair.highlighted, alpha: 1.0) - } else { - itemNodeTransition.updateFrameAdditive(node: itemNodePair.highlighted, frame: paneFrame) - } - - itemNodePair.regular.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0, useShortTitle: useShortTitle, transition: itemNodeTransition) - itemNodePair.regular.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) - - itemNodePair.highlighted.updateArea(size: paneFrame.size, sideInset: minSpacing / 2.0, useShortTitle: useShortTitle, transition: itemNodeTransition) - itemNodePair.highlighted.hitTestSlop = UIEdgeInsets(top: 0.0, left: -minSpacing / 2.0, bottom: 0.0, right: -minSpacing / 2.0) - - leftOffset += paneNodeSize.width + minSpacing - } - leftOffset -= minSpacing - let itemsBackgroundRightX = leftOffset + 14.0 + 4.0 - - leftOffset += resolvedSideInset - - let backgroundFrame = CGRect(origin: CGPoint(x: itemsBackgroundLeftX, y: 0.0), size: CGSize(width: itemsBackgroundRightX - itemsBackgroundLeftX, height: size.height)) - transition.updateFrame(view: self.itemsBackgroundView, frame: backgroundFrame) - transition.updateFrame(node: self.itemsBackgroundTintNode, frame: backgroundFrame) - - self.scrollNode.view.contentSize = CGSize(width: itemsBackgroundRightX + 8.0, height: size.height) - - var selectedFrame: CGRect? - if let selectedFilter = selectedFilter, let currentIndex = reorderedFilters.firstIndex(where: { $0.id == selectedFilter }) { - func interpolateFrame(from fromValue: CGRect, to toValue: CGRect, t: CGFloat) -> CGRect { - return CGRect(x: floorToScreenPixels(toValue.origin.x * t + fromValue.origin.x * (1.0 - t)), y: floorToScreenPixels(toValue.origin.y * t + fromValue.origin.y * (1.0 - t)), width: floorToScreenPixels(toValue.size.width * t + fromValue.size.width * (1.0 - t)), height: floorToScreenPixels(toValue.size.height * t + fromValue.size.height * (1.0 - t))) - } - - if currentIndex != 0 && transitionFraction > 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex - 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else if currentIndex != filters.count - 1 && transitionFraction < 0.0 { - let currentFrame = selectionFrames[currentIndex] - let previousFrame = selectionFrames[currentIndex + 1] - selectedFrame = interpolateFrame(from: currentFrame, to: previousFrame, t: abs(transitionFraction)) - } else { - selectedFrame = selectionFrames[currentIndex] - } - } - - transition.updateFrame(node: self.itemsContainer, frame: CGRect(origin: CGPoint(), size: self.scrollNode.view.contentSize)) - - if let selectedFrame = selectedFrame { - let wasAdded = self.selectedBackgroundNode.isHidden - self.selectedBackgroundNode.isHidden = false - let lineFrame = CGRect(origin: CGPoint(x: selectedFrame.minX - 14.0, y: floor((size.height - 32.0) / 2.0)), size: CGSize(width: selectedFrame.width + 14.0 * 2.0, height: 32.0)) - if wasAdded { - self.selectedBackgroundNode.frame = lineFrame - self.selectedBackgroundNode.alpha = 0.0 - } else { - transition.updateFrame(node: self.selectedBackgroundNode, frame: lineFrame) - } - transition.updateFrame(node: self.highlightedItemsClippingContainer, frame: lineFrame) - transition.updateFrame(node: self.highlightedItemsContainer, frame: CGRect(origin: CGPoint(x: -lineFrame.minX, y: -lineFrame.minY), size: self.scrollNode.view.contentSize)) - transition.updateAlpha(node: self.selectedBackgroundNode, alpha: isReordering ? 0.0 : 1.0) - transition.updateAlpha(node: self.highlightedItemsClippingContainer, alpha: isReordering ? 0.0 : 1.0) - - if let previousSelectedFrame = self.previousSelectedFrame { - let previousContentOffsetX = max(0.0, min(previousContentWidth - previousScrollBounds.width, floor(previousSelectedFrame.midX - previousScrollBounds.width / 2.0))) - if abs(previousContentOffsetX - previousScrollBounds.minX) < 1.0 { - focusOnSelectedFilter = true - } - } - - if focusOnSelectedFilter && self.reorderingItem == nil { - let updatedBounds: CGRect - if transitionFraction.isZero && selectedFilter == reorderedFilters.first?.id { - updatedBounds = CGRect(origin: CGPoint(), size: self.scrollNode.bounds.size) - } else if transitionFraction.isZero && selectedFilter == reorderedFilters.last?.id { - updatedBounds = CGRect(origin: CGPoint(x: max(0.0, self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width), y: 0.0), size: self.scrollNode.bounds.size) - } else { - let contentOffsetX = max(0.0, min(self.scrollNode.view.contentSize.width - self.scrollNode.bounds.width, floor(selectedFrame.midX - self.scrollNode.bounds.width / 2.0))) - updatedBounds = CGRect(origin: CGPoint(x: contentOffsetX, y: 0.0), size: self.scrollNode.bounds.size) - } - self.scrollNode.bounds = updatedBounds - } - transition.animateHorizontalOffsetAdditive(node: self.scrollNode, offset: previousScrollBounds.minX - self.scrollNode.bounds.minX) - - self.previousSelectedAbsFrame = selectedFrame.offsetBy(dx: -self.scrollNode.bounds.minX, dy: 0.0) - self.previousSelectedFrame = selectedFrame - } else { - self.selectedBackgroundNode.isHidden = true - self.previousSelectedAbsFrame = nil - self.previousSelectedFrame = nil - } - } -} - -private class ReorderingGestureRecognizerTimerTarget: NSObject { - private let f: () -> Void - - init(_ f: @escaping () -> Void) { - self.f = f - - super.init() - } - - @objc func timerEvent() { - self.f() - } -} - -private final class ReorderingGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { - private let shouldBegin: (CGPoint) -> Bool - private let began: (CGPoint) -> Void - private let ended: () -> Void - private let moved: (CGFloat) -> Void - - private var initialLocation: CGPoint? - private var delayTimer: Foundation.Timer? - - var currentLocation: CGPoint? - - init(shouldBegin: @escaping (CGPoint) -> Bool, began: @escaping (CGPoint) -> Void, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { - self.shouldBegin = shouldBegin - self.began = began - self.ended = ended - self.moved = moved - - super.init(target: nil, action: nil) - - self.delegate = self - } - - override func reset() { - super.reset() - - self.initialLocation = nil - self.delayTimer?.invalidate() - self.delayTimer = nil - self.currentLocation = nil - } - - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if otherGestureRecognizer is UIPanGestureRecognizer { - return true - } else { - return false - } - } - - override func touchesBegan(_ touches: Set, with event: UIEvent) { - super.touchesBegan(touches, with: event) - - guard let location = touches.first?.location(in: self.view) else { - self.state = .failed - return - } - - if self.state == .possible { - if self.delayTimer == nil { - if !self.shouldBegin(location) { - self.state = .failed - return - } - self.initialLocation = location - let timer = Foundation.Timer(timeInterval: 0.2, target: ReorderingGestureRecognizerTimerTarget { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.delayTimer = nil - strongSelf.state = .began - strongSelf.began(location) - }, selector: #selector(ReorderingGestureRecognizerTimerTarget.timerEvent), userInfo: nil, repeats: false) - self.delayTimer = timer - RunLoop.main.add(timer, forMode: .common) - } else { - self.state = .failed - } - } - } - - override func touchesEnded(_ touches: Set, with event: UIEvent) { - super.touchesEnded(touches, with: event) - - self.delayTimer?.invalidate() - - if self.state == .began || self.state == .changed { - self.ended() - } - - self.state = .failed - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent) { - super.touchesCancelled(touches, with: event) - - if self.state == .began || self.state == .changed { - self.delayTimer?.invalidate() - self.ended() - self.state = .failed - } - } - - override func touchesMoved(_ touches: Set, with event: UIEvent) { - super.touchesMoved(touches, with: event) - - guard let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) else { - return - } - let offset = location.x - initialLocation.x - self.currentLocation = location - - if self.delayTimer != nil { - if abs(offset) > 4.0 { - self.delayTimer?.invalidate() - self.state = .failed - return - } - } else { - if self.state == .began || self.state == .changed { - self.state = .changed - self.moved(offset) - } - } - } -} diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 0dd0e80308..e8a666aa55 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -665,13 +665,9 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction?.openPasswordSetup() case .premiumUpgrade, .premiumAnnualDiscount: nodeInteraction?.openPremiumIntro() - case .chatFolderUpdates: - nodeInteraction?.openChatFolderUpdates() } case .hide: switch notice { - case .chatFolderUpdates: - nodeInteraction?.hideChatFolderUpdates() default: break } @@ -966,13 +962,9 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction?.openPasswordSetup() case .premiumUpgrade, .premiumAnnualDiscount: nodeInteraction?.openPremiumIntro() - case .chatFolderUpdates: - nodeInteraction?.openChatFolderUpdates() } case .hide: switch notice { - case .chatFolderUpdates: - nodeInteraction?.hideChatFolderUpdates() default: break } diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift index d9a9c13a7a..31ead65ee0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift @@ -210,8 +210,6 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) switch item.notice { - case .chatFolderUpdates: - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.ChatList_HideAction, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) default: strongSelf.setRevealOptions((left: [], right: [])) } diff --git a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift index dd9de01a1a..a8428853f7 100644 --- a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift +++ b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift @@ -1,12 +1,11 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import UniversalMediaPlayer import AccountContext -private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { +private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .single(nil) } @@ -21,7 +20,7 @@ private func internalMessageFileMediaPlaybackStatus(context: AccountContext, fil } } -public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { +public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { var duration = 0.0 if let value = file.duration { duration = Double(value) @@ -33,7 +32,7 @@ public func messageFileMediaPlaybackStatus(context: AccountContext, file: Telegr } } -public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { +public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal { guard let playerType = peerMessageMediaPlayerType(message) else { return .never() } @@ -45,7 +44,7 @@ public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, fi } } -public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false, isDownloadList: Bool = false) -> Signal { +public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: EngineMessage, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false, isDownloadList: Bool = false) -> Signal { let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) |> map { status -> MediaPlayerPlaybackStatus? in return status?.status } @@ -99,7 +98,7 @@ public func messageFileMediaResourceStatus(context: AccountContext, file: Telegr } } -public func messageImageMediaResourceStatus(context: AccountContext, image: TelegramMediaImage, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal { +public func messageImageMediaResourceStatus(context: AccountContext, image: TelegramMediaImage, message: EngineMessage, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal { if message.flags.isSending { return combineLatest(messageMediaImageStatus(context: context, messageId: message.id, image: image), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 10acbaaa3e..a429a45efd 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -37,13 +37,13 @@ private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, med switch block { case let .image(id, caption, _, _): if let m = media[id] { - let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: EngineMedia(m), url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } case let .video(id, caption, _, _): if let m = media[id] { - let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] + let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: EngineMedia(m), url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))] counter += 1 return result } @@ -82,7 +82,7 @@ public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galle } if !found { - result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0) + result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: EngineMedia(galleryMedia), url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0) } for i in 0 ..< result.count { @@ -123,7 +123,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati if case .suggestedProfilePhoto = action.action { isSuggested = true } - let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer._asPeer(), message.timestamp, nil, message.id, image.immediateThumbnailData, "action", false, nil)]) + let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action", false, nil)]) let sourceCorners: AvatarGalleryController.SourceCorners if case .photoUpdated = action.action { @@ -131,7 +131,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati } else { sourceCorners = .round } - let galleryController = AvatarGalleryController(context: context, peer: peer._asPeer(), sourceCorners: sourceCorners, remoteEntries: promise, isSuggested: isSuggested, skipInitial: true, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: sourceCorners, remoteEntries: promise, isSuggested: isSuggested, skipInitial: true, replaceRootController: { controller, ready in }) return .chatAvatars(galleryController, image) diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index c24a292438..8f35db5067 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramBaseController diff --git a/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift b/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift index b0ba996cad..cc08759bd6 100644 --- a/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift +++ b/submodules/ImageContentAnalysis/Sources/ImageContentAnalysis.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Vision import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -27,8 +26,8 @@ private final class CachedImageRecognizedContent: Codable { } } -private func cachedImageRecognizedContent(engine: TelegramEngine, messageId: MessageId) -> Signal { - let key = ValueBoxKey(length: 20) +private func cachedImageRecognizedContent(engine: TelegramEngine, messageId: EngineMessage.Id) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) @@ -40,8 +39,8 @@ private func cachedImageRecognizedContent(engine: TelegramEngine, messageId: Mes } } -private func updateCachedImageRecognizedContent(engine: TelegramEngine, messageId: MessageId, content: CachedImageRecognizedContent?) -> Signal { - let key = ValueBoxKey(length: 20) +private func updateCachedImageRecognizedContent(engine: TelegramEngine, messageId: EngineMessage.Id, content: CachedImageRecognizedContent?) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) @@ -333,7 +332,7 @@ private func recognizeContent(in image: UIImage?) -> Signal<[RecognizedContent], } } -public func recognizedContent(context: AccountContext, image: @escaping () -> UIImage?, messageId: MessageId) -> Signal<[RecognizedContent], NoError> { +public func recognizedContent(context: AccountContext, image: @escaping () -> UIImage?, messageId: EngineMessage.Id) -> Signal<[RecognizedContent], NoError> { if context.sharedContext.immediateExperimentalUISettings.disableImageContentAnalysis { return .single([]) } diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift index 922ad9a4d8..070a81fe86 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPack.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore enum StickerVerificationStatus { @@ -74,7 +73,7 @@ public class ImportStickerPack { let emojis: [String] let keywords: String let uuid: UUID - var resource: MediaResource? + var resource: EngineMediaResource? init(content: Content, emojis: [String], keywords: String, uuid: UUID = UUID()) { self.content = content diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift index 3fdf142b98..01c79d7df3 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramUIPreferences @@ -87,23 +86,23 @@ public final class ImportStickerPackController: ViewController, StandalonePresen return } - var signals: [Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError>] = [] + var signals: [Signal<(UUID, StickerVerificationStatus, EngineMediaResource?), NoError>] = [] for sticker in strongSelf.stickerPack.stickers { if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] { - signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), mimeType: sticker.mimeType) - |> map { result -> (UUID, StickerVerificationStatus, MediaResource?) in + signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource._asResource(), alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), mimeType: sticker.mimeType) + |> map { result -> (UUID, StickerVerificationStatus, EngineMediaResource?) in switch result { case .progress: return (sticker.uuid, .loading, nil) case let .complete(resource, mimeType): if ["application/x-tgsticker", "video/webm"].contains(mimeType) { - return (sticker.uuid, .verified, resource) + return (sticker.uuid, .verified, EngineMediaResource(resource)) } else { return (sticker.uuid, .declined, nil) } } } - |> `catch` { _ -> Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError> in + |> `catch` { _ -> Signal<(UUID, StickerVerificationStatus, EngineMediaResource?), NoError> in return .single((sticker.uuid, .declined, nil)) }) } @@ -115,7 +114,7 @@ public final class ImportStickerPackController: ViewController, StandalonePresen } var verifiedStickers = Set() var declinedStickers = Set() - var uploadedStickerResources: [UUID: MediaResource] = [:] + var uploadedStickerResources: [UUID: EngineMediaResource] = [:] for (uuid, result, resource) in results { switch result { case .verified: diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index 7a9600b7a0..1a64cdec19 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -52,8 +51,8 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll private let context: AccountContext private var presentationData: PresentationData private var stickerPack: ImportStickerPack? - var stickerResources: [UUID: MediaResource] = [:] - private var uploadedStickerResources: [UUID: MediaResource] = [:] + var stickerResources: [UUID: EngineMediaResource] = [:] + private var uploadedStickerResources: [UUID: EngineMediaResource] = [:] private var stickerPackReady = true private var containerLayout: (ContainerViewLayout, CGFloat)? @@ -623,11 +622,11 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll } if let resource = self.uploadedStickerResources[item.stickerItem.uuid] { if let localResource = item.stickerItem.resource { - self.context.account.postbox.mediaBox.copyResourceData(from: localResource.id, to: resource.id) + self.context.account.postbox.mediaBox.copyResourceData(from: localResource._asResource().id, to: resource._asResource().id) } - stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) + stickers.append(ImportSticker(resource: resource._asResource(), emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) } else if let resource = item.stickerItem.resource { - stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) + stickers.append(ImportSticker(resource: resource._asResource(), emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) } } var thumbnailSticker: ImportSticker? @@ -695,23 +694,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll let context = strongSelf.context Queue.mainQueue().after(1.0) { - var firstItem: StickerPackItem? - if let firstStickerItem = firstStickerItem, let resource = firstStickerItem.resource as? TelegramMediaResource { - var fileAttributes: [TelegramMediaFileAttribute] = [] - if firstStickerItem.mimeType == "video/webm" { - fileAttributes.append(.FileName(fileName: "sticker.webm")) - fileAttributes.append(.Animated) - fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - } else if firstStickerItem.mimeType == "application/x-tgsticker" { - fileAttributes.append(.FileName(fileName: "sticker.tgs")) - fileAttributes.append(.Animated) - fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - } else { - fileAttributes.append(.FileName(fileName: "sticker.webp")) - } - fileAttributes.append(.ImageSize(size: firstStickerItem.dimensions)) - firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: firstStickerItem.mimeType, size: nil, attributes: fileAttributes), indexKeys: []) - } + let firstItem: StickerPackItem? = firstStickerItem?.stickerPackItem strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: firstItem ?? items.first, context: strongSelf.context), elevatedLayout: false, action: { action in if case .info = action { (navigationController?.viewControllers.last as? ViewController)?.present(StickerPackScreen(context: context, mode: .settings, mainStickerPack: .id(id: info.id.id, accessHash: info.accessHash), stickerPacks: [], parentNavigationController: navigationController, actionPerformed: { _ in @@ -800,7 +783,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll }) } - func updateStickerPack(_ stickerPack: ImportStickerPack, verifiedStickers: Set, declinedStickers: Set, uploadedStickerResources: [UUID: MediaResource]) { + func updateStickerPack(_ stickerPack: ImportStickerPack, verifiedStickers: Set, declinedStickers: Set, uploadedStickerResources: [UUID: EngineMediaResource]) { self.stickerPack = stickerPack self.uploadedStickerResources = uploadedStickerResources var updatedItems: [StickerPackPreviewGridEntry] = [] @@ -813,8 +796,8 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll } else { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.data) - item.resource = resource - self.stickerResources[item.uuid] = resource + item.resource = EngineMediaResource(resource) + self.stickerResources[item.uuid] = EngineMediaResource(resource) } var isInitiallyVerified = false if case .image = item.content { diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift index 6ff204a44a..c8f34868f9 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackTitleController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift b/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift index d60f0656bf..38d89c37e4 100644 --- a/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/ImportStickerPackUI/Sources/StickerPackPreviewGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import StickerResources import AccountContext import AnimatedStickerNode @@ -142,7 +141,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode { if case .video = stickerItem.content { isVideo = true } - animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource, isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil)) + animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource._asResource(), isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil)) } animationNode.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true } else { diff --git a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift index 4e815ee4f8..73bd3744ea 100644 --- a/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/ImportStickerPackUI/Sources/StickerPreviewPeekContent.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import StickerResources @@ -87,7 +86,7 @@ private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekController if case .video = item.content { isVideo = true } - self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: resource, isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: resource._asResource(), isVideo: isVideo), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) } self.animationNode?.visibility = true } diff --git a/submodules/InstantPageCache/Sources/CachedInstantPages.swift b/submodules/InstantPageCache/Sources/CachedInstantPages.swift index aef6b86364..182e3d47bf 100644 --- a/submodules/InstantPageCache/Sources/CachedInstantPages.swift +++ b/submodules/InstantPageCache/Sources/CachedInstantPages.swift @@ -1,8 +1,8 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore +import Postbox import TelegramUIPreferences import PersistentStringHash diff --git a/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift b/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift index 5ac6bde65b..efea3cbf91 100644 --- a/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift +++ b/submodules/InstantPageCache/Sources/CachedInternalInstantPages.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import UrlHandling diff --git a/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift b/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift index 3fc1125c2a..3ba3e794a6 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAnchorItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift b/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift index 831d1082a1..38d2a817e1 100644 --- a/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageArticleItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData @@ -20,11 +19,11 @@ public final class InstantPageArticleItem: InstantPageItem { let contentSize: CGSize let cover: TelegramMediaImage? let url: String - let webpageId: MediaId + let webpageId: EngineMedia.Id let rtl: Bool let hasRTL: Bool - init(frame: CGRect, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, rtl: Bool, hasRTL: Bool) { + init(frame: CGRect, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: EngineMedia.Id, rtl: Bool, hasRTL: Bool) { self.frame = frame self.userLocation = userLocation self.webPage = webPage @@ -73,7 +72,7 @@ public final class InstantPageArticleItem: InstantPageItem { } } -func layoutArticleItem(theme: InstantPageTheme, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, title: NSAttributedString, description: NSAttributedString, cover: TelegramMediaImage?, url: String, webpageId: MediaId, boundingWidth: CGFloat, rtl: Bool) -> InstantPageArticleItem { +func layoutArticleItem(theme: InstantPageTheme, userLocation: MediaResourceUserLocation, webPage: TelegramMediaWebpage, title: NSAttributedString, description: NSAttributedString, cover: TelegramMediaImage?, url: String, webpageId: EngineMedia.Id, boundingWidth: CGFloat, rtl: Bool) -> InstantPageArticleItem { let inset: CGFloat = 17.0 let imageSpacing: CGFloat = 10.0 var sideInset = inset diff --git a/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift b/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift index 26b67ecd55..9b48a70b68 100644 --- a/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageArticleNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -20,14 +19,14 @@ final class InstantPageArticleNode: ASDisplayNode, InstantPageNode { private var imageNode: TransformImageNode? let url: String - let webpageId: MediaId + let webpageId: EngineMedia.Id let cover: TelegramMediaImage? private let openUrl: (InstantPageUrlItem) -> Void private var fetchedDisposable = MetaDisposable() - init(context: AccountContext, item: InstantPageArticleItem, webPage: TelegramMediaWebpage, strings: PresentationStrings, theme: InstantPageTheme, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: MediaId, openUrl: @escaping (InstantPageUrlItem) -> Void) { + init(context: AccountContext, item: InstantPageArticleItem, webPage: TelegramMediaWebpage, strings: PresentationStrings, theme: InstantPageTheme, contentItems: [InstantPageItem], contentSize: CGSize, cover: TelegramMediaImage?, url: String, webpageId: EngineMedia.Id, openUrl: @escaping (InstantPageUrlItem) -> Void) { self.item = item self.url = url self.webpageId = webpageId diff --git a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift index 0d3005a60e..b1962eb684 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAudioItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift b/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift index b4d8b96eb1..4da50a07c0 100644 --- a/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageAudioNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import AsyncDisplayKit import Display @@ -36,7 +35,7 @@ private func generatePauseButton(color: UIColor) -> UIImage? { private func titleString(media: InstantPageMedia, theme: InstantPageTheme, strings: PresentationStrings) -> NSAttributedString { let string = NSMutableAttributedString() - if let file = media.media as? TelegramMediaFile { + if case let .file(file) = media.media { loop: for attribute in file.attributes { if case let .Audio(isVoice, _, title, performer, _) = attribute, !isVoice { let titleText: String = title ?? strings.MediaPlayer_UnknownTrack @@ -101,7 +100,7 @@ final class InstantPageAudioNode: ASDisplayNode, InstantPageNode { self.scrubbingNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 3.0, lineCap: .round, scrubberHandle: .line, backgroundColor: theme.textCategories.paragraph.color.withAlphaComponent(backgroundAlpha), foregroundColor: theme.textCategories.paragraph.color, bufferingColor: theme.textCategories.paragraph.color.withAlphaComponent(0.5), chapters: [])) let playlistType: MediaManagerPlayerType - if let file = self.media.media as? TelegramMediaFile { + if case let .file(file) = self.media.media { playlistType = file.isVoice ? .voice : .music } else { playlistType = .music diff --git a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift index 9cf0244341..b2c8810209 100644 --- a/submodules/InstantPageUI/Sources/InstantPageContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 6a64e41cf9..ab7a175820 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -555,7 +555,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { if item is InstantPageWebEmbedItem { embedIndex += 1 } - if let imageItem = item as? InstantPageImageItem, imageItem.media.media is TelegramMediaWebpage { + if let imageItem = item as? InstantPageImageItem, case .webpage = imageItem.media.media { embedIndex += 1 } if item is InstantPageDetailsItem { @@ -1003,17 +1003,17 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { private func longPressMedia(_ media: InstantPageMedia) { let controller = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.strings.Conversation_ContextMenuCopy), action: { [weak self] in - if let strongSelf = self, let image = media.media as? TelegramMediaImage { + if let strongSelf = self, case let .image(image) = media.media { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) let _ = copyToPasteboard(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.strings.Conversation_LinkDialogSave, accessibilityLabel: self.strings.Conversation_LinkDialogSave), action: { [weak self] in - if let strongSelf = self, let image = media.media as? TelegramMediaImage { + if let strongSelf = self, case let .image(image) = media.media { let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: image.representations, immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) let _ = saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start() } }), ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in - if let strongSelf = self, let webPage = strongSelf.webPage, let image = media.media as? TelegramMediaImage { + if let strongSelf = self, let webPage = strongSelf.webPage, case let .image(image) = media.media { strongSelf.present(ShareController(context: strongSelf.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil) } })], catchTapsOutside: true) @@ -1406,7 +1406,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return } - if let map = media.media as? TelegramMediaMap { + if case let .geo(map) = media.media { let controllerParams = LocationViewParams(sendLiveLocation: { _ in }, stopLiveLocation: { _ in }, openUrl: { _ in }, openPeer: { _ in @@ -1420,12 +1420,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { return } - if let file = media.media as? TelegramMediaFile, (file.isVoice || file.isMusic) { + if case let .file(file) = media.media, (file.isVoice || file.isMusic) { var medias: [InstantPageMedia] = [] var initialIndex = 0 for item in items { for itemMedia in item.medias { - if let itemFile = itemMedia.media as? TelegramMediaFile, (itemFile.isVoice || itemFile.isMusic) { + if case let .file(itemFile) = itemMedia.media, (itemFile.isVoice || itemFile.isMusic) { if itemMedia.index == media.index { initialIndex = medias.count } @@ -1440,16 +1440,21 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { var fromPlayingVideo = false var entries: [InstantPageGalleryEntry] = [] - if media.media is TelegramMediaWebpage { + if case let .webpage(webPage) = media.media { entries.append(InstantPageGalleryEntry(index: 0, pageId: webPage.webpageId, media: media, caption: nil, credit: nil, location: nil)) - } else if let file = media.media as? TelegramMediaFile, file.isAnimated { + } else if case let .file(file) = media.media, file.isAnimated { fromPlayingVideo = true entries.append(InstantPageGalleryEntry(index: Int32(media.index), pageId: webPage.webpageId, media: media, caption: media.caption, credit: media.credit, location: nil)) } else { fromPlayingVideo = true var medias: [InstantPageMedia] = mediasFromItems(items) - medias = medias.filter { - return $0.media is TelegramMediaImage || $0.media is TelegramMediaFile + medias = medias.filter { item in + switch item.media { + case .image, .file: + return true + default: + return false + } } for media in medias { diff --git a/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift b/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift index b3e93eb27a..a590813aa1 100644 --- a/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageDetailsItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import Display diff --git a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift index d3fafb2a9c..ccb2c59081 100644 --- a/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageDetailsNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift b/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift index a6f4080321..84d2350655 100644 --- a/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageFeedbackItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift b/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift index bc641baa98..9a756f1997 100644 --- a/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageFeedbackNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift index caefa5bdf4..144a5f833b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageGalleryController.swift @@ -96,9 +96,9 @@ public struct InstantPageGalleryEntry: Equatable { } } - if let image = self.media.media as? TelegramMediaImage { + if case let .image(image) = self.media.media { return InstantImageGalleryItem(context: context, presentationData: presentationData, itemId: self.index, userLocation: userLocation, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions) - } else if let file = self.media.media as? TelegramMediaFile { + } else if case let .file(file) = self.media.media { if file.isVideo { var indexData: GalleryItemIndexData? if let location = self.location { @@ -122,7 +122,7 @@ public struct InstantPageGalleryEntry: Equatable { let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) return InstantImageGalleryItem(context: context, presentationData: presentationData, itemId: self.index, userLocation: userLocation, imageReference: .webPage(webPage: WebpageReference(webPage), media: image), caption: caption, credit: credit, location: self.location, openUrl: openUrl, openUrlOptions: openUrlOptions) } - } else if let embedWebpage = self.media.media as? TelegramMediaWebpage, case let .Loaded(webpageContent) = embedWebpage.content { + } else if case let .webpage(embedWebpage) = self.media.media, case let .Loaded(webpageContent) = embedWebpage.content { if webpageContent.url.hasSuffix(".m3u8") { let content = PlatformVideoContent(id: .instantPage(embedWebpage.webpageId, embedWebpage.webpageId), userLocation: userLocation, content: .url(webpageContent.url), streamVideo: true, loopVideo: false) return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage, { makeArguments, navigationController, present in diff --git a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift index 585b50cc1c..7d3ac039cf 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift index 4b2a6f0329..3607bbdff3 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -43,7 +42,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { private var currentSize: CGSize? - private var fetchStatus: MediaResourceStatus? + private var fetchStatus: EngineMediaResource.FetchStatus? private var fetchedDisposable = MetaDisposable() private var statusDisposable = MetaDisposable() @@ -72,7 +71,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.pinchContainerNode.contentNode.addSubnode(self.imageNode) self.addSubnode(self.pinchContainerNode) - if let image = media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = media.media, let largest = largestImageRepresentation(image.representations) { let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference)) @@ -92,7 +91,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(largest.resource) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = status + strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) strongSelf.updateFetchStatus() } } @@ -105,7 +104,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.pinchContainerNode.contentNode.addSubnode(self.statusNode) } - } else if let file = media.media as? TelegramMediaFile { + } else if case let .file(file) = media.media { let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) if file.mimeType.hasPrefix("image/") { if !interactive || shouldDownloadMediaAutomatically(settings: context.sharedContext.currentAutomaticMediaDownloadSettings, peerType: sourceLocation.peerType, networkType: MediaAutoDownloadNetworkType(context.account.immediateNetworkType), authorPeerId: nil, contactsPeerIds: Set(), media: file) { @@ -119,7 +118,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { self.statusNode.transitionToState(.play(.white), animated: false, completion: {}) self.pinchContainerNode.contentNode.addSubnode(self.statusNode) } - } else if let map = media.media as? TelegramMediaMap { + } else if case let .geo(map) = media.media { self.addSubnode(self.pinNode) var dimensions = CGSize(width: 200.0, height: 100.0) @@ -131,7 +130,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height)) self.imageNode.setSignal(chatMapSnapshotImage(engine: context.engine, resource: resource)) - } else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image { + } else if case let .webpage(webPage) = media.media, case let .Loaded(content) = webPage.content, let image = content.image { let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, userLocation: sourceLocation.userLocation, photoReference: imageReference)) self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(context: context, userLocation: sourceLocation.userLocation, photoReference: imageReference, displayAtSize: nil, storeToDownloadsPeerId: nil).start()) @@ -211,7 +210,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { if self.currentSize != size || self.themeUpdated { self.currentSize = size self.themeUpdated = false - + self.pinchContainerNode.frame = CGRect(origin: CGPoint(), size: size) self.pinchContainerNode.update(size: size, transition: .immediate) self.imageNode.frame = CGRect(origin: CGPoint(), size: size) @@ -219,7 +218,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let radialStatusSize: CGFloat = 50.0 self.statusNode.frame = CGRect(x: floorToScreenPixels((size.width - radialStatusSize) / 2.0), y: floorToScreenPixels((size.height - radialStatusSize) / 2.0), width: radialStatusSize, height: radialStatusSize) - if let image = self.media.media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = self.media.media, let largest = largestImageRepresentation(image.representations) { let imageSize = largest.dimensions.cgSize.aspectFilled(size) let boundingSize = size let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0 @@ -228,15 +227,15 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { apply() self.linkIconNode.frame = CGRect(x: size.width - 38.0, y: 14.0, width: 24.0, height: 24.0) - } else if let file = self.media.media as? TelegramMediaFile, let dimensions = file.dimensions { + } else if case let .file(file) = self.media.media, let dimensions = file.dimensions { let emptyColor = file.mimeType.hasPrefix("image/") ? self.theme.imageTintColor : nil - + let imageSize = dimensions.cgSize.aspectFilled(size) let boundingSize = size let makeLayout = self.imageNode.asyncLayout() let apply = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), emptyColor: emptyColor)) apply() - } else if self.media.media is TelegramMediaMap { + } else if case .geo = self.media.media { for attribute in self.attributes { if let mapAttribute = attribute as? InstantPageMapAttribute { let imageSize = mapAttribute.dimensions.aspectFilled(size) @@ -254,7 +253,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { let (pinSize, pinApply) = makePinLayout(self.context, theme, .location(nil)) self.pinNode.frame = CGRect(origin: CGPoint(x: floor((size.width - pinSize.width) / 2.0), y: floor(size.height * 0.5 - 10.0 - pinSize.height / 2.0)), size: pinSize) pinApply() - } else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) { + } else if case let .webpage(webPage) = media.media, case let .Loaded(content) = webPage.content, let image = content.image, let largest = largestImageRepresentation(image.representations) { let imageSize = largest.dimensions.cgSize.aspectFilled(size) let boundingSize = size let radius: CGFloat = self.roundCorners ? floor(min(imageSize.width, imageSize.height) / 2.0) : 0.0 @@ -290,7 +289,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { case .Local: switch gesture { case .tap: - if self.media.media is TelegramMediaImage && self.media.index == -1 { + if case .image = self.media.media, self.media.index == -1 { return } self.openMedia(self.media) @@ -311,7 +310,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } else { switch gesture { case .tap: - if self.media.media is TelegramMediaImage && self.media.index == -1 { + if case .image = self.media.media, self.media.index == -1 { return } self.openMedia(self.media) diff --git a/submodules/InstantPageUI/Sources/InstantPageItem.swift b/submodules/InstantPageUI/Sources/InstantPageItem.swift index ab26eabba9..9daf839f5c 100644 --- a/submodules/InstantPageUI/Sources/InstantPageItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageLayout.swift b/submodules/InstantPageUI/Sources/InstantPageLayout.swift index c91f2c9c83..686cebb5a5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageLayout.swift +++ b/submodules/InstantPageUI/Sources/InstantPageLayout.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import Display import TelegramPresentationData import TelegramUIPreferences @@ -48,7 +47,7 @@ private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantP } } -public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: MediaResourceUserLocation, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [MediaId: Media], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], excludeCaptions: Bool) -> InstantPageLayout { +public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: MediaResourceUserLocation, rtl: Bool, block: InstantPageBlock, boundingWidth: CGFloat, horizontalInset: CGFloat, safeInset: CGFloat, isCover: Bool, previousItems: [InstantPageItem], fillToSize: CGSize?, media: [EngineMedia.Id: EngineMedia], mediaIndexCounter: inout Int, embedIndexCounter: inout Int, detailsIndexCounter: inout Int, theme: InstantPageTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:], excludeCaptions: Bool) -> InstantPageLayout { let layoutCaption: (InstantPageCaption, CGSize) -> ([InstantPageItem], CGSize) = { caption, contentSize in var items: [InstantPageItem] = [] @@ -373,7 +372,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: contentSize.height += verticalInset return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items) case let .image(id, caption, url, webpageId): - if let image = media[id] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = media[id], let largest = largestImageRepresentation(image.representations) { let imageSize = largest.dimensions var filledSize = imageSize.cgSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) @@ -397,7 +396,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: mediaUrl = InstantPageUrlItem(url: url, webpageId: webpageId) } - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: image, url: mediaUrl, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: .image(image), url: mediaUrl, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height @@ -413,7 +412,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: []) } case let .video(id, caption, autoplay, _): - if let file = media[id] as? TelegramMediaFile, let dimensions = file.dimensions { + if case let .file(file) = media[id], let dimensions = file.dimensions { let imageSize = dimensions var filledSize = imageSize.cgSize.aspectFitted(CGSize(width: boundingWidth - safeInset * 2.0, height: 1200.0)) @@ -433,11 +432,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var items: [InstantPageItem] = [] if autoplay { - let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, url: nil, caption: caption.text, credit: caption.credit), interactive: true) + let mediaItem = InstantPagePlayableVideoItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: .file(file), url: nil, caption: caption.text, credit: caption.credit), interactive: true) items.append(mediaItem) } else { - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: file, url: nil, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: mediaIndex, media: .file(file), url: nil, caption: caption.text, credit: caption.credit), interactive: true, roundCorners: false, fit: false) items.append(mediaItem) } @@ -460,11 +459,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var size = CGSize() switch subItem { case let .image(id, _, _, _): - if let image = media[id] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { + if case let .image(image) = media[id], let largest = largestImageRepresentation(image.representations) { size = largest.dimensions.cgSize } case let .video(id, _, _, _): - if let file = media[id] as? TelegramMediaFile, let dimensions = file.dimensions { + if case let .file(file) = media[id], let dimensions = file.dimensions { size = dimensions.cgSize } default: @@ -502,9 +501,15 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var items: [InstantPageItem] = [] if !author.isEmpty { - let avatar: TelegramMediaImage? = avatarId.flatMap { media[$0] as? TelegramMediaImage } + let avatar: TelegramMediaImage? = avatarId.flatMap { id -> TelegramMediaImage? in + if case let .image(image) = media[id] { + return image + } else { + return nil + } + } if let avatar = avatar { - let avatarItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: horizontalInset + lineInset + 1.0, y: contentSize.height - 2.0), size: CGSize(width: 50.0, height: 50.0)), webPage: webpage, media: InstantPageMedia(index: -1, media: avatar, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: true, fit: false) + let avatarItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: horizontalInset + lineInset + 1.0, y: contentSize.height - 2.0), size: CGSize(width: 50.0, height: 50.0)), webPage: webpage, media: InstantPageMedia(index: -1, media: .image(avatar), url: nil, caption: nil, credit: nil), interactive: false, roundCorners: true, fit: false) items.append(avatarItem) avatarInset += 62.0 @@ -572,7 +577,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: for subBlock in subItems { switch subBlock { case let .image(id, caption, url, webpageId): - if let image = media[id] as? TelegramMediaImage, let imageSize = largestImageRepresentation(image.representations)?.dimensions { + if case let .image(image) = media[id], let imageSize = largestImageRepresentation(image.representations)?.dimensions { let mediaIndex = mediaIndexCounter mediaIndexCounter += 1 @@ -583,7 +588,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: if let url = url { mediaUrl = InstantPageUrlItem(url: url, webpageId: webpageId) } - itemMedias.append(InstantPageMedia(index: mediaIndex, media: image, url: mediaUrl, caption: caption.text, credit: caption.credit)) + itemMedias.append(InstantPageMedia(index: mediaIndex, media: .image(image), url: mediaUrl, caption: caption.text, credit: caption.credit)) } break default: @@ -626,11 +631,11 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var contentSize: CGSize let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size) let item: InstantPageItem - if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage { + if let url = url, let coverId = coverId, case let .image(image) = media[coverId] { let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: PixelDimensions(size), duration: nil, author: nil, image: image, file: nil, attributes: [], instantPage: nil) let content = TelegramMediaWebpageContent.Loaded(loadedContent) - item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false) + item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: .webpage(TelegramMediaWebpage(webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalWebpage, id: -1), content: content)), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false) } else { item = InstantPageWebEmbedItem(frame: frame, url: url, html: html, enableScrolling: allowScrolling) @@ -665,7 +670,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: } if let peer = peer { - let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(x: 0.0, y: offset), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, safeInset: safeInset, transparent: !offset.isZero, rtl: rtl || previousItemHasRTL) + let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(x: 0.0, y: offset), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: .channel(peer), safeInset: safeInset, transparent: !offset.isZero, rtl: rtl || previousItemHasRTL) items.append(item) if offset.isZero { contentSize.height += 40.0 @@ -679,10 +684,10 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] - if let file = media[audioId] as? TelegramMediaFile { + if case let .file(file) = media[audioId] { let mediaIndex = mediaIndexCounter mediaIndexCounter += 1 - let item = InstantPageAudioItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: 48.0)), media: InstantPageMedia(index: mediaIndex, media: file, url: nil, caption: nil, credit: nil), webpage: webpage) + let item = InstantPageAudioItem(frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: boundingWidth, height: 48.0)), media: InstantPageMedia(index: mediaIndex, media: .file(file), url: nil, caption: nil, credit: nil), webpage: webpage) contentSize.height += item.frame.height items.append(item) @@ -765,7 +770,9 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: for (i, article) in articles.enumerated() { var cover: TelegramMediaImage? if let coverId = article.photoId { - cover = media[coverId] as? TelegramMediaImage + if case let .image(image) = media[coverId] { + cover = image + } } var styleStack = InstantPageTextStyleStack() @@ -820,7 +827,7 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation: var contentSize = CGSize(width: boundingWidth - safeInset * 2.0, height: 0.0) var items: [InstantPageItem] = [] - let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: -1, media: map, url: nil, caption: caption.text, credit: caption.credit), attributes: attributes, interactive: true, roundCorners: false, fit: false) + let mediaItem = InstantPageImageItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - filledSize.width) / 2.0), y: 0.0), size: filledSize), webPage: webpage, media: InstantPageMedia(index: -1, media: .geo(map), url: nil, caption: caption.text, credit: caption.credit), attributes: attributes, interactive: true, roundCorners: false, fit: false) items.append(mediaItem) contentSize.height += filledSize.height @@ -850,12 +857,12 @@ public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, userLoc var contentSize = CGSize(width: boundingWidth, height: 0.0) var items: [InstantPageItem] = [] - var media = instantPage.media + var media = instantPage.media.mapValues(EngineMedia.init) if let image = loadedContent.image, let id = image.id { - media[id] = image + media[id] = .image(image) } if let video = loadedContent.file, let id = video.id { - media[id] = video + media[id] = .file(video) } var mediaIndexCounter: Int = 0 diff --git a/submodules/InstantPageUI/Sources/InstantPageMedia.swift b/submodules/InstantPageUI/Sources/InstantPageMedia.swift index 757ba68a04..ff9b2bc3f5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMedia.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMedia.swift @@ -1,15 +1,14 @@ import Foundation -import Postbox import TelegramCore public struct InstantPageMedia: Equatable { public let index: Int - public let media: Media + public let media: EngineMedia public let url: InstantPageUrlItem? public let caption: RichText? public let credit: RichText? - public init(index: Int, media: Media, url: InstantPageUrlItem?, caption: RichText?, credit: RichText?) { + public init(index: Int, media: EngineMedia, url: InstantPageUrlItem?, caption: RichText?, credit: RichText?) { self.index = index self.media = media self.url = url @@ -18,6 +17,6 @@ public struct InstantPageMedia: Equatable { } public static func ==(lhs: InstantPageMedia, rhs: InstantPageMedia) -> Bool { - return lhs.index == rhs.index && lhs.media.isEqual(to: rhs.media) && lhs.url == rhs.url && lhs.caption == rhs.caption && lhs.credit == rhs.credit + return lhs.index == rhs.index && lhs.media == rhs.media && lhs.url == rhs.url && lhs.caption == rhs.caption && lhs.credit == rhs.credit } } diff --git a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift index ec14e033a1..7a29e48b2d 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -22,7 +21,11 @@ struct InstantPageMediaPlaylistItemId: SharedMediaPlaylistItemId { } private func extractFileMedia(_ item: InstantPageMedia) -> TelegramMediaFile? { - return item.media as? TelegramMediaFile + if case let .file(file) = item.media { + return file + } else { + return nil + } } final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { @@ -114,7 +117,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { } struct InstantPageMediaPlaylistId: SharedMediaPlaylistId { - let webpageId: MediaId + let webpageId: EngineMedia.Id func isEqual(to: SharedMediaPlaylistId) -> Bool { if let to = to as? InstantPageMediaPlaylistId { @@ -125,7 +128,7 @@ struct InstantPageMediaPlaylistId: SharedMediaPlaylistId { } struct InstantPagePlaylistLocation: Equatable, SharedMediaPlaylistLocation { - let webpageId: MediaId + let webpageId: EngineMedia.Id func isEqual(to: SharedMediaPlaylistLocation) -> Bool { guard let to = to as? InstantPagePlaylistLocation else { diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift index 5aa04bf6da..a5f7adf60a 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData @@ -14,12 +13,12 @@ public final class InstantPagePeerReferenceItem: InstantPageItem { public let separatesTiles: Bool = false public let medias: [InstantPageMedia] = [] - let initialPeer: Peer + let initialPeer: EnginePeer let safeInset: CGFloat let transparent: Bool let rtl: Bool - init(frame: CGRect, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool) { + init(frame: CGRect, initialPeer: EnginePeer, safeInset: CGFloat, transparent: Bool, rtl: Bool) { self.frame = frame self.initialPeer = initialPeer self.safeInset = safeInset diff --git a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift index ab4b3d8caa..8958c6aef3 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePeerReferenceNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import AsyncDisplayKit import Display @@ -64,13 +63,13 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { private let activityIndicator: ActivityIndicator private let checkNode: ASImageNode - var peer: Peer? + var peer: EnginePeer? private var peerDisposable: Disposable? private let joinDisposable = MetaDisposable() private var joinState: JoinState = .none - init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (EnginePeer) -> Void) { + init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, initialPeer: EnginePeer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.strings = strings self.nameDisplayOrder = nameDisplayOrder @@ -147,26 +146,26 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { let account = self.context.account let context = self.context - let signal = actualizedPeer(postbox: account.postbox, network: account.network, peer: initialPeer) - |> mapToSignal({ peer -> Signal in + let signal: Signal = actualizedPeer(postbox: account.postbox, network: account.network, peer: initialPeer._asPeer()) + |> mapToSignal({ peer -> Signal in if let peer = peer as? TelegramChannel, let username = peer.addressName, peer.accessHash == nil { - return .single(peer) |> then(context.engine.peers.resolvePeerByName(name: username) - |> mapToSignal({ updatedPeer -> Signal in + return .single(.channel(peer)) |> then(context.engine.peers.resolvePeerByName(name: username) + |> mapToSignal({ updatedPeer -> Signal in if let updatedPeer = updatedPeer { - return .single(updatedPeer._asPeer()) + return .single(updatedPeer) } else { - return .single(peer) + return .single(.channel(peer)) } })) } else { - return .single(peer) + return .single(EnginePeer(peer)) } }) self.peerDisposable = (signal |> deliverOnMainQueue).start(next: { [weak self] peer in if let strongSelf = self { strongSelf.peer = peer - if let peer = peer as? TelegramChannel { + if case let .channel(peer) = peer { var joinState = strongSelf.joinState if case .member = peer.participationStatus { switch joinState { @@ -210,7 +209,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { private func applyThemeAndStrings(themeUpdated: Bool) { if let peer = self.peer { let textColor = self.transparent ? UIColor.white : self.theme.panelPrimaryColor - self.nameNode.attributedText = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: textColor) + self.nameNode.attributedText = NSAttributedString(string: peer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: textColor) } let accentColor = self.transparent ? UIColor.white : self.theme.panelAccentColor self.joinNode.setAttributedTitle(NSAttributedString(string: self.strings.Channel_JoinChannel, font: Font.medium(17.0), textColor: accentColor), for: []) @@ -300,7 +299,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode { @objc func buttonPressed() { if let peer = self.peer { - self.openPeer(EnginePeer(peer)) + self.openPeer(peer) } } diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift index 91e0442785..3470e9d9c6 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift index 7f03324a4f..df36f4c416 100644 --- a/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPagePlayableVideoNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -29,7 +28,7 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler private var currentSize: CGSize? - private var fetchStatus: MediaResourceStatus? + private var fetchStatus: EngineMediaResource.FetchStatus? private var fetchedDisposable = MetaDisposable() private var statusDisposable = MetaDisposable() @@ -47,17 +46,19 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler self.openMedia = openMedia var imageReference: ImageMediaReference? - if let file = media.media as? TelegramMediaFile, let presentation = smallestImageRepresentation(file.previewRepresentations) { - let image = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [presentation], immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) + if case let .file(file) = media.media, let presentation = smallestImageRepresentation(file.previewRepresentations) { + let image = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: [presentation], immediateThumbnailData: file.immediateThumbnailData, reference: nil, partialReference: nil, flags: []) imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) } var streamVideo = false - if let file = media.media as? TelegramMediaFile { + var fileValue: TelegramMediaFile? + if case let .file(file) = media.media { streamVideo = isMediaStreamable(media: file) + fileValue = file } - self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), userLocation: userLocation, fileReference: .webPage(webPage: WebpageReference(webPage), media: media.media as! TelegramMediaFile), imageReference: imageReference, streamVideo: streamVideo ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor, storeAfterDownload: nil), priority: .embedded, autoplay: true) + self.videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: NativeVideoContent(id: .instantPage(webPage.webpageId, media.media.id!), userLocation: userLocation, fileReference: .webPage(webPage: WebpageReference(webPage), media: fileValue!), imageReference: imageReference, streamVideo: streamVideo ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, placeholderColor: theme.pageBackgroundColor, storeAfterDownload: nil), priority: .embedded, autoplay: true) self.videoNode.isUserInteractionEnabled = false self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6)) @@ -66,13 +67,13 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler self.addSubnode(self.videoNode) - if let file = media.media as? TelegramMediaFile { + if case let .file(file) = media.media { self.fetchedDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .video, reference: AnyMediaReference.webPage(webPage: WebpageReference(webPage), media: file).resourceReference(file.resource)).start()) self.statusDisposable.set((context.account.postbox.mediaBox.resourceStatus(file.resource) |> deliverOnMainQueue).start(next: { [weak self] status in displayLinkDispatcher.dispatch { if let strongSelf = self { - strongSelf.fetchStatus = status + strongSelf.fetchStatus = EngineMediaResource.FetchStatus(status) strongSelf.updateFetchStatus() } } diff --git a/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift b/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift index 64860688c0..e08a437250 100644 --- a/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift +++ b/submodules/InstantPageUI/Sources/InstantPageReferenceController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift index 35c7193402..a393f70ef0 100644 --- a/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageReferenceControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SafariServices import TelegramPresentationData @@ -197,9 +196,9 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie if self.contentNode == nil || self.contentNode?.frame.width != width { self.contentNode?.removeFromSupernode() - var media: [MediaId: Media] = [:] + var media: [EngineMedia.Id: EngineMedia] = [:] if case let .Loaded(content) = self.webPage.content, let instantPage = content.instantPage { - media = instantPage.media + media = instantPage.media.mapValues(EngineMedia.init) } let sideInset: CGFloat = 16.0 diff --git a/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift b/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift index 27dd83326a..cf9902a482 100644 --- a/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageScrollableNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import Display import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift b/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift index b269e96484..da94bff3d6 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSettingsNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift b/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift index 4c99765ada..0d463b3d90 100644 --- a/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageShapeItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift index ad1d996d50..d812d7e85e 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift index f0a902acf0..335f060cc2 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSlideshowItemNode.swift @@ -186,9 +186,9 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe private func makeNodeForItem(at index: Int) -> InstantPageSlideshowItemNode { let media = self.items[index] let contentNode: ASDisplayNode - if let _ = media.media as? TelegramMediaImage { + if case .image = media.media { contentNode = InstantPageImageNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia, activatePinchPreview: self.activatePinchPreview, pinchPreviewFinished: self.pinchPreviewFinished) - } else if let _ = media.media as? TelegramMediaFile { + } else if case .file = media.media { contentNode = ASDisplayNode() } else { contentNode = ASDisplayNode() diff --git a/submodules/InstantPageUI/Sources/InstantPageStoredState.swift b/submodules/InstantPageUI/Sources/InstantPageStoredState.swift index 9d2606e675..e9184c3243 100644 --- a/submodules/InstantPageUI/Sources/InstantPageStoredState.swift +++ b/submodules/InstantPageUI/Sources/InstantPageStoredState.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences @@ -58,7 +57,7 @@ public final class InstantPageStoredState: Codable { } public func instantPageStoredState(engine: TelegramEngine, webPage: TelegramMediaWebpage) -> Signal { - let key = ValueBoxKey(length: 8) + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: webPage.webpageId.id) return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.instantPageStoredState, id: key)) @@ -68,7 +67,7 @@ public func instantPageStoredState(engine: TelegramEngine, webPage: TelegramMedi } public func updateInstantPageStoredStateInteractively(engine: TelegramEngine, webPage: TelegramMediaWebpage, state: InstantPageStoredState?) -> Signal { - let key = ValueBoxKey(length: 8) + let key = EngineDataBuffer(length: 8) key.setInt64(0, value: webPage.webpageId.id) if let state = state { diff --git a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift index d8146a8b30..bc552dc995 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift index c94f059ab4..01895ba2ac 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTableItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTableItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import TelegramCore -import Postbox import Display import TelegramPresentationData import TelegramUIPreferences @@ -277,7 +276,7 @@ private func offestForVerticalAlignment(_ verticalAlignment: TableVerticalAlignm } } -func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: InstantPageTextStyleStack, theme: InstantPageTheme, bordered: Bool, striped: Bool, boundingWidth: CGFloat, horizontalInset: CGFloat, media: [MediaId: Media], webpage: TelegramMediaWebpage) -> InstantPageTableItem { +func layoutTableItem(rtl: Bool, rows: [InstantPageTableRow], styleStack: InstantPageTextStyleStack, theme: InstantPageTheme, bordered: Bool, striped: Bool, boundingWidth: CGFloat, horizontalInset: CGFloat, media: [EngineMedia.Id: EngineMedia], webpage: TelegramMediaWebpage) -> InstantPageTableItem { if rows.count == 0 { return InstantPageTableItem(frame: CGRect(), totalWidth: 0.0, horizontalInset: 0.0, borderWidth: 0.0, theme: theme, cells: [], rtl: rtl) } diff --git a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift index bdf7262ebf..6fa1ee6ec3 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTextItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTextItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import TelegramCore import Display -import Postbox import AsyncDisplayKit import TelegramPresentationData import TelegramUIPreferences @@ -12,9 +11,9 @@ import ContextUI public final class InstantPageUrlItem: Equatable { public let url: String - public let webpageId: MediaId? + public let webpageId: EngineMedia.Id? - public init(url: String, webpageId: MediaId?) { + public init(url: String, webpageId: EngineMedia.Id?) { self.url = url self.webpageId = webpageId } @@ -36,7 +35,7 @@ struct InstantPageTextStrikethroughItem { struct InstantPageTextImageItem { let frame: CGRect let range: NSRange - let id: MediaId + let id: EngineMedia.Id } struct InstantPageTextAnchorItem { @@ -649,7 +648,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt } } -func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [MediaId: Media] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0, opaqueBackground: Bool = false) -> (InstantPageTextItem?, [InstantPageItem], CGSize) { +func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFloat, horizontalInset: CGFloat = 0.0, alignment: NSTextAlignment = .natural, offset: CGPoint, media: [EngineMedia.Id: EngineMedia] = [:], webpage: TelegramMediaWebpage? = nil, minimizeWidth: Bool = false, maxNumberOfLines: Int = 0, opaqueBackground: Bool = false) -> (InstantPageTextItem?, [InstantPageItem], CGSize) { if string.length == 0 { return (nil, [], CGSize()) } @@ -771,7 +770,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo extraDescent = max(extraDescent, imageFrame.maxY - (workingLineOrigin.y + fontLineHeight + minSpacing)) } maxImageHeight = max(maxImageHeight, imageFrame.height) - lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: MediaId(namespace: Namespaces.Media.CloudFile, id: id))) + lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: id))) } } } @@ -876,8 +875,8 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo for line in textItem.lines { let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) for imageItem in line.imageItems { - if let image = media[imageItem.id] as? TelegramMediaFile { - let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: image, url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) + if case let .image(image) = media[imageItem.id] { + let item = InstantPageImageItem(frame: imageItem.frame.offsetBy(dx: lineFrame.minX + offset.x, dy: offset.y), webPage: webpage, media: InstantPageMedia(index: -1, media: .image(image), url: nil, caption: nil, credit: nil), interactive: false, roundCorners: false, fit: false) additionalItems.append(item) if item.frame.minY < topInset { diff --git a/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift b/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift index ec8ebdf9a8..b8863934f5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift +++ b/submodules/InstantPageUI/Sources/InstantPageWebEmbedItem.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import AsyncDisplayKit import TelegramPresentationData diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift index f15d215be8..658898edae 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteRequestItem.swift @@ -389,7 +389,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode { let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context) avatarListWrapperNode.contentNode.clipsToBounds = true avatarListNode.backgroundColor = .clear - avatarListNode.peer = peer + avatarListNode.peer = EnginePeer(peer) avatarListNode.firstFullSizeOnly = true avatarListNode.offsetLocation = true avatarListNode.customCenterTapAction = { [weak self] in @@ -405,7 +405,7 @@ public class ItemListInviteRequestItemNode: ListViewItemNode, ItemListItemNode { avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode) avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode) - avatarListNode.update(size: targetRect.size, peer: peer, customNode: nil, additionalEntry: .single(nil), isExpanded: true, transition: .immediate) + avatarListNode.update(size: targetRect.size, peer: EnginePeer(peer), customNode: nil, additionalEntry: .single(nil), isExpanded: true, transition: .immediate) strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode) strongSelf.avatarListWrapperNode = avatarListWrapperNode diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 45111f5b29..7902eedd0c 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -878,7 +878,7 @@ public final class ListMessageFileItemNode: ListMessageNode { if statusUpdated && item.displayFileInfo { if let file = selectedMedia as? TelegramMediaFile { - updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) + updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) |> mapToSignal { value -> Signal in if case .Fetching = value.fetchStatus, !item.isDownloadList { return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) @@ -905,10 +905,10 @@ public final class ListMessageFileItemNode: ListMessageNode { } } if isVoice { - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: EngineMessage(message), isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList) } } else if let image = selectedMedia as? TelegramMediaImage { - updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList) + updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: EngineMessage(message), isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList) |> mapToSignal { value -> Signal in if case .Fetching = value.fetchStatus, !item.isDownloadList { return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) diff --git a/submodules/LottieMeshSwift/BUILD b/submodules/LottieMeshSwift/BUILD deleted file mode 100644 index ef088b0ef7..0000000000 --- a/submodules/LottieMeshSwift/BUILD +++ /dev/null @@ -1,156 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -load( - "@build_bazel_rules_apple//apple:resources.bzl", - "apple_resource_bundle", - "apple_resource_group", -) -load("//build-system/bazel-utils:plist_fragment.bzl", - "plist_fragment", -) - -filegroup( - name = "LottieMeshSwiftMetalResources", - srcs = glob([ - "Resources/**/*.metal", - ]), - visibility = ["//visibility:public"], -) - -plist_fragment( - name = "LottieMeshSwiftBundleInfoPlist", - extension = "plist", - template = - """ - CFBundleIdentifier - org.telegram.LottieMeshSwift - CFBundleDevelopmentRegion - en - CFBundleName - LottieMeshSwift - """ -) - -apple_resource_bundle( - name = "LottieMeshSwiftBundle", - infoplists = [ - ":LottieMeshSwiftBundleInfoPlist", - ], - resources = [ - ":LottieMeshSwiftMetalResources", - ], -) - -config_setting( - name = "debug_build", - values = { - "compilation_mode": "dbg", - }, -) - -optimization_flags = select({ - ":debug_build": [ - "-O2", - ], - "//conditions:default": [], -}) - -swift_optimization_flags = select({ - ":debug_build": [ - #"-O", - ], - "//conditions:default": [], -}) - -swift_library( - name = "LottieMeshSwift", - module_name = "LottieMeshSwift", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ] + swift_optimization_flags, - data = [ - ":LottieMeshSwiftBundle", - ], - deps = [ - ":LottieMeshBinding", - "//submodules/Postbox:Postbox", - "//submodules/ManagedFile:ManagedFile", - ], - visibility = [ - "//visibility:public", - ], -) - -objc_library( - name = "LottieMeshBinding", - enable_modules = True, - module_name = "LottieMeshBinding", - srcs = glob([ - "LottieMeshBinding/Sources/**/*.m", - "LottieMeshBinding/Sources/**/*.mm", - "LottieMeshBinding/Sources/**/*.h", - ]), - copts = optimization_flags, - hdrs = glob([ - "LottieMeshBinding/PublicHeaders/**/*.h", - ]), - includes = [ - "LottieMeshBinding/PublicHeaders", - ], - deps = [ - ":LottieMesh", - ], - sdk_frameworks = [ - "Foundation", - ], - visibility = [ - "//visibility:public", - ], -) - -cc_library( - name = "LottieMesh", - srcs = glob([ - "LottieMesh/Sources/**/*.cpp", - "LottieMesh/Sources/**/*.h", - "LottieMesh/Sources/**/*.hpp", - ]), - copts = [ - "-Isubmodules/LottieMeshSwift/libtess2/Include", - ] + optimization_flags, - hdrs = glob([ - "LottieMesh/PublicHeaders/**/*.h", - "LottieMesh/PublicHeaders/**/*.hpp", - ]), - includes = [ - "LottieMesh/PublicHeaders", - ], - deps = [ - ":libtess2", - ], - visibility = [ - "//visibility:public", - ], -) - -cc_library( - name = "libtess2", - srcs = glob([ - "libtess2/Sources/**/*.c", - "libtess2/Sources/**/*.h", - "libtess2/Include/**/*.h", - ]), - copts = [ - "-Isubmodules/LottieMeshSwift/libtess2/Include", - ] + optimization_flags, - hdrs = glob([ - "libtess2/Include/**/*.h", - ]), - deps = [ - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/LottieMesh.h b/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/LottieMesh.h deleted file mode 100644 index 2a2bb42154..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/LottieMesh.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef LottieMesh_h -#define LottieMesh_h - -#include -#include - -#include "Point.h" - -namespace MeshGenerator { - -struct Path { - std::vector points; -}; - -struct Fill { - enum class Rule { - EvenOdd, - NonZero - }; - - Rule rule = Rule::EvenOdd; - - explicit Fill(Rule rule_) : - rule(rule_) { - } -}; - -struct Stroke { - enum class LineJoin { - Miter, - Round, - Bevel - }; - - enum class LineCap { - Butt, - Round, - Square - }; - - float lineWidth = 0.0f; - LineJoin lineJoin = LineJoin::Round; - LineCap lineCap = LineCap::Round; - float miterLimit = 10.0f; - - explicit Stroke(float lineWidth_, LineJoin lineJoin_, LineCap lineCap_, float miterLimit_) : - lineWidth(lineWidth_), lineJoin(lineJoin_), lineCap(lineCap_), miterLimit(miterLimit_) { - } -}; - -struct Mesh { - std::vector vertices; - std::vector triangles; -}; - -std::unique_ptr generateMesh(std::vector const &paths, std::unique_ptr fill, std::unique_ptr stroke); - -} - -#endif /* LottieMesh_h */ diff --git a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/Point.h b/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/Point.h deleted file mode 100644 index 5c412f6252..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/PublicHeaders/LottieMesh/Point.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef Point_h -#define Point_h - -#include -#include - -namespace MeshGenerator { - -struct Point { - float x = 0.0f; - float y = 0.0f; - - Point(float x_, float y_) : - x(x_), y(y_) { - } - - Point() : Point(0.0f, 0.0f) { - } - - bool isEqual(Point const &other, float epsilon = 0.0001f) const { - return std::abs(x - other.x) <= epsilon && std::abs(y - other.y) <= epsilon; - } - - float distance(Point const &other) const { - float dx = x - other.x; - float dy = y - other.y; - return sqrtf(dx * dx + dy * dy); - } - - bool operator< (Point const &other) const { - if (x < other.x) { - return true; - } - if (x > other.x) { - return false; - } - return y < other.y; - } - - bool operator== (Point const &other) const { - return isEqual(other); - } -}; - -} - -#endif diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/LineSegment.h b/submodules/LottieMeshSwift/LottieMesh/Sources/LineSegment.h deleted file mode 100644 index 890351c2bc..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/LineSegment.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "Vec2.h" -#include - -namespace crushedpixel { - -template -struct LineSegment { - LineSegment(const Vec2 &a, const Vec2 &b) : - a(a), b(b) {} - - Vec2 a, b; - - /** - * @return A copy of the line segment, offset by the given vector. - */ - LineSegment operator+(const Vec2 &toAdd) const { - return {Vec2Maths::add(a, toAdd), Vec2Maths::add(b, toAdd)}; - } - - /** - * @return A copy of the line segment, offset by the given vector. - */ - LineSegment operator-(const Vec2 &toRemove) const { - return {Vec2Maths::subtract(a, toRemove), Vec2Maths::subtract(b, toRemove)}; - } - - /** - * @return The line segment's normal vector. - */ - Vec2 normal() const { - auto dir = direction(); - - // return the direction vector - // rotated by 90 degrees counter-clockwise - return {-dir.y, dir.x}; - } - - /** - * @return The line segment's direction vector. - */ - Vec2 direction(bool normalized = true) const { - auto vec = Vec2Maths::subtract(b, a); - - return normalized - ? Vec2Maths::normalized(vec) - : vec; - } - - static Vec2 intersection(const LineSegment &a, const LineSegment &b, bool infiniteLines, bool &success) { - success = true; - - // calculate un-normalized direction vectors - auto r = a.direction(false); - auto s = b.direction(false); - - auto originDist = Vec2Maths::subtract(b.a, a.a); - - auto uNumerator = Vec2Maths::cross(originDist, r); - auto denominator = Vec2Maths::cross(r, s); - - if (std::abs(denominator) < 0.0001f) { - // The lines are parallel - success = false; - return Vec2(); - } - - // solve the intersection positions - auto u = uNumerator / denominator; - auto t = Vec2Maths::cross(originDist, s) / denominator; - - if (!infiniteLines && (t < 0 || t > 1 || u < 0 || u > 1)) { - // the intersection lies outside of the line segments - success = false; - return Vec2(); - } - - // calculate the intersection point - // a.a + r * t; - return Vec2Maths::add(a.a, Vec2Maths::multiply(r, t)); - } -}; - - -} // namespace crushedpixel diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/LottieMesh.cpp b/submodules/LottieMeshSwift/LottieMesh/Sources/LottieMesh.cpp deleted file mode 100644 index 06a75612be..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/LottieMesh.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include -#include "Triangulation.h" - -#include "tesselator.h" -#include "Polyline2D.h" - -namespace MeshGenerator { - -std::unique_ptr generateMesh(std::vector const &paths, std::unique_ptr fill, std::unique_ptr stroke) { - if (stroke) { - std::unique_ptr mesh = std::make_unique(); - - for (const auto &path : paths) { - crushedpixel::Polyline2D::JointStyle jointStyle = crushedpixel::Polyline2D::JointStyle::ROUND; - crushedpixel::Polyline2D::EndCapStyle endCapStyle = crushedpixel::Polyline2D::EndCapStyle::SQUARE; - switch (stroke->lineJoin) { - case Stroke::LineJoin::Miter: - jointStyle = crushedpixel::Polyline2D::JointStyle::MITER; - break; - case Stroke::LineJoin::Round: - jointStyle = crushedpixel::Polyline2D::JointStyle::ROUND; - break; - case Stroke::LineJoin::Bevel: - jointStyle = crushedpixel::Polyline2D::JointStyle::BEVEL; - break; - default: { - break; - } - } - switch (stroke->lineCap) { - case Stroke::LineCap::Round: { - endCapStyle = crushedpixel::Polyline2D::EndCapStyle::ROUND; - break; - } - case Stroke::LineCap::Square: { - endCapStyle = crushedpixel::Polyline2D::EndCapStyle::SQUARE; - break; - } - case Stroke::LineCap::Butt: { - endCapStyle = crushedpixel::Polyline2D::EndCapStyle::BUTT; - break; - } - default: { - break; - } - } - - auto vertices = crushedpixel::Polyline2D::create(path.points, stroke->lineWidth, jointStyle, endCapStyle); - for (const auto &vertex : vertices) { - mesh->triangles.push_back((int)mesh->vertices.size()); - mesh->vertices.push_back(vertex); - } - } - - assert(mesh->triangles.size() % 3 == 0); - return mesh; - } else if (fill) { - TESStesselator *tessellator = tessNewTess(NULL); - tessSetOption(tessellator, TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, 1); - for (const auto &path : paths) { - tessAddContour(tessellator, 2, path.points.data(), sizeof(Point), (int)path.points.size()); - } - - switch (fill->rule) { - case Fill::Rule::EvenOdd: { - tessTesselate(tessellator, TESS_WINDING_ODD, TESS_POLYGONS, 3, 2, NULL); - break; - } - default: { - tessTesselate(tessellator, TESS_WINDING_NONZERO, TESS_POLYGONS, 3, 2, NULL); - break; - } - } - - int vertexCount = tessGetVertexCount(tessellator); - const TESSreal *vertices = tessGetVertices(tessellator); - int indexCount = tessGetElementCount(tessellator) * 3; - const TESSindex *indices = tessGetElements(tessellator); - - std::unique_ptr mesh = std::make_unique(); - for (int i = 0; i < vertexCount; i++) { - mesh->vertices.push_back(Point(vertices[i * 2 + 0], vertices[i * 2 + 1])); - } - for (int i = 0; i < indexCount; i++) { - mesh->triangles.push_back(indices[i]); - } - return mesh; - } else { - return nullptr; - } -} - -} diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Polyline2D.h b/submodules/LottieMeshSwift/LottieMesh/Sources/Polyline2D.h deleted file mode 100644 index 7c95177d1d..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Polyline2D.h +++ /dev/null @@ -1,446 +0,0 @@ -#pragma once - -#include "LineSegment.h" -#include -#include -#include - -namespace crushedpixel { - -class Polyline2D { -public: - enum class JointStyle { - /** - * Corners are drawn with sharp joints. - * If the joint's outer angle is too large, - * the joint is drawn as beveled instead, - * to avoid the miter extending too far out. - */ - MITER, - /** - * Corners are flattened. - */ - BEVEL, - /** - * Corners are rounded off. - */ - ROUND - }; - - enum class EndCapStyle { - /** - * Path ends are drawn flat, - * and don't exceed the actual end point. - */ - BUTT, // lol - /** - * Path ends are drawn flat, - * but extended beyond the end point - * by half the line thickness. - */ - SQUARE, - /** - * Path ends are rounded off. - */ - ROUND, - /** - * Path ends are connected according to the JointStyle. - * When using this EndCapStyle, don't specify the common start/end point twice, - * as Polyline2D connects the first and last input point itself. - */ - JOINT - }; - - /** - * Creates a vector of vertices describing a solid path through the input points. - * @param points The points of the path. - * @param thickness The path's thickness. - * @param jointStyle The path's joint style. - * @param endCapStyle The path's end cap style. - * @param allowOverlap Whether to allow overlapping vertices. - * This yields better results when dealing with paths - * whose points have a distance smaller than the thickness, - * but may introduce overlapping vertices, - * which is undesirable when rendering transparent paths. - * @return The vertices describing the path. - * @tparam Vec2 The vector type to use for the vertices. - * Must have public non-const float fields "x" and "y". - * Must have a two-args constructor taking x and y values. - * See crushedpixel::Vec2 for a type that satisfies these requirements. - * @tparam InputCollection The collection type of the input points. - * Must contain elements of type Vec2. - * Must expose size() and operator[] functions. - */ - template - static std::vector create(const InputCollection &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - std::vector vertices; - create(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap); - return vertices; - } - - template - static std::vector create(const std::vector &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - std::vector vertices; - create>(vertices, points, thickness, jointStyle, endCapStyle, allowOverlap); - return vertices; - } - - template - static size_t create(std::vector &vertices, const InputCollection &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - auto numVerticesBefore = vertices.size(); - - create(std::back_inserter(vertices), points, thickness, - jointStyle, endCapStyle, allowOverlap); - - return vertices.size() - numVerticesBefore; - } - - template - static OutputIterator create(OutputIterator vertices, const InputCollection &points, float thickness, - JointStyle jointStyle = JointStyle::MITER, - EndCapStyle endCapStyle = EndCapStyle::BUTT, - bool allowOverlap = false) { - // operate on half the thickness to make our lives easier - thickness /= 2; - - // create poly segments from the points - std::vector> segments; - for (size_t i = 0; i + 1 < points.size(); i++) { - auto &point1 = points[i]; - auto &point2 = points[i + 1]; - - // to avoid division-by-zero errors, - // only create a line segment for non-identical points - if (!Vec2Maths::equal(point1, point2)) { - segments.emplace_back(LineSegment(point1, point2), thickness); - } - } - - if (endCapStyle == EndCapStyle::JOINT) { - // create a connecting segment from the last to the first point - - auto &point1 = points[points.size() - 1]; - auto &point2 = points[0]; - - // to avoid division-by-zero errors, - // only create a line segment for non-identical points - if (!Vec2Maths::equal(point1, point2)) { - segments.emplace_back(LineSegment(point1, point2), thickness); - } - } - - if (segments.empty()) { - // handle the case of insufficient input points - return vertices; - } - - Vec2 nextStart1{0, 0}; - Vec2 nextStart2{0, 0}; - Vec2 start1{0, 0}; - Vec2 start2{0, 0}; - Vec2 end1{0, 0}; - Vec2 end2{0, 0}; - - // calculate the path's global start and end points - auto &firstSegment = segments[0]; - auto &lastSegment = segments[segments.size() - 1]; - - auto pathStart1 = firstSegment.edge1.a; - auto pathStart2 = firstSegment.edge2.a; - auto pathEnd1 = lastSegment.edge1.b; - auto pathEnd2 = lastSegment.edge2.b; - - // handle different end cap styles - if (endCapStyle == EndCapStyle::SQUARE) { - // extend the start/end points by half the thickness - pathStart1 = Vec2Maths::subtract(pathStart1, Vec2Maths::multiply(firstSegment.edge1.direction(), thickness)); - pathStart2 = Vec2Maths::subtract(pathStart2, Vec2Maths::multiply(firstSegment.edge2.direction(), thickness)); - pathEnd1 = Vec2Maths::add(pathEnd1, Vec2Maths::multiply(lastSegment.edge1.direction(), thickness)); - pathEnd2 = Vec2Maths::add(pathEnd2, Vec2Maths::multiply(lastSegment.edge2.direction(), thickness)); - - } else if (endCapStyle == EndCapStyle::ROUND) { - // draw half circle end caps - createTriangleFan(vertices, firstSegment.center.a, firstSegment.center.a, - firstSegment.edge1.a, firstSegment.edge2.a, false); - createTriangleFan(vertices, lastSegment.center.b, lastSegment.center.b, - lastSegment.edge1.b, lastSegment.edge2.b, true); - - } else if (endCapStyle == EndCapStyle::JOINT) { - // join the last (connecting) segment and the first segment - createJoint(vertices, lastSegment, firstSegment, jointStyle, - pathEnd1, pathEnd2, pathStart1, pathStart2, allowOverlap); - } - - // generate mesh data for path segments - for (size_t i = 0; i < segments.size(); i++) { - auto &segment = segments[i]; - - // calculate start - if (i == 0) { - // this is the first segment - start1 = pathStart1; - start2 = pathStart2; - } - - if (i + 1 == segments.size()) { - // this is the last segment - end1 = pathEnd1; - end2 = pathEnd2; - - } else { - createJoint(vertices, segment, segments[i + 1], jointStyle, - end1, end2, nextStart1, nextStart2, allowOverlap); - } - - // emit vertices - *vertices++ = start1; - *vertices++ = start2; - *vertices++ = end1; - - *vertices++ = end1; - *vertices++ = start2; - *vertices++ = end2; - - start1 = nextStart1; - start2 = nextStart2; - } - - return vertices; - } - -private: - static constexpr float pi = 3.14159265358979323846f; - - /** - * The threshold for mitered joints. - * If the joint's angle is smaller than this angle, - * the joint will be drawn beveled instead. - */ - static constexpr float miterMinAngle = 0.349066; // ~20 degrees - - /** - * The minimum angle of a round joint's triangles. - */ - static constexpr float roundMinAngle = 0.174533; // ~10 degrees - - template - struct PolySegment { - PolySegment(const LineSegment ¢er, float thickness) : - center(center), - // calculate the segment's outer edges by offsetting - // the central line by the normal vector - // multiplied with the thickness - - // center + center.normal() * thickness - edge1(center + Vec2Maths::multiply(center.normal(), thickness)), - edge2(center - Vec2Maths::multiply(center.normal(), thickness)) {} - - LineSegment center, edge1, edge2; - }; - - template - static OutputIterator createJoint(OutputIterator vertices, - const PolySegment &segment1, const PolySegment &segment2, - JointStyle jointStyle, Vec2 &end1, Vec2 &end2, - Vec2 &nextStart1, Vec2 &nextStart2, - bool allowOverlap) { - // calculate the angle between the two line segments - auto dir1 = segment1.center.direction(); - auto dir2 = segment2.center.direction(); - - auto angle = Vec2Maths::angle(dir1, dir2); - - // wrap the angle around the 180° mark if it exceeds 90° - // for minimum angle detection - auto wrappedAngle = angle; - if (wrappedAngle > pi / 2) { - wrappedAngle = pi - wrappedAngle; - } - - if (jointStyle == JointStyle::MITER && wrappedAngle < miterMinAngle) { - // the minimum angle for mitered joints wasn't exceeded. - // to avoid the intersection point being extremely far out, - // thus producing an enormous joint like a rasta on 4/20, - // we render the joint beveled instead. - jointStyle = JointStyle::BEVEL; - } - - if (jointStyle == JointStyle::MITER) { - // calculate each edge's intersection point - // with the next segment's central line - bool sec1Success = true; - bool sec2Success = true; - auto sec1 = LineSegment::intersection(segment1.edge1, segment2.edge1, true, sec1Success); - auto sec2 = LineSegment::intersection(segment1.edge2, segment2.edge2, true, sec2Success); - - end1 = sec1Success ? sec1 : segment1.edge1.b; - end2 = sec2Success ? sec2 : segment1.edge2.b; - - nextStart1 = end1; - nextStart2 = end2; - - } else { - // joint style is either BEVEL or ROUND - - // find out which are the inner edges for this joint - auto x1 = dir1.x; - auto x2 = dir2.x; - auto y1 = dir1.y; - auto y2 = dir2.y; - - auto clockwise = x1 * y2 - x2 * y1 < 0; - - const LineSegment *inner1, *inner2, *outer1, *outer2; - - // as the normal vector is rotated counter-clockwise, - // the first edge lies to the left - // from the central line's perspective, - // and the second one to the right. - if (clockwise) { - outer1 = &segment1.edge1; - outer2 = &segment2.edge1; - inner1 = &segment1.edge2; - inner2 = &segment2.edge2; - } else { - outer1 = &segment1.edge2; - outer2 = &segment2.edge2; - inner1 = &segment1.edge1; - inner2 = &segment2.edge1; - } - - // calculate the intersection point of the inner edges - bool innerSecOptSuccess = true; - auto innerSecOpt = LineSegment::intersection(*inner1, *inner2, allowOverlap, innerSecOptSuccess); - - auto innerSec = innerSecOptSuccess - ? innerSecOpt - // for parallel lines, simply connect them directly - : inner1->b; - - // if there's no inner intersection, flip - // the next start position for near-180° turns - Vec2 innerStart; - if (innerSecOptSuccess) { - innerStart = innerSec; - } else if (angle > pi / 2) { - innerStart = outer1->b; - } else { - innerStart = inner1->b; - } - - if (clockwise) { - end1 = outer1->b; - end2 = innerSec; - - nextStart1 = outer2->a; - nextStart2 = innerStart; - - } else { - end1 = innerSec; - end2 = outer1->b; - - nextStart1 = innerStart; - nextStart2 = outer2->a; - } - - // connect the intersection points according to the joint style - - if (jointStyle == JointStyle::BEVEL) { - // simply connect the intersection points - *vertices++ = outer1->b; - *vertices++ = outer2->a; - *vertices++ = innerSec; - - } else if (jointStyle == JointStyle::ROUND) { - // draw a circle between the ends of the outer edges, - // centered at the actual point - // with half the line thickness as the radius - createTriangleFan(vertices, innerSec, segment1.center.b, outer1->b, outer2->a, clockwise); - } else { - assert(false); - } - } - - return vertices; - } - - /** - * Creates a partial circle between two points. - * The points must be equally far away from the origin. - * @param vertices The vector to add vertices to. - * @param connectTo The position to connect the triangles to. - * @param origin The circle's origin. - * @param start The circle's starting point. - * @param end The circle's ending point. - * @param clockwise Whether the circle's rotation is clockwise. - */ - template - static OutputIterator createTriangleFan(OutputIterator vertices, Vec2 connectTo, Vec2 origin, - Vec2 start, Vec2 end, bool clockwise) { - - auto point1 = Vec2Maths::subtract(start, origin); - auto point2 = Vec2Maths::subtract(end, origin); - - // calculate the angle between the two points - auto angle1 = atan2(point1.y, point1.x); - auto angle2 = atan2(point2.y, point2.x); - - // ensure the outer angle is calculated - if (clockwise) { - if (angle2 > angle1) { - angle2 = angle2 - 2 * pi; - } - } else { - if (angle1 > angle2) { - angle1 = angle1 - 2 * pi; - } - } - - auto jointAngle = angle2 - angle1; - - // calculate the amount of triangles to use for the joint - auto numTriangles = std::max(1, (int) std::floor(std::abs(jointAngle) / roundMinAngle)); - - // calculate the angle of each triangle - auto triAngle = jointAngle / numTriangles; - - Vec2 startPoint = start; - Vec2 endPoint; - for (int t = 0; t < numTriangles; t++) { - if (t + 1 == numTriangles) { - // it's the last triangle - ensure it perfectly - // connects to the next line - endPoint = end; - } else { - auto rot = (t + 1) * triAngle; - - // rotate the original point around the origin - endPoint.x = std::cos(rot) * point1.x - std::sin(rot) * point1.y; - endPoint.y = std::sin(rot) * point1.x + std::cos(rot) * point1.y; - - // re-add the rotation origin to the target point - endPoint = Vec2Maths::add(endPoint, origin); - } - - // emit the triangle - *vertices++ = startPoint; - *vertices++ = endPoint; - *vertices++ = connectTo; - - startPoint = endPoint; - } - - return vertices; - } -}; - -} // namespace crushedpixel diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.cpp b/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.cpp deleted file mode 100644 index b293bc1b1b..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "Triangulation.h" - -#include -#include - -#include "earcut.hpp" - -namespace MeshGenerator { - -std::vector triangulatePolygon(std::vector const &points, std::vector &indices, std::vector> const &holeIndices) { - // The index type. Defaults to uint32_t, but you can also pass uint16_t if you know that your - // data won't have more than 65536 vertices. - using N = uint32_t; - - // Create array - using EarPoint = std::array; - std::vector> polygon; - - std::map facePointMapping; - int nextFacePointIndex = 0; - - std::vector facePoints; - for (auto index : indices) { - facePointMapping[nextFacePointIndex] = index; - nextFacePointIndex++; - - facePoints.push_back({ points[index].x, points[index].y }); - } - polygon.push_back(std::move(facePoints)); - - for (const auto &list : holeIndices) { - std::vector holePoints; - for (auto index : list) { - facePointMapping[nextFacePointIndex] = index; - nextFacePointIndex++; - - holePoints.push_back({ points[index].x, points[index].y }); - } - polygon.push_back(std::move(holePoints)); - } - - std::vector triangleIndices = mapbox::earcut(polygon); - - std::vector mappedIndices; - for (auto index : triangleIndices) { - mappedIndices.push_back(facePointMapping[index]); - } - return mappedIndices; -} - -} diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.h b/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.h deleted file mode 100644 index 4dfe9746a8..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Triangulation.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef Triangulation_h -#define Triangulation_h - -#include - -#include - -namespace MeshGenerator { - -std::vector triangulatePolygon(std::vector const &points, std::vector &indices, std::vector> const &holeIndices); - -} - -#endif /* Triangulation_h */ diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/Vec2.h b/submodules/LottieMeshSwift/LottieMesh/Sources/Vec2.h deleted file mode 100644 index 08b9ad1e30..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/Vec2.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include - -namespace crushedpixel { - -/** - * A two-dimensional float vector. - * It exposes the x and y fields - * as required by the Polyline2D functions. - */ -struct Vec2 { - Vec2() : - Vec2(0, 0) {} - - Vec2(float x, float y) : - x(x), y(y) {} - - virtual ~Vec2() = default; - - float x, y; -}; - -namespace Vec2Maths { - -template -static bool equal(const Vec2 &a, const Vec2 &b) { - return a.x == b.x && a.y == b.y; -} - -template -static Vec2 multiply(const Vec2 &a, const Vec2 &b) { - return {a.x * b.x, a.y * b.y}; -} - -template -static Vec2 multiply(const Vec2 &vec, float factor) { - return {vec.x * factor, vec.y * factor}; -} - -template -static Vec2 divide(const Vec2 &vec, float factor) { - return {vec.x / factor, vec.y / factor}; -} - -template -static Vec2 add(const Vec2 &a, const Vec2 &b) { - return {a.x + b.x, a.y + b.y}; -} - -template -static Vec2 subtract(const Vec2 &a, const Vec2 &b) { - return {a.x - b.x, a.y - b.y}; -} - -template -static float magnitude(const Vec2 &vec) { - return std::sqrt(vec.x * vec.x + vec.y * vec.y); -} - -template -static Vec2 withLength(const Vec2 &vec, float len) { - auto mag = magnitude(vec); - auto factor = mag / len; - return divide(vec, factor); -} - -template -static Vec2 normalized(const Vec2 &vec) { - return withLength(vec, 1); -} - -/** - * Calculates the dot product of two vectors. - */ -template -static float dot(const Vec2 &a, const Vec2 &b) { - return a.x * b.x + a.y * b.y; -} - -/** - * Calculates the cross product of two vectors. - */ -template -static float cross(const Vec2 &a, const Vec2 &b) { - return a.x * b.y - a.y * b.x; -} - -/** - * Calculates the angle between two vectors. - */ -template -static float angle(const Vec2 &a, const Vec2 &b) { - return std::acos(dot(a, b) / (magnitude(a) * magnitude(b))); -} - -} // namespace Vec2Maths - -} \ No newline at end of file diff --git a/submodules/LottieMeshSwift/LottieMesh/Sources/earcut.hpp b/submodules/LottieMeshSwift/LottieMesh/Sources/earcut.hpp deleted file mode 100644 index e7d43088bf..0000000000 --- a/submodules/LottieMeshSwift/LottieMesh/Sources/earcut.hpp +++ /dev/null @@ -1,823 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace mapbox { - -namespace util { - -template struct nth { - inline static typename std::tuple_element::type - get(const T& t) { return std::get(t); }; -}; - -} - -namespace detail { - -template -class Earcut { -public: - std::vector indices; - std::size_t vertices = 0; - - template - void operator()(const Polygon& points); - -private: - struct Node { - Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {} - Node(const Node&) = delete; - Node& operator=(const Node&) = delete; - Node(Node&&) = delete; - Node& operator=(Node&&) = delete; - - const N i; - const double x; - const double y; - - // previous and next vertice nodes in a polygon ring - Node* prev = nullptr; - Node* next = nullptr; - - // z-order curve value - int32_t z = 0; - - // previous and next nodes in z-order - Node* prevZ = nullptr; - Node* nextZ = nullptr; - - // indicates whether this is a steiner point - bool steiner = false; - }; - - template Node* linkedList(const Ring& points, const bool clockwise); - Node* filterPoints(Node* start, Node* end = nullptr); - void earcutLinked(Node* ear, int pass = 0); - bool isEar(Node* ear); - bool isEarHashed(Node* ear); - Node* cureLocalIntersections(Node* start); - void splitEarcut(Node* start); - template Node* eliminateHoles(const Polygon& points, Node* outerNode); - Node* eliminateHole(Node* hole, Node* outerNode); - Node* findHoleBridge(Node* hole, Node* outerNode); - bool sectorContainsSector(const Node* m, const Node* p); - void indexCurve(Node* start); - Node* sortLinked(Node* list); - int32_t zOrder(const double x_, const double y_); - Node* getLeftmost(Node* start); - bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const; - bool isValidDiagonal(Node* a, Node* b); - double area(const Node* p, const Node* q, const Node* r) const; - bool equals(const Node* p1, const Node* p2); - bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2); - bool onSegment(const Node* p, const Node* q, const Node* r); - int sign(double val); - bool intersectsPolygon(const Node* a, const Node* b); - bool locallyInside(const Node* a, const Node* b); - bool middleInside(const Node* a, const Node* b); - Node* splitPolygon(Node* a, Node* b); - template Node* insertNode(std::size_t i, const Point& p, Node* last); - void removeNode(Node* p); - - bool hashing; - double minX, maxX; - double minY, maxY; - double inv_size = 0; - - template > - class ObjectPool { - public: - ObjectPool() { } - ObjectPool(std::size_t blockSize_) { - reset(blockSize_); - } - ~ObjectPool() { - clear(); - } - template - T* construct(Args&&... args) { - if (currentIndex >= blockSize) { - currentBlock = alloc_traits::allocate(alloc, blockSize); - allocations.emplace_back(currentBlock); - currentIndex = 0; - } - T* object = ¤tBlock[currentIndex++]; - alloc_traits::construct(alloc, object, std::forward(args)...); - return object; - } - void reset(std::size_t newBlockSize) { - for (auto allocation : allocations) { - alloc_traits::deallocate(alloc, allocation, blockSize); - } - allocations.clear(); - blockSize = std::max(1, newBlockSize); - currentBlock = nullptr; - currentIndex = blockSize; - } - void clear() { reset(blockSize); } - private: - T* currentBlock = nullptr; - std::size_t currentIndex = 1; - std::size_t blockSize = 1; - std::vector allocations; - Alloc alloc; - typedef typename std::allocator_traits alloc_traits; - }; - ObjectPool nodes; -}; - -template template -void Earcut::operator()(const Polygon& points) { - // reset - indices.clear(); - vertices = 0; - - if (points.empty()) return; - - double x; - double y; - int threshold = 80; - std::size_t len = 0; - - for (size_t i = 0; threshold >= 0 && i < points.size(); i++) { - threshold -= static_cast(points[i].size()); - len += points[i].size(); - } - - //estimate size of nodes and indices - nodes.reset(len * 3 / 2); - indices.reserve(len + points[0].size()); - - Node* outerNode = linkedList(points[0], true); - if (!outerNode || outerNode->prev == outerNode->next) return; - - if (points.size() > 1) outerNode = eliminateHoles(points, outerNode); - - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - hashing = threshold < 0; - if (hashing) { - Node* p = outerNode->next; - minX = maxX = outerNode->x; - minY = maxY = outerNode->y; - do { - x = p->x; - y = p->y; - minX = std::min(minX, x); - minY = std::min(minY, y); - maxX = std::max(maxX, x); - maxY = std::max(maxY, y); - p = p->next; - } while (p != outerNode); - - // minX, minY and size are later used to transform coords into integers for z-order calculation - inv_size = std::max(maxX - minX, maxY - minY); - inv_size = inv_size != .0 ? (1. / inv_size) : .0; - } - - earcutLinked(outerNode); - - nodes.clear(); -} - -// create a circular doubly linked list from polygon points in the specified winding order -template template -typename Earcut::Node* -Earcut::linkedList(const Ring& points, const bool clockwise) { - using Point = typename Ring::value_type; - double sum = 0; - const std::size_t len = points.size(); - std::size_t i, j; - Node* last = nullptr; - - // calculate original winding order of a polygon ring - for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) { - const auto& p1 = points[i]; - const auto& p2 = points[j]; - const double p20 = util::nth<0, Point>::get(p2); - const double p10 = util::nth<0, Point>::get(p1); - const double p11 = util::nth<1, Point>::get(p1); - const double p21 = util::nth<1, Point>::get(p2); - sum += (p20 - p10) * (p11 + p21); - } - - // link points into circular doubly-linked list in the specified winding order - if (clockwise == (sum > 0)) { - for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last); - } else { - for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last); - } - - if (last && equals(last, last->next)) { - removeNode(last); - last = last->next; - } - - vertices += len; - - return last; -} - -// eliminate colinear or duplicate points -template -typename Earcut::Node* -Earcut::filterPoints(Node* start, Node* end) { - if (!end) end = start; - - Node* p = start; - bool again; - do { - again = false; - - if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) { - removeNode(p); - p = end = p->prev; - - if (p == p->next) break; - again = true; - - } else { - p = p->next; - } - } while (again || p != end); - - return end; -} - -// main ear slicing loop which triangulates a polygon (given as a linked list) -template -void Earcut::earcutLinked(Node* ear, int pass) { - if (!ear) return; - - // interlink polygon nodes in z-order - if (!pass && hashing) indexCurve(ear); - - Node* stop = ear; - Node* prev; - Node* next; - - int iterations = 0; - - // iterate through ears, slicing them one by one - while (ear->prev != ear->next) { - iterations++; - prev = ear->prev; - next = ear->next; - - if (hashing ? isEarHashed(ear) : isEar(ear)) { - // cut off the triangle - indices.emplace_back(prev->i); - indices.emplace_back(ear->i); - indices.emplace_back(next->i); - - removeNode(ear); - - // skipping the next vertice leads to less sliver triangles - ear = next->next; - stop = next->next; - - continue; - } - - ear = next; - - // if we looped through the whole remaining polygon and can't find any more ears - if (ear == stop) { - // try filtering points and slicing again - if (!pass) earcutLinked(filterPoints(ear), 1); - - // if this didn't work, try curing all small self-intersections locally - else if (pass == 1) { - ear = cureLocalIntersections(filterPoints(ear)); - earcutLinked(ear, 2); - - // as a last resort, try splitting the remaining polygon into two - } else if (pass == 2) splitEarcut(ear); - - break; - } - } -} - -// check whether a polygon node forms a valid ear with adjacent nodes -template -bool Earcut::isEar(Node* ear) { - const Node* a = ear->prev; - const Node* b = ear; - const Node* c = ear->next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - // now make sure we don't have other points inside the potential ear - Node* p = ear->next->next; - - while (p != ear->prev) { - if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && - area(p->prev, p, p->next) >= 0) return false; - p = p->next; - } - - return true; -} - -template -bool Earcut::isEarHashed(Node* ear) { - const Node* a = ear->prev; - const Node* b = ear; - const Node* c = ear->next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - // triangle bbox; min & max are calculated like this for speed - const double minTX = std::min(a->x, std::min(b->x, c->x)); - const double minTY = std::min(a->y, std::min(b->y, c->y)); - const double maxTX = std::max(a->x, std::max(b->x, c->x)); - const double maxTY = std::max(a->y, std::max(b->y, c->y)); - - // z-order range for the current triangle bbox; - const int32_t minZ = zOrder(minTX, minTY); - const int32_t maxZ = zOrder(maxTX, maxTY); - - // first look for points inside the triangle in increasing z-order - Node* p = ear->nextZ; - - while (p && p->z <= maxZ) { - if (p != ear->prev && p != ear->next && - pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && - area(p->prev, p, p->next) >= 0) return false; - p = p->nextZ; - } - - // then look for points in decreasing z-order - p = ear->prevZ; - - while (p && p->z >= minZ) { - if (p != ear->prev && p != ear->next && - pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && - area(p->prev, p, p->next) >= 0) return false; - p = p->prevZ; - } - - return true; -} - -// go through all polygon nodes and cure small local self-intersections -template -typename Earcut::Node* -Earcut::cureLocalIntersections(Node* start) { - Node* p = start; - do { - Node* a = p->prev; - Node* b = p->next->next; - - // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) - if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) { - indices.emplace_back(a->i); - indices.emplace_back(p->i); - indices.emplace_back(b->i); - - // remove two nodes involved - removeNode(p); - removeNode(p->next); - - p = start = b; - } - p = p->next; - } while (p != start); - - return filterPoints(p); -} - -// try splitting polygon into two and triangulate them independently -template -void Earcut::splitEarcut(Node* start) { - // look for a valid diagonal that divides the polygon into two - Node* a = start; - do { - Node* b = a->next->next; - while (b != a->prev) { - if (a->i != b->i && isValidDiagonal(a, b)) { - // split the polygon in two by the diagonal - Node* c = splitPolygon(a, b); - - // filter colinear points around the cuts - a = filterPoints(a, a->next); - c = filterPoints(c, c->next); - - // run earcut on each half - earcutLinked(a); - earcutLinked(c); - return; - } - b = b->next; - } - a = a->next; - } while (a != start); -} - -// link every hole into the outer loop, producing a single-ring polygon without holes -template template -typename Earcut::Node* -Earcut::eliminateHoles(const Polygon& points, Node* outerNode) { - const size_t len = points.size(); - - std::vector queue; - for (size_t i = 1; i < len; i++) { - Node* list = linkedList(points[i], false); - if (list) { - if (list == list->next) list->steiner = true; - queue.push_back(getLeftmost(list)); - } - } - std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) { - return a->x < b->x; - }); - - // process holes from left to right - for (size_t i = 0; i < queue.size(); i++) { - outerNode = eliminateHole(queue[i], outerNode); - outerNode = filterPoints(outerNode, outerNode->next); - } - - return outerNode; -} - -// find a bridge between vertices that connects hole with an outer ring and and link it -template -typename Earcut::Node* -Earcut::eliminateHole(Node* hole, Node* outerNode) { - Node* bridge = findHoleBridge(hole, outerNode); - if (!bridge) { - return outerNode; - } - - Node* bridgeReverse = splitPolygon(bridge, hole); - - // filter collinear points around the cuts - Node* filteredBridge = filterPoints(bridge, bridge->next); - filterPoints(bridgeReverse, bridgeReverse->next); - - // Check if input node was removed by the filtering - return outerNode == bridge ? filteredBridge : outerNode; -} - -// David Eberly's algorithm for finding a bridge between hole and outer polygon -template -typename Earcut::Node* -Earcut::findHoleBridge(Node* hole, Node* outerNode) { - Node* p = outerNode; - double hx = hole->x; - double hy = hole->y; - double qx = -std::numeric_limits::infinity(); - Node* m = nullptr; - - // find a segment intersected by a ray from the hole's leftmost Vertex to the left; - // segment's endpoint with lesser x will be potential connection Vertex - do { - if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) { - double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y); - if (x <= hx && x > qx) { - qx = x; - if (x == hx) { - if (hy == p->y) return p; - if (hy == p->next->y) return p->next; - } - m = p->x < p->next->x ? p : p->next; - } - } - p = p->next; - } while (p != outerNode); - - if (!m) return 0; - - if (hx == qx) return m; // hole touches outer segment; pick leftmost endpoint - - // look for points inside the triangle of hole Vertex, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the Vertex of the minimum angle with the ray as connection Vertex - - const Node* stop = m; - double tanMin = std::numeric_limits::infinity(); - double tanCur = 0; - - p = m; - double mx = m->x; - double my = m->y; - - do { - if (hx >= p->x && p->x >= mx && hx != p->x && - pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) { - - tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential - - if (locallyInside(p, hole) && - (tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) { - m = p; - tanMin = tanCur; - } - } - - p = p->next; - } while (p != stop); - - return m; -} - -// whether sector in vertex m contains sector in vertex p in the same coordinates -template -bool Earcut::sectorContainsSector(const Node* m, const Node* p) { - return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0; -} - -// interlink polygon nodes in z-order -template -void Earcut::indexCurve(Node* start) { - assert(start); - Node* p = start; - - do { - p->z = p->z ? p->z : zOrder(p->x, p->y); - p->prevZ = p->prev; - p->nextZ = p->next; - p = p->next; - } while (p != start); - - p->prevZ->nextZ = nullptr; - p->prevZ = nullptr; - - sortLinked(p); -} - -// Simon Tatham's linked list merge sort algorithm -// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -template -typename Earcut::Node* -Earcut::sortLinked(Node* list) { - assert(list); - Node* p; - Node* q; - Node* e; - Node* tail; - int i, numMerges, pSize, qSize; - int inSize = 1; - - for (;;) { - p = list; - list = nullptr; - tail = nullptr; - numMerges = 0; - - while (p) { - numMerges++; - q = p; - pSize = 0; - for (i = 0; i < inSize; i++) { - pSize++; - q = q->nextZ; - if (!q) break; - } - - qSize = inSize; - - while (pSize > 0 || (qSize > 0 && q)) { - - if (pSize == 0) { - e = q; - q = q->nextZ; - qSize--; - } else if (qSize == 0 || !q) { - e = p; - p = p->nextZ; - pSize--; - } else if (p->z <= q->z) { - e = p; - p = p->nextZ; - pSize--; - } else { - e = q; - q = q->nextZ; - qSize--; - } - - if (tail) tail->nextZ = e; - else list = e; - - e->prevZ = tail; - tail = e; - } - - p = q; - } - - tail->nextZ = nullptr; - - if (numMerges <= 1) return list; - - inSize *= 2; - } -} - -// z-order of a Vertex given coords and size of the data bounding box -template -int32_t Earcut::zOrder(const double x_, const double y_) { - // coords are transformed into non-negative 15-bit integer range - int32_t x = static_cast(32767.0 * (x_ - minX) * inv_size); - int32_t y = static_cast(32767.0 * (y_ - minY) * inv_size); - - x = (x | (x << 8)) & 0x00FF00FF; - x = (x | (x << 4)) & 0x0F0F0F0F; - x = (x | (x << 2)) & 0x33333333; - x = (x | (x << 1)) & 0x55555555; - - y = (y | (y << 8)) & 0x00FF00FF; - y = (y | (y << 4)) & 0x0F0F0F0F; - y = (y | (y << 2)) & 0x33333333; - y = (y | (y << 1)) & 0x55555555; - - return x | (y << 1); -} - -// find the leftmost node of a polygon ring -template -typename Earcut::Node* -Earcut::getLeftmost(Node* start) { - Node* p = start; - Node* leftmost = start; - do { - if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y)) - leftmost = p; - p = p->next; - } while (p != start); - - return leftmost; -} - -// check if a point lies within a convex triangle -template -bool Earcut::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const { - return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && - (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && - (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0; -} - -// check if a diagonal between two polygon nodes is valid (lies in polygon interior) -template -bool Earcut::isValidDiagonal(Node* a, Node* b) { - return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges - ((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible - (area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors - (equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case -} - -// signed area of a triangle -template -double Earcut::area(const Node* p, const Node* q, const Node* r) const { - return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); -} - -// check if two points are equal -template -bool Earcut::equals(const Node* p1, const Node* p2) { - return p1->x == p2->x && p1->y == p2->y; -} - -// check if two segments intersect -template -bool Earcut::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) { - int o1 = sign(area(p1, q1, p2)); - int o2 = sign(area(p1, q1, q2)); - int o3 = sign(area(p2, q2, p1)); - int o4 = sign(area(p2, q2, q1)); - - if (o1 != o2 && o3 != o4) return true; // general case - - if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 - - return false; -} - -// for collinear points p, q, r, check if point q lies on segment pr -template -bool Earcut::onSegment(const Node* p, const Node* q, const Node* r) { - return q->x <= std::max(p->x, r->x) && - q->x >= std::min(p->x, r->x) && - q->y <= std::max(p->y, r->y) && - q->y >= std::min(p->y, r->y); -} - -template -int Earcut::sign(double val) { - return (0.0 < val) - (val < 0.0); -} - -// check if a polygon diagonal intersects any polygon segments -template -bool Earcut::intersectsPolygon(const Node* a, const Node* b) { - const Node* p = a; - do { - if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i && - intersects(p, p->next, a, b)) return true; - p = p->next; - } while (p != a); - - return false; -} - -// check if a polygon diagonal is locally inside the polygon -template -bool Earcut::locallyInside(const Node* a, const Node* b) { - return area(a->prev, a, a->next) < 0 ? - area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 : - area(a, b, a->prev) < 0 || area(a, a->next, b) < 0; -} - -// check if the middle Vertex of a polygon diagonal is inside the polygon -template -bool Earcut::middleInside(const Node* a, const Node* b) { - const Node* p = a; - bool inside = false; - double px = (a->x + b->x) / 2; - double py = (a->y + b->y) / 2; - do { - if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y && - (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x)) - inside = !inside; - p = p->next; - } while (p != a); - - return inside; -} - -// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits -// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a -// single ring -template -typename Earcut::Node* -Earcut::splitPolygon(Node* a, Node* b) { - Node* a2 = nodes.construct(a->i, a->x, a->y); - Node* b2 = nodes.construct(b->i, b->x, b->y); - Node* an = a->next; - Node* bp = b->prev; - - a->next = b; - b->prev = a; - - a2->next = an; - an->prev = a2; - - b2->next = a2; - a2->prev = b2; - - bp->next = b2; - b2->prev = bp; - - return b2; -} - -// create a node and util::optionally link it with previous one (in a circular doubly linked list) -template template -typename Earcut::Node* -Earcut::insertNode(std::size_t i, const Point& pt, Node* last) { - Node* p = nodes.construct(static_cast(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt)); - - if (!last) { - p->prev = p; - p->next = p; - - } else { - assert(last); - p->next = last->next; - p->prev = last; - last->next->prev = p; - last->next = p; - } - return p; -} - -template -void Earcut::removeNode(Node* p) { - p->next->prev = p->prev; - p->prev->next = p->next; - - if (p->prevZ) p->prevZ->nextZ = p->nextZ; - if (p->nextZ) p->nextZ->prevZ = p->prevZ; -} -} - -template -std::vector earcut(const Polygon& poly) { - mapbox::detail::Earcut earcut; - earcut(poly); - return std::move(earcut.indices); -} -} diff --git a/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h b/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h deleted file mode 100644 index 18d6073a22..0000000000 --- a/submodules/LottieMeshSwift/LottieMeshBinding/PublicHeaders/LottieMeshBinding/LottieMeshBinding.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef LOTTIE_MESH_BINDING_H -#define LOTTIE_MESH_BINDING_H - -#import -#import - -typedef NS_CLOSED_ENUM(NSInteger, LottieMeshFillRule) { - LottieMeshFillRuleEvenOdd, - LottieMeshFillRuleNonZero -}; - -@interface LottieMeshFill : NSObject - -@property (nonatomic, readonly) LottieMeshFillRule fillRule; - -- (instancetype _Nonnull)initWithFillRule:(LottieMeshFillRule)fillRule; - -@end - -@interface LottieMeshStroke : NSObject - -@property (nonatomic, readonly) CGFloat lineWidth; -@property (nonatomic, readonly) CGLineJoin lineJoin; -@property (nonatomic, readonly) CGLineCap lineCap; -@property (nonatomic, readonly) CGFloat miterLimit; - -- (instancetype _Nonnull)initWithLineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit; - -@end - -@interface LottieMeshData : NSObject - -- (NSInteger)vertexCount; -- (void)getVertexAt:(NSInteger)index x:(float * _Nullable)x y:(float * _Nullable)y; - -- (NSInteger)triangleCount; -- (void * _Nonnull)getTriangles; - -+ (LottieMeshData * _Nullable)generateWithPath:(UIBezierPath * _Nonnull)path fill:(LottieMeshFill * _Nullable)fill stroke:(LottieMeshStroke * _Nullable)stroke; - -@end - -#endif diff --git a/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm b/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm deleted file mode 100644 index 9e61e1ea33..0000000000 --- a/submodules/LottieMeshSwift/LottieMeshBinding/Sources/LottieMeshBinding.mm +++ /dev/null @@ -1,315 +0,0 @@ -#import - -#import - -namespace { - -MeshGenerator::Point bezierQuadraticPointAt(MeshGenerator::Point const &p0, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2, float t) { - float x = powf((1.0 - t), 2.0) * p0.x + 2.0 * (1.0 - t) * t * p1.x + powf(t, 2.0) * p2.x; - float y = powf((1.0 - t), 2.0) * p0.y + 2.0 * (1.0 - t) * t * p1.y + powf(t, 2.0) * p2.y; - return MeshGenerator::Point(x, y); -} - -float approximateBezierQuadraticLength(MeshGenerator::Point const &p0, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2) { - float length = 0.0f; - float t = 0.1; - MeshGenerator::Point last = p0; - while (t < 1.01) { - auto point = bezierQuadraticPointAt(p0, p1, p2, t); - length += last.distance(point); - last = point; - t += 0.1; - } - return length; -} - -void tesselateBezier(MeshGenerator::Path &path, MeshGenerator::Point const &p1, MeshGenerator::Point const &p2, MeshGenerator::Point const &p3, MeshGenerator::Point const &p4, int level) { - const float tessTol = 0.25f / 0.1f; - - float x1 = p1.x; - float y1 = p1.y; - float x2 = p2.x; - float y2 = p2.y; - float x3 = p3.x; - float y3 = p3.y; - float x4 = p4.x; - float y4 = p4.y; - - float x12, y12, x23, y23, x34, y34, x123, y123, x234, y234, x1234, y1234; - float dx, dy, d2, d3; - - if (level > 10) { - return; - } - - x12 = (x1 + x2) * 0.5f; - y12 = (y1 + y2) * 0.5f; - x23 = (x2 + x3) * 0.5f; - y23 = (y2 + y3) * 0.5f; - x34 = (x3 + x4) * 0.5f; - y34 = (y3 + y4) * 0.5f; - x123 = (x12 + x23) * 0.5f; - y123 = (y12 + y23) * 0.5f; - - dx = x4 - x1; - dy = y4 - y1; - d2 = std::abs(((x2 - x4) * dy - (y2 - y4) * dx)); - d3 = std::abs(((x3 - x4) * dy - (y3 - y4) * dx)); - - if ((d2 + d3) * (d2 + d3) < tessTol * (dx * dx + dy * dy)) { - path.points.emplace_back(x4, y4); - return; - } - - x234 = (x23+x34) * 0.5f; - y234 = (y23+y34) * 0.5f; - x1234 = (x123 + x234) * 0.5f; - y1234 = (y123 + y234) * 0.5f; - - tesselateBezier(path, MeshGenerator::Point(x1, y1), MeshGenerator::Point(x12, y12), MeshGenerator::Point(x123, y123), MeshGenerator::Point(x1234, y1234), level + 1); - tesselateBezier(path, MeshGenerator::Point(x1234, y1234), MeshGenerator::Point(x234, y234), MeshGenerator::Point(x34, y34), MeshGenerator::Point(x4, y4), level + 1); -} - -} - -@interface LottieMeshData () { - std::unique_ptr _mesh; -} - -- (instancetype _Nonnull)initWithMesh:(std::unique_ptr &&)mesh; - -@end - -@implementation LottieMeshData - -- (instancetype _Nonnull)initWithMesh:(std::unique_ptr &&)mesh { - self = [super init]; - if (self != nil) { - _mesh = std::move(mesh); - } - return self; -} - -- (NSInteger)vertexCount { - return (NSInteger)_mesh->vertices.size(); -} - -- (void)getVertexAt:(NSInteger)index x:(float * _Nullable)x y:(float * _Nullable)y { - MeshGenerator::Point const &point = _mesh->vertices[index]; - if (x) { - *x = point.x; - } - if (y) { - *y = point.y; - } -} - -- (NSInteger)triangleCount { - return (NSInteger)(_mesh->triangles.size() / 3); -} - -- (void * _Nonnull)getTriangles { - return _mesh->triangles.data(); -} - -/*- (void)getTriangleAt:(NSInteger)index v0:(NSInteger * _Nullable)v0 v1:(NSInteger * _Nullable)v1 v2:(NSInteger * _Nullable)v2 { - if (v0) { - *v0 = (NSInteger)_mesh->triangles[index * 3 + 0]; - } - if (v1) { - *v1 = (NSInteger)_mesh->triangles[index * 3 + 1]; - } - if (v2) { - *v2 = (NSInteger)_mesh->triangles[index * 3 + 2]; - } -}*/ - -+ (LottieMeshData * _Nullable)generateWithPath:(UIBezierPath * _Nonnull)path fill: (LottieMeshFill * _Nullable)fill stroke:(LottieMeshStroke * _Nullable)stroke { - float scale = 1.0f; - float flatness = 1.0; - __block MeshGenerator::Point startingPoint(0.0f, 0.0f); - __block bool hasStartingPoint = false; - __block std::vector paths; - paths.push_back(MeshGenerator::Path()); - - CGPathApplyWithBlock(path.CGPath, ^(const CGPathElement * _Nonnull element) { - switch (element->type) { - case kCGPathElementMoveToPoint: { - if (!paths[paths.size() - 1].points.empty()) { - if (!paths[paths.size() - 1].points[0].isEqual(paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1])) { - paths[paths.size() - 1].points.push_back(paths[paths.size() - 1].points[0]); - } - paths.push_back(MeshGenerator::Path()); - } - - startingPoint = MeshGenerator::Point((float)(element->points[0].x) * scale, (float)(element->points[0].y) * scale); - hasStartingPoint = true; - break; - } - case kCGPathElementAddLineToPoint: { - bool canAddPoints = false; - if (paths[paths.size() - 1].points.empty()) { - if (hasStartingPoint) { - paths[paths.size() - 1].points.push_back(startingPoint); - canAddPoints = true; - } - } else { - canAddPoints = true; - } - if (canAddPoints) { - paths[paths.size() - 1].points.push_back(MeshGenerator::Point((float)(element->points[0].x) * scale, (float)(element->points[0].y) * scale)); - } - break; - } - case kCGPathElementAddQuadCurveToPoint: { - bool canAddPoints = false; - if (paths[paths.size() - 1].points.empty()) { - if (hasStartingPoint) { - paths[paths.size() - 1].points.push_back(startingPoint); - canAddPoints = true; - } - } else { - canAddPoints = true; - } - if (canAddPoints) { - float t = 0.001f; - - MeshGenerator::Point p0 = paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1]; - MeshGenerator::Point p1(element->points[0].x * scale, element->points[0].y * scale); - MeshGenerator::Point p2(element->points[1].x * scale, element->points[1].y * scale); - - float step = 10.0f * flatness / approximateBezierQuadraticLength(p0, p1, p2); - while (t < 1.0f) { - auto point = bezierQuadraticPointAt(p0, p1, p2, t); - paths[paths.size() - 1].points.push_back(point); - t += step; - } - paths[paths.size() - 1].points.push_back(p2); - } - break; - } - case kCGPathElementAddCurveToPoint: { - bool canAddPoints = false; - if (paths[paths.size() - 1].points.empty()) { - if (hasStartingPoint) { - paths[paths.size() - 1].points.push_back(startingPoint); - canAddPoints = true; - } - } else { - canAddPoints = true; - } - if (canAddPoints) { - float t = 0.001f; - - MeshGenerator::Point p0 = paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1]; - MeshGenerator::Point p1(element->points[0].x * scale, element->points[0].y * scale); - MeshGenerator::Point p2(element->points[1].x * scale, element->points[1].y * scale); - MeshGenerator::Point p3(element->points[2].x * scale, element->points[2].y * scale); - - tesselateBezier(paths[paths.size() - 1], p0, p1, p2, p3, 0); - } - break; - } - case kCGPathElementCloseSubpath: { - if (!paths[paths.size() - 1].points.empty()) { - if (!paths[paths.size() - 1].points[0].isEqual(paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1])) { - paths[paths.size() - 1].points.push_back(paths[paths.size() - 1].points[0]); - } - - hasStartingPoint = true; - startingPoint = paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1]; - paths.push_back(MeshGenerator::Path()); - } - } - default: { - break; - } - } - }); - - if (!paths[paths.size() - 1].points.empty()) { - if (stroke == nil && !paths[paths.size() - 1].points[0].isEqual(paths[paths.size() - 1].points[paths[paths.size() - 1].points.size() - 1])) { - paths[paths.size() - 1].points.push_back(paths[paths.size() - 1].points[0]); - } - } else { - paths.pop_back(); - } - - std::unique_ptr mappedFill; - if (fill) { - mappedFill = std::make_unique(fill.fillRule == LottieMeshFillRuleEvenOdd ? MeshGenerator::Fill::Rule::EvenOdd : MeshGenerator::Fill::Rule::NonZero); - } - - std::unique_ptr mappedStroke; - if (stroke) { - MeshGenerator::Stroke::LineJoin lineJoin; - switch (stroke.lineJoin) { - case kCGLineJoinRound: - lineJoin = MeshGenerator::Stroke::LineJoin::Round; - break; - case kCGLineJoinBevel: - lineJoin = MeshGenerator::Stroke::LineJoin::Bevel; - break; - case kCGLineJoinMiter: - lineJoin = MeshGenerator::Stroke::LineJoin::Miter; - break; - default: - lineJoin = MeshGenerator::Stroke::LineJoin::Round; - break; - } - - MeshGenerator::Stroke::LineCap lineCap; - switch (stroke.lineCap) { - case kCGLineCapRound: - lineCap = MeshGenerator::Stroke::LineCap::Round; - break; - case kCGLineCapButt: - lineCap = MeshGenerator::Stroke::LineCap::Butt; - break; - case kCGLineCapSquare: - lineCap = MeshGenerator::Stroke::LineCap::Square; - break; - default: - lineCap = MeshGenerator::Stroke::LineCap::Round; - break; - } - - mappedStroke = std::make_unique((float)stroke.lineWidth, lineJoin, lineCap, (float)stroke.miterLimit); - } - - std::unique_ptr resultMesh = MeshGenerator::generateMesh(paths, std::move(mappedFill), std::move(mappedStroke)); - if (resultMesh) { - return [[LottieMeshData alloc] initWithMesh:std::move(resultMesh)]; - } else { - return nil; - } -} - -@end - -@implementation LottieMeshFill - -- (instancetype _Nonnull)initWithFillRule:(LottieMeshFillRule)fillRule { - self = [super init]; - if (self != nil) { - _fillRule = fillRule; - } - return self; -} - -@end - -@implementation LottieMeshStroke - -- (instancetype _Nonnull)initWithLineWidth:(CGFloat)lineWidth lineJoin:(CGLineJoin)lineJoin lineCap:(CGLineCap)lineCap miterLimit:(CGFloat)miterLimit { - self = [super init]; - if (self != nil) { - _lineWidth = lineWidth; - _lineJoin = lineJoin; - _lineCap = lineCap; - _miterLimit = miterLimit; - } - return self; -} - -@end diff --git a/submodules/LottieMeshSwift/Resources/Shapes.metal b/submodules/LottieMeshSwift/Resources/Shapes.metal deleted file mode 100644 index d042bf25f8..0000000000 --- a/submodules/LottieMeshSwift/Resources/Shapes.metal +++ /dev/null @@ -1,82 +0,0 @@ -#include -using namespace metal; - -typedef struct { - packed_float2 position; -} Vertex; - -typedef struct { - packed_float2 offset; -} Offset; - -typedef struct { - float4 position[[position]]; - float2 localPosition[[center_no_perspective]]; -} Varyings; - -float2 screenSpaceToRelative(float2 point, float2 viewSize) { - float2 inverseViewSize = 1 / viewSize; - float clipX = (2.0f * point.x * inverseViewSize.x) - 2.0f; - float clipY = (2.0f * -point.y * inverseViewSize.y) + 2.0f; - - return float2(clipX, clipY); -} - -vertex Varyings vertexPassthrough( - constant Vertex *verticies[[buffer(0)]], - constant float2 &offset[[buffer(1)]], - unsigned int vid[[vertex_id]], - constant float4x4 &transformMatrix[[buffer(2)]], - constant int &indexOffset[[buffer(3)]] -) { - Varyings out; - constant Vertex &v = verticies[vid + indexOffset]; - float2 viewSize(512.0f, 512.0f); - float4 transformedVertex = transformMatrix * float4(v.position, 0.0, 1.0); - out.position = float4(screenSpaceToRelative(float2(transformedVertex.x, transformedVertex.y) + offset, viewSize), 0.0, 1.0); - out.localPosition = float2(v.position); - - return out; -} - -fragment half4 fragmentPassthrough( - Varyings in[[stage_in]], - constant float4 &color[[buffer(1)]] -) { - float4 out = color; - - return half4(out); -} - -template -half4 mixGradientColors(float dist, constant float4 *colors, constant float *steps) { - float4 color = colors[0]; - for (int i = 1; i < N; i++) { - color = mix(color, colors[i], smoothstep(steps[i - 1], steps[i], dist)); - } - - return half4(color); -} - -#define radialGradientFunc(N) fragment half4 fragmentRadialGradient##N( \ - Varyings in[[stage_in]], \ - constant float2 &start[[buffer(1)]], \ - constant float2 &end[[buffer(2)]], \ - constant float4 *colors[[buffer(3)]], \ - constant float *steps[[buffer(4)]] \ -) { \ - float centerDistance = distance(in.localPosition, start); \ - float endDistance = distance(start, end); \ - float dist = min(1.0, centerDistance / endDistance); \ - return mixGradientColors(dist, colors, steps); \ -} - -radialGradientFunc(2) -radialGradientFunc(3) -radialGradientFunc(4) -radialGradientFunc(5) -radialGradientFunc(6) -radialGradientFunc(7) -radialGradientFunc(8) -radialGradientFunc(9) -radialGradientFunc(10) diff --git a/submodules/LottieMeshSwift/Sources/Buffer.swift b/submodules/LottieMeshSwift/Sources/Buffer.swift deleted file mode 100644 index 3601d4f89a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Buffer.swift +++ /dev/null @@ -1,122 +0,0 @@ -import Foundation -import Postbox -import ManagedFile - -private let emptyMemory = malloc(1)! - -public class MeshMemoryBuffer { - public internal(set) var data: Data - public internal(set) var length: Int - - public init(data: Data) { - self.data = data - self.length = data.count - } - - public func makeData() -> Data { - if self.data.count == self.length { - return self.data - } else { - return self.data.subdata(in: 0 ..< self.length) - } - } -} - -extension WriteBuffer { - func writeInt32(_ value: Int32) { - var value = value - self.write(&value, length: 4) - } - - func writeFloat(_ value: Float) { - var value: Float32 = value - self.write(&value, length: 4) - } -} - -public final class MeshWriteBuffer { - let file: ManagedFile - private(set) var offset: Int = 0 - - public init(file: ManagedFile) { - self.file = file - } - - public func write(_ data: UnsafeRawPointer, length: Int) { - let _ = self.file.write(data, count: length) - self.offset += length - } - - public func writeInt8(_ value: Int8) { - var value = value - self.write(&value, length: 1) - } - - public func writeInt32(_ value: Int32) { - var value = value - self.write(&value, length: 4) - } - - public func writeFloat(_ value: Float) { - var value: Float32 = value - self.write(&value, length: 4) - } - - public func write(_ data: Data) { - data.withUnsafeBytes { bytes in - self.write(bytes.baseAddress!, length: bytes.count) - } - } - - func write(_ data: DataRange) { - data.data.withUnsafeBytes { bytes in - self.write(bytes.baseAddress!.advanced(by: data.range.lowerBound), length: data.count) - } - } - - public func seek(offset: Int) { - let _ = self.file.seek(position: Int64(offset)) - self.offset = offset - } -} - -public final class MeshReadBuffer: MeshMemoryBuffer { - public var offset = 0 - - override public init(data: Data) { - super.init(data: data) - } - - public func read(_ data: UnsafeMutableRawPointer, length: Int) { - self.data.copyBytes(to: data.assumingMemoryBound(to: UInt8.self), from: self.offset ..< (self.offset + length)) - self.offset += length - } - - func readDataRange(count: Int) -> DataRange { - let result = DataRange(data: self.data, range: self.offset ..< (self.offset + count)) - self.offset += count - return result - } - - public func readInt8() -> Int8 { - var result: Int8 = 0 - self.read(&result, length: 1) - return result - } - - public func readInt32() -> Int32 { - var result: Int32 = 0 - self.read(&result, length: 4) - return result - } - - public func readFloat() -> Float { - var result: Float32 = 0 - self.read(&result, length: 4) - return result - } - - public func skip(_ length: Int) { - self.offset += length - } -} diff --git a/submodules/LottieMeshSwift/Sources/CapturedGeometry.swift b/submodules/LottieMeshSwift/Sources/CapturedGeometry.swift deleted file mode 100644 index 6f85f794cf..0000000000 --- a/submodules/LottieMeshSwift/Sources/CapturedGeometry.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Foundation -import UIKit - -final class CapturedGeometryNode { - final class DisplayItem { - enum Display { - enum Style { - enum GradientType { - case linear - case radial - } - - case color(color: UIColor, alpha: CGFloat) - case gradient(colors: [UIColor], positions: [CGFloat], start: CGPoint, end: CGPoint, type: GradientType) - } - - struct Fill { - var style: Style - var fillRule: CGPathFillRule - } - - struct Stroke { - var style: Style - var lineWidth: CGFloat - var lineCap: CGLineCap - var lineJoin: CGLineJoin - var miterLimit: CGFloat - } - - case fill(Fill) - case stroke(Stroke) - } - - let path: CGPath - let display: Display - - init(path: CGPath, display: Display) { - self.path = path - self.display = display - } - } - - var transform: CATransform3D - let alpha: CGFloat - let isHidden: Bool - let displayItem: DisplayItem? - let subnodes: [CapturedGeometryNode] - - init( - transform: CATransform3D, - alpha: CGFloat, - isHidden: Bool, - displayItem: DisplayItem?, - subnodes: [CapturedGeometryNode] - ) { - self.transform = transform - self.alpha = alpha - self.isHidden = isHidden - self.displayItem = displayItem - self.subnodes = subnodes - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyCompositionLayer.swift deleted file mode 100644 index 5728e6742c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyCompositionLayer.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Foundation -import QuartzCore - -/** - The base class for a child layer of CompositionContainer - */ -class MyCompositionLayer { - var bounds: CGRect = CGRect() - - let transformNode: LayerTransformNode - - //let contentsLayer: CALayer = CALayer() - - let maskLayer: MyMaskContainerLayer? - - let matteType: MatteType? - - var matteLayer: MyCompositionLayer? { - didSet { - //NOTE - /*if let matte = matteLayer { - if let type = matteType, type == .invert { - - mask = InvertedMatteLayer(inputMatte: matte) - } else { - mask = matte - } - } else { - mask = nil - }*/ - } - } - - let inFrame: CGFloat - let outFrame: CGFloat - let startFrame: CGFloat - let timeStretch: CGFloat - - init(layer: LayerModel, size: CGSize) { - self.transformNode = LayerTransformNode(transform: layer.transform) - if let masks = layer.masks { - maskLayer = MyMaskContainerLayer(masks: masks) - } else { - maskLayer = nil - } - self.matteType = layer.matte - self.inFrame = layer.inFrame.cgFloat - self.outFrame = layer.outFrame.cgFloat - self.timeStretch = layer.timeStretch.cgFloat - self.startFrame = layer.startTime.cgFloat - - //NOTE - //self.anchorPoint = .zero - - //NOTE - /*contentsLayer.anchorPoint = .zero - contentsLayer.bounds = CGRect(origin: .zero, size: size) - contentsLayer.actions = [ - "opacity" : NSNull(), - "transform" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "sublayerTransform" : NSNull(), - "hidden" : NSNull() - ] - addSublayer(contentsLayer) - - if let maskLayer = maskLayer { - contentsLayer.mask = maskLayer - }*/ - } - - private(set) var isHidden = false - - final func displayWithFrame(frame: CGFloat, forceUpdates: Bool) { - transformNode.updateTree(frame, forceUpdates: forceUpdates) - let layerVisible = frame.isInRangeOrEqual(inFrame, outFrame) - /// Only update contents if current time is within the layers time bounds. - if layerVisible { - displayContentsWithFrame(frame: frame, forceUpdates: forceUpdates) - maskLayer?.updateWithFrame(frame: frame, forceUpdates: forceUpdates) - } - self.isHidden = !layerVisible - //NOTE - /*contentsLayer.transform = transformNode.globalTransform - contentsLayer.opacity = transformNode.opacity - contentsLayer.isHidden = !layerVisible*/ - } - - func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - /// To be overridden by subclass - } - - func captureGeometry() -> CapturedGeometryNode { - return CapturedGeometryNode( - transform: self.transformNode.globalTransform, - alpha: CGFloat(self.transformNode.opacity), - isHidden: self.isHidden, - displayItem: self.captureDisplayItem(), - subnodes: self.captureChildren() - ) - } - - func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - preconditionFailure() - } - - func captureChildren() -> [CapturedGeometryNode] { - preconditionFailure() - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyImageCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyImageCompositionLayer.swift deleted file mode 100644 index 5ba14d64d1..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyImageCompositionLayer.swift +++ /dev/null @@ -1,36 +0,0 @@ -import Foundation -import CoreGraphics -import QuartzCore - -final class MyImageCompositionLayer: MyCompositionLayer { - - var image: CGImage? = nil { - didSet { - //NOTE - /*if let image = image { - contentsLayer.contents = image - } else { - contentsLayer.contents = nil - }*/ - } - } - - let imageReferenceID: String - - init(imageLayer: ImageLayerModel, size: CGSize) { - self.imageReferenceID = imageLayer.referenceID - super.init(layer: imageLayer, size: size) - - //NOTE - //contentsLayer.masksToBounds = true - //contentsLayer.contentsGravity = CALayerContentsGravity.resize - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - preconditionFailure() - } - - override func captureChildren() -> [CapturedGeometryNode] { - return [] - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyMaskContainerLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyMaskContainerLayer.swift deleted file mode 100644 index e132deaa87..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyMaskContainerLayer.swift +++ /dev/null @@ -1,150 +0,0 @@ -import Foundation -import QuartzCore - -extension MaskMode { - var usableMode: MaskMode { - switch self { - case .add: - return .add - case .subtract: - return .subtract - case .intersect: - return .intersect - case .lighten: - return .add - case .darken: - return .darken - case .difference: - return .intersect - case .none: - return .none - } - } -} - -extension CGRect { - static var veryLargeRect: CGRect { - return CGRect(x: -100_000_000, - y: -100_000_000, - width: 200_000_000, - height: 200_000_000) - } -} - -final class MyMaskContainerLayer { - - init(masks: [Mask]) { - //NOTE - //anchorPoint = .zero - var containerLayer = CALayer() - var firstObject: Bool = true - for mask in masks { - let maskLayer = MaskLayer(mask: mask) - maskLayers.append(maskLayer) - if mask.mode.usableMode == .none { - continue - } else if mask.mode.usableMode == .add || firstObject { - firstObject = false - containerLayer.addSublayer(maskLayer) - } else { - containerLayer.mask = maskLayer - let newContainer = CALayer() - newContainer.addSublayer(containerLayer) - containerLayer = newContainer - } - } - //NOTE - //addSublayer(containerLayer) - } - - fileprivate var maskLayers: [MaskLayer] = [] - - func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { - maskLayers.forEach({ $0.updateWithFrame(frame: frame, forceUpdates: forceUpdates) }) - } -} - -fileprivate class MaskLayer: CALayer { - - let properties: MaskNodeProperties? - - let maskLayer = CAShapeLayer() - - init(mask: Mask) { - self.properties = MaskNodeProperties(mask: mask) - super.init() - addSublayer(maskLayer) - anchorPoint = .zero - maskLayer.fillColor = mask.mode == .add ? CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1]) : - CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1]) - maskLayer.fillRule = CAShapeLayerFillRule.evenOdd - self.actions = [ - "opacity" : NSNull() - ] - - } - - override init(layer: Any) { - self.properties = nil - super.init(layer: layer) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func updateWithFrame(frame: CGFloat, forceUpdates: Bool) { - guard let properties = properties else { return } - if properties.opacity.needsUpdate(frame: frame) || forceUpdates { - properties.opacity.update(frame: frame) - self.opacity = Float(properties.opacity.value.cgFloatValue) - } - - if properties.shape.needsUpdate(frame: frame) || forceUpdates { - properties.shape.update(frame: frame) - properties.expansion.update(frame: frame) - - let shapePath = properties.shape.value.cgPath() - var path = shapePath - if properties.mode.usableMode == .subtract && !properties.inverted || - (properties.mode.usableMode == .add && properties.inverted) { - /// Add a bounds rect to invert the mask - let newPath = CGMutablePath() - newPath.addRect(CGRect.veryLargeRect) - newPath.addPath(shapePath) - path = newPath - } - maskLayer.path = path - } - - } -} - -fileprivate class MaskNodeProperties: NodePropertyMap { - - var propertyMap: [String : AnyNodeProperty] - - var properties: [AnyNodeProperty] - - init(mask: Mask) { - self.mode = mask.mode - self.inverted = mask.inverted - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.opacity.keyframes)) - self.shape = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.shape.keyframes)) - self.expansion = NodeProperty(provider: KeyframeInterpolator(keyframes: mask.expansion.keyframes)) - self.propertyMap = [ - "Opacity" : opacity, - "Shape" : shape, - "Expansion" : expansion - ] - self.properties = Array(self.propertyMap.values) - } - - let mode: MaskMode - let inverted: Bool - - let opacity: NodeProperty - let shape: NodeProperty - let expansion: NodeProperty -} - diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyNullCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyNullCompositionLayer.swift deleted file mode 100644 index ed771fd378..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyNullCompositionLayer.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation - -final class MyNullCompositionLayer: MyCompositionLayer { - init(layer: LayerModel) { - super.init(layer: layer, size: .zero) - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - return nil - } - - override func captureChildren() -> [CapturedGeometryNode] { - return [] - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyPreCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyPreCompositionLayer.swift deleted file mode 100644 index 55edee4c14..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyPreCompositionLayer.swift +++ /dev/null @@ -1,84 +0,0 @@ -import Foundation -import QuartzCore - -final class MyPreCompositionLayer: MyCompositionLayer { - - let frameRate: CGFloat - let remappingNode: NodeProperty? - fileprivate var animationLayers: [MyCompositionLayer] - - init(precomp: PreCompLayerModel, - asset: PrecompAsset, - assetLibrary: AssetLibrary?, - frameRate: CGFloat) { - self.animationLayers = [] - if let keyframes = precomp.timeRemapping?.keyframes { - self.remappingNode = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframes)) - } else { - self.remappingNode = nil - } - self.frameRate = frameRate - super.init(layer: precomp, size: CGSize(width: precomp.width, height: precomp.height)) - bounds = CGRect(origin: .zero, size: CGSize(width: precomp.width, height: precomp.height)) - - //NOTE - //contentsLayer.masksToBounds = true - //contentsLayer.bounds = bounds - - let layers = initializeCompositionLayers(layers: asset.layers, assetLibrary: assetLibrary, frameRate: frameRate) - - var imageLayers = [MyImageCompositionLayer]() - - var mattedLayer: MyCompositionLayer? = nil - - for layer in layers.reversed() { - layer.bounds = bounds - //NOTE - animationLayers.append(layer) - if let imageLayer = layer as? MyImageCompositionLayer { - imageLayers.append(imageLayer) - } - if let matte = mattedLayer { - /// The previous layer requires this layer to be its matte - matte.matteLayer = layer - mattedLayer = nil - continue - } - if let matte = layer.matteType, - (matte == .add || matte == .invert) { - /// We have a layer that requires a matte. - mattedLayer = layer - } - //NOTE - //contentsLayer.addSublayer(layer) - } - - //NOTE - //layerImageProvider.addImageLayers(imageLayers) - } - - override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - let localFrame: CGFloat - if let remappingNode = remappingNode { - remappingNode.update(frame: frame) - localFrame = remappingNode.value.cgFloatValue * frameRate - } else { - localFrame = (frame - startFrame) / timeStretch - } - for animationLayer in self.animationLayers { - animationLayer.displayWithFrame(frame: localFrame, forceUpdates: forceUpdates) - } - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - return nil - } - - override func captureChildren() -> [CapturedGeometryNode] { - var result: [CapturedGeometryNode] = [] - for animationLayer in self.animationLayers { - result.append(animationLayer.captureGeometry()) - } - return result - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MyShapeCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MyShapeCompositionLayer.swift deleted file mode 100644 index 06697a0a29..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MyShapeCompositionLayer.swift +++ /dev/null @@ -1,54 +0,0 @@ -import Foundation -import CoreGraphics - -/** - A CompositionLayer responsible for initializing and rendering shapes - */ -final class MyShapeCompositionLayer: MyCompositionLayer { - - let rootNode: AnimatorNode? - let renderContainer: ShapeContainerLayer? - - init(shapeLayer: ShapeLayerModel) { - let results = shapeLayer.items.initializeNodeTree() - let renderContainer = ShapeContainerLayer() - self.renderContainer = renderContainer - self.rootNode = results.rootNode - super.init(layer: shapeLayer, size: .zero) - - //NOTE - //contentsLayer.addSublayer(renderContainer) - for container in results.renderContainers { - renderContainer.insertRenderLayer(container) - } - rootNode?.updateTree(0, forceUpdates: true) - } - - override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - rootNode?.updateTree(frame, forceUpdates: forceUpdates) - renderContainer?.markRenderUpdates(forFrame: frame) - } - - override func captureGeometry() -> CapturedGeometryNode { - var subnodes: [CapturedGeometryNode] = [] - if let renderContainer = self.renderContainer { - subnodes.append(renderContainer.captureGeometry()) - } - - return CapturedGeometryNode( - transform: self.transformNode.globalTransform, - alpha: CGFloat(self.transformNode.opacity), - isHidden: self.isHidden, - displayItem: nil, - subnodes: subnodes - ) - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - preconditionFailure() - } - - override func captureChildren() -> [CapturedGeometryNode] { - preconditionFailure() - } -} diff --git a/submodules/LottieMeshSwift/Sources/Layers/MySolidCompositionLayer.swift b/submodules/LottieMeshSwift/Sources/Layers/MySolidCompositionLayer.swift deleted file mode 100644 index a39877bf16..0000000000 --- a/submodules/LottieMeshSwift/Sources/Layers/MySolidCompositionLayer.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation -import UIKit - -final class MySolidCompositionLayer: MyCompositionLayer { - let colorProperty: NodeProperty? - let solidShape: CAShapeLayer = CAShapeLayer() - - init(solid: SolidLayerModel) { - let components = solid.colorHex.hexColorComponents() - self.colorProperty = NodeProperty(provider: SingleValueProvider(Color(r: Double(components.red), g: Double(components.green), b: Double(components.blue), a: 1))) - - super.init(layer: solid, size: .zero) - solidShape.path = CGPath(rect: CGRect(x: 0, y: 0, width: solid.width, height: solid.height), transform: nil) - //NOTE - //contentsLayer.addSublayer(solidShape) - } - - override func displayContentsWithFrame(frame: CGFloat, forceUpdates: Bool) { - guard let colorProperty = colorProperty else { return } - colorProperty.update(frame: frame) - solidShape.fillColor = colorProperty.value.cgColorValue - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - guard let colorProperty = colorProperty else { return nil } - guard let path = self.solidShape.path else { - return nil - } - return CapturedGeometryNode.DisplayItem( - path: path, - display: .fill(CapturedGeometryNode.DisplayItem.Display.Fill( - style: .color(color: UIColor(cgColor: colorProperty.value.cgColorValue), alpha: 1.0), - fillRule: .evenOdd - )) - ) - } - - override func captureChildren() -> [CapturedGeometryNode] { - return [] - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/InvertedMatteLayer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/InvertedMatteLayer.swift deleted file mode 100644 index 4f4aff2385..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/InvertedMatteLayer.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// InvertedMatteLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/28/19. -// - -import Foundation -import QuartzCore - -/** - A layer that inverses the alpha output of its input layer. - - WARNING: This is experimental and probably not very performant. - */ -/*final class InvertedMatteLayer: CALayer, CompositionLayerDelegate { - - let inputMatte: CompositionLayer? - let wrapperLayer = CALayer() - - init(inputMatte: CompositionLayer) { - self.inputMatte = inputMatte - super.init() - inputMatte.layerDelegate = self - self.anchorPoint = .zero - self.bounds = inputMatte.bounds - self.setNeedsDisplay() - } - - override init(layer: Any) { - guard let layer = layer as? InvertedMatteLayer else { - fatalError("init(layer:) wrong class.") - } - self.inputMatte = nil - super.init(layer: layer) - } - - func frameUpdated(frame: CGFloat) { - self.setNeedsDisplay() - self.displayIfNeeded() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func draw(in ctx: CGContext) { - guard let inputMatte = inputMatte else { return } - guard let fillColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 1]) - else { return } - ctx.setFillColor(fillColor) - ctx.fill(bounds) - ctx.setBlendMode(.destinationOut) - inputMatte.render(in: ctx) - } - -} -*/ diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/LayerTransformNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/LayerTransformNode.swift deleted file mode 100644 index 3895900997..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/LayerContainers/Utility/LayerTransformNode.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// LayerTransformPropertyMap.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -final class LayerTransformProperties: NodePropertyMap { - - init(transform: Transform) { - - self.anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.anchorPoint.keyframes)) - self.scale = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.scale.keyframes)) - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.rotation.keyframes)) - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: transform.opacity.keyframes)) - - var propertyMap: [String: AnyNodeProperty] = [ - "Anchor Point" : anchor, - "Scale" : scale, - "Rotation" : rotation, - "Opacity" : opacity - ] - - if let positionKeyframesX = transform.positionX?.keyframes, - let positionKeyframesY = transform.positionY?.keyframes { - let xPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesX)) - let yPosition: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframesY)) - propertyMap["X Position"] = xPosition - propertyMap["Y Position"] = yPosition - self.positionX = xPosition - self.positionY = yPosition - self.position = nil - } else if let positionKeyframes = transform.position?.keyframes { - let position: NodeProperty = NodeProperty(provider: KeyframeInterpolator(keyframes: positionKeyframes)) - propertyMap["Position"] = position - self.position = position - self.positionX = nil - self.positionY = nil - } else { - self.position = nil - self.positionY = nil - self.positionX = nil - } - - self.properties = Array(propertyMap.values) - } - - let properties: [AnyNodeProperty] - - let anchor: NodeProperty - let scale: NodeProperty - let rotation: NodeProperty - let position: NodeProperty? - let positionX: NodeProperty? - let positionY: NodeProperty? - let opacity: NodeProperty - -} - -class LayerTransformNode: AnimatorNode { - let outputNode: NodeOutput = PassThroughOutputNode(parent: nil) - - init(transform: Transform) { - self.transformProperties = LayerTransformProperties(transform: transform) - } - - let transformProperties: LayerTransformProperties - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return transformProperties - } - - var parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true - - func shouldRebuildOutputs(frame: CGFloat) -> Bool { - return hasLocalUpdates || hasUpstreamUpdates - } - - func rebuildOutputs(frame: CGFloat) { - opacity = Float(transformProperties.opacity.value.cgFloatValue) * 0.01 - - let position: CGPoint - if let point = transformProperties.position?.value.pointValue { - position = point - } else if let xPos = transformProperties.positionX?.value.cgFloatValue, - let yPos = transformProperties.positionY?.value.cgFloatValue { - position = CGPoint(x: xPos, y: yPos) - } else { - position = .zero - } - - localTransform = CATransform3D.makeTransform(anchor: transformProperties.anchor.value.pointValue, - position: position, - scale: transformProperties.scale.value.sizeValue, - rotation: transformProperties.rotation.value.cgFloatValue, - skew: nil, - skewAxis: nil) - - if let parentNode = parentNode as? LayerTransformNode { - globalTransform = CATransform3DConcat(localTransform, parentNode.globalTransform) - } else { - globalTransform = localTransform - } - } - - var opacity: Float = 1 - var localTransform: CATransform3D = CATransform3DIdentity - var globalTransform: CATransform3D = CATransform3DIdentity - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Animation.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Animation.swift deleted file mode 100644 index 2ba10329de..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Animation.swift +++ /dev/null @@ -1,107 +0,0 @@ -// -// Animation.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation - -public enum CoordinateSpace: Int, Codable { - case type2d - case type3d -} - -/** - The `Animation` model is the top level model object in Lottie. - - An `Animation` holds all of the animation data backing a Lottie Animation. - Codable, see JSON schema [here](https://github.com/airbnb/lottie-web/tree/master/docs/json). - */ -public final class Animation: Codable { - - /// The version of the JSON Schema. - let version: String - - /// The coordinate space of the composition. - let type: CoordinateSpace - - /// The start time of the composition in frameTime. - public let startFrame: AnimationFrameTime - - /// The end time of the composition in frameTime. - public let endFrame: AnimationFrameTime - - /// The frame rate of the composition. - public let framerate: Double - - /// The height of the composition in points. - let width: Int - - /// The width of the composition in points. - let height: Int - - /// The list of animation layers - let layers: [LayerModel] - - /// The list of glyphs used for text rendering - let glyphs: [Glyph]? - - /// The list of fonts used for text rendering - let fonts: FontList? - - /// Asset Library - let assetLibrary: AssetLibrary? - - /// Markers - let markers: [Marker]? - let markerMap: [String : Marker]? - - /// Return all marker names, in order, or an empty list if none are specified - public var markerNames: [String] { - guard let markers = markers else { return [] } - return markers.map { $0.name } - } - - enum CodingKeys : String, CodingKey { - case version = "v" - case type = "ddd" - case startFrame = "ip" - case endFrame = "op" - case framerate = "fr" - case width = "w" - case height = "h" - case layers = "layers" - case glyphs = "chars" - case fonts = "fonts" - case assetLibrary = "assets" - case markers = "markers" - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Animation.CodingKeys.self) - self.version = try container.decode(String.self, forKey: .version) - self.type = try container.decodeIfPresent(CoordinateSpace.self, forKey: .type) ?? .type2d - self.startFrame = try container.decode(AnimationFrameTime.self, forKey: .startFrame) - self.endFrame = try container.decode(AnimationFrameTime.self, forKey: .endFrame) - self.framerate = try container.decode(Double.self, forKey: .framerate) - self.width = try container.decode(Int.self, forKey: .width) - self.height = try container.decode(Int.self, forKey: .height) - self.layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) - self.glyphs = try container.decodeIfPresent([Glyph].self, forKey: .glyphs) - self.fonts = try container.decodeIfPresent(FontList.self, forKey: .fonts) - self.assetLibrary = try container.decodeIfPresent(AssetLibrary.self, forKey: .assetLibrary) - self.markers = try container.decodeIfPresent([Marker].self, forKey: .markers) - - if let markers = markers { - var markerMap: [String : Marker] = [:] - for marker in markers { - markerMap[marker.name] = marker - } - self.markerMap = markerMap - } else { - self.markerMap = nil - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/Asset.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/Asset.swift deleted file mode 100644 index 85d47f81ff..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/Asset.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Asset.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -public class Asset: Codable { - - /// The ID of the asset - public let id: String - - private enum CodingKeys : String, CodingKey { - case id = "id" - } - - required public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Asset.CodingKeys.self) - if let id = try? container.decode(String.self, forKey: .id) { - self.id = id - } else { - self.id = String(try container.decode(Int.self, forKey: .id)) - } - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/AssetLibrary.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/AssetLibrary.swift deleted file mode 100644 index d9dbf9095d..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/AssetLibrary.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// AssetLibrary.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class AssetLibrary: Codable { - - /// The Assets - let assets: [String : Asset] - - let imageAssets: [String : ImageAsset] - let precompAssets: [String : PrecompAsset] - - required init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - var containerForKeys = container - - var decodedAssets = [String : Asset]() - - var imageAssets = [String : ImageAsset]() - var precompAssets = [String : PrecompAsset]() - - while !container.isAtEnd { - let keyContainer = try containerForKeys.nestedContainer(keyedBy: PrecompAsset.CodingKeys.self) - if keyContainer.contains(.layers) { - let precompAsset = try container.decode(PrecompAsset.self) - decodedAssets[precompAsset.id] = precompAsset - precompAssets[precompAsset.id] = precompAsset - } else { - let imageAsset = try container.decode(ImageAsset.self) - decodedAssets[imageAsset.id] = imageAsset - imageAssets[imageAsset.id] = imageAsset - } - } - self.assets = decodedAssets - self.precompAssets = precompAssets - self.imageAssets = imageAssets - } - - func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - try container.encode(contentsOf: Array(assets.values)) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/ImageAsset.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/ImageAsset.swift deleted file mode 100644 index aeff137341..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/ImageAsset.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// ImageAsset.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -public final class ImageAsset: Asset { - - /// Image name - public let name: String - - /// Image Directory - public let directory: String - - /// Image Size - public let width: Double - - public let height: Double - - enum CodingKeys : String, CodingKey { - case name = "p" - case directory = "u" - case width = "w" - case height = "h" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ImageAsset.CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.directory = try container.decode(String.self, forKey: .directory) - self.width = try container.decode(Double.self, forKey: .width) - self.height = try container.decode(Double.self, forKey: .height) - try super.init(from: decoder) - } - - override public func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(name, forKey: .name) - try container.encode(directory, forKey: .directory) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/PrecompAsset.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/PrecompAsset.swift deleted file mode 100644 index 963e0bb184..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Assets/PrecompAsset.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// PrecompAsset.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class PrecompAsset: Asset { - - /// Layers of the precomp - let layers: [LayerModel] - - enum CodingKeys : String, CodingKey { - case layers = "layers" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: PrecompAsset.CodingKeys.self) - self.layers = try container.decode([LayerModel].self, ofFamily: LayerType.self, forKey: .layers) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(layers, forKey: .layers) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/Bundle.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/Bundle.swift deleted file mode 100644 index 98d8a762d3..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/Bundle.swift +++ /dev/null @@ -1,21 +0,0 @@ -import Foundation -#if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) -import UIKit -#endif - -extension Bundle { - func getAnimationData(_ name: String, subdirectory: String? = nil) throws -> Data? { - // Check for files in the bundle at the given path - if let url = self.url(forResource: name, withExtension: "json", subdirectory: subdirectory) { - return try Data(contentsOf: url) - } - - // Check for data assets (not available on macOS) - #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) - let assetKey = subdirectory != nil ? "\(subdirectory ?? "")/\(name)" : name - return NSDataAsset.init(name: assetKey, bundle: self)?.data - #else - return nil - #endif - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift deleted file mode 100644 index 29fe104ddd..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Extensions/KeyedDecodingContainerExtensions.swift +++ /dev/null @@ -1,40 +0,0 @@ -// From: https://medium.com/@kewindannerfjordremeczki/swift-4-0-decodable-heterogeneous-collections-ecc0e6b468cf - -import Foundation - -/// To support a new class family, create an enum that conforms to this protocol and contains the different types. -protocol ClassFamily: Decodable { - /// The discriminator key. - static var discriminator: Discriminator { get } - - /// Returns the class type of the object corresponding to the value. - func getType() -> AnyObject.Type -} - -/// Discriminator key enum used to retrieve discriminator fields in JSON payloads. -enum Discriminator: String, CodingKey { - case type = "ty" -} - -extension KeyedDecodingContainer { - - /// Decode a heterogeneous list of objects for a given family. - /// - Parameters: - /// - heterogeneousType: The decodable type of the list. - /// - family: The ClassFamily enum for the type family. - /// - key: The CodingKey to look up the list in the current container. - /// - Returns: The resulting list of heterogeneousType elements. - func decode(_ heterogeneousType: [T].Type, ofFamily family: U.Type, forKey key: K) throws -> [T] { - var container = try self.nestedUnkeyedContainer(forKey: key) - var list = [T]() - var tmpContainer = container - while !container.isAtEnd { - let typeContainer = try container.nestedContainer(keyedBy: Discriminator.self) - let family: U = try typeContainer.decode(U.self, forKey: U.discriminator) - if let type = family.getType() as? T.Type { - list.append(try tmpContainer.decode(type)) - } - } - return list - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/Keyframe.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/Keyframe.swift deleted file mode 100644 index c88065b2e4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/Keyframe.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// Keyframe.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation -import CoreGraphics - -/** - Keyframe represents a point in time and is the container for datatypes. - Note: This is a parent class and should not be used directly. - */ -final class Keyframe { - - /// The value of the keyframe - let value: T - /// The time in frames of the keyframe. - let time: CGFloat - /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. - let isHold: Bool - /// The in tangent for the time interpolation curve. - let inTangent: Vector2D? - /// The out tangent for the time interpolation curve. - let outTangent: Vector2D? - - /// The spacial in tangent of the vector. - let spatialInTangent: Vector3D? - /// The spacial out tangent of the vector. - let spatialOutTangent: Vector3D? - - /// Initialize a value-only keyframe with no time data. - init(_ value: T, - spatialInTangent: Vector3D? = nil, - spatialOutTangent: Vector3D? = nil) { - self.value = value - self.time = 0 - self.isHold = true - self.inTangent = nil - self.outTangent = nil - self.spatialInTangent = spatialInTangent - self.spatialOutTangent = spatialOutTangent - } - - /// Initialize a keyframe - init(value: T, - time: Double, - isHold: Bool, - inTangent: Vector2D?, - outTangent: Vector2D?, - spatialInTangent: Vector3D? = nil, - spatialOutTangent: Vector3D? = nil) { - self.value = value - self.time = CGFloat(time) - self.isHold = isHold - self.outTangent = outTangent - self.inTangent = inTangent - self.spatialInTangent = spatialInTangent - self.spatialOutTangent = spatialOutTangent - } - -} - -/** - A generic class used to parse and remap keyframe json. - - Keyframe json has a couple of different variations and formats depending on the - type of keyframea and also the version of the JSON. By parsing the raw data - we can reconfigure it into a constant format. - */ -final class KeyframeData: Codable { - - /// The start value of the keyframe - let startValue: T? - /// The End value of the keyframe. Note: Newer versions animation json do not have this field. - let endValue: T? - /// The time in frames of the keyframe. - let time: Double? - /// A hold keyframe freezes interpolation until the next keyframe that is not a hold. - let hold: Int? - - /// The in tangent for the time interpolation curve. - let inTangent: Vector2D? - /// The out tangent for the time interpolation curve. - let outTangent: Vector2D? - - /// The spacial in tangent of the vector. - let spatialInTangent: Vector3D? - /// The spacial out tangent of the vector. - let spatialOutTangent:Vector3D? - - init(startValue: T?, - endValue: T?, - time: Double?, - hold: Int?, - inTangent: Vector2D?, - outTangent: Vector2D?, - spatialInTangent: Vector3D?, - spatialOutTangent: Vector3D?) { - self.startValue = startValue - self.endValue = endValue - self.time = time - self.hold = hold - self.inTangent = inTangent - self.outTangent = outTangent - self.spatialInTangent = spatialInTangent - self.spatialOutTangent = spatialOutTangent - } - - enum CodingKeys : String, CodingKey { - case startValue = "s" - case endValue = "e" - case time = "t" - case hold = "h" - case inTangent = "i" - case outTangent = "o" - case spatialInTangent = "ti" - case spatialOutTangent = "to" - } - - var isHold: Bool { - if let hold = hold { - return hold > 0 - } - return false - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.swift deleted file mode 100644 index f64adc0bda..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Keyframes/KeyframeGroup.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// KeyframeGroup.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation - -/** - Used for coding/decoding a group of Keyframes by type. - - Keyframe data is wrapped in a dictionary { "k" : KeyframeData }. - The keyframe data can either be an array of keyframes or, if no animation is present, the raw value. - This helper object is needed to properly decode the json. - */ - -final class KeyframeGroup: Codable where T: Codable, T: Interpolatable { - - let keyframes: ContiguousArray> - - private enum KeyframeWrapperKey: String, CodingKey { - case keyframeData = "k" - } - - init(keyframes: ContiguousArray>) { - self.keyframes = keyframes - } - - init(_ value: T) { - self.keyframes = [Keyframe(value)] - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: KeyframeWrapperKey.self) - - if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) { - /// Try to decode raw value; No keyframe data. - self.keyframes = [Keyframe(keyframeData)] - } else { - /** - Decode and array of keyframes. - - Body Movin and Lottie deal with keyframes in different ways. - - A keyframe object in Body movin defines a span of time with a START - and an END, from the current keyframe time to the next keyframe time. - - A keyframe object in Lottie defines a singular point in time/space. - This point has an in-tangent and an out-tangent. - - To properly decode this we must iterate through keyframes while holding - reference to the previous keyframe. - */ - - var keyframesContainer = try container.nestedUnkeyedContainer(forKey: .keyframeData) - var keyframes = ContiguousArray>() - var previousKeyframeData: KeyframeData? - while(!keyframesContainer.isAtEnd) { - // Ensure that Time and Value are present. - - let keyframeData = try keyframesContainer.decode(KeyframeData.self) - - guard let value: T = keyframeData.startValue ?? previousKeyframeData?.endValue, - let time = keyframeData.time else { - /// Missing keyframe data. JSON must be corrupt. - throw DecodingError.dataCorruptedError(forKey: KeyframeWrapperKey.keyframeData, in: container, debugDescription: "Missing keyframe data.") - } - - keyframes.append(Keyframe(value: value, - time: time, - isHold: keyframeData.isHold, - inTangent: previousKeyframeData?.inTangent, - outTangent: keyframeData.outTangent, - spatialInTangent: previousKeyframeData?.spatialInTangent, - spatialOutTangent: keyframeData.spatialOutTangent)) - previousKeyframeData = keyframeData - } - self.keyframes = keyframes - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: KeyframeWrapperKey.self) - - if keyframes.count == 1 { - let keyframe = keyframes[0] - try container.encode(keyframe.value, forKey: .keyframeData) - } else { - var keyframeContainer = container.nestedUnkeyedContainer(forKey: .keyframeData) - - for i in 1..(startValue: keyframe.value, - endValue: nextKeyframe.value, - time: Double(keyframe.time), - hold: keyframe.isHold ? 1 : nil, - inTangent: nextKeyframe.inTangent, - outTangent: keyframe.outTangent, - spatialInTangent: nil, - spatialOutTangent: nil) - try keyframeContainer.encode(keyframeData) - } - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ImageLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ImageLayerModel.swift deleted file mode 100644 index 696196239b..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ImageLayerModel.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ImageLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds an image. -final class ImageLayerModel: LayerModel { - - /// The reference ID of the image. - let referenceID: String - - private enum CodingKeys : String, CodingKey { - case referenceID = "refId" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ImageLayerModel.CodingKeys.self) - self.referenceID = try container.decode(String.self, forKey: .referenceID) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(referenceID, forKey: .referenceID) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/LayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/LayerModel.swift deleted file mode 100644 index 6b2667a0de..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/LayerModel.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// Layer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation - -/// Used for mapping a heterogeneous list to classes for parsing. -extension LayerType: ClassFamily { - static var discriminator: Discriminator = .type - - func getType() -> AnyObject.Type { - switch self { - case .precomp: - return PreCompLayerModel.self - case .solid: - return SolidLayerModel.self - case .image: - return ImageLayerModel.self - case .null: - return LayerModel.self - case .shape: - return ShapeLayerModel.self - case .text: - return TextLayerModel.self - } - } -} - -public enum LayerType: Int, Codable { - case precomp - case solid - case image - case null - case shape - case text - - public init(from decoder: Decoder) throws { - self = try LayerType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .null - } -} - -public enum MatteType: Int, Codable { - case none - case add - case invert - case unknown -} - -public enum BlendMode: Int, Codable { - case normal - case multiply - case screen - case overlay - case darken - case lighten - case colorDodge - case colorBurn - case hardLight - case softLight - case difference - case exclusion - case hue - case saturation - case color - case luminosity -} - -/** - A base top container for shapes, images, and other view objects. - */ -class LayerModel: Codable { - - /// The readable name of the layer - let name: String - - /// The index of the layer - let index: Int - - /// The type of the layer. - let type: LayerType - - /// The coordinate space - let coordinateSpace: CoordinateSpace - - /// The in time of the layer in frames. - let inFrame: Double - /// The out time of the layer in frames. - let outFrame: Double - - /// The start time of the layer in frames. - let startTime: Double - - /// The transform of the layer - let transform: Transform - - /// The index of the parent layer, if applicable. - let parent: Int? - - /// The blending mode for the layer - let blendMode: BlendMode - - /// An array of masks for the layer. - let masks: [Mask]? - - /// A number that stretches time by a multiplier - let timeStretch: Double - - /// The type of matte if any. - let matte: MatteType? - - let hidden: Bool - - private enum CodingKeys : String, CodingKey { - case name = "nm" - case index = "ind" - case type = "ty" - case coordinateSpace = "ddd" - case inFrame = "ip" - case outFrame = "op" - case startTime = "st" - case transform = "ks" - case parent = "parent" - case blendMode = "bm" - case masks = "masksProperties" - case timeStretch = "sr" - case matte = "tt" - case hidden = "hd" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: LayerModel.CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" - self.index = try container.decode(Int.self, forKey: .index) - self.type = try container.decode(LayerType.self, forKey: .type) - self.coordinateSpace = try container.decodeIfPresent(CoordinateSpace.self, forKey: .coordinateSpace) ?? .type2d - self.inFrame = try container.decode(Double.self, forKey: .inFrame) - self.outFrame = try container.decode(Double.self, forKey: .outFrame) - self.startTime = try container.decode(Double.self, forKey: .startTime) - self.transform = try container.decode(Transform.self, forKey: .transform) - self.parent = try container.decodeIfPresent(Int.self, forKey: .parent) - self.blendMode = try container.decodeIfPresent(BlendMode.self, forKey: .blendMode) ?? .normal - self.masks = try container.decodeIfPresent([Mask].self, forKey: .masks) - self.timeStretch = try container.decodeIfPresent(Double.self, forKey: .timeStretch) ?? 1 - self.matte = try container.decodeIfPresent(MatteType.self, forKey: .matte) - self.hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.swift deleted file mode 100644 index e107ea1b92..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// PreCompLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds another animation composition. -final class PreCompLayerModel: LayerModel { - - /// The reference ID of the precomp. - let referenceID: String - - /// A value that remaps time over time. - let timeRemapping: KeyframeGroup? - - /// Precomp Width - let width: Double - - /// Precomp Height - let height: Double - - private enum CodingKeys : String, CodingKey { - case referenceID = "refId" - case timeRemapping = "tm" - case width = "w" - case height = "h" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: PreCompLayerModel.CodingKeys.self) - self.referenceID = try container.decode(String.self, forKey: .referenceID) - self.timeRemapping = try container.decodeIfPresent(KeyframeGroup.self, forKey: .timeRemapping) - self.width = try container.decode(Double.self, forKey: .width) - self.height = try container.decode(Double.self, forKey: .height) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(referenceID, forKey: .referenceID) - try container.encode(timeRemapping, forKey: .timeRemapping) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.swift deleted file mode 100644 index eb6299f813..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/ShapeLayerModel.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// ShapeLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds vector shape objects. -final class ShapeLayerModel: LayerModel { - - /// A list of shape items. - let items: [ShapeItem] - - private enum CodingKeys : String, CodingKey { - case items = "shapes" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ShapeLayerModel.CodingKeys.self) - self.items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.items, forKey: .items) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/SolidLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/SolidLayerModel.swift deleted file mode 100644 index afa00d7ea8..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/SolidLayerModel.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// SolidLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds a solid color. -final class SolidLayerModel: LayerModel { - - /// The color of the solid in Hex // Change to value provider. - let colorHex: String - - /// The Width of the color layer - let width: Double - - /// The height of the color layer - let height: Double - - private enum CodingKeys : String, CodingKey { - case colorHex = "sc" - case width = "sw" - case height = "sh" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: SolidLayerModel.CodingKeys.self) - self.colorHex = try container.decode(String.self, forKey: .colorHex) - self.width = try container.decode(Double.self, forKey: .width) - self.height = try container.decode(Double.self, forKey: .height) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(colorHex, forKey: .colorHex) - try container.encode(width, forKey: .width) - try container.encode(height, forKey: .height) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/TextLayerModel.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/TextLayerModel.swift deleted file mode 100644 index f08bcef3bb..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Layers/TextLayerModel.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// TextLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// A layer that holds text. -final class TextLayerModel: LayerModel { - - /// The text for the layer - let text: KeyframeGroup - - /// Text animators - let animators: [TextAnimator] - - private enum CodingKeys : String, CodingKey { - case textGroup = "t" - } - - private enum TextCodingKeys : String, CodingKey { - case text = "d" - case animators = "a" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: TextLayerModel.CodingKeys.self) - let textContainer = try container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) - self.text = try textContainer.decode(KeyframeGroup.self, forKey: .text) - self.animators = try textContainer.decode([TextAnimator].self, forKey: .animators) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - var textContainer = container.nestedContainer(keyedBy: TextCodingKeys.self, forKey: .textGroup) - try textContainer.encode(text, forKey: .text) - try textContainer.encode(animators, forKey: .animators) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/DashPattern.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/DashPattern.swift deleted file mode 100644 index efc7532886..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/DashPattern.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// DashPattern.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation - -enum DashElementType: String, Codable { - case offset = "o" - case dash = "d" - case gap = "g" -} - -final class DashElement: Codable { - let type: DashElementType - let value: KeyframeGroup - - enum CodingKeys : String, CodingKey { - case type = "n" - case value = "v" - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Marker.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Marker.swift deleted file mode 100644 index 9c9512f934..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Marker.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Marker.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -/// A time marker -final class Marker: Codable { - - /// The Marker Name - let name: String - - /// The Frame time of the marker - let frameTime: AnimationFrameTime - - enum CodingKeys : String, CodingKey { - case name = "cm" - case frameTime = "tm" - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Mask.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Mask.swift deleted file mode 100644 index 1b7dc49939..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Mask.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// Mask.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum MaskMode: String, Codable { - case add = "a" - case subtract = "s" - case intersect = "i" - case lighten = "l" - case darken = "d" - case difference = "f" - case none = "n" -} - -final class Mask: Codable { - - let mode: MaskMode - - let opacity: KeyframeGroup - - let shape: KeyframeGroup - - let inverted: Bool - - let expansion: KeyframeGroup - - enum CodingKeys : String, CodingKey { - case mode = "mode" - case opacity = "o" - case inverted = "inv" - case shape = "pt" - case expansion = "x" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Mask.CodingKeys.self) - self.mode = try container.decodeIfPresent(MaskMode.self, forKey: .mode) ?? .add - self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) - self.shape = try container.decode(KeyframeGroup.self, forKey: .shape) - self.inverted = try container.decodeIfPresent(Bool.self, forKey: .inverted) ?? false - self.expansion = try container.decodeIfPresent(KeyframeGroup.self, forKey: .expansion) ?? KeyframeGroup(Vector1D(0)) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Transform.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Transform.swift deleted file mode 100644 index c7e383490a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Objects/Transform.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// Transform.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation - -/// The animatable transform for a layer. Controls position, rotation, scale, and opacity. -final class Transform: Codable { - - /// The anchor point of the transform. - let anchorPoint: KeyframeGroup - - /// The position of the transform. This is nil if the position data was split. - let position: KeyframeGroup? - - /// The positionX of the transform. This is nil if the position property is set. - let positionX: KeyframeGroup? - - /// The positionY of the transform. This is nil if the position property is set. - let positionY: KeyframeGroup? - - /// The scale of the transform - let scale: KeyframeGroup - - /// The rotation of the transform. Note: This is single dimensional rotation. - let rotation: KeyframeGroup - - /// The opacity of the transform. - let opacity: KeyframeGroup - - /// Should always be nil. - let rotationZ: KeyframeGroup? - - enum CodingKeys : String, CodingKey { - case anchorPoint = "a" - case position = "p" - case positionX = "px" - case positionY = "py" - case scale = "s" - case rotation = "r" - case rotationZ = "rz" - case opacity = "o" - } - - enum PositionCodingKeys : String, CodingKey { - case split = "s" - case positionX = "x" - case positionY = "y" - } - - - required init(from decoder: Decoder) throws { - /** - This manual override of decode is required because we want to throw an error - in the case that there is not position data. - */ - let container = try decoder.container(keyedBy: Transform.CodingKeys.self) - - // AnchorPoint - self.anchorPoint = try container.decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - - // Position - if container.contains(.positionX), container.contains(.positionY) { - // Position dimensions are split into two keyframe groups - self.positionX = try container.decode(KeyframeGroup.self, forKey: .positionX) - self.positionY = try container.decode(KeyframeGroup.self, forKey: .positionY) - self.position = nil - } else if let positionKeyframes = try? container.decode(KeyframeGroup.self, forKey: .position) { - // Position dimensions are a single keyframe group. - self.position = positionKeyframes - self.positionX = nil - self.positionY = nil - } else if let positionContainer = try? container.nestedContainer(keyedBy: PositionCodingKeys.self, forKey: .position), - let positionX = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionX), - let positionY = try? positionContainer.decode(KeyframeGroup.self, forKey: .positionY) { - /// Position keyframes are split and nested. - self.positionX = positionX - self.positionY = positionY - self.position = nil - } else { - /// Default value. - self.position = KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.positionX = nil - self.positionY = nil - } - - - // Scale - self.scale = try container.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) - - // Rotation - if let rotationZ = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotationZ) { - self.rotation = rotationZ - } else { - self.rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) - } - self.rotationZ = nil - - // Opacity - self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Ellipse.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Ellipse.swift deleted file mode 100644 index 5e6b9185e2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Ellipse.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// EllipseItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum PathDirection: Int, Codable { - case clockwise = 1 - case userSetClockwise = 2 - case counterClockwise = 3 -} - -/// An item that define an ellipse shape -final class Ellipse: ShapeItem { - - /// The direction of the ellipse. - let direction: PathDirection - - /// The position of the ellipse - let position: KeyframeGroup - - /// The size of the ellipse - let size: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case direction = "d" - case position = "p" - case size = "s" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Ellipse.CodingKeys.self) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise - self.position = try container.decode(KeyframeGroup.self, forKey: .position) - self.size = try container.decode(KeyframeGroup.self, forKey: .size) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(direction, forKey: .direction) - try container.encode(position, forKey: .position) - try container.encode(size, forKey: .size) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/FillI.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/FillI.swift deleted file mode 100644 index 11aded7709..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/FillI.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// FillShape.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum FillRule: Int, Codable { - case none - case nonZeroWinding - case evenOdd -} - -/// An item that defines a fill render -final class Fill: ShapeItem { - - /// The opacity of the fill - let opacity: KeyframeGroup - - /// The color keyframes for the fill - let color: KeyframeGroup - - let fillRule: FillRule - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case color = "c" - case fillRule = "r" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Fill.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.color = try container.decode(KeyframeGroup.self, forKey: .color) - self.fillRule = try container.decodeIfPresent(FillRule.self, forKey: .fillRule) ?? .nonZeroWinding - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(color, forKey: .color) - try container.encode(fillRule, forKey: .fillRule) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientFill.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientFill.swift deleted file mode 100644 index 05627c861e..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientFill.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// GradientFill.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum GradientType: Int, Codable { - case none - case linear - case radial -} - -/// An item that define a gradient fill -final class GradientFill: ShapeItem { - - /// The opacity of the fill - let opacity: KeyframeGroup - - /// The start of the gradient - let startPoint: KeyframeGroup - - /// The end of the gradient - let endPoint: KeyframeGroup - - /// The type of gradient - let gradientType: GradientType - - /// Gradient Highlight Length. Only if type is Radial - let highlightLength: KeyframeGroup? - - /// Highlight Angle. Only if type is Radial - let highlightAngle: KeyframeGroup? - - /// The number of color points in the gradient - let numberOfColors: Int - - /// The Colors of the gradient. - let colors: KeyframeGroup<[Double]> - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case startPoint = "s" - case endPoint = "e" - case gradientType = "t" - case highlightLength = "h" - case highlightAngle = "a" - case colors = "g" - } - - private enum GradientDataKeys : String, CodingKey { - case numberOfColors = "p" - case colors = "k" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: GradientFill.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) - self.endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) - self.gradientType = try container.decode(GradientType.self, forKey: .gradientType) - self.highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) - self.highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) - let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - self.colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) - self.numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(startPoint, forKey: .startPoint) - try container.encode(endPoint, forKey: .endPoint) - try container.encode(gradientType, forKey: .gradientType) - try container.encodeIfPresent(highlightLength, forKey: .highlightLength) - try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) - var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) - try colorsContainer.encode(colors, forKey: .colors) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.swift deleted file mode 100644 index 163a6128a2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// GradientStroke.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum LineCap: Int, Codable { - case none - case butt - case round - case square -} - -enum LineJoin: Int, Codable { - case none - case miter - case round - case bevel -} - -/// An item that define an ellipse shape -final class GradientStroke: ShapeItem { - - /// The opacity of the fill - let opacity: KeyframeGroup - - /// The start of the gradient - let startPoint: KeyframeGroup - - /// The end of the gradient - let endPoint: KeyframeGroup - - /// The type of gradient - let gradientType: GradientType - - /// Gradient Highlight Length. Only if type is Radial - let highlightLength: KeyframeGroup? - - /// Highlight Angle. Only if type is Radial - let highlightAngle: KeyframeGroup? - - /// The number of color points in the gradient - let numberOfColors: Int - - /// The Colors of the gradient. - let colors: KeyframeGroup<[Double]> - - /// The width of the stroke - let width: KeyframeGroup - - /// Line Cap - let lineCap: LineCap - - /// Line Join - let lineJoin: LineJoin - - /// Miter Limit - let miterLimit: Double - - /// The dash pattern of the stroke - let dashPattern: [DashElement]? - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case startPoint = "s" - case endPoint = "e" - case gradientType = "t" - case highlightLength = "h" - case highlightAngle = "a" - case colors = "g" - case width = "w" - case lineCap = "lc" - case lineJoin = "lj" - case miterLimit = "ml" - case dashPattern = "d" - } - - private enum GradientDataKeys : String, CodingKey { - case numberOfColors = "p" - case colors = "k" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: GradientStroke.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.startPoint = try container.decode(KeyframeGroup.self, forKey: .startPoint) - self.endPoint = try container.decode(KeyframeGroup.self, forKey: .endPoint) - self.gradientType = try container.decode(GradientType.self, forKey: .gradientType) - self.highlightLength = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightLength) - self.highlightAngle = try container.decodeIfPresent(KeyframeGroup.self, forKey: .highlightAngle) - self.width = try container.decode(KeyframeGroup.self, forKey: .width) - self.lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round - self.lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round - self.miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 - // TODO Decode Color Objects instead of array. - let colorsContainer = try container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - self.colors = try colorsContainer.decode(KeyframeGroup<[Double]>.self, forKey: .colors) - self.numberOfColors = try colorsContainer.decode(Int.self, forKey: .numberOfColors) - self.dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(startPoint, forKey: .startPoint) - try container.encode(endPoint, forKey: .endPoint) - try container.encode(gradientType, forKey: .gradientType) - try container.encodeIfPresent(highlightLength, forKey: .highlightLength) - try container.encodeIfPresent(highlightAngle, forKey: .highlightAngle) - try container.encode(width, forKey: .width) - try container.encode(lineCap, forKey: .lineCap) - try container.encode(lineJoin, forKey: .lineJoin) - try container.encode(miterLimit, forKey: .miterLimit) - var colorsContainer = container.nestedContainer(keyedBy: GradientDataKeys.self, forKey: .colors) - try colorsContainer.encode(numberOfColors, forKey: .numberOfColors) - try colorsContainer.encode(colors, forKey: .colors) - try container.encodeIfPresent(dashPattern, forKey: .dashPattern) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Group.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Group.swift deleted file mode 100644 index c676d04cff..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Group.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// GroupItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Group: ShapeItem { - - /// A list of shape items. - let items: [ShapeItem] - - private enum CodingKeys : String, CodingKey { - case items = "it" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Group.CodingKeys.self) - self.items = try container.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .items) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(items, forKey: .items) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Merge.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Merge.swift deleted file mode 100644 index 3143bb5bfb..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Merge.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Merge.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum MergeMode: Int, Codable { - case none - case merge - case add - case subtract - case intersect - case exclude -} - -/// An item that define an ellipse shape -final class Merge: ShapeItem { - - /// The mode of the merge path - let mode: MergeMode - - private enum CodingKeys : String, CodingKey { - case mode = "mm" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Merge.CodingKeys.self) - self.mode = try container.decode(MergeMode.self, forKey: .mode) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(mode, forKey: .mode) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Rectangle.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Rectangle.swift deleted file mode 100644 index b880e75414..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Rectangle.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Rectangle.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Rectangle: ShapeItem { - - /// The direction of the rect. - let direction: PathDirection - - /// The position - let position: KeyframeGroup - - /// The size - let size: KeyframeGroup - - /// The Corner radius of the rectangle - let cornerRadius: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case direction = "d" - case position = "p" - case size = "s" - case cornerRadius = "r" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Rectangle.CodingKeys.self) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise - self.position = try container.decode(KeyframeGroup.self, forKey: .position) - self.size = try container.decode(KeyframeGroup.self, forKey: .size) - self.cornerRadius = try container.decode(KeyframeGroup.self, forKey: .cornerRadius) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(direction, forKey: .direction) - try container.encode(position, forKey: .position) - try container.encode(size, forKey: .size) - try container.encode(cornerRadius, forKey: .cornerRadius) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Repeater.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Repeater.swift deleted file mode 100644 index e8780f4bf4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Repeater.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// Repeater.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Repeater: ShapeItem { - - /// The number of copies to repeat - let copies: KeyframeGroup - - /// The offset of each copy - let offset: KeyframeGroup - - /// Start Opacity - let startOpacity: KeyframeGroup - - /// End opacity - let endOpacity: KeyframeGroup - - /// The rotation - let rotation: KeyframeGroup - - /// Anchor Point - let anchorPoint: KeyframeGroup - - /// Position - let position: KeyframeGroup - - /// Scale - let scale: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case copies = "c" - case offset = "o" - case transform = "tr" - } - - private enum TransformKeys : String, CodingKey { - case rotation = "r" - case startOpacity = "so" - case endOpacity = "eo" - case anchorPoint = "a" - case position = "p" - case scale = "s" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Repeater.CodingKeys.self) - self.copies = try container.decodeIfPresent(KeyframeGroup.self, forKey: .copies) ?? KeyframeGroup(Vector1D(0)) - self.offset = try container.decodeIfPresent(KeyframeGroup.self, forKey: .offset) ?? KeyframeGroup(Vector1D(0)) - let transformContainer = try container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) - self.startOpacity = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .startOpacity) ?? KeyframeGroup(Vector1D(100)) - self.endOpacity = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .endOpacity) ?? KeyframeGroup(Vector1D(100)) - self.rotation = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) - self.position = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.anchorPoint = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchorPoint) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.scale = try transformContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(copies, forKey: .copies) - try container.encode(offset, forKey: .offset) - var transformContainer = container.nestedContainer(keyedBy: TransformKeys.self, forKey: .transform) - try transformContainer.encode(startOpacity, forKey: .startOpacity) - try transformContainer.encode(endOpacity, forKey: .endOpacity) - try transformContainer.encode(rotation, forKey: .rotation) - try transformContainer.encode(position, forKey: .position) - try transformContainer.encode(anchorPoint, forKey: .anchorPoint) - try transformContainer.encode(scale, forKey: .scale) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Shape.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Shape.swift deleted file mode 100644 index e2681e65b2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Shape.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// VectorShape.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Shape: ShapeItem { - - /// The Path - let path: KeyframeGroup - - let direction: PathDirection? - - private enum CodingKeys : String, CodingKey { - case path = "ks" - case direction = "d" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Shape.CodingKeys.self) - self.path = try container.decode(KeyframeGroup.self, forKey: .path) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(path, forKey: .path) - try container.encodeIfPresent(direction, forKey: .direction) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.swift deleted file mode 100644 index 0444361979..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeItem.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// ShapeItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// Used for mapping a heterogeneous list to classes for parsing. -extension ShapeType: ClassFamily { - - static var discriminator: Discriminator = .type - - func getType() -> AnyObject.Type { - switch self { - case .ellipse: - return Ellipse.self - case .fill: - return Fill.self - case .gradientFill: - return GradientFill.self - case .group: - return Group.self - case .gradientStroke: - return GradientStroke.self - case .merge: - return Merge.self - case .rectangle: - return Rectangle.self - case .repeater: - return Repeater.self - case .shape: - return Shape.self - case .star: - return Star.self - case .stroke: - return Stroke.self - case .trim: - return Trim.self - case .transform: - return ShapeTransform.self - default: - return ShapeItem.self - } - } -} - -enum ShapeType: String, Codable { - case ellipse = "el" - case fill = "fl" - case gradientFill = "gf" - case group = "gr" - case gradientStroke = "gs" - case merge = "mm" - case rectangle = "rc" - case repeater = "rp" - case round = "rd" - case shape = "sh" - case star = "sr" - case stroke = "st" - case trim = "tm" - case transform = "tr" - case unknown - - public init(from decoder: Decoder) throws { - self = try ShapeType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .unknown - } -} - -/// An item belonging to a Shape Layer -class ShapeItem: Codable { - - /// The name of the shape - let name: String - - /// The type of shape - let type: ShapeType - - let hidden: Bool - - private enum CodingKeys : String, CodingKey { - case name = "nm" - case type = "ty" - case hidden = "hd" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ShapeItem.CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer" - self.type = try container.decode(ShapeType.self, forKey: .type) - self.hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.swift deleted file mode 100644 index 5fba165760..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/ShapeTransform.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// TransformItem.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class ShapeTransform: ShapeItem { - - /// Anchor Point - let anchor: KeyframeGroup - - /// Position - let position: KeyframeGroup - - /// Scale - let scale: KeyframeGroup - - /// Rotation - let rotation: KeyframeGroup - - /// opacity - let opacity: KeyframeGroup - - /// Skew - let skew: KeyframeGroup - - /// Skew Axis - let skewAxis: KeyframeGroup - - private enum CodingKeys : String, CodingKey { - case anchor = "a" - case position = "p" - case scale = "s" - case rotation = "r" - case opacity = "o" - case skew = "sk" - case skewAxis = "sa" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: ShapeTransform.CodingKeys.self) - self.anchor = try container.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.position = try container.decodeIfPresent(KeyframeGroup.self, forKey: .position) ?? KeyframeGroup(Vector3D(x: Double(0), y: 0, z: 0)) - self.scale = try container.decodeIfPresent(KeyframeGroup.self, forKey: .scale) ?? KeyframeGroup(Vector3D(x: Double(100), y: 100, z: 100)) - self.rotation = try container.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) ?? KeyframeGroup(Vector1D(0)) - self.opacity = try container.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) ?? KeyframeGroup(Vector1D(100)) - self.skew = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skew) ?? KeyframeGroup(Vector1D(0)) - self.skewAxis = try container.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) ?? KeyframeGroup(Vector1D(0)) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(anchor, forKey: .anchor) - try container.encode(position, forKey: .position) - try container.encode(scale, forKey: .scale) - try container.encode(rotation, forKey: .rotation) - try container.encode(opacity, forKey: .opacity) - try container.encode(skew, forKey: .skew) - try container.encode(skewAxis, forKey: .skewAxis) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Star.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Star.swift deleted file mode 100644 index b280441860..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Star.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// Star.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum StarType: Int, Codable { - case none - case star - case polygon -} - -/// An item that define an ellipse shape -final class Star: ShapeItem { - - /// The direction of the star. - let direction: PathDirection - - /// The position of the star - let position: KeyframeGroup - - /// The outer radius of the star - let outerRadius: KeyframeGroup - - /// The outer roundness of the star - let outerRoundness: KeyframeGroup - - /// The outer radius of the star - let innerRadius: KeyframeGroup? - - /// The outer roundness of the star - let innerRoundness: KeyframeGroup? - - /// The rotation of the star - let rotation: KeyframeGroup - - /// The number of points on the star - let points: KeyframeGroup - - /// The type of star - let starType: StarType - - private enum CodingKeys : String, CodingKey { - case direction = "d" - case position = "p" - case outerRadius = "or" - case outerRoundness = "os" - case innerRadius = "ir" - case innerRoundness = "is" - case rotation = "r" - case points = "pt" - case starType = "sy" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Star.CodingKeys.self) - self.direction = try container.decodeIfPresent(PathDirection.self, forKey: .direction) ?? .clockwise - self.position = try container.decode(KeyframeGroup.self, forKey: .position) - self.outerRadius = try container.decode(KeyframeGroup.self, forKey: .outerRadius) - self.outerRoundness = try container.decode(KeyframeGroup.self, forKey: .outerRoundness) - self.innerRadius = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRadius) - self.innerRoundness = try container.decodeIfPresent(KeyframeGroup.self, forKey: .innerRoundness) - self.rotation = try container.decode(KeyframeGroup.self, forKey: .rotation) - self.points = try container.decode(KeyframeGroup.self, forKey: .points) - self.starType = try container.decode(StarType.self, forKey: .starType) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(direction, forKey: .direction) - try container.encode(position, forKey: .position) - try container.encode(outerRadius, forKey: .outerRadius) - try container.encode(outerRoundness, forKey: .outerRoundness) - try container.encode(innerRadius, forKey: .innerRadius) - try container.encode(innerRoundness, forKey: .innerRoundness) - try container.encode(rotation, forKey: .rotation) - try container.encode(points, forKey: .points) - try container.encode(starType, forKey: .starType) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Stroke.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Stroke.swift deleted file mode 100644 index 5e043edb4b..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Stroke.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// Stroke.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -/// An item that define an ellipse shape -final class Stroke: ShapeItem { - - /// The opacity of the stroke - let opacity: KeyframeGroup - - /// The Color of the stroke - let color: KeyframeGroup - - /// The width of the stroke - let width: KeyframeGroup - - /// Line Cap - let lineCap: LineCap - - /// Line Join - let lineJoin: LineJoin - - /// Miter Limit - let miterLimit: Double - - /// The dash pattern of the stroke - let dashPattern: [DashElement]? - - private enum CodingKeys : String, CodingKey { - case opacity = "o" - case color = "c" - case width = "w" - case lineCap = "lc" - case lineJoin = "lj" - case miterLimit = "ml" - case dashPattern = "d" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Stroke.CodingKeys.self) - self.opacity = try container.decode(KeyframeGroup.self, forKey: .opacity) - self.color = try container.decode(KeyframeGroup.self, forKey: .color) - self.width = try container.decode(KeyframeGroup.self, forKey: .width) - self.lineCap = try container.decodeIfPresent(LineCap.self, forKey: .lineCap) ?? .round - self.lineJoin = try container.decodeIfPresent(LineJoin.self, forKey: .lineJoin) ?? .round - self.miterLimit = try container.decodeIfPresent(Double.self, forKey: .miterLimit) ?? 4 - self.dashPattern = try container.decodeIfPresent([DashElement].self, forKey: .dashPattern) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(opacity, forKey: .opacity) - try container.encode(color, forKey: .color) - try container.encode(width, forKey: .width) - try container.encode(lineCap, forKey: .lineCap) - try container.encode(lineJoin, forKey: .lineJoin) - try container.encode(miterLimit, forKey: .miterLimit) - try container.encodeIfPresent(dashPattern, forKey: .dashPattern) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Trim.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Trim.swift deleted file mode 100644 index dba3d67c9c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/ShapeItems/Trim.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Trim.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation - -enum TrimType: Int, Codable { - case simultaneously = 1 - case individually = 2 -} -/// An item that define an ellipse shape -final class Trim: ShapeItem { - - /// The start of the trim - let start: KeyframeGroup - - /// The end of the trim - let end: KeyframeGroup - - /// The offset of the trim - let offset: KeyframeGroup - - let trimType: TrimType - - private enum CodingKeys : String, CodingKey { - case start = "s" - case end = "e" - case offset = "o" - case trimType = "m" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Trim.CodingKeys.self) - self.start = try container.decode(KeyframeGroup.self, forKey: .start) - self.end = try container.decode(KeyframeGroup.self, forKey: .end) - self.offset = try container.decode(KeyframeGroup.self, forKey: .offset) - self.trimType = try container.decode(TrimType.self, forKey: .trimType) - try super.init(from: decoder) - } - - override func encode(to encoder: Encoder) throws { - try super.encode(to: encoder) - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(start, forKey: .start) - try container.encode(end, forKey: .end) - try container.encode(offset, forKey: .offset) - try container.encode(trimType, forKey: .trimType) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Font.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Font.swift deleted file mode 100644 index 54d75407d8..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Font.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// Font.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class Font: Codable { - - let name: String - let familyName: String - let style: String - let ascent: Double - - private enum CodingKeys: String, CodingKey { - case name = "fName" - case familyName = "fFamily" - case style = "fStyle" - case ascent = "ascent" - } - -} - -/// A list of fonts -final class FontList: Codable { - - let fonts: [Font] - - enum CodingKeys : String, CodingKey { - case fonts = "list" - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Glyph.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Glyph.swift deleted file mode 100644 index 755f00a0c2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/Glyph.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// Glyph.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -/// A model that holds a vector character -final class Glyph: Codable { - - /// The character - let character: String - - /// The font size of the character - let fontSize: Double - - /// The font family of the character - let fontFamily: String - - /// The Style of the character - let fontStyle: String - - /// The Width of the character - let width: Double - - /// The Shape Data of the Character - let shapes: [ShapeItem] - - private enum CodingKeys: String, CodingKey { - case character = "ch" - case fontSize = "size" - case fontFamily = "fFamily" - case fontStyle = "style" - case width = "w" - case shapeWrapper = "data" - } - - private enum ShapeKey: String, CodingKey { - case shapes = "shapes" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Glyph.CodingKeys.self) - self.character = try container.decode(String.self, forKey: .character) - self.fontSize = try container.decode(Double.self, forKey: .fontSize) - self.fontFamily = try container.decode(String.self, forKey: .fontFamily) - self.fontStyle = try container.decode(String.self, forKey: .fontStyle) - self.width = try container.decode(Double.self, forKey: .width) - if container.contains(.shapeWrapper), - let shapeContainer = try? container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper), - shapeContainer.contains(.shapes) { - self.shapes = try shapeContainer.decode([ShapeItem].self, ofFamily: ShapeType.self, forKey: .shapes) - } else { - self.shapes = [] - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(character, forKey: .character) - try container.encode(fontSize, forKey: .fontSize) - try container.encode(fontFamily, forKey: .fontFamily) - try container.encode(fontStyle, forKey: .fontStyle) - try container.encode(width, forKey: .width) - - var shapeContainer = container.nestedContainer(keyedBy: ShapeKey.self, forKey: .shapeWrapper) - try shapeContainer.encode(shapes, forKey: .shapes) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextAnimator.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextAnimator.swift deleted file mode 100644 index dd93cdf6f2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextAnimator.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// TextAnimator.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -final class TextAnimator: Codable { - - let name: String - - /// Anchor - let anchor: KeyframeGroup? - - /// Position - let position: KeyframeGroup? - - /// Scale - let scale: KeyframeGroup? - - /// Skew - let skew: KeyframeGroup? - - /// Skew Axis - let skewAxis: KeyframeGroup? - - /// Rotation - let rotation: KeyframeGroup? - - /// Opacity - let opacity: KeyframeGroup? - - /// Stroke Color - let strokeColor: KeyframeGroup? - - /// Fill Color - let fillColor: KeyframeGroup? - - /// Stroke Width - let strokeWidth: KeyframeGroup? - - /// Tracking - let tracking: KeyframeGroup? - - private enum CodingKeys: String, CodingKey { -// case textSelector = "s" TODO - case textAnimator = "a" - case name = "nm" - } - - private enum TextSelectorKeys: String, CodingKey { - case start = "s" - case end = "e" - case offset = "o" - } - - private enum TextAnimatorKeys: String, CodingKey { - case fillColor = "fc" - case strokeColor = "sc" - case strokeWidth = "sw" - case tracking = "t" - case anchor = "a" - case position = "p" - case scale = "s" - case skew = "sk" - case skewAxis = "sa" - case rotation = "r" - case opacity = "o" - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: TextAnimator.CodingKeys.self) - self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "" - let animatorContainer = try container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) - self.fillColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .fillColor) - self.strokeColor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeColor) - self.strokeWidth = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .strokeWidth) - self.tracking = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .tracking) - self.anchor = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .anchor) - self.position = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .position) - self.scale = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .scale) - self.skew = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skew) - self.skewAxis = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .skewAxis) - self.rotation = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .rotation) - self.opacity = try animatorContainer.decodeIfPresent(KeyframeGroup.self, forKey: .opacity) - - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - var animatorContainer = container.nestedContainer(keyedBy: TextAnimatorKeys.self, forKey: .textAnimator) - try animatorContainer.encodeIfPresent(fillColor, forKey: .fillColor) - try animatorContainer.encodeIfPresent(strokeColor, forKey: .strokeColor) - try animatorContainer.encodeIfPresent(strokeWidth, forKey: .strokeWidth) - try animatorContainer.encodeIfPresent(tracking, forKey: .tracking) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextDocument.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextDocument.swift deleted file mode 100644 index 016fd76803..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Model/Text/TextDocument.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// TextDocument.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/9/19. -// - -import Foundation - -enum TextJustification: Int, Codable { - case left - case right - case center -} - -final class TextDocument: Codable { - - /// The Text - let text: String - - /// The Font size - let fontSize: Double - - /// The Font Family - let fontFamily: String - - /// Justification - let justification: TextJustification - - /// Tracking - let tracking: Int - - /// Line Height - let lineHeight: Double - - /// Baseline - let baseline: Double? - - /// Fill Color data - let fillColorData: Color? - - /// Scroke Color data - let strokeColorData: Color? - - /// Stroke Width - let strokeWidth: Double? - - /// Stroke Over Fill - let strokeOverFill: Bool? - - let textFramePosition: Vector3D? - - let textFrameSize: Vector3D? - - private enum CodingKeys : String, CodingKey { - case text = "t" - case fontSize = "s" - case fontFamily = "f" - case justification = "j" - case tracking = "tr" - case lineHeight = "lh" - case baseline = "ls" - case fillColorData = "fc" - case strokeColorData = "sc" - case strokeWidth = "sw" - case strokeOverFill = "of" - case textFramePosition = "ps" - case textFrameSize = "sz" - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Extensions/ItemsExtension.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Extensions/ItemsExtension.swift deleted file mode 100644 index 9220a637ab..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Extensions/ItemsExtension.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// ItemsExtension.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/18/19. -// - -import Foundation - -final class NodeTree { - var rootNode: AnimatorNode? = nil - var transform: ShapeTransform? = nil - var renderContainers: [ShapeContainerLayer] = [] - var paths: [PathOutputNode] = [] - var childrenNodes: [AnimatorNode] = [] -} - -extension Array where Element == ShapeItem { - func initializeNodeTree() -> NodeTree { - - let nodeTree = NodeTree() - - for item in self { - guard item.hidden == false, item.type != .unknown else { continue } - if let fill = item as? Fill { - let node = FillNode(parentNode: nodeTree.rootNode, fill: fill) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let stroke = item as? Stroke { - let node = StrokeNode(parentNode: nodeTree.rootNode, stroke: stroke) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let gradientFill = item as? GradientFill { - let node = GradientFillNode(parentNode: nodeTree.rootNode, gradientFill: gradientFill) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let gradientStroke = item as? GradientStroke { - let node = GradientStrokeNode(parentNode: nodeTree.rootNode, gradientStroke: gradientStroke) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let ellipse = item as? Ellipse { - let node = EllipseNode(parentNode: nodeTree.rootNode, ellipse: ellipse) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let rect = item as? Rectangle { - let node = RectangleNode(parentNode: nodeTree.rootNode, rectangle: rect) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let star = item as? Star { - switch star.starType { - case .none: - continue - case .polygon: - let node = PolygonNode(parentNode: nodeTree.rootNode, star: star) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - case .star: - let node = StarNode(parentNode: nodeTree.rootNode, star: star) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } - } else if let shape = item as? Shape { - let node = ShapeNode(parentNode: nodeTree.rootNode, shape: shape) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let trim = item as? Trim { - let node = TrimPathNode(parentNode: nodeTree.rootNode, trim: trim, upstreamPaths: nodeTree.paths) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - } else if let xform = item as? ShapeTransform { - nodeTree.transform = xform - continue - } else if let group = item as? Group { - - let tree = group.items.initializeNodeTree() - let node = GroupNode(name: group.name, parentNode: nodeTree.rootNode, tree: tree) - nodeTree.rootNode = node - nodeTree.childrenNodes.append(node) - /// Now add all child paths to current tree - nodeTree.paths.append(contentsOf: tree.paths) - nodeTree.renderContainers.append(node.container) - } - - if let pathNode = nodeTree.rootNode as? PathNode { - //// Add path container to the node tree - nodeTree.paths.append(pathNode.pathOutput) - } - - if let renderNode = nodeTree.rootNode as? RenderNode { - nodeTree.renderContainers.append(ShapeRenderLayer(renderer: renderNode.renderer)) - } - } - return nodeTree - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift deleted file mode 100644 index 5e1048153f..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/NodeProperty.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// NodeProperty.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// A node property that holds a reference to a T ValueProvider and a T ValueContainer. -class NodeProperty: AnyNodeProperty { - - var valueType: Any.Type { return T.self } - - var value: T { - return typedContainer.outputValue - } - - var valueContainer: AnyValueContainer { - return typedContainer - } - - var valueProvider: AnyValueProvider - - init(provider: AnyValueProvider) { - self.valueProvider = provider - self.typedContainer = ValueContainer(provider.value(frame: 0) as! T) - self.typedContainer.setNeedsUpdate() - } - - func needsUpdate(frame: CGFloat) -> Bool { - return valueContainer.needsUpdate || valueProvider.hasUpdate(frame: frame) - } - - func setProvider(provider: AnyValueProvider) { - guard provider.valueType == valueType else { return } - self.valueProvider = provider - valueContainer.setNeedsUpdate() - } - - func update(frame: CGFloat) { - typedContainer.setValue(valueProvider.value(frame: frame), forFrame: frame) - } - - fileprivate var typedContainer: ValueContainer -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift deleted file mode 100644 index 7691f39eb9..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// AnyNodeProperty.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics -/// A property of a node. The node property holds a provider and a container -protocol AnyNodeProperty { - - /// Returns true if the property needs to recompute its stored value - func needsUpdate(frame: CGFloat) -> Bool - - /// Updates the property for the frame - func update(frame: CGFloat) - - /// The stored value container for the property - var valueContainer: AnyValueContainer { get } - - /// The value provider for the property - var valueProvider: AnyValueProvider { get } - - /// The Type of the value provider - var valueType: Any.Type { get } - - /// Sets the value provider for the property. - func setProvider(provider: AnyValueProvider) -} - -extension AnyNodeProperty { - - /// Returns the most recently computed value for the keypath, returns nil if property wasn't found - func getValueOfType() -> T? { - return valueContainer.value as? T - } - - /// Returns the most recently computed value for the keypath, returns nil if property wasn't found - func getValue() -> Any? { - return valueContainer.value - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift deleted file mode 100644 index 96def0b64d..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// AnyValueContainer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// The container for the value of a property. -protocol AnyValueContainer: AnyObject { - - /// The stored value of the container - var value: Any { get } - - /// Notifies the provider that it should update its container - func setNeedsUpdate() - - /// When true the container needs to have its value updated by its provider - var needsUpdate: Bool { get } - - /// The frame time of the last provided update - var lastUpdateFrame: CGFloat { get } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift deleted file mode 100644 index 8b13789179..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/KeypathSearchable.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift deleted file mode 100644 index 11329248a6..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// NodePropertyMap.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/21/19. -// - -import Foundation -import QuartzCore - -protocol NodePropertyMap { - var properties: [AnyNodeProperty] { get } -} - -extension NodePropertyMap { - /// Checks if the node's local contents need to be rebuilt. - func needsLocalUpdate(frame: CGFloat) -> Bool { - for property in properties { - if property.needsUpdate(frame: frame) { - return true - } - } - return false - } - - /// Rebuilds only the local nodes that have an update for the frame - func updateNodeProperties(frame: CGFloat) { - properties.forEach { (property) in - property.update(frame: frame) - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift deleted file mode 100644 index 7f13644fb2..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueContainer.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// ValueContainer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// A container for a node value that is Typed to T. -class ValueContainer: AnyValueContainer { - - private(set) var lastUpdateFrame: CGFloat = CGFloat.infinity - - func setValue(_ value: Any, forFrame: CGFloat) { - if let typedValue = value as? T { - needsUpdate = false - lastUpdateFrame = forFrame - outputValue = typedValue - } - } - - func setNeedsUpdate() { - needsUpdate = true - } - - var value: Any { - return outputValue as Any - } - - var outputValue: T { - didSet { - needsUpdate = false - } - } - - init(_ value: T) { - self.outputValue = value - } - - fileprivate(set) var needsUpdate: Bool = true -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift deleted file mode 100644 index 9470cd6c1d..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/GroupInterpolator.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// KeyframeGroupInterpolator.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation -import CoreGraphics - -/// A value provider that produces an array of values from an array of Keyframe Interpolators -final class GroupInterpolator: AnyValueProvider where ValueType: Interpolatable { - var valueType: Any.Type { - return [ValueType].self - } - - func hasUpdate(frame: CGFloat) -> Bool { - let updated = keyframeInterpolators.first(where: {$0.hasUpdate(frame: frame)}) - return updated != nil - } - - func value(frame: CGFloat) -> Any { - let output = keyframeInterpolators.map({$0.value(frame: frame) as! ValueType}) - return output - } - - /// Initialize with an array of array of keyframes. - init(keyframeGroups: ContiguousArray>>) { - self.keyframeInterpolators = ContiguousArray(keyframeGroups.map({KeyframeInterpolator(keyframes: $0)})) - } - let keyframeInterpolators: ContiguousArray> - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift deleted file mode 100644 index baf46feff5..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.swift +++ /dev/null @@ -1,233 +0,0 @@ -// -// KeyframeInterpolator.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/15/19. -// - -import Foundation -import CoreGraphics - -/// A value provider that produces a value at Time from a group of keyframes -final class KeyframeInterpolator: AnyValueProvider where ValueType: Interpolatable { - - init(keyframes: ContiguousArray>) { - self.keyframes = keyframes - } - let keyframes: ContiguousArray> - - var valueType: Any.Type { - return ValueType.self - } - - /** - Returns true to trigger a frame update for this interpolator. - - An interpolator will be asked if it needs to update every frame. - If the interpolator needs updating it will be asked to compute its value for - the given frame. - - Cases a keyframe should not be updated: - - If time is in span and leading keyframe is hold - - If time is after the last keyframe. - - If time is before the first keyframe - - Cases for updating a keyframe: - - If time is in the span, and is not a hold - - If time is outside of the span, and there are more keyframes - - If a value delegate is set - - If leading and trailing are both nil. - */ - func hasUpdate(frame: CGFloat) -> Bool { - if lastUpdatedFrame == nil { - return true - } - - if let leading = leadingKeyframe, - trailingKeyframe == nil, - leading.time < frame { - /// Frame is after bounds of keyframes - return false - } - if let trailing = trailingKeyframe, - leadingKeyframe == nil, - frame < trailing.time { - /// Frame is before bounds of keyframes - return false - } - if let leading = leadingKeyframe, - let trailing = trailingKeyframe, - leading.isHold, - leading.time < frame, - frame < trailing.time { - return false - } - return true - } - - fileprivate var lastUpdatedFrame: CGFloat? - - @discardableResult - func value(frame: CGFloat) -> Any { - // First set the keyframe span for the frame. - updateSpanIndices(frame: frame) - lastUpdatedFrame = frame - // If only one keyframe return its value - let progress: CGFloat - let value: ValueType - - if let leading = leadingKeyframe, - let trailing = trailingKeyframe { - /// We have leading and trailing keyframe. - progress = leading.interpolatedProgress(trailing, keyTime: frame) - value = leading.interpolate(trailing, progress: progress) - } else if let leading = leadingKeyframe { - progress = 0 - value = leading.value - } else if let trailing = trailingKeyframe { - progress = 1 - value = trailing.value - } else { - /// Satisfy the compiler. - progress = 0 - value = keyframes[0].value - } - return value - } - - fileprivate var leadingIndex: Int? = nil - fileprivate var trailingIndex: Int? = nil - fileprivate var leadingKeyframe: Keyframe? = nil - fileprivate var trailingKeyframe: Keyframe? = nil - - /// Finds the appropriate Leading and Trailing keyframe index for the given time. - fileprivate func updateSpanIndices(frame: CGFloat) { - guard keyframes.count > 0 else { - leadingIndex = nil - trailingIndex = nil - leadingKeyframe = nil - trailingKeyframe = nil - return - } - - /** - This function searches through the array to find the span of two keyframes - that contain the current time. - - We could use Array.first(where:) but that would search through the entire array - each frame. - Instead we track the last used index and search either forwards or - backwards from there. This reduces the iterations and complexity from - - O(n), where n is the length of the sequence to - O(n), where n is the number of items after or before the last used index. - - */ - - if keyframes.count == 1 { - /// Only one keyframe. Set it as first and move on. - leadingIndex = 0 - trailingIndex = nil - leadingKeyframe = keyframes[0] - trailingKeyframe = nil - return - } - - /// Sets the initial keyframes. This is often only needed for the first check. - if leadingIndex == nil && - trailingIndex == nil { - if frame < keyframes[0].time { - /// Time is before the first keyframe. Set it as the trailing. - trailingIndex = 0 - } else { - /// Time is after the first keyframe. Set the keyframe and the trailing. - leadingIndex = 0 - trailingIndex = 1 - } - } - - if let currentTrailing = trailingIndex, - keyframes[currentTrailing].time <= frame { - /// Time is after the current span. Iterate forward. - var newLeading = currentTrailing - var keyframeFound: Bool = false - while !keyframeFound { - - leadingIndex = newLeading - trailingIndex = keyframes.validIndex(newLeading + 1) - - guard let trailing = trailingIndex else { - /// We have reached the end of our keyframes. Time is after the last keyframe. - keyframeFound = true - continue - } - if frame < keyframes[trailing].time { - /// Keyframe in current span. - keyframeFound = true - continue - } - /// Advance the array. - newLeading = trailing - } - - } else if let currentLeading = leadingIndex, - frame < keyframes[currentLeading].time { - - /// Time is before the current span. Iterate backwards - var newTrailing = currentLeading - - var keyframeFound: Bool = false - while !keyframeFound { - - leadingIndex = keyframes.validIndex(newTrailing - 1) - trailingIndex = newTrailing - - guard let leading = leadingIndex else { - /// We have reached the end of our keyframes. Time is after the last keyframe. - keyframeFound = true - continue - } - if keyframes[leading].time <= frame { - /// Keyframe in current span. - keyframeFound = true - continue - } - /// Step back - newTrailing = leading - } - } - if let keyFrame = leadingIndex { - leadingKeyframe = keyframes[keyFrame] - } else { - leadingKeyframe = nil - } - - if let keyFrame = trailingIndex { - trailingKeyframe = keyframes[keyFrame] - } else { - trailingKeyframe = nil - } - } -} - -fileprivate extension Array { - - func validIndex(_ index: Int) -> Int? { - if 0 <= index, index < endIndex { - return index - } - return nil - } - -} - -fileprivate extension ContiguousArray { - - func validIndex(_ index: Int) -> Int? { - if 0 <= index, index < endIndex { - return index - } - return nil - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift deleted file mode 100644 index ba244023ab..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// SingleValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -/// Returns a value for every frame. -final class SingleValueProvider: AnyValueProvider { - - var value: ValueType { - didSet { - hasUpdate = true - } - } - - init(_ value: ValueType) { - self.value = value - } - - var valueType: Any.Type { - return ValueType.self - } - - func hasUpdate(frame: CGFloat) -> Bool { - return hasUpdate - } - - func value(frame: CGFloat) -> Any { - hasUpdate = false - return value - } - - private var hasUpdate: Bool = true -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift deleted file mode 100644 index 0da2b98dca..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/ModifierNodes/TrimPathNode.swift +++ /dev/null @@ -1,244 +0,0 @@ -// -// TrimPathNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/23/19. -// - -import Foundation -import QuartzCore - -final class TrimPathProperties: NodePropertyMap { - - init(trim: Trim) { - self.start = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.start.keyframes)) - self.end = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.end.keyframes)) - self.offset = NodeProperty(provider: KeyframeInterpolator(keyframes: trim.offset.keyframes)) - self.type = trim.trimType - let keypathProperties: [String : AnyNodeProperty] = [ - "Start" : start, - "End" : end, - "Offset" : offset - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let start: NodeProperty - let end: NodeProperty - let offset: NodeProperty - let type: TrimType -} - -final class TrimPathNode: AnimatorNode { - - let properties: TrimPathProperties - - fileprivate let upstreamPaths: [PathOutputNode] - - init(parentNode: AnimatorNode?, trim: Trim, upstreamPaths: [PathOutputNode]) { - self.outputNode = PassThroughOutputNode(parent: parentNode?.outputNode) - self.parentNode = parentNode - self.properties = TrimPathProperties(trim: trim) - self.upstreamPaths = upstreamPaths - } - - // MARK: Animator Node - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - let outputNode: NodeOutput - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true - - func forceUpstreamOutputUpdates() -> Bool { - return hasLocalUpdates || hasUpstreamUpdates - } - - func rebuildOutputs(frame: CGFloat) { - /// Make sure there is a trim. - let startValue = properties.start.value.cgFloatValue * 0.01 - let endValue = properties.end.value.cgFloatValue * 0.01 - let start = min(startValue, endValue) - let end = max(startValue, endValue) - - let offset = properties.offset.value.cgFloatValue.truncatingRemainder(dividingBy: 360) / 360 - - /// No need to trim, it's a full path - if start == 0, end == 1 { - return - } - - /// All paths are empty. - if start == end { - for pathContainer in upstreamPaths { - pathContainer.removePaths(updateFrame: frame) - } - return - } - - if properties.type == .simultaneously { - /// Just trim each path - for pathContainer in upstreamPaths { - let pathObjects = pathContainer.removePaths(updateFrame: frame) - for path in pathObjects { - // We are treating each compount path as an individual path. Its subpaths are treated as a whole. - pathContainer.appendPath(path.trim(fromPosition: start, toPosition: end, offset: offset, trimSimultaneously: false), updateFrame: frame) - } - } - return - } - - /// Individual path trimming. - - /// Brace yourself for the below code. - - /// Normalize lengths with offset. - var startPosition = (start+offset).truncatingRemainder(dividingBy: 1) - var endPosition = (end+offset).truncatingRemainder(dividingBy: 1) - - if startPosition < 0 { - startPosition = 1 + startPosition - } - - if endPosition < 0 { - endPosition = 1 + endPosition - } - if startPosition == 1 { - startPosition = 0 - } - if endPosition == 0 { - endPosition = 1 - } - - - /// First get the total length of all paths. - var totalLength: CGFloat = 0 - upstreamPaths.forEach({ totalLength = totalLength + $0.totalLength }) - - /// Now determine the start and end cut lengths - let startLength = startPosition * totalLength - let endLength = endPosition * totalLength - var pathStart: CGFloat = 0 - - /// Now loop through all path containers - for pathContainer in upstreamPaths { - - let pathEnd = pathStart + pathContainer.totalLength - - if !startLength.isInRange(pathStart, pathEnd) && - endLength.isInRange(pathStart, pathEnd) { - // pathStart|=======E----------------------|pathEnd - // Cut path components, removing after end. - - let pathCutLength = endLength - pathStart - let subpaths = pathContainer.removePaths(updateFrame: frame) - var subpathStart: CGFloat = 0 - for path in subpaths { - let subpathEnd = subpathStart + path.length - if pathCutLength < subpathEnd { - /// This is the subpath that needs to be cut. - let cutLength = pathCutLength - subpathStart - let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - break - } else { - /// Add to container and move on - pathContainer.appendPath(path, updateFrame: frame) - } - if pathCutLength == subpathEnd { - /// Right on the end. The next subpath is not included. Break. - break - } - subpathStart = subpathEnd - } - - } else if !endLength.isInRange(pathStart, pathEnd) && - startLength.isInRange(pathStart, pathEnd) { - // pathStart|-------S======================|pathEnd - // - - // Cut path components, removing before beginning. - let pathCutLength = startLength - pathStart - // Clear paths from container - let subpaths = pathContainer.removePaths(updateFrame: frame) - var subpathStart: CGFloat = 0 - for path in subpaths { - let subpathEnd = subpathStart + path.length - - if subpathStart < pathCutLength, pathCutLength < subpathEnd { - /// This is the subpath that needs to be cut. - let cutLength = pathCutLength - subpathStart - let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - } else if pathCutLength <= subpathStart { - pathContainer.appendPath(path, updateFrame: frame) - } - subpathStart = subpathEnd - } - } else if endLength.isInRange(pathStart, pathEnd) && - startLength.isInRange(pathStart, pathEnd) { - // pathStart|-------S============E---------|endLength - // pathStart|=====E----------------S=======|endLength - // trim from path beginning to endLength. - - // Cut path components, removing before beginnings. - let startCutLength = startLength - pathStart - let endCutLength = endLength - pathStart - // Clear paths from container - let subpaths = pathContainer.removePaths(updateFrame: frame) - var subpathStart: CGFloat = 0 - for path in subpaths { - - let subpathEnd = subpathStart + path.length - - if !startCutLength.isInRange(subpathStart, subpathEnd) && - !endCutLength.isInRange(subpathStart, subpathEnd) { - // The whole path is included. Add - // S|==============================|E - pathContainer.appendPath(path, updateFrame: frame) - - } else if startCutLength.isInRange(subpathStart, subpathEnd) && - !endCutLength.isInRange(subpathStart, subpathEnd) { - /// The start of the path needs to be trimmed - // |-------S======================|E - let cutLength = startCutLength - subpathStart - let newPath = path.trim(fromPosition: cutLength / path.length, toPosition: 1, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - } else if !startCutLength.isInRange(subpathStart, subpathEnd) && - endCutLength.isInRange(subpathStart, subpathEnd) { - // S|=======E----------------------| - let cutLength = endCutLength - subpathStart - let newPath = path.trim(fromPosition: 0, toPosition: cutLength / path.length, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - break - } else if startCutLength.isInRange(subpathStart, subpathEnd) && - endCutLength.isInRange(subpathStart, subpathEnd) { - // |-------S============E---------| - let cutFromLength = startCutLength - subpathStart - let cutToLength = endCutLength - subpathStart - let newPath = path.trim(fromPosition: cutFromLength / path.length, toPosition: cutToLength / path.length, offset: 0, trimSimultaneously: false) - pathContainer.appendPath(newPath, updateFrame: frame) - break - } - - subpathStart = subpathEnd - } - } else if (endLength <= pathStart && pathEnd <= startLength) || - (startLength <= pathStart && endLength <= pathStart) || - (pathEnd <= startLength && pathEnd <= endLength) { - /// The Path needs to be cleared - pathContainer.removePaths(updateFrame: frame) - } - - pathStart = pathEnd - } - - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift deleted file mode 100644 index 2f48762b7c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/GroupOutputNode.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// TransformNodeOutput.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -class GroupOutputNode: NodeOutput { - - init(parent: NodeOutput?, rootNode: NodeOutput?) { - self.parent = parent - self.rootNode = rootNode - } - - let parent: NodeOutput? - let rootNode: NodeOutput? - var isEnabled: Bool = true - - private(set) var outputPath: CGPath? = nil - private(set) var transform: CATransform3D = CATransform3DIdentity - - func setTransform(_ xform: CATransform3D, forFrame: CGFloat) { - transform = xform - outputPath = nil - } - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - guard isEnabled else { - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - outputPath = parent?.outputPath - return upstreamUpdates - } - - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - if upstreamUpdates { - outputPath = nil - } - let rootUpdates = rootNode?.hasOutputUpdates(forFrame) ?? false - if rootUpdates { - outputPath = nil - } - - var localUpdates: Bool = false - if outputPath == nil { - localUpdates = true - - let newPath = CGMutablePath() - if let parentNode = parent, let parentPath = parentNode.outputPath { - /// First add parent path. - newPath.addPath(parentPath) - } - var xform = CATransform3DGetAffineTransform(transform) - if let rootNode = rootNode, - let rootPath = rootNode.outputPath, - let xformedPath = rootPath.copy(using: &xform) { - /// Now add root path. Note root path is transformed. - newPath.addPath(xformedPath) - } - - outputPath = newPath - } - - return upstreamUpdates || localUpdates - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift deleted file mode 100644 index 8a911db243..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// PassThroughOutputNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -class PassThroughOutputNode: NodeOutput { - - init(parent: NodeOutput?) { - self.parent = parent - } - - let parent: NodeOutput? - - var hasUpdate: Bool = false - var isEnabled: Bool = true - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - /// Changes to this node do not affect downstream nodes. - let parentUpdate = parent?.hasOutputUpdates(forFrame) ?? false - /// Changes to upstream nodes do, however, affect this nodes state. - hasUpdate = hasUpdate || parentUpdate - return parentUpdate - } - - var outputPath: CGPath? { - if let parent = parent { - return parent.outputPath - } - return nil - } - - func hasRenderUpdates(_ forFrame: CGFloat) -> Bool { - /// Return true if there are upstream updates or if this node has updates - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - hasUpdate = hasUpdate || upstreamUpdates - return hasUpdate - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift deleted file mode 100644 index f71655b384..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/PathOutputNode.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// PathNodeOutput.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/// A node that has an output of a BezierPath -class PathOutputNode: NodeOutput { - - init(parent: NodeOutput?) { - self.parent = parent - } - - let parent: NodeOutput? - - fileprivate(set) var outputPath: CGPath? = nil - - var lastUpdateFrame: CGFloat? = nil - var lastPathBuildFrame: CGFloat? = nil - var isEnabled: Bool = true - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - guard isEnabled else { - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - outputPath = parent?.outputPath - return upstreamUpdates - } - - /// Ask if parent was updated - let upstreamUpdates = parent?.hasOutputUpdates(forFrame) ?? false - - /// If parent was updated and the path hasn't been built for this frame, clear the path. - if upstreamUpdates && lastPathBuildFrame != forFrame { - outputPath = nil - } - - if outputPath == nil { - /// If the path is clear, build the new path. - lastPathBuildFrame = forFrame - let newPath = CGMutablePath() - if let parentNode = parent, let parentPath = parentNode.outputPath { - newPath.addPath(parentPath) - } - for path in pathObjects { - for subPath in path.paths { - newPath.addPath(subPath.cgPath()) - } - } - outputPath = newPath - } - - /// Return true if there were upstream updates or if this node was updated. - return upstreamUpdates || (lastUpdateFrame == forFrame) - } - - // MARK: Internal - - fileprivate(set) var totalLength: CGFloat = 0 - fileprivate(set) var pathObjects: [CompoundBezierPath] = [] - - @discardableResult func removePaths(updateFrame: CGFloat?) -> [CompoundBezierPath] { - lastUpdateFrame = updateFrame - let returnPaths = pathObjects - outputPath = nil - totalLength = 0 - pathObjects = [] - return returnPaths - } - - func setPath(_ path: BezierPath, updateFrame: CGFloat) { - lastUpdateFrame = updateFrame - outputPath = nil - totalLength = path.length - pathObjects = [CompoundBezierPath(path: path)] - } - - func appendPath(_ path: CompoundBezierPath, updateFrame: CGFloat) { - lastUpdateFrame = updateFrame - outputPath = nil - totalLength = totalLength + path.length - pathObjects.append(path) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift deleted file mode 100644 index dc781210ff..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/FillRenderer.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// FillRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore -import CoreGraphics - -extension FillRule { - var cgFillRule: CGPathFillRule { - switch self { - case .evenOdd: - return .evenOdd - default: - return .winding - } - } - - var caFillRule: CAShapeLayerFillRule { - switch self { - case .evenOdd: - return CAShapeLayerFillRule.evenOdd - default: - return CAShapeLayerFillRule.nonZero - } - } -} - -/// A rendered for a Path Fill -final class FillRenderer: PassThroughOutputNode, Renderable { - - let shouldRenderInContext: Bool = false - - func updateShapeLayer(layer: CAShapeLayer) { - layer.fillColor = color - layer.opacity = Float(opacity) - layer.fillRule = fillRule.caFillRule - hasUpdate = false - } - - var color: CGColor? { - didSet { - hasUpdate = true - } - } - - var opacity: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var fillRule: FillRule = .none { - didSet { - hasUpdate = true - } - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - guard let color = color else { return } - hasUpdate = false - inContext.setAlpha(opacity * 0.01) - inContext.setFillColor(color) - inContext.fillPath(using: fillRule.cgFillRule) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift deleted file mode 100644 index cd54a286ba..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientFillRenderer.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// GradientFillRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -/// A rendered for a Path Fill -final class GradientFillRenderer: PassThroughOutputNode, Renderable { - - var shouldRenderInContext: Bool = true - - func updateShapeLayer(layer: CAShapeLayer) { - // Not applicable - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - hasUpdate = false - var alphaColors = [CGColor]() - var alphaLocations = [CGFloat]() - - var gradientColors = [CGColor]() - var colorLocations = [CGFloat]() - let colorSpace = CGColorSpaceCreateDeviceRGB() - let maskColorSpace = CGColorSpaceCreateDeviceGray() - for i in 0.. ix, let color = CGColor(colorSpace: colorSpace, components: [colors[ix + 1], colors[ix + 2], colors[ix + 3], 1]) { - gradientColors.append(color) - colorLocations.append(colors[ix]) - } - } - - var drawMask = false - for i in stride(from: (numberOfColors * 4), to: colors.endIndex, by: 2) { - let alpha = colors[i + 1] - if alpha < 1 { - drawMask = true - } - if let color = CGColor(colorSpace: maskColorSpace, components: [alpha, 1]) { - alphaLocations.append(colors[i]) - alphaColors.append(color) - } - } - - inContext.setAlpha(opacity) - inContext.clip() - - /// First draw a mask is necessary. - if drawMask { - guard let maskGradient = CGGradient(colorsSpace: maskColorSpace, - colors: alphaColors as CFArray, - locations: alphaLocations), - let maskContext = CGContext(data: nil, - width: inContext.width, - height: inContext.height, - bitsPerComponent: 8, - bytesPerRow: inContext.width, - space: maskColorSpace, - bitmapInfo: 0) else { return } - let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(maskContext.height)) - maskContext.concatenate(flipVertical) - maskContext.concatenate(inContext.ctm) - if type == .linear { - maskContext.drawLinearGradient(maskGradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } else { - maskContext.drawRadialGradient(maskGradient, startCenter: start, startRadius: 0, endCenter: start, endRadius: start.distanceTo(end), options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } - /// Clips the gradient - if let alphaMask = maskContext.makeImage() { - inContext.clip(to: inContext.boundingBoxOfClipPath, mask: alphaMask) - } - } - - /// Now draw the gradient - guard let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors as CFArray, locations: colorLocations) else { return } - if type == .linear { - inContext.drawLinearGradient(gradient, start: start, end: end, options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } else { - inContext.drawRadialGradient(gradient, startCenter: start, startRadius: 0, endCenter: start, endRadius: start.distanceTo(end), options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]) - } - } - - var start: CGPoint = .zero { - didSet { - hasUpdate = true - } - } - - var numberOfColors: Int = 0 { - didSet { - hasUpdate = true - } - } - - var colors: [CGFloat] = [] { - didSet { - hasUpdate = true - } - } - - var end: CGPoint = .zero { - didSet { - hasUpdate = true - } - } - - var opacity: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var type: GradientType = .none { - didSet { - hasUpdate = true - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift deleted file mode 100644 index 7639fa8a4a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/GradientStrokeRenderer.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// GradientStrokeRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -// MARK: - Renderer - -final class GradientStrokeRenderer: PassThroughOutputNode, Renderable { - - override func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - let updates = super.hasOutputUpdates(forFrame) - return updates || strokeRender.hasUpdate || gradientRender.hasUpdate - } - - var shouldRenderInContext: Bool = true - - func updateShapeLayer(layer: CAShapeLayer) { - /// Not Applicable - } - - let strokeRender: StrokeRenderer - let gradientRender: GradientFillRenderer - - override init(parent: NodeOutput?) { - self.strokeRender = StrokeRenderer(parent: nil) - self.gradientRender = GradientFillRenderer(parent: nil) - self.strokeRender.color = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 1, 1, 1]) - super.init(parent: parent) - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - - strokeRender.hasUpdate = false - hasUpdate = false - gradientRender.hasUpdate = false - - strokeRender.setupForStroke(inContext) - - inContext.replacePathWithStrokedPath() - - /// Now draw the gradient. - gradientRender.render(inContext) - - } - - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { - return strokeRender.renderBoundsFor(boundingBox) - } - - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift deleted file mode 100644 index e8d0e8f67a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/OutputNodes/Renderables/StrokeRenderer.swift +++ /dev/null @@ -1,162 +0,0 @@ -// -// StrokeRenderer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -extension LineJoin { - var cgLineJoin: CGLineJoin { - switch self { - case .bevel: - return .bevel - case .none: - return .miter - case .miter: - return .miter - case .round: - return .round - } - } - - var caLineJoin: CAShapeLayerLineJoin { - switch self { - case .none: - return CAShapeLayerLineJoin.miter - case .miter: - return CAShapeLayerLineJoin.miter - case .round: - return CAShapeLayerLineJoin.round - case .bevel: - return CAShapeLayerLineJoin.bevel - } - } -} - -extension LineCap { - var cgLineCap: CGLineCap { - switch self { - case .none: - return .butt - case .butt: - return .butt - case .round: - return .round - case .square: - return .square - } - } - - var caLineCap: CAShapeLayerLineCap { - switch self { - case .none: - return CAShapeLayerLineCap.butt - case .butt: - return CAShapeLayerLineCap.butt - case .round: - return CAShapeLayerLineCap.round - case .square: - return CAShapeLayerLineCap.square - } - } -} - -// MARK: - Renderer - -/// A rendered that renders a stroke on a path. -final class StrokeRenderer: PassThroughOutputNode, Renderable { - - var shouldRenderInContext: Bool = false - - var color: CGColor? { - didSet { - hasUpdate = true - } - } - - var opacity: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var width: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var miterLimit: CGFloat = 0 { - didSet { - hasUpdate = true - } - } - - var lineCap: LineCap = .none { - didSet { - hasUpdate = true - } - } - - var lineJoin: LineJoin = .none { - didSet { - hasUpdate = true - } - } - - var dashPhase: CGFloat? { - didSet { - hasUpdate = true - } - } - - var dashLengths: [CGFloat]? { - didSet { - hasUpdate = true - } - } - - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { - return boundingBox.insetBy(dx: -width, dy: -width) - } - - func setupForStroke(_ inContext: CGContext) { - inContext.setLineWidth(width) - inContext.setMiterLimit(miterLimit) - inContext.setLineCap(lineCap.cgLineCap) - inContext.setLineJoin(lineJoin.cgLineJoin) - if let dashPhase = dashPhase, let lengths = dashLengths { - inContext.setLineDash(phase: dashPhase, lengths: lengths) - } else { - inContext.setLineDash(phase: 0, lengths: []) - } - } - - func render(_ inContext: CGContext) { - guard inContext.path != nil && inContext.path!.isEmpty == false else { - return - } - guard let color = color else { return } - hasUpdate = false - setupForStroke(inContext) - inContext.setAlpha(opacity) - inContext.setStrokeColor(color) - inContext.strokePath() - } - - func updateShapeLayer(layer: CAShapeLayer) { - layer.strokeColor = color - layer.opacity = Float(opacity) - layer.lineWidth = width - layer.lineJoin = lineJoin.caLineJoin - layer.lineCap = lineCap.caLineCap - layer.lineDashPhase = dashPhase ?? 0 - layer.fillColor = nil - if let dashPattern = dashLengths { - layer.lineDashPattern = dashPattern.map({ NSNumber(value: Double($0)) }) - } - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift deleted file mode 100644 index e5e3e7dd5b..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/EllipseNode.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// EllipseNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation -import QuartzCore - -final class EllipseNodeProperties: NodePropertyMap { - - init(ellipse: Ellipse) { - self.direction = ellipse.direction - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.position.keyframes)) - self.size = NodeProperty(provider: KeyframeInterpolator(keyframes: ellipse.size.keyframes)) - self.properties = [] - } - - let direction: PathDirection - let position: NodeProperty - let size: NodeProperty - - let properties: [AnyNodeProperty] -} - -final class EllipseNode: AnimatorNode, PathNode { - - let pathOutput: PathOutputNode - - let properties: EllipseNodeProperties - - init(parentNode: AnimatorNode?, ellipse: Ellipse) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = EllipseNodeProperties(ellipse: ellipse) - self.parentNode = parentNode - } - - // MARK: Animator Node - - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - func rebuildOutputs(frame: CGFloat) { - let ellipseSize = properties.size.value.sizeValue - let center = properties.position.value.pointValue - - // Unfortunately we HAVE to manually build out the ellipse. - // Every Apple method constructs an ellipse from the 3 o-clock position - // After effects constructs from the Noon position. - // After effects does clockwise, but also has a flag for reversed. - - var half = ellipseSize * 0.5 - if properties.direction == .counterClockwise { - half.width = half.width * -1 - } - - - let q1 = CGPoint(x: center.x, y: center.y - half.height) - let q2 = CGPoint(x: center.x + half.width, y: center.y) - let q3 = CGPoint(x: center.x, y: center.y + half.height) - let q4 = CGPoint(x: center.x - half.width, y: center.y) - - let cp = half * EllipseNode.ControlPointConstant - - var path = BezierPath(startPoint: CurveVertex(point: q1, - inTangentRelative: CGPoint(x: -cp.width, y: 0), - outTangentRelative: CGPoint(x: cp.width, y: 0))) - path.addVertex(CurveVertex(point: q2, - inTangentRelative: CGPoint(x: 0, y: -cp.height), - outTangentRelative: CGPoint(x: 0, y: cp.height))) - - path.addVertex(CurveVertex(point: q3, - inTangentRelative: CGPoint(x: cp.width, y: 0), - outTangentRelative: CGPoint(x: -cp.width, y: 0))) - - path.addVertex(CurveVertex(point: q4, - inTangentRelative: CGPoint(x: 0, y: cp.height), - outTangentRelative: CGPoint(x: 0, y: -cp.height))) - - path.addVertex(CurveVertex(point: q1, - inTangentRelative: CGPoint(x: -cp.width, y: 0), - outTangentRelative: CGPoint(x: cp.width, y: 0))) - path.close() - pathOutput.setPath(path, updateFrame: frame) - } - - static let ControlPointConstant: CGFloat = 0.55228 - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift deleted file mode 100644 index 50b4c53f10..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/PolygonNode.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// PolygonNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/21/19. -// - -import Foundation -import QuartzCore - -final class PolygonNodeProperties: NodePropertyMap { - - init(star: Star) { - self.direction = star.direction - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) - self.outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) - self.outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) - self.points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) - let keypathProperties: [String : AnyNodeProperty] = [ - "Position" : position, - "Outer Radius" : outerRadius, - "Outer Roundedness" : outerRoundedness, - "Rotation" : rotation, - "Points" : points - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let direction: PathDirection - let position: NodeProperty - let outerRadius: NodeProperty - let outerRoundedness: NodeProperty - let rotation: NodeProperty - let points: NodeProperty -} - -final class PolygonNode: AnimatorNode, PathNode { - - let properties: PolygonNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, star: Star) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = PolygonNodeProperties(star: star) - self.parentNode = parentNode - } - - // MARK: Animator Node - - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - /// Magic number needed for constructing path. - static let PolygonConstant: CGFloat = 0.25 - - func rebuildOutputs(frame: CGFloat) { - let outerRadius = properties.outerRadius.value.cgFloatValue - let outerRoundedness = properties.outerRoundedness.value.cgFloatValue * 0.01 - let numberOfPoints = properties.points.value.cgFloatValue - let rotation = properties.rotation.value.cgFloatValue - let position = properties.position.value.pointValue - - var currentAngle = (rotation - 90).toRadians() - let anglePerPoint = ((2 * CGFloat.pi) / numberOfPoints) - - var point = CGPoint(x: (outerRadius * cos(currentAngle)), - y: (outerRadius * sin(currentAngle))) - var vertices = [CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)] - - var previousPoint = point - currentAngle += anglePerPoint; - for _ in 0.. - let size: NodeProperty - let cornerRadius: NodeProperty - -} - -final class RectangleNode: AnimatorNode, PathNode { - - let properties: RectNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, rectangle: Rectangle) { - self.properties = RectNodeProperties(rectangle: rectangle) - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.parentNode = parentNode - } - - // MARK: Animator Node - - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - func rebuildOutputs(frame: CGFloat) { - - let size = properties.size.value.sizeValue * 0.5 - let radius = min(min(properties.cornerRadius.value.cgFloatValue, size.width) , size.height) - let position = properties.position.value.pointValue - var bezierPath = BezierPath() - let points: [CurveVertex] - - if radius <= 0 { - /// No Corners - points = [ - /// Lead In - CurveVertex(point: CGPoint(x: size.width, y: -size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 1 - CurveVertex(point: CGPoint(x: size.width, y: size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 2 - CurveVertex(point: CGPoint(x: -size.width, y: size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 3 - CurveVertex(point: CGPoint(x: -size.width, y: -size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - /// Corner 4 - CurveVertex(point: CGPoint(x: size.width, y: -size.height), - inTangentRelative: .zero, - outTangentRelative: .zero) - .translated(position), - ] - } else { - let controlPoint = radius * EllipseNode.ControlPointConstant - points = [ - /// Lead In - CurveVertex( - CGPoint(x: radius, y: 0), - CGPoint(x: radius, y: 0), - CGPoint(x: radius, y: 0)) - .translated(CGPoint(x: -radius, y: radius)) - .translated(CGPoint(x: size.width, y: -size.height)) - .translated(position), - /// Corner 1 - CurveVertex( - CGPoint(x: radius, y: 0), // In tangent - CGPoint(x: radius, y: 0), // Point - CGPoint(x: radius, y: controlPoint)) - .translated(CGPoint(x: -radius, y: -radius)) - .translated(CGPoint(x: size.width, y: size.height)) - .translated(position), - CurveVertex( - CGPoint(x: controlPoint, y: radius), // In tangent - CGPoint(x: 0, y: radius), // Point - CGPoint(x: 0, y: radius)) // Out Tangent - .translated(CGPoint(x: -radius, y: -radius)) - .translated(CGPoint(x: size.width, y: size.height)) - .translated(position), - /// Corner 2 - CurveVertex( - CGPoint(x: 0, y: radius), // In tangent - CGPoint(x: 0, y: radius), // Point - CGPoint(x: -controlPoint, y: radius))// Out tangent - .translated(CGPoint(x: radius, y: -radius)) - .translated(CGPoint(x: -size.width, y: size.height)) - .translated(position), - CurveVertex( - CGPoint(x: -radius, y: controlPoint), // In tangent - CGPoint(x: -radius, y: 0), // Point - CGPoint(x: -radius, y: 0)) // Out tangent - .translated(CGPoint(x: radius, y: -radius)) - .translated(CGPoint(x: -size.width, y: size.height)) - .translated(position), - /// Corner 3 - CurveVertex( - CGPoint(x: -radius, y: 0), // In tangent - CGPoint(x: -radius, y: 0), // Point - CGPoint(x: -radius, y: -controlPoint)) // Out tangent - .translated(CGPoint(x: radius, y: radius)) - .translated(CGPoint(x: -size.width, y: -size.height)) - .translated(position), - CurveVertex( - CGPoint(x: -controlPoint, y: -radius), // In tangent - CGPoint(x: 0, y: -radius), // Point - CGPoint(x: 0, y: -radius)) // Out tangent - .translated(CGPoint(x: radius, y: radius)) - .translated(CGPoint(x: -size.width, y: -size.height)) - .translated(position), - /// Corner 4 - CurveVertex( - CGPoint(x: 0, y: -radius), // In tangent - CGPoint(x: 0, y: -radius), // Point - CGPoint(x: controlPoint, y: -radius)) // Out tangent - .translated(CGPoint(x: -radius, y: radius)) - .translated(CGPoint(x: size.width, y: -size.height)) - .translated(position), - CurveVertex( - CGPoint(x: radius, y: -controlPoint), // In tangent - CGPoint(x: radius, y: 0), // Point - CGPoint(x: radius, y: 0)) // Out tangent - .translated(CGPoint(x: -radius, y: radius)) - .translated(CGPoint(x: size.width, y: -size.height)) - .translated(position), - ] - } - let reversed = properties.direction == .counterClockwise - let pathPoints = reversed ? points.reversed() : points - for point in pathPoints { - bezierPath.addVertex(reversed ? point.reversed() : point) - } - bezierPath.close() - pathOutput.setPath(bezierPath, updateFrame: frame) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift deleted file mode 100644 index 9d05b85bdd..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/ShapeNode.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// PathNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/16/19. -// - -import Foundation -import CoreGraphics - -final class ShapeNodeProperties: NodePropertyMap { - - init(shape: Shape) { - self.path = NodeProperty(provider: KeyframeInterpolator(keyframes: shape.path.keyframes)) - let keypathProperties: [String : AnyNodeProperty] = [ - "Path" : path - ] - self.properties = Array(keypathProperties.values) - } - - let path: NodeProperty - let properties: [AnyNodeProperty] - -} - -final class ShapeNode: AnimatorNode, PathNode { - - let properties: ShapeNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, shape: Shape) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = ShapeNodeProperties(shape: shape) - self.parentNode = parentNode - } - - // MARK: Animator Node - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - func rebuildOutputs(frame: CGFloat) { - pathOutput.setPath(properties.path.value, updateFrame: frame) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift deleted file mode 100644 index 50bad3d9d4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/PathNodes/StarNode.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// StarNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/21/19. -// - -import Foundation -import QuartzCore - -final class StarNodeProperties: NodePropertyMap { - - init(star: Star) { - self.direction = star.direction - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: star.position.keyframes)) - self.outerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRadius.keyframes)) - self.outerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: star.outerRoundness.keyframes)) - if let innerRadiusKeyframes = star.innerRadius?.keyframes { - self.innerRadius = NodeProperty(provider: KeyframeInterpolator(keyframes: innerRadiusKeyframes)) - } else { - self.innerRadius = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - if let innderRoundedness = star.innerRoundness?.keyframes { - self.innerRoundedness = NodeProperty(provider: KeyframeInterpolator(keyframes: innderRoundedness)) - } else { - self.innerRoundedness = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: star.rotation.keyframes)) - self.points = NodeProperty(provider: KeyframeInterpolator(keyframes: star.points.keyframes)) - let keypathProperties: [String : AnyNodeProperty] = [ - "Position" : position, - "Outer Radius" : outerRadius, - "Outer Roundedness" : outerRoundedness, - "Inner Radius" : innerRadius, - "Inner Roundedness" : innerRoundedness, - "Rotation" : rotation, - "Points" : points - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let direction: PathDirection - let position: NodeProperty - let outerRadius: NodeProperty - let outerRoundedness: NodeProperty - let innerRadius: NodeProperty - let innerRoundedness: NodeProperty - let rotation: NodeProperty - let points: NodeProperty -} - -final class StarNode: AnimatorNode, PathNode { - - let properties: StarNodeProperties - - let pathOutput: PathOutputNode - - init(parentNode: AnimatorNode?, star: Star) { - self.pathOutput = PathOutputNode(parent: parentNode?.outputNode) - self.properties = StarNodeProperties(star: star) - self.parentNode = parentNode - } - - // MARK: Animator Node - var propertyMap: NodePropertyMap { - return properties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet{ - self.pathOutput.isEnabled = self.isEnabled - } - } - - /// Magic number needed for building path data - static let PolystarConstant: CGFloat = 0.47829 - - func rebuildOutputs(frame: CGFloat) { - let outerRadius = properties.outerRadius.value.cgFloatValue - let innerRadius = properties.innerRadius.value.cgFloatValue - let outerRoundedness = properties.outerRoundedness.value.cgFloatValue * 0.01 - let innerRoundedness = properties.innerRoundedness.value.cgFloatValue * 0.01 - let numberOfPoints = properties.points.value.cgFloatValue - let rotation = properties.rotation.value.cgFloatValue - let position = properties.position.value.pointValue - - var currentAngle = (rotation - 90).toRadians() - let anglePerPoint = (2 * CGFloat.pi) / numberOfPoints - let halfAnglePerPoint = anglePerPoint / 2.0 - let partialPointAmount = numberOfPoints - floor(numberOfPoints) - - var point: CGPoint = .zero - - var partialPointRadius: CGFloat = 0 - if partialPointAmount != 0 { - currentAngle += halfAnglePerPoint * (1 - partialPointAmount) - partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius) - point.x = (partialPointRadius * cos(currentAngle)) - point.y = (partialPointRadius * sin(currentAngle)) - currentAngle += anglePerPoint * partialPointAmount / 2 - } else { - point.x = (outerRadius * cos(currentAngle)) - point.y = (outerRadius * sin(currentAngle)) - currentAngle += halfAnglePerPoint - } - - var vertices = [CurveVertex]() - vertices.append(CurveVertex(point: point + position, inTangentRelative: .zero, outTangentRelative: .zero)) - - var previousPoint = point - var longSegment = false - let numPoints: Int = Int(ceil(numberOfPoints) * 2) - for i in 0.. - let position: NodeProperty - let scale: NodeProperty - let rotation: NodeProperty - let opacity: NodeProperty - let skew: NodeProperty - let skewAxis: NodeProperty - - var caTransform: CATransform3D { - return CATransform3D.makeTransform(anchor: anchor.value.pointValue, - position: position.value.pointValue, - scale: scale.value.sizeValue, - rotation: rotation.value.cgFloatValue, - skew: skew.value.cgFloatValue, - skewAxis: skewAxis.value.cgFloatValue) - } -} - -final class GroupNode: AnimatorNode { - - // MARK: Properties - let groupOutput: GroupOutputNode - - let properties: GroupNodeProperties - - let rootNode: AnimatorNode? - - var container: ShapeContainerLayer = ShapeContainerLayer() - - // MARK: Initializer - init(name: String, parentNode: AnimatorNode?, tree: NodeTree) { - self.parentNode = parentNode - self.rootNode = tree.rootNode - self.properties = GroupNodeProperties(transform: tree.transform) - self.groupOutput = GroupOutputNode(parent: parentNode?.outputNode, rootNode: rootNode?.outputNode) - - for childContainer in tree.renderContainers { - container.insertRenderLayer(childContainer) - } - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return properties - } - - var outputNode: NodeOutput { - return groupOutput - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - container.isHidden = !isEnabled - } - } - - func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { - return rootNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false - } - - func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { - rootNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) - } - - func rebuildOutputs(frame: CGFloat) { - container.opacity = Float(properties.opacity.value.cgFloatValue) * 0.01 - container.transform = properties.caTransform - groupOutput.setTransform(container.transform, forFrame: frame) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift deleted file mode 100644 index 9347d6c7d9..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/FillNode.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// FillNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation -import CoreGraphics - -final class FillNodeProperties: NodePropertyMap { - - init(fill: Fill) { - self.color = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.color.keyframes)) - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: fill.opacity.keyframes)) - self.type = fill.fillRule - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Color" : color - ] - self.properties = Array(keypathProperties.values) - } - - let opacity: NodeProperty - let color: NodeProperty - let type: FillRule - - let properties: [AnyNodeProperty] - -} - -final class FillNode: AnimatorNode, RenderNode { - - let fillRender: FillRenderer - var renderer: NodeOutput & Renderable { - return fillRender - } - - let fillProperties: FillNodeProperties - - init(parentNode: AnimatorNode?, fill: Fill) { - self.fillRender = FillRenderer(parent: parentNode?.outputNode) - self.fillProperties = FillNodeProperties(fill: fill) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return fillProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - fillRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - fillRender.color = fillProperties.color.value.cgColorValue - fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 - fillRender.fillRule = fillProperties.type - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift deleted file mode 100644 index 54de040e9a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientFillNode.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// GradientFillNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation -import QuartzCore - -final class GradientFillProperties: NodePropertyMap { - - init(gradientfill: GradientFill) { - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.opacity.keyframes)) - self.startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.startPoint.keyframes)) - self.endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.endPoint.keyframes)) - self.colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientfill.colors.keyframes)) - self.gradientType = gradientfill.gradientType - self.numberOfColors = gradientfill.numberOfColors - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Start Point" : startPoint, - "End Point" : endPoint, - "Colors" : colors - ] - self.properties = Array(keypathProperties.values) - } - - let opacity: NodeProperty - let startPoint: NodeProperty - let endPoint: NodeProperty - let colors: NodeProperty<[Double]> - - let gradientType: GradientType - let numberOfColors: Int - - let properties: [AnyNodeProperty] - -} - -final class GradientFillNode: AnimatorNode, RenderNode { - - let fillRender: GradientFillRenderer - - var renderer: NodeOutput & Renderable { - return fillRender - } - - let fillProperties: GradientFillProperties - - init(parentNode: AnimatorNode?, gradientFill: GradientFill) { - self.fillRender = GradientFillRenderer(parent: parentNode?.outputNode) - self.fillProperties = GradientFillProperties(gradientfill: gradientFill) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return fillProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - fillRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - fillRender.start = fillProperties.startPoint.value.pointValue - fillRender.end = fillProperties.endPoint.value.pointValue - fillRender.opacity = fillProperties.opacity.value.cgFloatValue * 0.01 - fillRender.colors = fillProperties.colors.value.map { CGFloat($0) } - fillRender.type = fillProperties.gradientType - fillRender.numberOfColors = fillProperties.numberOfColors - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift deleted file mode 100644 index 0c71467076..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/GradientStrokeNode.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// GradientStrokeNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/23/19. -// - -import Foundation -import CoreGraphics - -// MARK: - Properties - -final class GradientStrokeProperties: NodePropertyMap { - - init(gradientStroke: GradientStroke) { - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.opacity.keyframes)) - self.startPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.startPoint.keyframes)) - self.endPoint = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.endPoint.keyframes)) - self.colors = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.colors.keyframes)) - self.gradientType = gradientStroke.gradientType - self.numberOfColors = gradientStroke.numberOfColors - self.width = NodeProperty(provider: KeyframeInterpolator(keyframes: gradientStroke.width.keyframes)) - self.miterLimit = CGFloat(gradientStroke.miterLimit) - self.lineCap = gradientStroke.lineCap - self.lineJoin = gradientStroke.lineJoin - - if let dashes = gradientStroke.dashPattern { - var dashPatterns = ContiguousArray>>() - var dashPhase = ContiguousArray>() - for dash in dashes { - if dash.type == .offset { - dashPhase = dash.value.keyframes - } else { - dashPatterns.append(dash.value.keyframes) - } - } - self.dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) - self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) - } else { - self.dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) - self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Start Point" : startPoint, - "End Point" : endPoint, - "Colors" : colors, - "Stroke Width" : width, - "Dashes" : dashPattern, - "Dash Phase" : dashPhase - ] - self.properties = Array(keypathProperties.values) - } - - let opacity: NodeProperty - let startPoint: NodeProperty - let endPoint: NodeProperty - let colors: NodeProperty<[Double]> - let width: NodeProperty - - let dashPattern: NodeProperty<[Vector1D]> - let dashPhase: NodeProperty - - let lineCap: LineCap - let lineJoin: LineJoin - let miterLimit: CGFloat - let gradientType: GradientType - let numberOfColors: Int - - - let properties: [AnyNodeProperty] - -} - -// MARK: - Node - -final class GradientStrokeNode: AnimatorNode, RenderNode { - - let strokeRender: GradientStrokeRenderer - - var renderer: NodeOutput & Renderable { - return strokeRender - } - - let strokeProperties: GradientStrokeProperties - - init(parentNode: AnimatorNode?, gradientStroke: GradientStroke) { - self.strokeRender = GradientStrokeRenderer(parent: parentNode?.outputNode) - self.strokeProperties = GradientStrokeProperties(gradientStroke: gradientStroke) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return strokeProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - strokeRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - /// Update gradient properties - strokeRender.gradientRender.start = strokeProperties.startPoint.value.pointValue - strokeRender.gradientRender.end = strokeProperties.endPoint.value.pointValue - strokeRender.gradientRender.opacity = strokeProperties.opacity.value.cgFloatValue - strokeRender.gradientRender.colors = strokeProperties.colors.value.map { CGFloat($0) } - strokeRender.gradientRender.type = strokeProperties.gradientType - strokeRender.gradientRender.numberOfColors = strokeProperties.numberOfColors - - /// Now update stroke properties - strokeRender.strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue - strokeRender.strokeRender.width = strokeProperties.width.value.cgFloatValue - strokeRender.strokeRender.miterLimit = strokeProperties.miterLimit - strokeRender.strokeRender.lineCap = strokeProperties.lineCap - strokeRender.strokeRender.lineJoin = strokeProperties.lineJoin - - /// Get dash lengths - let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } - if dashLengths.count > 0 { - strokeRender.strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue - strokeRender.strokeRender.dashLengths = dashLengths - } else { - strokeRender.strokeRender.dashLengths = nil - strokeRender.strokeRender.dashPhase = nil - } - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift deleted file mode 100644 index 5942268724..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/RenderNodes/StrokeNode.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// StrokeNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/22/19. -// - -import Foundation -import QuartzCore -// MARK: - Properties - -final class StrokeNodeProperties: NodePropertyMap { - - init(stroke: Stroke) { - self.color = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.color.keyframes)) - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.opacity.keyframes)) - self.width = NodeProperty(provider: KeyframeInterpolator(keyframes: stroke.width.keyframes)) - self.miterLimit = CGFloat(stroke.miterLimit) - self.lineCap = stroke.lineCap - self.lineJoin = stroke.lineJoin - - if let dashes = stroke.dashPattern { - var dashPatterns = ContiguousArray>>() - var dashPhase = ContiguousArray>() - for dash in dashes { - if dash.type == .offset { - dashPhase = dash.value.keyframes - } else { - dashPatterns.append(dash.value.keyframes) - } - } - self.dashPattern = NodeProperty(provider: GroupInterpolator(keyframeGroups: dashPatterns)) - if dashPhase.count == 0 { - self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } else { - self.dashPhase = NodeProperty(provider: KeyframeInterpolator(keyframes: dashPhase)) - } - } else { - self.dashPattern = NodeProperty(provider: SingleValueProvider([Vector1D]())) - self.dashPhase = NodeProperty(provider: SingleValueProvider(Vector1D(0))) - } - let keypathProperties: [String : AnyNodeProperty] = [ - "Opacity" : opacity, - "Color" : color, - "Stroke Width" : width, - "Dashes" : dashPattern, - "Dash Phase" : dashPhase - ] - self.properties = Array(keypathProperties.values) - } - - let properties: [AnyNodeProperty] - - let opacity: NodeProperty - let color: NodeProperty - let width: NodeProperty - - let dashPattern: NodeProperty<[Vector1D]> - let dashPhase: NodeProperty - - let lineCap: LineCap - let lineJoin: LineJoin - let miterLimit: CGFloat - -} - -// MARK: - Node - -/// Node that manages stroking a path -final class StrokeNode: AnimatorNode, RenderNode { - - let strokeRender: StrokeRenderer - var renderer: NodeOutput & Renderable { - return strokeRender - } - - let strokeProperties: StrokeNodeProperties - - init(parentNode: AnimatorNode?, stroke: Stroke) { - self.strokeRender = StrokeRenderer(parent: parentNode?.outputNode) - self.strokeProperties = StrokeNodeProperties(stroke: stroke) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return strokeProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true { - didSet { - strokeRender.isEnabled = isEnabled - } - } - - func localUpdatesPermeateDownstream() -> Bool { - return false - } - - func rebuildOutputs(frame: CGFloat) { - strokeRender.color = strokeProperties.color.value.cgColorValue - strokeRender.opacity = strokeProperties.opacity.value.cgFloatValue * 0.01 - strokeRender.width = strokeProperties.width.value.cgFloatValue - strokeRender.miterLimit = strokeProperties.miterLimit - strokeRender.lineCap = strokeProperties.lineCap - strokeRender.lineJoin = strokeProperties.lineJoin - - /// Get dash lengths - let dashLengths = strokeProperties.dashPattern.value.map { $0.cgFloatValue } - if dashLengths.count > 0 { - strokeRender.dashPhase = strokeProperties.dashPhase.value.cgFloatValue - strokeRender.dashLengths = dashLengths - } else { - strokeRender.dashLengths = nil - strokeRender.dashPhase = nil - } - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift deleted file mode 100644 index fbe1117628..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Nodes/Text/TextAnimatorNode.swift +++ /dev/null @@ -1,245 +0,0 @@ -// -// TextAnimatorNode.swift -// lottie-ios-iOS -// -// Created by Brandon Withrow on 2/19/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -final class TextAnimatorNodeProperties: NodePropertyMap { - - init(textAnimator: TextAnimator) { - var properties = [String : AnyNodeProperty]() - - if let keyframeGroup = textAnimator.anchor { - self.anchor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Anchor"] = self.anchor - } else { - self.anchor = nil - } - - if let keyframeGroup = textAnimator.position { - self.position = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Position"] = self.position - } else { - self.position = nil - } - - if let keyframeGroup = textAnimator.scale { - self.scale = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Scale"] = self.scale - } else { - self.scale = nil - } - - if let keyframeGroup = textAnimator.skew { - self.skew = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Skew"] = self.skew - } else { - self.skew = nil - } - - if let keyframeGroup = textAnimator.skewAxis { - self.skewAxis = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Skew Axis"] = self.skewAxis - } else { - self.skewAxis = nil - } - - if let keyframeGroup = textAnimator.rotation { - self.rotation = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Rotation"] = self.rotation - } else { - self.rotation = nil - } - - if let keyframeGroup = textAnimator.opacity { - self.opacity = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Opacity"] = self.opacity - } else { - self.opacity = nil - } - - if let keyframeGroup = textAnimator.strokeColor { - self.strokeColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Stroke Color"] = self.strokeColor - } else { - self.strokeColor = nil - } - - if let keyframeGroup = textAnimator.fillColor { - self.fillColor = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Fill Color"] = self.fillColor - } else { - self.fillColor = nil - } - - if let keyframeGroup = textAnimator.strokeWidth { - self.strokeWidth = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Stroke Width"] = self.strokeWidth - } else { - self.strokeWidth = nil - } - - if let keyframeGroup = textAnimator.tracking { - self.tracking = NodeProperty(provider: KeyframeInterpolator(keyframes: keyframeGroup.keyframes)) - properties["Tracking"] = self.tracking - } else { - self.tracking = nil - } - - self.properties = Array(properties.values) - } - - let anchor: NodeProperty? - let position: NodeProperty? - let scale: NodeProperty? - let skew: NodeProperty? - let skewAxis: NodeProperty? - let rotation: NodeProperty? - let opacity: NodeProperty? - let strokeColor: NodeProperty? - let fillColor: NodeProperty? - let strokeWidth: NodeProperty? - let tracking: NodeProperty? - - let properties: [AnyNodeProperty] - - var caTransform: CATransform3D { - return CATransform3D.makeTransform(anchor: anchor?.value.pointValue ?? .zero, - position: position?.value.pointValue ?? .zero, - scale: scale?.value.sizeValue ?? CGSize(width: 100, height: 100), - rotation: rotation?.value.cgFloatValue ?? 0, - skew: skew?.value.cgFloatValue, - skewAxis: skewAxis?.value.cgFloatValue) - } -} - -final class TextOutputNode: NodeOutput { - - var parent: NodeOutput? { - return parentTextNode - } - - var parentTextNode: TextOutputNode? - var isEnabled: Bool = true - - init(parent: TextOutputNode?) { - self.parentTextNode = parent - } - - fileprivate var _xform: CATransform3D? - fileprivate var _opacity: CGFloat? - fileprivate var _strokeColor: CGColor? - fileprivate var _fillColor: CGColor? - fileprivate var _tracking: CGFloat? - fileprivate var _strokeWidth: CGFloat? - - var xform: CATransform3D { - get { - return _xform ?? parentTextNode?.xform ?? CATransform3DIdentity - } - set { - _xform = newValue - } - } - - var opacity: CGFloat { - get { - return _opacity ?? parentTextNode?.opacity ?? 1 - } - set { - _opacity = newValue - } - } - - var strokeColor: CGColor? { - get { - return _strokeColor ?? parentTextNode?.strokeColor - } - set { - _strokeColor = newValue - } - } - - var fillColor: CGColor? { - get { - return _fillColor ?? parentTextNode?.fillColor - } - set { - _fillColor = newValue - } - } - - var tracking: CGFloat { - get { - return _tracking ?? parentTextNode?.tracking ?? 0 - } - set { - _tracking = newValue - } - } - - var strokeWidth: CGFloat { - get { - return _strokeWidth ?? parentTextNode?.strokeWidth ?? 0 - } - set { - _strokeWidth = newValue - } - } - - - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool { - // TODO Fix This - return true - } - - var outputPath: CGPath? - -} - -class TextAnimatorNode: AnimatorNode { - - let textOutputNode: TextOutputNode - - var outputNode: NodeOutput { - return textOutputNode - } - - let textAnimatorProperties: TextAnimatorNodeProperties - - init(parentNode: TextAnimatorNode?, textAnimator: TextAnimator) { - self.textOutputNode = TextOutputNode(parent: parentNode?.textOutputNode) - self.textAnimatorProperties = TextAnimatorNodeProperties(textAnimator: textAnimator) - self.parentNode = parentNode - } - - // MARK: Animator Node Protocol - - var propertyMap: NodePropertyMap { - return textAnimatorProperties - } - - let parentNode: AnimatorNode? - var hasLocalUpdates: Bool = false - var hasUpstreamUpdates: Bool = false - var lastUpdateFrame: CGFloat? = nil - var isEnabled: Bool = true - - func localUpdatesPermeateDownstream() -> Bool { - return true - } - - func rebuildOutputs(frame: CGFloat) { - textOutputNode.xform = textAnimatorProperties.caTransform - textOutputNode.opacity = (textAnimatorProperties.opacity?.value.cgFloatValue ?? 100) * 0.01 - textOutputNode.strokeColor = textAnimatorProperties.strokeColor?.value.cgColorValue - textOutputNode.fillColor = textAnimatorProperties.fillColor?.value.cgColorValue - textOutputNode.tracking = textAnimatorProperties.tracking?.value.cgFloatValue ?? 1 - textOutputNode.strokeWidth = textAnimatorProperties.strokeWidth?.value.cgFloatValue ?? 0 - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/AnimatorNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/AnimatorNode.swift deleted file mode 100644 index 69413eafd6..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/AnimatorNode.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// AnimatorNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/15/19. -// - -import Foundation -import QuartzCore - -/** - Defines the basic outputs of an animator node. - - */ -protocol NodeOutput { - - /// The parent node. - var parent: NodeOutput? { get } - - /// Returns true if there are any updates upstream. OutputPath must be built before returning. - func hasOutputUpdates(_ forFrame: CGFloat) -> Bool - - var outputPath: CGPath? { get } - - var isEnabled: Bool { get set } -} - -/** - The Animator Node is the base node in the render system tree. - - It defines a single node that has an output path and option input node. - At animation time the root animation node is asked to update its contents for - the current frame. - The node reaches up its chain of nodes until the first node that does not need - updating is found. Then each node updates its contents down the render pipeline. - Each node adds its local path to its input path and passes it forward. - - An animator node holds a group of interpolators. These interpolators determine - if the node needs an update for the current frame. - - */ -protocol AnimatorNode: AnyObject { - - /** - The available properties of the Node. - - These properties are automatically updated each frame. - These properties are also settable and gettable through the dynamic - property system. - - */ - var propertyMap: NodePropertyMap { get } - - /// The upstream input node - var parentNode: AnimatorNode? { get } - - /// The output of the node. - var outputNode: NodeOutput { get } - - /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. - func rebuildOutputs(frame: CGFloat) - - /// Setters for marking current node state. - var isEnabled: Bool { get set } - var hasLocalUpdates: Bool { get set } - var hasUpstreamUpdates: Bool { get set } - var lastUpdateFrame: CGFloat? { get set } - - // MARK: Optional - - /// Marks if updates to this node affect nodes downstream. - func localUpdatesPermeateDownstream() -> Bool - func forceUpstreamOutputUpdates() -> Bool - - /// Called at the end of this nodes update cycle. Always called. Optional. - func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool - func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) - - /// The default simply returns `hasLocalUpdates` - func shouldRebuildOutputs(frame: CGFloat) -> Bool -} - -/// Basic Node Logic -extension AnimatorNode { - - func shouldRebuildOutputs(frame: CGFloat) -> Bool { - return hasLocalUpdates - } - - func localUpdatesPermeateDownstream() -> Bool { - /// Optional override - return true - } - - func forceUpstreamOutputUpdates() -> Bool { - /// Optional - return false - } - - func performAdditionalLocalUpdates(frame: CGFloat, forceLocalUpdate: Bool) -> Bool { - /// Optional - return forceLocalUpdate - } - - func performAdditionalOutputUpdates(_ frame: CGFloat, forceOutputUpdate: Bool) { - /// Optional - } - - @discardableResult func updateOutputs(_ frame: CGFloat, forceOutputUpdate: Bool) -> Bool { - guard isEnabled else { - // Disabled node, pass through. - lastUpdateFrame = frame - return parentNode?.updateOutputs(frame, forceOutputUpdate: forceOutputUpdate) ?? false - } - - if forceOutputUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { - /// This node has already updated for this frame. Go ahead and return the results. - return hasUpstreamUpdates || hasLocalUpdates - } - - /// Ask if this node should force output updates upstream. - let forceUpstreamUpdates = forceOutputUpdate || forceUpstreamOutputUpdates() - - /// Perform upstream output updates. Optionally mark upstream updates if any. - hasUpstreamUpdates = (parentNode?.updateOutputs(frame, forceOutputUpdate: forceUpstreamUpdates) ?? false || hasUpstreamUpdates) - - /// Perform additional local output updates - performAdditionalOutputUpdates(frame, forceOutputUpdate: forceUpstreamUpdates) - - /// If there are local updates, or if updates have been force, rebuild outputs - if forceUpstreamUpdates || shouldRebuildOutputs(frame: frame) { - lastUpdateFrame = frame - rebuildOutputs(frame: frame) - } - return hasUpstreamUpdates || hasLocalUpdates - } - - - /// Rebuilds the content of this node, and upstream nodes if necessary. - @discardableResult func updateContents(_ frame: CGFloat, forceLocalUpdate: Bool) -> Bool { - guard isEnabled else { - // Disabled node, pass through. - return parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false - } - - if forceLocalUpdate == false && lastUpdateFrame != nil && lastUpdateFrame! == frame { - /// This node has already updated for this frame. Go ahead and return the results. - return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates - } - - /// Are there local updates? If so mark the node. - hasLocalUpdates = forceLocalUpdate ? forceLocalUpdate : propertyMap.needsLocalUpdate(frame: frame) - - /// Were there upstream updates? If so mark the node - hasUpstreamUpdates = parentNode?.updateContents(frame, forceLocalUpdate: forceLocalUpdate) ?? false - - /// Perform property updates if necessary. - if hasLocalUpdates { - /// Rebuild local properties - propertyMap.updateNodeProperties(frame: frame) - } - - /// Ask the node to perform any other updates it might have. - hasUpstreamUpdates = performAdditionalLocalUpdates(frame: frame, forceLocalUpdate: forceLocalUpdate) || hasUpstreamUpdates - - /// If the node can update nodes downstream, notify them, otherwise pass on any upstream updates downstream. - return localUpdatesPermeateDownstream() ? hasUpstreamUpdates || hasLocalUpdates : hasUpstreamUpdates - } - - func updateTree(_ frame: CGFloat, forceUpdates: Bool = false) { - updateContents(frame, forceLocalUpdate: forceUpdates) - updateOutputs(frame, forceOutputUpdate: forceUpdates) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/PathNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/PathNode.swift deleted file mode 100644 index e7b2bfbbed..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/PathNode.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// PathNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation - -protocol PathNode { - var pathOutput: PathOutputNode { get } -} - -extension PathNode where Self: AnimatorNode { - - var outputNode: NodeOutput { - return pathOutput - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/RenderNode.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/RenderNode.swift deleted file mode 100644 index 4738f14005..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/Protocols/RenderNode.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// RenderNode.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/17/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -/// A protocol that defines a node that holds render instructions -protocol RenderNode { - var renderer: Renderable & NodeOutput { get } -} - -/// A protocol that defines anything with render instructions -protocol Renderable { - - /// The last frame in which this node was updated. - var hasUpdate: Bool { get } - - func hasRenderUpdates(_ forFrame: CGFloat) -> Bool - - /** - Determines if the renderer requires a custom context for drawing. - If yes the shape layer will perform a custom drawing pass. - If no the shape layer will be a standard CAShapeLayer - */ - var shouldRenderInContext: Bool { get } - - /// Passes in the CAShapeLayer to update - func updateShapeLayer(layer: CAShapeLayer) - - /// Asks the renderer what the renderable bounds is for the given box. - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect - - /// Renders the shape in a custom context - func render(_ inContext: CGContext) -} - -extension RenderNode where Self: AnimatorNode { - - var outputNode: NodeOutput { - return renderer - } - -} - -extension Renderable { - - func renderBoundsFor(_ boundingBox: CGRect) -> CGRect { - /// Optional - return boundingBox - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift deleted file mode 100644 index fcdb60da8e..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeContainerLayer.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// ShapeContainerLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import QuartzCore - -/** - The base layer that holds Shapes and Shape Renderers - */ -class ShapeContainerLayer: CALayer { - private(set) var renderLayers: [ShapeContainerLayer] = [] - - override init() { - super.init() - self.actions = [ - "position" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "transform" : NSNull(), - "opacity" : NSNull(), - "hidden" : NSNull(), - ] - self.anchorPoint = .zero - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override init(layer: Any) { - guard let layer = layer as? ShapeContainerLayer else { - fatalError("init(layer:) wrong class.") - } - super.init(layer: layer) - } - - var renderScale: CGFloat = 1 { - didSet { - updateRenderScale() - } - } - - func insertRenderLayer(_ layer: ShapeContainerLayer) { - renderLayers.append(layer) - insertSublayer(layer, at: 0) - } - - func markRenderUpdates(forFrame: CGFloat) { - if self.hasRenderUpdate(forFrame: forFrame) { - self.rebuildContents(forFrame: forFrame) - } - guard self.isHidden == false else { return } - renderLayers.forEach { $0.markRenderUpdates(forFrame: forFrame) } - } - - func hasRenderUpdate(forFrame: CGFloat) -> Bool { - return false - } - - func rebuildContents(forFrame: CGFloat) { - /// Override - } - - func updateRenderScale() { - renderLayers.forEach( { $0.renderScale = renderScale } ) - } - - func captureGeometry() -> CapturedGeometryNode { - var children: [CapturedGeometryNode] = [] - for renderLayer in self.renderLayers.reversed() { - children.append(renderLayer.captureGeometry()) - } - return CapturedGeometryNode( - transform: self.transform, - alpha: CGFloat(self.opacity), - isHidden: false, - displayItem: self.captureDisplayItem(), - subnodes: children - ) - } - - func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - return nil - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift deleted file mode 100644 index f7cfce2c65..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/NodeRenderSystem/RenderLayers/ShapeRenderLayer.swift +++ /dev/null @@ -1,204 +0,0 @@ -// -// RenderLayer.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/18/19. -// - -import Foundation -import UIKit - -/** - The layer responsible for rendering shape objects - */ -final class ShapeRenderLayer: ShapeContainerLayer { - - fileprivate(set) var renderer: Renderable & NodeOutput - - let shapeLayer: CAShapeLayer = CAShapeLayer() - - init(renderer: Renderable & NodeOutput) { - self.renderer = renderer - super.init() - self.anchorPoint = .zero - self.actions = [ - "position" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "path" : NSNull(), - "transform" : NSNull(), - "opacity" : NSNull(), - "hidden" : NSNull(), - ] - shapeLayer.actions = [ - "position" : NSNull(), - "bounds" : NSNull(), - "anchorPoint" : NSNull(), - "path" : NSNull(), - "fillColor" : NSNull(), - "strokeColor" : NSNull(), - "lineWidth" : NSNull(), - "miterLimit" : NSNull(), - "lineDashPhase" : NSNull(), - "hidden" : NSNull(), - ] - shapeLayer.anchorPoint = .zero - addSublayer(shapeLayer) - } - - override init(layer: Any) { - guard let layer = layer as? ShapeRenderLayer else { - fatalError("init(layer:) wrong class.") - } - self.renderer = layer.renderer - super.init(layer: layer) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func hasRenderUpdate(forFrame: CGFloat) -> Bool { - self.isHidden = !renderer.isEnabled - guard self.isHidden == false else { return false } - return renderer.hasRenderUpdates(forFrame) - } - - override func rebuildContents(forFrame: CGFloat) { - - if renderer.shouldRenderInContext { - if let newPath = renderer.outputPath { - self.bounds = renderer.renderBoundsFor(newPath.boundingBox) - } else { - self.bounds = .zero - } - self.position = bounds.origin - self.setNeedsDisplay() - } else { - shapeLayer.path = renderer.outputPath - renderer.updateShapeLayer(layer: shapeLayer) - } - } - - override func draw(in ctx: CGContext) { - if let path = renderer.outputPath { - if !path.isEmpty { - ctx.addPath(path) - } - } - renderer.render(ctx) - } - - override func captureDisplayItem() -> CapturedGeometryNode.DisplayItem? { - guard let path = renderer.outputPath, !path.isEmpty else { - return nil - } - let display: CapturedGeometryNode.DisplayItem.Display - if let renderer = self.renderer as? FillRenderer { - display = .fill(CapturedGeometryNode.DisplayItem.Display.Fill( - style: .color(color: UIColor(cgColor: renderer.color!), alpha: renderer.opacity), - fillRule: renderer.fillRule.cgFillRule - )) - } else if let renderer = self.renderer as? StrokeRenderer { - display = .stroke(CapturedGeometryNode.DisplayItem.Display.Stroke( - style: .color(color: UIColor(cgColor: renderer.color!), alpha: renderer.opacity), - lineWidth: renderer.width, - lineCap: renderer.lineCap.cgLineCap, - lineJoin: renderer.lineJoin.cgLineJoin, - miterLimit: renderer.miterLimit - )) - } else if let renderer = self.renderer as? GradientFillRenderer { - var gradientColors: [UIColor] = [] - var colorLocations: [CGFloat] = [] - - for i in 0 ..< renderer.numberOfColors { - let ix = i * 4 - if renderer.colors.count > ix { - let color = UIColor(red: CGFloat(renderer.colors[ix + 1]), green: CGFloat(renderer.colors[ix + 2]), blue: CGFloat(renderer.colors[ix + 3]), alpha: renderer.opacity) - gradientColors.append(color) - colorLocations.append(CGFloat(renderer.colors[ix])) - } - } - - var alphaIndex = 0 - for i in stride(from: (renderer.numberOfColors * 4), to: renderer.colors.endIndex, by: 2) { - let alpha = renderer.colors[i + 1] - var currentAlpha: CGFloat = 1.0 - if alphaIndex < gradientColors.count { - gradientColors[alphaIndex].getRed(nil, green: nil, blue: nil, alpha: ¤tAlpha) - gradientColors[alphaIndex] = gradientColors[alphaIndex].withAlphaComponent(alpha * currentAlpha) - } - alphaIndex += 1 - } - - let mappedType: CapturedGeometryNode.DisplayItem.Display.Style.GradientType - switch renderer.type { - case .linear: - mappedType = .linear - case .radial: - mappedType = .radial - case .none: - mappedType = .linear - } - - display = .fill(CapturedGeometryNode.DisplayItem.Display.Fill( - style: .gradient( - colors: gradientColors, - positions: colorLocations, - start: renderer.start, - end: renderer.end, - type: mappedType - ), - fillRule: .evenOdd - )) - } else if let renderer = renderer as? GradientStrokeRenderer { - var gradientColors: [UIColor] = [] - var colorLocations: [CGFloat] = [] - - let gradientRender = renderer.gradientRender - - for i in 0 ..< gradientRender.numberOfColors { - let ix = i * 4 - if gradientRender.colors.count > ix { - let color = UIColor(red: CGFloat(gradientRender.colors[ix + 1]), green: CGFloat(gradientRender.colors[ix + 2]), blue: CGFloat(gradientRender.colors[ix + 3]), alpha: 1.0) - gradientColors.append(color) - colorLocations.append(CGFloat(gradientRender.colors[ix])) - } - } - - var alphaIndex = 0 - for i in stride(from: (gradientRender.numberOfColors * 4), to: gradientRender.colors.endIndex, by: 2) { - let alpha = gradientRender.colors[i + 1] - gradientColors[alphaIndex] = gradientColors[alphaIndex].withAlphaComponent(alpha) - alphaIndex += 1 - } - - let mappedType: CapturedGeometryNode.DisplayItem.Display.Style.GradientType - switch gradientRender.type { - case .linear: - mappedType = .linear - case .radial: - mappedType = .radial - case .none: - mappedType = .linear - } - - display = .stroke(CapturedGeometryNode.DisplayItem.Display.Stroke( - style: .gradient( - colors: gradientColors, - positions: colorLocations, - start: gradientRender.start, - end: gradientRender.end, - type: mappedType - ), - lineWidth: renderer.strokeRender.width, - lineCap: renderer.strokeRender.lineCap.cgLineCap, - lineJoin: renderer.strokeRender.lineJoin.cgLineJoin, - miterLimit: renderer.strokeRender.miterLimit - )) - } else { - return nil - } - return CapturedGeometryNode.DisplayItem(path: path, display: display) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/AnimationKeypathExtension.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/AnimationKeypathExtension.swift deleted file mode 100644 index 8b13789179..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/AnimationKeypathExtension.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/CGFloatExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/CGFloatExtensions.swift deleted file mode 100644 index b7c0d9d283..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/CGFloatExtensions.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// CGFloatExtensions.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import QuartzCore - -extension CGFloat { - - func isInRangeOrEqual(_ from: CGFloat, _ to: CGFloat) -> Bool { - return (from <= self && self <= to) - } - - func isInRange(_ from: CGFloat, _ to: CGFloat) -> Bool { - return (from < self && self < to) - } - - var squared: CGFloat { - return self * self - } - - var cubed: CGFloat { - return self * self * self - } - - var cubicRoot: CGFloat { - return CGFloat(pow(Double(self), 1.0 / 3.0)) - } - - fileprivate static func SolveQuadratic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat) -> CGFloat { - var result = (-b + sqrt(b.squared - 4 * a * c)) / (2 * a); - guard !result.isInRangeOrEqual(0, 1) else { - return result - } - - result = (-b - sqrt(b.squared - 4 * a * c)) / (2 * a); - guard !result.isInRangeOrEqual(0, 1) else { - return result - } - - return -1; - } - - fileprivate static func SolveCubic(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat, _ d: CGFloat) -> CGFloat { - if (a == 0) { - return SolveQuadratic(b, c, d) - } - if (d == 0) { - return 0 - } - let a = a - var b = b - var c = c - var d = d - b /= a - c /= a - d /= a - var q = (3.0 * c - b.squared) / 9.0 - let r = (-27.0 * d + b * (9.0 * c - 2.0 * b.squared)) / 54.0 - let disc = q.cubed + r.squared - let term1 = b / 3.0 - - if (disc > 0) { - var s = r + sqrt(disc) - s = (s < 0) ? -((-s).cubicRoot) : s.cubicRoot - var t = r - sqrt(disc) - t = (t < 0) ? -((-t).cubicRoot) : t.cubicRoot - - let result = -term1 + s + t; - if result.isInRangeOrEqual(0, 1) { - return result - } - } else if (disc == 0) { - let r13 = (r < 0) ? -((-r).cubicRoot) : r.cubicRoot; - - var result = -term1 + 2.0 * r13; - if result.isInRangeOrEqual(0, 1) { - return result - } - - result = -(r13 + term1); - if result.isInRangeOrEqual(0, 1) { - return result - } - - } else { - q = -q; - var dum1 = q * q * q; - dum1 = acos(r / sqrt(dum1)); - let r13 = 2.0 * sqrt(q); - - var result = -term1 + r13 * cos(dum1 / 3.0); - if result.isInRangeOrEqual(0, 1) { - return result - } - result = -term1 + r13 * cos((dum1 + 2.0 * .pi) / 3.0); - if result.isInRangeOrEqual(0, 1) { - return result - } - result = -term1 + r13 * cos((dum1 + 4.0 * .pi) / 3.0); - if result.isInRangeOrEqual(0, 1) { - return result - } - } - - return -1; - } - - func cubicBezierInterpolate(_ P0: CGPoint, _ P1: CGPoint, _ P2: CGPoint, _ P3: CGPoint) -> CGFloat { - var t: CGFloat - if (self == P0.x) { - // Handle corner cases explicitly to prevent rounding errors - t = 0 - } else if (self == P3.x) { - t = 1 - } else { - // Calculate t - let a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; - let b = 3 * P0.x - 6 * P1.x + 3 * P2.x; - let c = -3 * P0.x + 3 * P1.x; - let d = P0.x - self; - let tTemp = CGFloat.SolveCubic(a, b, c, d); - if (tTemp == -1) { - return -1; - } - t = tTemp - } - - // Calculate y from t - return (1 - t).cubed * P0.y + 3 * t * (1 - t).squared * P1.y + 3 * t.squared * (1 - t) * P2.y + t.cubed * P3.y; - } - - func cubicBezier(_ t: CGFloat, _ c1: CGFloat, _ c2: CGFloat, _ end: CGFloat) -> CGFloat { - let t_ = (1.0 - t) - let tt_ = t_ * t_ - let ttt_ = t_ * t_ * t_ - let tt = t * t - let ttt = t * t * t - - return self * ttt_ - + 3.0 * c1 * tt_ * t - + 3.0 * c2 * t_ * tt - + end * ttt; - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/MathKit.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/MathKit.swift deleted file mode 100644 index 7a2ad297a4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/MathKit.swift +++ /dev/null @@ -1,539 +0,0 @@ -// -// MathKit.swift -// UIToolBox -// -// Created by Brandon Withrow on 10/10/18. -// -// From https://github.com/buba447/UIToolBox - -import Foundation -import CoreGraphics - -extension Int { - var cgFloat: CGFloat { - return CGFloat(self) - } -} - -extension Double { - var cgFloat: CGFloat { - return CGFloat(self) - } -} - -extension CGFloat: Interpolatable { - - - /** - Interpolates the receiver to the given number by Amount. - - Parameter toNumber: The number to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let number = 5 - let interpolated = number.interpolateTo(10, amount: 0.5) - print(interpolated) - // Result: 7.5 - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: CGFloat, amount: CGFloat) -> CGFloat { - return self + ((to - self) * CGFloat(amount)) - } - - func interpolateTo(_ to: CGFloat, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CGFloat { - return interpolateTo(to, amount: amount) - } - - func remap(fromLow: CGFloat, fromHigh: CGFloat, toLow: CGFloat, toHigh: CGFloat) -> CGFloat { - guard (fromHigh - fromLow) != 0 else { - // Would produce NAN - return 0 - } - return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) - } - - /** - Returns a value that is clamped between the two numbers - - 1. The order of arguments does not matter. - */ - func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat { - return CGFloat(Double(self).clamp(Double(a), Double(b))) - } - - /** - Returns the difference between the receiver and the given number. - - Parameter absolute: If *true* (Default) the returned value will always be positive. - */ - func diff(_ a: CGFloat, absolute: Bool = true) -> CGFloat { - return absolute ? abs(a - self) : a - self - } - - func toRadians() -> CGFloat { return self * .pi / 180 } - func toDegrees() -> CGFloat { return self * 180 / .pi } - -} - -extension Double: Interpolatable { - - /** - Interpolates the receiver to the given number by Amount. - - Parameter toNumber: The number to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let number = 5 - let interpolated = number.interpolateTo(10, amount: 0.5) - print(interpolated) - // Result: 7.5 - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: Double, amount: CGFloat) -> Double { - return self + ((to - self) * Double(amount)) - } - - func interpolateTo(_ to: Double, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Double { - return interpolateTo(to, amount: amount) - } - - func remap(fromLow: Double, fromHigh: Double, toLow: Double, toHigh: Double) -> Double { - return toLow + (self - fromLow) * (toHigh - toLow) / (fromHigh - fromLow) - } - - /** - Returns a value that is clamped between the two numbers - - 1. The order of arguments does not matter. - */ - func clamp(_ a: Double, _ b: Double) -> Double { - let minValue = a <= b ? a : b - let maxValue = a <= b ? b : a - return max(min(self, maxValue), minValue) - } - -} - -extension CGRect { - - /// Initializes a new CGRect with a center point and size. - init(center: CGPoint, size: CGSize) { - self.init(x: center.x - (size.width * 0.5), - y: center.y - (size.height * 0.5), - width: size.width, - height: size.height) - } - - /// Returns the total area of the rect. - var area: CGFloat { - return width * height - } - - - /// The center point of the rect. Settable. - var center: CGPoint { - get { - return CGPoint(x: midX, y: midY) - } - set { - origin = CGPoint(x: newValue.x - (size.width * 0.5), - y: newValue.y - (size.height * 0.5)) - } - } - - /// The top left point of the rect. Settable. - var topLeft: CGPoint { - get { - return CGPoint(x: minX, y: minY) - } - set { - origin = CGPoint(x: newValue.x, - y: newValue.y) - } - } - - /// The bottom left point of the rect. Settable. - var bottomLeft: CGPoint { - get { - return CGPoint(x: minX, y: maxY) - } - set { - origin = CGPoint(x: newValue.x, - y: newValue.y - size.height) - } - } - - /// The top right point of the rect. Settable. - var topRight: CGPoint { - get { - return CGPoint(x: maxX, y: minY) - } - set { - origin = CGPoint(x: newValue.x - size.width, - y: newValue.y) - } - } - - /// The bottom right point of the rect. Settable. - var bottomRight: CGPoint { - get { - return CGPoint(x: maxX, y: maxY) - } - set { - origin = CGPoint(x: newValue.x - size.width, - y: newValue.y - size.height) - } - } - - /** - Interpolates the receiver to the given rect by Amount. - - Parameter to: The rect to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let rect = CGRect(x:0, y:0, width: 50, height: 50) - let interpolated = rect.interpolateTo(CGRect(x:100, y:100, width: 100, height: 100), amount: 0.5) - print(interpolated) - // Result: (x: 50, y: 50, width: 75, height: 75) - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: CGRect, amount: CGFloat) -> CGRect { - return CGRect(x: origin.x.interpolateTo(to.origin.x, amount: amount), - y: origin.y.interpolateTo(to.origin.y, amount: amount), - width: width.interpolateTo(to.width, amount: amount), - height: height.interpolateTo(to.height, amount: amount)) - } - -} - -extension CGSize { - - /** - Interpolates the receiver to the given size by Amount. - - Parameter to: The size to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let size = CGSize(width: 50, height: 50) - let interpolated = rect.interpolateTo(CGSize(width: 100, height: 100), amount: 0.5) - print(interpolated) - // Result: (width: 75, height: 75) - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - func interpolateTo(_ to: CGSize, amount: CGFloat) -> CGSize { - return CGSize(width: width.interpolateTo(to.width, amount: amount), - height: height.interpolateTo(to.height, amount: amount)) - } - - /// Returns the scale float that will fit the receive inside of the given size. - func scaleThatFits(_ size: CGSize) -> CGFloat { - return CGFloat.minimum(width / size.width, height / size.height) - } - - /// Adds receiver size to give size. - func add(_ size: CGSize) -> CGSize { - return CGSize(width: width + size.width, height: height + size.height) - } - - /// Subtracts given size from receiver size. - func subtract(_ size: CGSize) -> CGSize { - return CGSize(width: width - size.width, height: height - size.height) - } - - /// Multiplies receiver size by the given size. - func multiply(_ size: CGSize) -> CGSize { - return CGSize(width: width * size.width, height: height * size.height) - } - - /// Operator convenience to add sizes with + - static func +(left: CGSize, right: CGSize) -> CGSize { - return left.add(right) - } - - /// Operator convenience to subtract sizes with - - static func -(left: CGSize, right: CGSize) -> CGSize { - return left.subtract(right) - } - - /// Operator convenience to multiply sizes with * - static func *(left: CGSize, right: CGFloat) -> CGSize { - return CGSize(width: left.width * right, height: left.height * right) - } - -} - -/// A struct that defines a line segment with two CGPoints -struct CGLine { - - /// The Start of the line segment. - var start: CGPoint - /// The End of the line segment. - var end: CGPoint - - /// Initializes a line segment with start and end points - init(start: CGPoint, end: CGPoint) { - self.start = start - self.end = end - } - - /// The length of the line segment. - var length: CGFloat { - return end.distanceTo(start) - } - - /// Returns a line segment that is normalized to a length of 1 - func normalize() -> CGLine { - let len = length - guard len > 0 else { - return self - } - let relativeEnd = end - start - let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) - let absoluteVector = relativeVector + start - return CGLine(start: start, end: absoluteVector) - } - - /// Trims a line segment to the given length - func trimmedToLength(_ toLength: CGFloat) -> CGLine { - let len = length - guard len > 0 else { - return self - } - let relativeEnd = end - start - let relativeVector = CGPoint(x: relativeEnd.x / len, y: relativeEnd.y / len) - let sizedVector = CGPoint(x: relativeVector.x * toLength, y: relativeVector.y * toLength) - let absoluteVector = sizedVector + start - return CGLine(start: start, end: absoluteVector) - } - - /// Flips a line vertically and horizontally from the start point. - func flipped() -> CGLine { - let relativeEnd = end - start - let flippedEnd = CGPoint(x: relativeEnd.x * -1, y: relativeEnd.y * -1) - return CGLine(start: start, end: flippedEnd + start) - } - - /// Move the line to the new start point. - func transpose(_ toPoint: CGPoint) -> CGLine { - let diff = toPoint - start - let newEnd = end + diff - return CGLine(start: toPoint, end: newEnd) - } - -} - -infix operator +| -infix operator +- - -extension CGPoint: Interpolatable { - - /// Returns the distance between the receiver and the given point. - func distanceTo(_ a: CGPoint) -> CGFloat { - let xDist = a.x - x - let yDist = a.y - y - return CGFloat(sqrt((xDist * xDist) + (yDist * yDist))) - } - - /// Returns the length between the receiver and *CGPoint.zero* - var vectorLength: CGFloat { - return distanceTo(.zero) - } - - func rounded(decimal: CGFloat) -> CGPoint { - return CGPoint(x: (round(decimal * x) / decimal), y: (round(decimal * y) / decimal)) - } - - /** - Interpolates the receiver to the given Point by Amount. - - Parameter to: The Point to interpolate to. - - Parameter amount: The amount to interpolate from 0-1 - - ``` - let point = CGPoint(width: 50, height: 50) - let interpolated = rect.interpolateTo(CGPoint(width: 100, height: 100), amount: 0.5) - print(interpolated) - // Result: (x: 75, y: 75) - ``` - - 1. The amount can be greater than one and less than zero. The interpolation will not be clipped. - */ - - func interpolate(_ to: CGPoint, amount: CGFloat) -> CGPoint { - return CGPoint(x: x.interpolateTo(to.x, amount: amount), - y: y.interpolateTo(to.y, amount: amount)) - } - - func interpolate(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat, maxIterations: Int = 3, samples: Int = 20, accuracy: CGFloat = 1) -> CGPoint { - if amount == 0 { - return self - } - if amount == 1 { - return to - } - - if self.colinear(outTangent, inTangent) == true, - outTangent.colinear(inTangent, to) == true { - return interpolate(to, amount: amount) - } - - let step = 1 / CGFloat(samples) - - var points: [(point: CGPoint, distance: CGFloat)] = [(point: self, distance: 0)] - var totalLength: CGFloat = 0 - - var previousPoint = self - var previousAmount = CGFloat(0) - - var closestPoint: Int = 0 - - while previousAmount < 1 { - - previousAmount = previousAmount + step - - if previousAmount < amount { - closestPoint = closestPoint + 1 - } - - let newPoint = self.pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: previousAmount) - let distance = previousPoint.distanceTo(newPoint) - totalLength = totalLength + distance - points.append((point: newPoint, distance: totalLength)) - previousPoint = newPoint - } - - let accurateDistance = amount * totalLength - var point = points[closestPoint] - - var foundPoint: Bool = false - - var pointAmount: CGFloat = CGFloat(closestPoint) * step - var nextPointAmount: CGFloat = pointAmount + step - - var refineIterations = 0 - while foundPoint == false { - refineIterations = refineIterations + 1 - /// First see if the next point is still less than the projected length. - let nextPoint = points[closestPoint + 1] - if nextPoint.distance < accurateDistance { - point = nextPoint - closestPoint = closestPoint + 1 - pointAmount = CGFloat(closestPoint) * step - nextPointAmount = pointAmount + step - if closestPoint == points.count { - foundPoint = true - } - continue - } - if accurateDistance < point.distance { - closestPoint = closestPoint - 1 - if closestPoint < 0 { - foundPoint = true - continue - } - point = points[closestPoint] - pointAmount = CGFloat(closestPoint) * step - nextPointAmount = pointAmount + step - continue - } - - /// Now we are certain the point is the closest point under the distance - let pointDiff = nextPoint.distance - point.distance - let proposedPointAmount = ((accurateDistance - point.distance) / pointDiff).remap(fromLow: 0, fromHigh: 1, toLow: pointAmount, toHigh: nextPointAmount) - - let newPoint = self.pointOnPath(to, outTangent: outTangent, inTangent: inTangent, amount: proposedPointAmount) - let newDistance = point.distance + point.point.distanceTo(newPoint) - pointAmount = proposedPointAmount - point = (point: newPoint, distance: newDistance) - if accurateDistance - newDistance <= accuracy || - newDistance - accurateDistance <= accuracy { - foundPoint = true - } - - if refineIterations == maxIterations { - foundPoint = true - } - } - return point.point - } - - func pointOnPath(_ to: CGPoint, outTangent: CGPoint, inTangent: CGPoint, amount: CGFloat) -> CGPoint { - let a = self.interpolate(outTangent, amount: amount) - let b = outTangent.interpolate(inTangent, amount: amount) - let c = inTangent.interpolate(to, amount: amount) - let d = a.interpolate(b, amount: amount) - let e = b.interpolate(c, amount: amount) - let f = d.interpolate(e, amount: amount) - return f - } - - func colinear(_ a: CGPoint, _ b: CGPoint) -> Bool { - let area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); - let accuracy: CGFloat = 0.05 - if area < accuracy && area > -accuracy { - return true - } - return false - } - - func interpolateTo(_ to: CGPoint, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CGPoint { - guard let outTan = spatialOutTangent, - let inTan = spatialInTangent else { - return interpolate(to, amount: amount) - } - let cp1 = self + outTan - let cp2 = to + inTan - - return interpolate(to, outTangent: cp1, inTangent: cp2, amount: amount) - } - - /// Subtracts the given point from the receiving point. - func subtract(_ point: CGPoint) -> CGPoint { - return CGPoint(x: x - point.x, - y: y - point.y) - } - - /// Adds the given point from the receiving point. - func add(_ point: CGPoint) -> CGPoint { - return CGPoint(x: x + point.x, - y: y + point.y) - } - - var isZero: Bool { - return (x == 0 && y == 0) - } - - /// Operator convenience to divide points with / - static func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { - return CGPoint(x: lhs.x / CGFloat(rhs), y: lhs.y / CGFloat(rhs)) - } - - /// Operator convenience to multiply points with * - static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { - return CGPoint(x: lhs.x * CGFloat(rhs), y: lhs.y * CGFloat(rhs)) - } - - /// Operator convenience to add points with + - static func +(left: CGPoint, right: CGPoint) -> CGPoint { - return left.add(right) - } - - /// Operator convenience to subtract points with - - static func -(left: CGPoint, right: CGPoint) -> CGPoint { - return left.subtract(right) - } - - static func +|(left: CGPoint, right: CGFloat) -> CGPoint { - return CGPoint(x: left.x, y: left.y + right) - } - - static func +-(left: CGPoint, right: CGFloat) -> CGPoint { - return CGPoint(x: left.x + right, y: left.y) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/StringExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/StringExtensions.swift deleted file mode 100644 index 99dd16e780..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Extensions/StringExtensions.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// StringExtensions.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/25/19. -// - -import Foundation -import CoreGraphics - -extension String { - - func hexColorComponents() -> (red: CGFloat, green: CGFloat, blue: CGFloat) { - - var cString:String = trimmingCharacters(in: .whitespacesAndNewlines).uppercased() - - if (cString.hasPrefix("#")) { - cString.remove(at: cString.startIndex) - } - - if ((cString.count) != 6) { - return (red: 0, green: 0, blue: 0) - } - - var rgbValue:UInt64 = 0 - Scanner(string: cString).scanHexInt64(&rgbValue) - - return (red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, - green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, - blue: CGFloat(rgbValue & 0x0000FF) / 255.0) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/Interpolatable.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/Interpolatable.swift deleted file mode 100644 index 061e03c4a7..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/Interpolatable.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Interpolatable.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -protocol Interpolatable { - - func interpolateTo(_ to: Self, - amount: CGFloat, - spatialOutTangent: CGPoint?, - spatialInTangent: CGPoint?) -> Self - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/InterpolatableExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/InterpolatableExtensions.swift deleted file mode 100644 index fd5d6e5c23..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Interpolatable/InterpolatableExtensions.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// InterpolatableExtensions.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -extension Vector1D: Interpolatable { - func interpolateTo(_ to: Vector1D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector1D { - return value.interpolateTo(to.value, amount: amount).vectorValue - } -} - -extension Vector2D: Interpolatable { - func interpolateTo(_ to: Vector2D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector2D { - return pointValue.interpolateTo(to.pointValue, amount: CGFloat(amount), spatialOutTangent: spatialOutTangent, spatialInTangent: spatialInTangent).vector2dValue - } - -} - -extension Vector3D: Interpolatable { - func interpolateTo(_ to: Vector3D, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Vector3D { - if spatialInTangent != nil || spatialOutTangent != nil { - // TODO Support third dimension spatial interpolation - let point = pointValue.interpolateTo(to.pointValue, amount: amount, spatialOutTangent: spatialOutTangent, spatialInTangent: spatialInTangent) - return Vector3D(x: point.x, - y: point.y, - z: CGFloat(z.interpolateTo(to.z, amount: amount))) - } - return Vector3D(x: x.interpolateTo(to.x, amount: amount), - y: y.interpolateTo(to.y, amount: amount), - z: z.interpolateTo(to.z, amount: amount)) - } -} - -extension Color: Interpolatable { - - /// Initialize a new color with Hue Saturation and Value - init(h: Double, s: Double, v: Double, a: Double) { - - let i = floor(h * 6) - let f = h * 6 - i - let p = v * (1 - s); - let q = v * (1 - f * s) - let t = v * (1 - (1 - f) * s) - - switch (i.truncatingRemainder(dividingBy: 6)) { - case 0: - self.r = v - self.g = t - self.b = p - case 1: - self.r = q - self.g = v - self.b = p - case 2: - self.r = p - self.g = v - self.b = t - case 3: - self.r = p - self.g = q - self.b = v - case 4: - self.r = t - self.g = p - self.b = v - case 5: - self.r = v - self.g = p - self.b = q - default: - self.r = 0 - self.g = 0 - self.b = 0 - } - self.a = a - } - - /// Hue Saturation Value of the color. - var hsva: (h: Double, s: Double, v: Double, a: Double) { - let maxValue = max(r, g, b) - let minValue = min(r, g, b) - - var h: Double, s: Double, v: Double = maxValue - - let d = maxValue - minValue - s = maxValue == 0 ? 0 : d / maxValue; - - if (maxValue == minValue) { - h = 0; // achromatic - } else { - switch (maxValue) { - case r: h = (g - b) / d + (g < b ? 6 : 0) - case g: h = (b - r) / d + 2 - case b: h = (r - g) / d + 4 - default: h = maxValue - } - h = h / 6 - } - return (h: h, s: s, v: v, a: a) - } - - init(y: Double, u: Double, v: Double, a: Double) { - // From https://www.fourcc.org/fccyvrgb.php - self.r = y + 1.403 * v - self.g = y - 0.344 * u - self.b = y + 1.770 * u - self.a = a - } - - var yuv: (y: Double, u: Double, v: Double, a: Double) { - /// From https://www.fourcc.org/fccyvrgb.php - let y = 0.299 * r + 0.587 * g + 0.114 * b - let u = -0.14713 * r - 0.28886 * g + 0.436 * b - let v = 0.615 * r - 0.51499 * g - 0.10001 * b - return (y: y, u: u, v: v, a: a) - } - - func interpolateTo(_ to: Color, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Color { - return Color(r: r.interpolateTo(to.r, amount: amount), - g: g.interpolateTo(to.g, amount: amount), - b: b.interpolateTo(to.b, amount: amount), - a: a.interpolateTo(to.a, amount: amount)) - } -} - -extension CurveVertex: Interpolatable { - func interpolateTo(_ to: CurveVertex, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> CurveVertex { - return CurveVertex(point: point.interpolate(to.point, amount: amount), - inTangent: inTangent.interpolate(to.inTangent, amount: amount), - outTangent: outTangent.interpolate(to.outTangent, amount: amount)) - } -} - -extension BezierPath: Interpolatable { - func interpolateTo(_ to: BezierPath, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> BezierPath { - var newPath = BezierPath() - for i in 0.. TextDocument { - if amount == 1 { - return to - } - return self - } -} - -extension Array: Interpolatable where Element == Double { - func interpolateTo(_ to: Array, amount: CGFloat, spatialOutTangent: CGPoint?, spatialInTangent: CGPoint?) -> Array { - var returnArray = [Double]() - for i in 0.. CGFloat { - let startTime = time - let endTime = to.time - if keyTime <= startTime { - return 0 - } - if endTime <= keyTime { - return 1 - } - - if isHold { - return 0 - } - - let outTanPoint = outTangent?.pointValue ?? .zero - let inTanPoint = to.inTangent?.pointValue ?? CGPoint(x: 1, y: 1) - var progress: CGFloat = keyTime.remap(fromLow: startTime, fromHigh: endTime, toLow: 0, toHigh: 1) - if !outTanPoint.isZero || !inTanPoint.equalTo(CGPoint(x: 1, y: 1)) { - /// Cubic interpolation - progress = progress.cubicBezierInterpolate(.zero, outTanPoint, inTanPoint, CGPoint(x: 1, y: 1)) - } - return progress - } - - /// Interpolates the keyframes' by a progress from 0-1 - func interpolate(_ to: Keyframe, progress: CGFloat) -> T { - return value.interpolateTo(to.value, amount: progress, spatialOutTangent: spatialOutTangent?.pointValue, spatialInTangent: to.spatialInTangent?.pointValue) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/BezierPath.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/BezierPath.swift deleted file mode 100644 index f5f60831b0..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/BezierPath.swift +++ /dev/null @@ -1,402 +0,0 @@ -// -// Shape.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/8/19. -// - -import Foundation -import CoreGraphics - -/// A container that holds instructions for creating a single, unbroken Bezier Path. -struct BezierPath { - - /// The elements of the path - fileprivate(set) var elements: [PathElement] - - /// If the path is closed or not. - fileprivate(set) var closed: Bool - - /// The total length of the path. - fileprivate(set) var length: CGFloat - - /// Initializes a new Bezier Path. - init(startPoint: CurveVertex) { - self.elements = [PathElement(vertex: startPoint)] - self.length = 0 - self.closed = false - } - - init() { - self.elements = [] - self.length = 0 - self.closed = false - } - - mutating func moveToStartPoint(_ vertex: CurveVertex) { - self.elements = [PathElement(vertex: vertex)] - self.length = 0 - } - - mutating func addVertex(_ vertex: CurveVertex) { - guard let previous = elements.last else { - addElement(PathElement(vertex: vertex)) - return - } - addElement(previous.pathElementTo(vertex)) - } - - mutating func addCurve(toPoint: CGPoint, outTangent: CGPoint, inTangent: CGPoint) { - guard let previous = elements.last else { return } - let newVertex = CurveVertex(inTangent, toPoint, toPoint) - updateVertex(CurveVertex(previous.vertex.inTangent, previous.vertex.point, outTangent), atIndex: elements.endIndex - 1, remeasure: false) - addVertex(newVertex) - } - - mutating func addLine(toPoint: CGPoint) { - guard let previous = elements.last else { return } - let newVertex = CurveVertex(point: toPoint, inTangentRelative: .zero, outTangentRelative: .zero) - updateVertex(CurveVertex(previous.vertex.inTangent, previous.vertex.point, previous.vertex.point), atIndex: elements.endIndex - 1, remeasure: false) - addVertex(newVertex) - } - - mutating func close() { - self.closed = true - } - - mutating func addElement(_ pathElement: PathElement) { - elements.append(pathElement) - length = length + pathElement.length - } - - mutating func updateVertex(_ vertex: CurveVertex, atIndex: Int, remeasure: Bool) { - if remeasure { - var newElement: PathElement - if atIndex > 0 { - let previousElement = elements[atIndex-1] - newElement = previousElement.pathElementTo(vertex) - } else { - newElement = PathElement(vertex: vertex) - } - elements[atIndex] = newElement - - if atIndex + 1 < elements.count{ - let nextElement = elements[atIndex + 1] - elements[atIndex + 1] = newElement.pathElementTo(nextElement.vertex) - } - - } else { - let oldElement = elements[atIndex] - elements[atIndex] = oldElement.updateVertex(newVertex: vertex) - } - } - - /** - Trims a path fromLength toLength with an offset. - - Length and offset are defined in the length coordinate space. - If any argument is outside the range of this path, then it will be looped over the path from finish to start. - - Cutting the curve when fromLength is less than toLength - x x x x - ~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooooooooooooooooooooooo------------------- - |Offset |fromLength toLength| | - - Cutting the curve when from Length is greater than toLength - x x x x x - oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo - | toLength| |Offset |fromLength | - - */ - func trim(fromLength: CGFloat, toLength: CGFloat, offsetLength: CGFloat) -> [BezierPath] { - guard elements.count > 1 else { - return [] - } - - if fromLength == toLength { - return [] - } - - /// Normalize lengths to the curve length. - var start = (fromLength+offsetLength).truncatingRemainder(dividingBy: length) - var end = (toLength+offsetLength).truncatingRemainder(dividingBy: length) - - if start < 0 { - start = length + start - } - - if end < 0 { - end = length + end - } - - if start == length { - start = 0 - } - if end == 0 { - end = length - } - - if start == 0 && end == length || - start == end || - start == length && end == 0 { - /// The trim encompasses the entire path. Return. - return [self] - } - - if start > end { - // Start is greater than end. Two paths are returned. - return trimPathAtLengths(positions: [(start: 0, end: end), (start: start, end: length)]) - } - - return trimPathAtLengths(positions: [(start: start, end: end)]) - } - - // MARK: File Private - - /// Trims a path by a list of positions and returns the sub paths - fileprivate func trimPathAtLengths(positions: [(start: CGFloat, end: CGFloat)]) -> [BezierPath] { - guard positions.count > 0 else { - return [] - } - var remainingPositions = positions - - var trim = remainingPositions.remove(at: 0) - - var paths = [BezierPath]() - - var runningLength: CGFloat = 0 - var finishedTrimming: Bool = false - var pathElements = elements - - var currentPath = BezierPath() - var i: Int = 0 - - while !finishedTrimming { - if pathElements.count <= i { - /// Do this for rounding errors - paths.append(currentPath) - finishedTrimming = true - continue - } - /// Loop through and add elements within start->end range. - /// Get current element - let element = pathElements[i] - - /// Calculate new running length. - let newLength = runningLength + element.length - - if newLength < trim.start { - /// Element is not included in the trim, continue. - runningLength = newLength - i = i + 1 - /// Increment index, we are done with this element. - continue - } - - if newLength == trim.start { - /// Current element IS the start element. - /// For start we want to add a zero length element. - currentPath.moveToStartPoint(element.vertex) - runningLength = newLength - i = i + 1 - /// Increment index, we are done with this element. - continue - } - - if runningLength < trim.start, trim.start < newLength, currentPath.elements.count == 0 { - /// The start of the trim is between this element and the previous, trim. - /// Get previous element. - let previousElement = pathElements[i-1] - /// Trim it - let trimLength = trim.start - runningLength - let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) - /// Add the right span start. - currentPath.moveToStartPoint(trimResults.rightSpan.start.vertex) - - pathElements[i] = trimResults.rightSpan.end - pathElements[i-1] = trimResults.rightSpan.start - runningLength = runningLength + trimResults.leftSpan.end.length - /// Dont increment index or the current length, the end of this path can be within this span. - continue - } - - if trim.start < newLength, newLength < trim.end { - /// Element lies within the trim span. - currentPath.addElement(element) - runningLength = newLength - i = i + 1 - continue - } - - if newLength == trim.end { - /// Element is the end element. - /// The element could have a new length if it's added right after the start node. - currentPath.addElement(element) - /// We are done with this span. - runningLength = newLength - i = i + 1 - /// Allow the path to be finalized. - /// Fall through to finalize path and move to next position - } - - if runningLength < trim.end, trim.end < newLength { - /// New element must be cut for end. - /// Get previous element. - let previousElement = pathElements[i-1] - /// Trim it - let trimLength = trim.end - runningLength - let trimResults = element.splitElementAtPosition(fromElement: previousElement, atLength: trimLength) - /// Add the left span end. - - currentPath.updateVertex(trimResults.leftSpan.start.vertex, atIndex: currentPath.elements.count - 1, remeasure: false) - currentPath.addElement(trimResults.leftSpan.end) - - pathElements[i] = trimResults.rightSpan.end - pathElements[i-1] = trimResults.rightSpan.start - runningLength = runningLength + trimResults.leftSpan.end.length - /// Dont increment index or the current length, the start of the next path can be within this span. - /// We are done with this span. - /// Allow the path to be finalized. - /// Fall through to finalize path and move to next position - } - - paths.append(currentPath) - currentPath = BezierPath() - if remainingPositions.count > 0 { - trim = remainingPositions.remove(at: 0) - } else { - finishedTrimming = true - } - } - return paths - } - -} - -extension BezierPath: Codable { - - /** - The BezierPath container is encoded and decoded from the JSON format - that defines points for a lottie animation. - - { - "c" = Bool - "i" = [[Double]], - "o" = [[Double]], - "v" = [[Double]] - } - - */ - - enum CodingKeys : String, CodingKey { - case closed = "c" - case inPoints = "i" - case outPoints = "o" - case vertices = "v" - } - - init(from decoder: Decoder) throws { - let container: KeyedDecodingContainer - - if let keyedContainer = try? decoder.container(keyedBy: BezierPath.CodingKeys.self) { - container = keyedContainer - } else { - var unkeyedContainer = try decoder.unkeyedContainer() - container = try unkeyedContainer.nestedContainer(keyedBy: BezierPath.CodingKeys.self) - } - - self.closed = try container.decodeIfPresent(Bool.self, forKey: .closed) ?? true - - var vertexContainer = try container.nestedUnkeyedContainer(forKey: .vertices) - var inPointsContainer = try container.nestedUnkeyedContainer(forKey: .inPoints) - var outPointsContainer = try container.nestedUnkeyedContainer(forKey: .outPoints) - - guard vertexContainer.count == inPointsContainer.count, inPointsContainer.count == outPointsContainer.count else { - /// Will throw an error if vertex, inpoints, and outpoints are not the same length. - /// This error is to be expected. - throw DecodingError.dataCorruptedError(forKey: CodingKeys.vertices, - in: container, - debugDescription: "Vertex data does not match In Tangents and Out Tangents") - } - - guard let count = vertexContainer.count, count > 0 else { - self.length = 0 - self.elements = [] - return - } - - var decodedElements = [PathElement]() - - /// Create first point - let firstVertex = CurveVertex(point: try vertexContainer.decode(CGPoint.self), - inTangentRelative: try inPointsContainer.decode(CGPoint.self), - outTangentRelative: try outPointsContainer.decode(CGPoint.self)) - var previousElement = PathElement(vertex: firstVertex) - decodedElements.append(previousElement) - - var totalLength: CGFloat = 0 - while !vertexContainer.isAtEnd { - /// Get the next vertex data. - let vertex = CurveVertex(point: try vertexContainer.decode(CGPoint.self), - inTangentRelative: try inPointsContainer.decode(CGPoint.self), - outTangentRelative: try outPointsContainer.decode(CGPoint.self)) - let pathElement = previousElement.pathElementTo(vertex) - decodedElements.append(pathElement) - previousElement = pathElement - totalLength = totalLength + pathElement.length - } - if closed { - let closeElement = previousElement.pathElementTo(firstVertex) - decodedElements.append(closeElement) - totalLength = totalLength + closeElement.length - } - self.length = totalLength - self.elements = decodedElements - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: BezierPath.CodingKeys.self) - try container.encode(closed, forKey: .closed) - - var vertexContainer = container.nestedUnkeyedContainer(forKey: .vertices) - var inPointsContainer = container.nestedUnkeyedContainer(forKey: .inPoints) - var outPointsContainer = container.nestedUnkeyedContainer(forKey: .outPoints) - - /// If closed path, ignore the final element. - let finalIndex = closed ? self.elements.endIndex - 1 : self.elements.endIndex - for i in 0.. CGPath { - let cgPath = CGMutablePath() - - var previousElement: PathElement? - for element in elements { - if let previous = previousElement { - if previous.vertex.outTangentRelative.isZero && element.vertex.inTangentRelative.isZero { - cgPath.addLine(to: element.vertex.point) - } else { - //cgPath.addLine(to: element.vertex.point) - cgPath.addCurve(to: element.vertex.point, control1: previous.vertex.outTangent, control2: element.vertex.inTangent) - } - } else { - cgPath.move(to: element.vertex.point) - } - previousElement = element - } - if self.closed { - cgPath.closeSubpath() - } - return cgPath - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/ColorExtension.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/ColorExtension.swift deleted file mode 100644 index 309031a2a4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/ColorExtension.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// Color.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -extension Color: Codable { - - public init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - - var r1: Double - if !container.isAtEnd { - r1 = try container.decode(Double.self) - } else { - r1 = 0 - } - - var g1: Double - if !container.isAtEnd { - g1 = try container.decode(Double.self) - } else { - g1 = 0 - } - - var b1: Double - if !container.isAtEnd { - b1 = try container.decode(Double.self) - } else { - b1 = 0 - } - - var a1: Double - if !container.isAtEnd { - a1 = try container.decode(Double.self) - } else { - a1 = 1 - } - if r1 > 1, g1 > 1, b1 > 1, a1 > 1 { - r1 = r1 / 255 - g1 = g1 / 255 - b1 = b1 / 255 - a1 = a1 / 255 - } - self.r = r1 - self.g = g1 - self.b = b1 - self.a = a1 - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - try container.encode(r) - try container.encode(g) - try container.encode(b) - try container.encode(a) - } - -} - -extension Color { - - static var clearColor: CGColor { - return CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 0])! - } - - var cgColorValue: CGColor { - // TODO: Fix color spaces - let colorspace = CGColorSpaceCreateDeviceRGB() - return CGColor(colorSpace: colorspace, components: [CGFloat(r), CGFloat(g), CGFloat(b), CGFloat(a)]) ?? Color.clearColor - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.swift deleted file mode 100644 index 068a13dd63..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.swift +++ /dev/null @@ -1,158 +0,0 @@ -// -// CompoundBezierPath.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/14/19. -// - -import Foundation -import CoreGraphics - -/** - A collection of BezierPath objects that can be trimmed and added. - - */ -struct CompoundBezierPath { - - let paths: [BezierPath] - - let length: CGFloat - - init() { - paths = [] - length = 0 - } - - init(path: BezierPath) { - self.paths = [path] - self.length = path.length - } - - init(paths: [BezierPath], length: CGFloat) { - self.paths = paths - self.length = length - } - - init(paths: [BezierPath]) { - self.paths = paths - var l: CGFloat = 0 - for path in paths { - l = l + path.length - } - self.length = l - } - - func addPath(path: BezierPath) -> CompoundBezierPath { - var newPaths = paths - newPaths.append(path) - return CompoundBezierPath(paths: newPaths, length: length + path.length) - } - - func combine(_ compoundBezier: CompoundBezierPath) -> CompoundBezierPath { - var newPaths = paths - newPaths.append(contentsOf: compoundBezier.paths) - return CompoundBezierPath(paths: newPaths, length: length + compoundBezier.length) - } - - func trim(fromPosition: CGFloat, toPosition: CGFloat, offset: CGFloat, trimSimultaneously: Bool) -> CompoundBezierPath { - if fromPosition == toPosition { - return CompoundBezierPath() - } - - if trimSimultaneously { - /// Trim each path individually. - var newPaths = [BezierPath]() - for path in paths { - newPaths.append(contentsOf: path.trim(fromLength: fromPosition * path.length, - toLength: toPosition * path.length, - offsetLength: offset * path.length)) - } - return CompoundBezierPath(paths: newPaths) - } - - /// Normalize lengths to the curve length. - var startPosition = (fromPosition+offset).truncatingRemainder(dividingBy: 1) - var endPosition = (toPosition+offset).truncatingRemainder(dividingBy: 1) - - if startPosition < 0 { - startPosition = 1 + startPosition - } - - if endPosition < 0 { - endPosition = 1 + endPosition - } - - if startPosition == 1 { - startPosition = 0 - } - if endPosition == 0 { - endPosition = 1 - } - - if startPosition == 0 && endPosition == 1 || - startPosition == endPosition || - startPosition == 1 && endPosition == 0 { - /// The trim encompasses the entire path. Return. - return self - } - - var positions: [(start: CGFloat, end: CGFloat)] - if endPosition < startPosition { - positions = [(start: 0, end: endPosition * length), - (start: startPosition * length, end: length)] - } else { - positions = [(start: startPosition * length, end: endPosition * length)] - } - - var compoundPath = CompoundBezierPath() - var trim = positions.remove(at: 0) - var pathStartPosition: CGFloat = 0 - - var finishedTrimming: Bool = false - var i: Int = 0 - - while !finishedTrimming { - if paths.count <= i { - /// Rounding errors - finishedTrimming = true - continue - } - let path = paths[i] - - let pathEndPosition = pathStartPosition + path.length - - if pathEndPosition < trim.start { - /// Path is not included in the trim, continue. - pathStartPosition = pathEndPosition - i = i + 1 - continue - - } else if trim.start <= pathStartPosition, pathEndPosition <= trim.end { - /// Full Path is inside of trim. Add full path. - compoundPath = compoundPath.addPath(path: path) - } else { - if let trimPath = path.trim(fromLength: trim.start > pathStartPosition ? (trim.start - pathStartPosition) : 0, - toLength: trim.end < pathEndPosition ? (trim.end - pathStartPosition) : path.length, - offsetLength: 0).first { - compoundPath = compoundPath.addPath(path: trimPath) - } - } - - - if trim.end <= pathEndPosition { - /// We are done with the current trim. - /// Advance trim but remain on the same path in case the next trim overlaps it. - if positions.count > 0 { - trim = positions.remove(at: 0) - } else { - finishedTrimming = true - } - } else { - pathStartPosition = pathEndPosition - i = i + 1 - } - } - return compoundPath - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CurveVertex.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CurveVertex.swift deleted file mode 100644 index d4b5eb18f0..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/CurveVertex.swift +++ /dev/null @@ -1,177 +0,0 @@ -// -// CurveVertex.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/11/19. -// - -import Foundation -import CoreGraphics - -/// A single vertex with an in and out tangent -struct CurveVertex { - - let point: CGPoint - - let inTangent: CGPoint - let outTangent: CGPoint - - /// Initializes a curve point with absolute values - init(_ inTangent: CGPoint, _ point: CGPoint, _ outTangent: CGPoint) { - self.point = point - self.inTangent = inTangent - self.outTangent = outTangent - } - - /// Initializes a curve point with relative values - init(point: CGPoint, inTangentRelative: CGPoint, outTangentRelative: CGPoint) { - self.point = point - self.inTangent = point.add(inTangentRelative) - self.outTangent = point.add(outTangentRelative) - } - - /// Initializes a curve point with absolute values - init(point: CGPoint, inTangent: CGPoint, outTangent: CGPoint) { - self.point = point - self.inTangent = inTangent - self.outTangent = outTangent - } - - var inTangentRelative: CGPoint { - return inTangent.subtract(point) - } - - var outTangentRelative: CGPoint { - return outTangent.subtract(point) - } - - func reversed() -> CurveVertex { - return CurveVertex(point: point, inTangent: outTangent, outTangent: inTangent) - } - - func translated(_ translation: CGPoint) -> CurveVertex { - return CurveVertex(point: point + translation, inTangent: inTangent + translation, outTangent: outTangent + translation) - } - - /** - Trims a path defined by two Vertices at a specific position, from 0 to 1 - - The path can be visualized below. - - F is fromVertex. - V is the vertex of the receiver. - P is the position from 0-1. - O is the outTangent of fromVertex. - F====O=========P=======I====V - - After trimming the curve can be visualized below. - - S is the returned Start vertex. - E is the returned End vertex. - T is the trim point. - TI and TO are the new tangents for the trimPoint - NO and NI are the new tangents for the startPoint and endPoints - S==NO=========TI==T==TO=======NI==E - */ - func splitCurve(toVertex: CurveVertex, position: CGFloat) -> - (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) { - - /// If position is less than or equal to 0, trim at start. - if position <= 0 { - return (start: CurveVertex(point: point, inTangentRelative: inTangentRelative, outTangentRelative: .zero), - trimPoint: CurveVertex(point: point, inTangentRelative: .zero, outTangentRelative: outTangentRelative), - end: toVertex) - } - - /// If position is greater than or equal to 1, trim at end. - if position >= 1 { - return (start: self, - trimPoint: CurveVertex(point: toVertex.point, inTangentRelative: toVertex.inTangentRelative, outTangentRelative: .zero), - end: CurveVertex(point: toVertex.point, inTangentRelative: .zero, outTangentRelative: toVertex.outTangentRelative)) - } - - if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { - /// If both tangents are zero, then span to be trimmed is a straight line. - let trimPoint = point.interpolate(toVertex.point, amount: position) - return (start: self, - trimPoint: CurveVertex(point: trimPoint, inTangentRelative: .zero, outTangentRelative: .zero), - end: toVertex) - } - /// Cutting by amount gives incorrect length.... - /// One option is to cut by a stride until it gets close then edge it down. - /// Measuring a percentage of the spans does not equal the same as measuring a percentage of length. - /// This is where the historical trim path bugs come from. - let a = point.interpolate(outTangent, amount: position) - let b = outTangent.interpolate(toVertex.inTangent, amount: position) - let c = toVertex.inTangent.interpolate(toVertex.point, amount: position) - let d = a.interpolate(b, amount: position) - let e = b.interpolate(c, amount: position) - let f = d.interpolate(e, amount: position) - return (start: CurveVertex(point: point, inTangent: inTangent, outTangent: a), - trimPoint: CurveVertex(point: f, inTangent: d, outTangent: e), - end: CurveVertex(point: toVertex.point, inTangent: c, outTangent: toVertex.outTangent)) - } - - /** - Trims a curve of a known length to a specific length and returns the points. - - There is not a performant yet accurate way to cut a curve to a specific length. - This calls splitCurve(toVertex: position:) to split the curve and then measures - the length of the new curve. The function then iterates through the samples, - adjusting the position of the cut for a more precise cut. - Usually a single iteration is enough to get within 0.5 points of the desired - length. - - This function should probably live in PathElement, since it deals with curve - lengths. - */ - func trimCurve(toVertex: CurveVertex, atLength: CGFloat, curveLength: CGFloat, maxSamples: Int, accuracy: CGFloat = 1) -> - (start: CurveVertex, trimPoint: CurveVertex, end: CurveVertex) { - var currentPosition = atLength / curveLength - var results = splitCurve(toVertex: toVertex, position: currentPosition) - - if maxSamples == 0 { - return results - } - - for _ in 1...maxSamples { - let length = results.start.distanceTo(results.trimPoint) - let lengthDiff = atLength - length - /// Check if length is correct. - if lengthDiff < accuracy { - return results - } - let diffPosition = max(min(((currentPosition / length) * lengthDiff), currentPosition * 0.5), currentPosition * -0.5) - currentPosition = diffPosition + currentPosition - results = splitCurve(toVertex: toVertex, position: currentPosition) - } - return results - } - - - /** - The distance from the receiver to the provided vertex. - - For lines (zeroed tangents) the distance between the two points is measured. - For curves the curve is iterated over by sample count and the points are measured. - This is ~99% accurate at a sample count of 30 - */ - func distanceTo(_ toVertex: CurveVertex, sampleCount: Int = 25) -> CGFloat { - - if outTangentRelative.isZero && toVertex.inTangentRelative.isZero { - /// Return a linear distance. - return point.distanceTo(toVertex.point) - } - - var distance: CGFloat = 0 - - var previousPoint = point - for i in 0.. PathElement { - return PathElement(length: vertex.distanceTo(toVertex), vertex: toVertex) - } - - /// Initializes a new path with length of 0 - init(vertex: CurveVertex) { - self.length = 0 - self.vertex = vertex - } - - /// Initializes a new path with length - fileprivate init(length: CGFloat, vertex: CurveVertex) { - self.length = length - self.vertex = vertex - } - - func updateVertex(newVertex: CurveVertex) -> PathElement { - return PathElement(length: length, vertex: newVertex) - } - - /// Splits an element span defined by the receiver and fromElement to a position 0-1 - func splitElementAtPosition(fromElement: PathElement, atLength: CGFloat) -> - (leftSpan: (start: PathElement, end: PathElement), rightSpan: (start: PathElement, end: PathElement)) { - /// Trim the span. Start and trim go into the first, trim and end go into second. - let trimResults = fromElement.vertex.trimCurve(toVertex: vertex, atLength: atLength, curveLength: length, maxSamples: 3) - - /// Create the elements for the break - let spanAStart = PathElement(length: fromElement.length, - vertex: CurveVertex(point: fromElement.vertex.point, - inTangent: fromElement.vertex.inTangent, - outTangent: trimResults.start.outTangent)) - /// Recalculating the length here is a waste as the trimCurve function also accurately calculates this length. - let spanAEnd = spanAStart.pathElementTo(trimResults.trimPoint) - - let spanBStart = PathElement(vertex: trimResults.trimPoint) - let spanBEnd = spanBStart.pathElementTo(trimResults.end) - return (leftSpan: (start: spanAStart, end: spanAEnd), - rightSpan: (start: spanBStart, end: spanBEnd)) - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/VectorsExtensions.swift b/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/VectorsExtensions.swift deleted file mode 100644 index 5b0e23f66e..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Private/Utility/Primitives/VectorsExtensions.swift +++ /dev/null @@ -1,218 +0,0 @@ -// -// Vector.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/7/19. -// - -import Foundation -import CoreGraphics -import QuartzCore - -/** - Single value container. Needed because lottie sometimes wraps a Double in an array. - */ -extension Vector1D: Codable { - - public init(from decoder: Decoder) throws { - /// Try to decode an array of doubles - do { - var container = try decoder.unkeyedContainer() - self.value = try container.decode(Double.self) - } catch { - self.value = try decoder.singleValueContainer().decode(Double.self) - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(value) - } - - var cgFloatValue: CGFloat { - return CGFloat(value) - } - -} - -extension Double { - var vectorValue: Vector1D { - return Vector1D(self) - } -} - -/** - Needed for decoding json {x: y:} to a CGPoint - */ -struct Vector2D: Codable { - - var x: Double - var y: Double - - init(x: Double, y: Double) { - self.x = x - self.y = y - } - - private enum CodingKeys : String, CodingKey { - case x = "x" - case y = "y" - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Vector2D.CodingKeys.self) - - do { - let xValue: [Double] = try container.decode([Double].self, forKey: .x) - self.x = xValue[0] - } catch { - self.x = try container.decode(Double.self, forKey: .x) - } - - do { - let yValue: [Double] = try container.decode([Double].self, forKey: .y) - self.y = yValue[0] - } catch { - self.y = try container.decode(Double.self, forKey: .y) - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: Vector2D.CodingKeys.self) - try container.encode(x, forKey: .x) - try container.encode(y, forKey: .y) - } - - var pointValue: CGPoint { - return CGPoint(x: x, y: y) - } -} - -extension Vector2D { - -} - -extension CGPoint { - var vector2dValue: Vector2D { - return Vector2D(x: Double(x), y: Double(y)) - } -} - -/** - A three dimensional vector. - These vectors are encoded and decoded from [Double] - */ - -extension Vector3D: Codable { - - init(x: CGFloat, y: CGFloat, z: CGFloat) { - self.x = Double(x) - self.y = Double(y) - self.z = Double(z) - } - - public init(from decoder: Decoder) throws { - var container = try decoder.unkeyedContainer() - - if !container.isAtEnd { - self.x = try container.decode(Double.self) - } else { - self.x = 0 - } - - if !container.isAtEnd { - self.y = try container.decode(Double.self) - } else { - self.y = 0 - } - - if !container.isAtEnd { - self.z = try container.decode(Double.self) - } else { - self.z = 0 - } - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.unkeyedContainer() - try container.encode(x) - try container.encode(y) - try container.encode(z) - } - -} - -public extension Vector3D { - var pointValue: CGPoint { - return CGPoint(x: x, y: y) - } - - var sizeValue: CGSize { - return CGSize(width: x, height: y) - } -} - -extension CGPoint { - var vector3dValue: Vector3D { - return Vector3D(x: x, y: y, z: 0) - } -} - -extension CGSize { - var vector3dValue: Vector3D { - return Vector3D(x: width, y: height, z: 1) - } -} - -extension CATransform3D { - - func rotated(_ degrees: CGFloat) -> CATransform3D { - return CATransform3DRotate(self, degrees.toRadians(), 0, 0, 1) - } - - func translated(_ translation: CGPoint) -> CATransform3D { - return CATransform3DTranslate(self, translation.x, translation.y, 0) - } - - func scaled(_ scale: CGSize) -> CATransform3D { - return CATransform3DScale(self, scale.width, scale.height, 1) - } - - func skewed(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { - return CATransform3DConcat(CATransform3D.makeSkew(skew: skew, skewAxis: skewAxis), self) - } - - static func makeSkew(skew: CGFloat, skewAxis: CGFloat) -> CATransform3D { - let mCos = cos(skewAxis.toRadians()) - let mSin = sin(skewAxis.toRadians()) - let aTan = tan(skew.toRadians()) - - let transform1 = CATransform3D(m11: mCos, m12: mSin, m13: 0, m14: 0, - m21: -mSin, m22: mCos, m23: 0, m24: 0, - m31: 0, m32: 0, m33: 1, m34: 0, - m41: 0, m42: 0, m43: 0, m44: 1) - - let transform2 = CATransform3D(m11: 1, m12: 0, m13: 0, m14: 0, - m21: aTan, m22: 1, m23: 0, m24: 0, - m31: 0, m32: 0, m33: 1, m34: 0, - m41: 0, m42: 0, m43: 0, m44: 1) - - let transform3 = CATransform3D(m11: mCos, m12: -mSin, m13: 0, m14: 0, - m21: mSin, m22: mCos, m23: 0, m24: 0, - m31: 0, m32: 0, m33: 1, m34: 0, - m41: 0, m42: 0, m43: 0, m44: 1) - return CATransform3DConcat(transform3, CATransform3DConcat(transform2, transform1)) - } - - static func makeTransform(anchor: CGPoint, - position: CGPoint, - scale: CGSize, - rotation: CGFloat, - skew: CGFloat?, - skewAxis: CGFloat?) -> CATransform3D { - if let skew = skew, skew != 0.0, let skewAxis = skewAxis { - return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).skewed(skew: -skew, skewAxis: skewAxis).scaled(scale * 0.01).translated(anchor * -1) - } - return CATransform3DMakeTranslation(position.x, position.y, 0).rotated(rotation).scaled(scale * 0.01).translated(anchor * -1) - } -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.swift deleted file mode 100644 index e9ece1b4da..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnimationKeypath.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// AnimationKeypath.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation - -/** - `AnimationKeypath` is an object that describes a keypath search for nodes in the - animation JSON. `AnimationKeypath` matches views and properties inside of `AnimationView` - to their backing `Animation` model by name. - - A keypath can be used to set properties on an existing animation, or can be validated - with an existing `Animation`. - - `AnimationKeypath` can describe a specific object, or can use wildcards for fuzzy matching - of objects. Acceptable wildcards are either "*" (star) or "**" (double star). - Single star will search a single depth for the next object. - Double star will search any depth. - - Read More at https://airbnb.io/lottie/#/ios?id=dynamic-animation-properties - - EG: - @"Layer.Shape Group.Stroke 1.Color" - Represents a specific color node on a specific stroke. - - @"**.Stroke 1.Color" - Represents the color node for every Stroke named "Stroke 1" in the animation. - */ -public struct AnimationKeypath { - - /// Creates a keypath from a dot separated string. The string is separated by "." - public init(keypath: String) { - self.keys = keypath.components(separatedBy: ".") - } - - /// Creates a keypath from a list of strings. - public init(keys: [String]) { - self.keys = keys - } - - let keys: [String] - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.swift deleted file mode 100644 index 1684cd90ab..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/AnyValueProvider.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// AnyValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 1/30/19. -// - -import Foundation -import CoreGraphics - -/** - `AnyValueProvider` is a protocol that return animation data for a property at a - given time. Every fame an `AnimationView` queries all of its properties and asks - if their ValueProvider has an update. If it does the AnimationView will read the - property and update that portion of the animation. - - Value Providers can be used to dynamically set animation properties at run time. - */ -public protocol AnyValueProvider { - - /// The Type of the value provider - var valueType: Any.Type { get } - - /// Asks the provider if it has an update for the given frame. - func hasUpdate(frame: AnimationFrameTime) -> Bool - - /// Asks the provider to update the container with its value for the frame. - func value(frame: AnimationFrameTime) -> Any -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift deleted file mode 100644 index 4d594d2f0a..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/ColorValueProvider.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// ColorValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a CGColor Value -public final class ColorValueProvider: AnyValueProvider { - - /// Returns a Color for a CGColor(Frame Time) - public typealias ColorValueBlock = (CGFloat) -> Color - - /// The color value of the provider. - public var color: Color { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping ColorValueBlock) { - self.block = block - self.color = Color(r: 0, g: 0, b: 0, a: 1) - } - - /// Initializes with a single color. - public init(_ color: Color) { - self.color = color - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Color.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newColor: Color - if let block = block { - newColor = block(frame) - } else { - newColor = color - } - return newColor - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: ColorValueBlock? -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift deleted file mode 100644 index ecd43e12df..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/FloatValueProvider.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// DoubleValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a CGFloat Value -public final class FloatValueProvider: AnyValueProvider { - - /// Returns a CGFloat for a CGFloat(Frame Time) - public typealias CGFloatValueBlock = (CGFloat) -> CGFloat - - public var float: CGFloat { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping CGFloatValueBlock) { - self.block = block - self.float = 0 - } - - /// Initializes with a single float. - public init(_ float: CGFloat) { - self.float = float - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Vector1D.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newCGFloat: CGFloat - if let block = block { - newCGFloat = block(frame) - } else { - newCGFloat = float - } - return Vector1D(Double(newCGFloat)) - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: CGFloatValueBlock? -} - diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift deleted file mode 100644 index d19bfc46a4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/GradientValueProvider.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// GradientValueProvider.swift -// lottie-swift -// -// Created by Enrique Bermúdez on 10/27/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a Gradient Color Value. -public final class GradientValueProvider: AnyValueProvider { - - /// Returns a [Color] for a CGFloat(Frame Time). - public typealias ColorsValueBlock = (CGFloat) -> [Color] - /// Returns a [Double](Color locations) for a CGFloat(Frame Time). - public typealias ColorLocationsBlock = (CGFloat) -> [Double] - - /// The colors values of the provider. - public var colors: [Color] { - didSet { - updateValueArray() - hasUpdate = true - } - } - - /// The color location values of the provider. - public var locations: [Double] { - didSet { - updateValueArray() - hasUpdate = true - } - } - - /// Initializes with a block provider. - public init(block: @escaping ColorsValueBlock, - locations: ColorLocationsBlock? = nil) { - self.block = block - self.locationsBlock = locations - self.colors = [] - self.locations = [] - } - - /// Initializes with an array of colors. - public init(_ colors: [Color], - locations: [Double] = []) { - self.colors = colors - self.locations = locations - updateValueArray() - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return [Double].self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil || locationsBlock != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - - if let block = block { - let newColors = block(frame) - let newLocations = locationsBlock?(frame) ?? [] - value = value(from: newColors, locations: newLocations) - } - - return value - } - - // MARK: Private - - private func value(from colors: [Color], locations: [Double]) -> [Double] { - - var colorValues = [Double]() - var alphaValues = [Double]() - var shouldAddAlphaValues = false - - for i in 0.. i ? locations[i] : - (Double(i) / Double(colors.count - 1)) - - colorValues.append(location) - colorValues.append(colors[i].r) - colorValues.append(colors[i].g) - colorValues.append(colors[i].b) - - alphaValues.append(location) - alphaValues.append(colors[i].a) - } - - return colorValues + (shouldAddAlphaValues ? alphaValues : []) - } - - private func updateValueArray() { - value = value(from: colors, locations: locations) - } - - private var hasUpdate: Bool = true - - private var block: ColorsValueBlock? - private var locationsBlock: ColorLocationsBlock? - private var value: [Double] = [] -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/PointValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/PointValueProvider.swift deleted file mode 100644 index 83579fba0c..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/PointValueProvider.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// PointValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics -/// A `ValueProvider` that returns a CGPoint Value -public final class PointValueProvider: AnyValueProvider { - - /// Returns a CGPoint for a CGFloat(Frame Time) - public typealias PointValueBlock = (CGFloat) -> CGPoint - - public var point: CGPoint { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping PointValueBlock) { - self.block = block - self.point = .zero - } - - /// Initializes with a single point. - public init(_ point: CGPoint) { - self.point = point - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Vector3D.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newPoint: CGPoint - if let block = block { - newPoint = block(frame) - } else { - newPoint = point - } - return newPoint.vector3dValue - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: PointValueBlock? -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift deleted file mode 100644 index 4e893202bd..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/DynamicProperties/ValueProviders/SizeValueProvider.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// SizeValueProvider.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation -import CoreGraphics - -/// A `ValueProvider` that returns a CGSize Value -public final class SizeValueProvider: AnyValueProvider { - - /// Returns a CGSize for a CGFloat(Frame Time) - public typealias SizeValueBlock = (CGFloat) -> CGSize - - public var size: CGSize { - didSet { - hasUpdate = true - } - } - - /// Initializes with a block provider - public init(block: @escaping SizeValueBlock) { - self.block = block - self.size = .zero - } - - /// Initializes with a single size. - public init(_ size: CGSize) { - self.size = size - self.block = nil - hasUpdate = true - } - - // MARK: ValueProvider Protocol - - public var valueType: Any.Type { - return Vector3D.self - } - - public func hasUpdate(frame: CGFloat) -> Bool { - if block != nil { - return true - } - return hasUpdate - } - - public func value(frame: CGFloat) -> Any { - hasUpdate = false - let newSize: CGSize - if let block = block { - newSize = block(frame) - } else { - newSize = size - } - return newSize.vector3dValue - } - - // MARK: Private - - private var hasUpdate: Bool = true - - private var block: SizeValueBlock? -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/AnimationTime.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/AnimationTime.swift deleted file mode 100644 index b765c50ab8..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/AnimationTime.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// AnimationTime.swift -// lottie-swift-iOS -// -// Created by Brandon Withrow on 2/6/19. -// - -import Foundation -import CoreGraphics - -/// Defines animation time in Frames (Seconds * Framerate). -public typealias AnimationFrameTime = CGFloat - -/// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) -public typealias AnimationProgressTime = CGFloat diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Color.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Color.swift deleted file mode 100644 index 4550f4cef4..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Color.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Color.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation - -public enum ColorFormatDenominator { - case One - case OneHundred - case TwoFiftyFive - - var value: Double { - switch self { - case .One: - return 1.0 - case .OneHundred: - return 100.0 - case .TwoFiftyFive: - return 255.0 - } - } -} - -public struct Color { - - public var r: Double - public var g: Double - public var b: Double - public var a: Double - - public init(r: Double, g: Double, b: Double, a: Double, denominator: ColorFormatDenominator = .One) { - self.r = r / denominator.value - self.g = g / denominator.value - self.b = b / denominator.value - self.a = a / denominator.value - } - -} diff --git a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Vectors.swift b/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Vectors.swift deleted file mode 100644 index bdf9381e56..0000000000 --- a/submodules/LottieMeshSwift/Sources/Lottie/Public/Primitives/Vectors.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Vectors.swift -// lottie-swift -// -// Created by Brandon Withrow on 2/4/19. -// - -import Foundation - -public struct Vector1D { - - public init(_ value: Double) { - self.value = value - } - - public let value: Double - -} - - -/** - A three dimensional vector. - These vectors are encoded and decoded from [Double] - */ -public struct Vector3D { - - public let x: Double - public let y: Double - public let z: Double - - public init(x: Double, y: Double, z: Double) { - self.x = x - self.y = y - self.z = z - } - -} diff --git a/submodules/LottieMeshSwift/Sources/MeshAnimation.swift b/submodules/LottieMeshSwift/Sources/MeshAnimation.swift deleted file mode 100644 index d1236bdf23..0000000000 --- a/submodules/LottieMeshSwift/Sources/MeshAnimation.swift +++ /dev/null @@ -1,693 +0,0 @@ -import Foundation -import Metal -import MetalKit -import LottieMeshBinding -import Postbox -import ManagedFile - -enum TriangleFill { - struct Color { - var r: Float - var g: Float - var b: Float - var a: Float - } - - struct Gradient { - var colors: [Color] - var colorLocations: [Float] - var start: CGPoint - var end: CGPoint - var isRadial: Bool - - static func read(buffer: MeshReadBuffer) -> Gradient { - var colors: [Color] = [] - var colorLocations: [Float] = [] - - let numColors = buffer.readInt8() - for _ in 0 ..< numColors { - colors.append(Color(argb: UInt32(bitPattern: buffer.readInt32()))) - } - for _ in 0 ..< numColors { - colorLocations.append(buffer.readFloat()) - } - - return Gradient( - colors: colors, - colorLocations: colorLocations, - start: CGPoint(x: CGFloat(buffer.readFloat()), y: CGFloat(buffer.readFloat())), - end: CGPoint(x: CGFloat(buffer.readFloat()), y: CGFloat(buffer.readFloat())), - isRadial: buffer.readInt8() != 0 - ) - } - - func write(buffer: MeshWriteBuffer) { - buffer.writeInt8(Int8(self.colors.count)) - for color in self.colors { - buffer.writeInt32(Int32(bitPattern: color.argb)) - } - for location in self.colorLocations { - buffer.writeFloat(location) - } - buffer.writeFloat(Float(self.start.x)) - buffer.writeFloat(Float(self.start.y)) - buffer.writeFloat(Float(self.end.x)) - buffer.writeFloat(Float(self.end.y)) - buffer.writeInt8(self.isRadial ? 1 : 0) - } - } - - case color(Color) - case gradient(Gradient) - - static func read(buffer: MeshReadBuffer) -> TriangleFill { - let key = buffer.readInt8() - if key == 0 { - return .color(Color(argb: UInt32(bitPattern: buffer.readInt32()))) - } else { - return .gradient(Gradient.read(buffer: buffer)) - } - } - - func write(buffer: MeshWriteBuffer) { - switch self { - case let .color(color): - buffer.writeInt8(0) - buffer.writeInt32(Int32(bitPattern: color.argb)) - case let .gradient(gradient): - buffer.writeInt8(1) - gradient.write(buffer: buffer) - } - } -} - -extension TriangleFill.Color { - init(_ color: UIColor) { - var r: CGFloat = 0.0 - var g: CGFloat = 0.0 - var b: CGFloat = 0.0 - var a: CGFloat = 0.0 - color.getRed(&r, green: &g, blue: &b, alpha: &a) - self.init(r: Float(r), g: Float(g), b: Float(b), a: Float(a)) - } - - init(argb: UInt32) { - self.init(r: Float((argb >> 16) & 0xff) / 255.0, g: Float((argb >> 8) & 0xff) / 255.0, b: Float(argb & 0xff) / 255.0, a: Float((argb >> 24) & 0xff) / 255.0) - } - - var argb: UInt32 { - return (UInt32(self.a * 255.0) << 24) | (UInt32(max(0.0, self.r) * 255.0) << 16) | (UInt32(max(0.0, self.g) * 255.0) << 8) | (UInt32(max(0.0, self.b) * 255.0)) - } - - func multiplied(alpha: Float) -> TriangleFill.Color { - var color = self - color.a *= alpha - return color - } -} - -enum MeshOption { - case fill(rule: CGPathFillRule) - case stroke(lineWidth: CGFloat, miterLimit: CGFloat, lineJoin: CGLineJoin, lineCap: CGLineCap) -} - -final class DataRange { - let data: Data - let range: Range - - init(data: Data, range: Range) { - self.data = data - self.range = range - } - - var count: Int { - return self.range.upperBound - self.range.lowerBound - } -} - -public final class MeshAnimation { - final class Frame { - final class Segment { - let vertices: DataRange - let triangles: DataRange - let fill: TriangleFill - let transform: CGAffineTransform - - init(vertices: DataRange, triangles: DataRange, fill: TriangleFill, transform: CGAffineTransform) { - self.vertices = vertices - self.triangles = triangles - self.fill = fill - self.transform = transform - } - - static func read(buffer: MeshReadBuffer) -> Segment { - let vertCount = Int(buffer.readInt32()) - let vertices = buffer.readDataRange(count: vertCount) - - let triCount = Int(buffer.readInt32()) - let triangles = buffer.readDataRange(count: triCount) - - return Segment(vertices: vertices, triangles: triangles, fill: TriangleFill.read(buffer: buffer), transform: CGAffineTransform(a: CGFloat(buffer.readFloat()), b: CGFloat(buffer.readFloat()), c: CGFloat(buffer.readFloat()), d: CGFloat(buffer.readFloat()), tx: CGFloat(buffer.readFloat()), ty: CGFloat(buffer.readFloat()))) - } - - func write(buffer: MeshWriteBuffer) { - buffer.writeInt32(Int32(self.vertices.count)) - buffer.write(self.vertices) - buffer.writeInt32(Int32(self.triangles.count)) - buffer.write(self.triangles) - - self.fill.write(buffer: buffer) - buffer.writeFloat(Float(self.transform.a)) - buffer.writeFloat(Float(self.transform.b)) - buffer.writeFloat(Float(self.transform.c)) - buffer.writeFloat(Float(self.transform.d)) - buffer.writeFloat(Float(self.transform.tx)) - buffer.writeFloat(Float(self.transform.ty)) - } - } - - let segments: [Segment] - - init(segments: [Segment]) { - self.segments = segments - } - - static func read(buffer: MeshReadBuffer) -> Frame { - var segments: [Segment] = [] - let count = buffer.readInt32() - for _ in 0 ..< count { - segments.append(Segment.read(buffer: buffer)) - } - return Frame(segments: segments) - } - - /*func write(buffer: MeshWriteBuffer) { - buffer.writeInt32(Int32(self.segments.count)) - for segment in self.segments { - segment.write(buffer: buffer) - } - }*/ - } - - let frames: [Frame] - - init(frames: [Frame]) { - self.frames = frames - } - - public static func read(buffer: MeshReadBuffer) -> MeshAnimation { - var frames: [Frame] = [] - let count = buffer.readInt32() - for _ in 0 ..< count { - frames.append(Frame.read(buffer: buffer)) - } - return MeshAnimation(frames: frames) - } - - /*public func write(buffer: MeshWriteBuffer) { - buffer.writeInt32(Int32(self.frames.count)) - for frame in self.frames { - frame.write(buffer: buffer) - } - }*/ -} - -@available(iOS 13.0, *) -public final class MeshRenderer: MTKView { - private final class RenderingMesh { - let mesh: MeshAnimation - let offset: CGPoint - let loop: Bool - var currentFrame: Int = 0 - let vertexBuffer: MTLBuffer - let indexBuffer: MTLBuffer - let transformBuffer: MTLBuffer - let maxVertices: Int - let maxTriangles: Int - - init(device: MTLDevice, mesh: MeshAnimation, offset: CGPoint, loop: Bool) { - self.mesh = mesh - self.offset = offset - self.loop = loop - - var maxTriangles = 0 - var maxVertices = 0 - for i in 0 ..< mesh.frames.count { - var frameTriangles = 0 - var frameVertices = 0 - for segment in mesh.frames[i].segments { - frameTriangles += segment.triangles.count / (4 * 3) - frameVertices += segment.vertices.count / (4 * 2) - } - maxTriangles = max(maxTriangles, frameTriangles) - maxVertices = max(maxVertices, frameVertices) - } - - self.maxVertices = maxVertices - self.maxTriangles = maxTriangles - - let vertexBufferArray = Array(repeating: 0.0, count: self.maxVertices * 2) - guard let vertexBuffer = device.makeBuffer(bytes: vertexBufferArray, length: vertexBufferArray.count * MemoryLayout.size(ofValue: vertexBufferArray[0]), options: [.cpuCacheModeWriteCombined]) else { - preconditionFailure() - } - self.vertexBuffer = vertexBuffer - - let indexBufferArray = Array(repeating: 0, count: self.maxTriangles * 3) - guard let indexBuffer = device.makeBuffer(bytes: indexBufferArray, length: indexBufferArray.count * MemoryLayout.size(ofValue: indexBufferArray[0]), options: [.cpuCacheModeWriteCombined]) else { - preconditionFailure() - } - self.indexBuffer = indexBuffer - - let transformBufferArray = Array(repeating: 0.0, count: 2) - guard let transformBuffer = device.makeBuffer(bytes: transformBufferArray, length: transformBufferArray.count * MemoryLayout.size(ofValue: transformBufferArray[0]), options: [.cpuCacheModeWriteCombined]) else { - preconditionFailure() - } - self.transformBuffer = transformBuffer - } - } - - private let wireframe: Bool - private let commandQueue: MTLCommandQueue - private let drawPassthroughPipelineState: MTLRenderPipelineState - private let drawRadialGradientPipelineStates: [Int: MTLRenderPipelineState] - - private var displayLink: CADisplayLink? - - private var metalLayer: CAMetalLayer { - return self.layer as! CAMetalLayer - } - - private var meshes: [RenderingMesh] = [] - public var animationCount: Int { - return self.meshes.count - } - - public var allAnimationsCompleted: (() -> Void)? - - public init?(wireframe: Bool = false) { - self.wireframe = wireframe - - let mainBundle = Bundle(for: MeshRenderer.self) - - guard let path = mainBundle.path(forResource: "LottieMeshSwiftBundle", ofType: "bundle") else { - return nil - } - guard let bundle = Bundle(path: path) else { - return nil - } - - guard let device = MTLCreateSystemDefaultDevice() else { - return nil - } - - guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: bundle) else { - return nil - } - - guard let commandQueue = device.makeCommandQueue() else { - return nil - } - self.commandQueue = commandQueue - - guard let loadedVertexProgram = defaultLibrary.makeFunction(name: "vertexPassthrough") else { - return nil - } - - func makeDescriptor(fragmentProgram: String) -> MTLRenderPipelineDescriptor { - guard let loadedFragmentProgram = defaultLibrary.makeFunction(name: fragmentProgram) else { - preconditionFailure() - } - - let pipelineStateDescriptor = MTLRenderPipelineDescriptor() - pipelineStateDescriptor.vertexFunction = loadedVertexProgram - pipelineStateDescriptor.fragmentFunction = loadedFragmentProgram - pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm - pipelineStateDescriptor.colorAttachments[0].isBlendingEnabled = true - pipelineStateDescriptor.colorAttachments[0].rgbBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].alphaBlendOperation = .add - pipelineStateDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha - pipelineStateDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha - pipelineStateDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha - pipelineStateDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha - pipelineStateDescriptor.sampleCount = 4 - return pipelineStateDescriptor - } - - self.drawPassthroughPipelineState = try! device.makeRenderPipelineState(descriptor: makeDescriptor(fragmentProgram: "fragmentPassthrough")) - var drawRadialGradientPipelineStates: [Int: MTLRenderPipelineState] = [:] - - for i in 2 ... 10 { - drawRadialGradientPipelineStates[i] = try! device.makeRenderPipelineState(descriptor: makeDescriptor(fragmentProgram: "fragmentRadialGradient\(i)")) - } - - self.drawRadialGradientPipelineStates = drawRadialGradientPipelineStates - - super.init(frame: CGRect(), device: device) - - self.sampleCount = 4 - - self.isOpaque = false - self.backgroundColor = .clear - - //self.metalLayer.device = self.device - //self.pixelFormat = .bgra8Unorm - self.framebufferOnly = true - //self.metalLayer.framebufferOnly = true - //if #available(iOS 11.0, *) { - self.metalLayer.allowsNextDrawableTimeout = true - //} - //self.metalLayer.contentsScale = 2.0 - - class DisplayLinkProxy: NSObject { - weak var target: MeshRenderer? - init(target: MeshRenderer) { - self.target = target - } - - @objc func displayLinkEvent() { - self.target?.displayLinkEvent() - } - } - - self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) - if #available(iOS 15.0, *) { - self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 60.0, preferred: 60.0) - //self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 10.0, maximum: 60.0, preferred: 10.0) - } - self.displayLink?.add(to: .main, forMode: .common) - self.displayLink?.isPaused = false - - self.isPaused = true - } - - required public init(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.displayLink?.invalidate() - } - - public func add(mesh: MeshAnimation, offset: CGPoint, loop: Bool = false) { - self.meshes.append(RenderingMesh(device: self.device!, mesh: mesh, offset: offset, loop: loop)) - } - - @objc private func displayLinkEvent() { - self.draw() - } - - override public func draw(_ rect: CGRect) { - self.redraw(drawable: self.currentDrawable!) - } - - private func redraw(drawable: MTLDrawable) { - guard let commandBuffer = self.commandQueue.makeCommandBuffer() else { - return - } - - var removeMeshes: [Int] = [] - - let renderPassDescriptor = self.currentRenderPassDescriptor! - renderPassDescriptor.colorAttachments[0].loadAction = .clear - renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) - - guard let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { - return - } - - renderEncoder.setCullMode(.none) - if self.wireframe { - renderEncoder.setTriangleFillMode(.lines) - } - - func addTriangle(vertexData: UnsafeMutablePointer, maxVertices: Int, nextVertexIndex: inout Int, vertices: Data, triangles: Data, triangleIndex: Int) { - assert(nextVertexIndex + 3 <= maxVertices) - vertices.withUnsafeBytes { vertices in - let verticesPointer = vertices.baseAddress!.assumingMemoryBound(to: Float.self) - - triangles.withUnsafeBytes { triangles in - let trianglesPointer = triangles.baseAddress!.assumingMemoryBound(to: Int32.self) - - for i in 0 ..< 3 { - let vertexBase = vertexData.advanced(by: nextVertexIndex * 2) - - let vertexIndex = Int(trianglesPointer.advanced(by: triangleIndex * 3 + i).pointee) - let vertex = verticesPointer.advanced(by: vertexIndex * 2) - - vertexBase.advanced(by: 0).pointee = vertex.advanced(by: 0).pointee - vertexBase.advanced(by: 1).pointee = vertex.advanced(by: 1).pointee - - nextVertexIndex += 1 - } - } - } - } - - for i in 0 ..< self.meshes.count { - let mesh = self.meshes[i] - - let transformData = mesh.transformBuffer.contents().assumingMemoryBound(to: Float.self) - transformData.advanced(by: 0).pointee = Float(mesh.offset.x) - transformData.advanced(by: 1).pointee = Float(mesh.offset.y) - - renderEncoder.setVertexBuffer(mesh.transformBuffer, offset: 0, index: 1) - - var colorBytes: [Float] = [1.0, 0.0, 1.0, 1.0] - - var segmentVertexData: [Int: (vStart: Int, vCount: Int, iStart: Int, iCount: Int)] = [:] - - let vertexData = mesh.vertexBuffer.contents().assumingMemoryBound(to: Float.self) - let indexData = mesh.indexBuffer.contents().assumingMemoryBound(to: Int32.self) - var nextVertexIndex = 0 - var nextIndexIndex = 0 - - for i in 0 ..< mesh.mesh.frames[mesh.currentFrame].segments.count { - let segment = mesh.mesh.frames[mesh.currentFrame].segments[i] - let startVertexIndex = nextVertexIndex - let startIndexIndex = nextIndexIndex - - segment.vertices.data.withUnsafeBytes { vertices in - let _ = memcpy(vertexData.advanced(by: nextVertexIndex * 2), vertices.baseAddress!.advanced(by: segment.vertices.range.lowerBound), segment.vertices.count) - } - nextVertexIndex += segment.vertices.count / (4 * 2) - - let baseVertexIndex = Int32(startVertexIndex) - - segment.triangles.data.withUnsafeBytes { triangles in - let _ = memcpy(indexData.advanced(by: nextIndexIndex), triangles.baseAddress!.advanced(by: segment.triangles.range.lowerBound), segment.triangles.count) - } - nextIndexIndex += segment.triangles.count / 4 - - segmentVertexData[i] = (startVertexIndex, nextVertexIndex - startVertexIndex, startIndexIndex, nextIndexIndex - startIndexIndex) - - let (_, _, iStart, iCount) = segmentVertexData[i]! - - switch segment.fill { - case let .color(color): - renderEncoder.setRenderPipelineState(self.drawPassthroughPipelineState) - - colorBytes[0] = color.r - colorBytes[1] = color.g - colorBytes[2] = color.b - colorBytes[3] = color.a - - renderEncoder.setFragmentBytes(&colorBytes, length: 4 * 4, index: 1) - case let .gradient(gradient): - renderEncoder.setRenderPipelineState(self.drawRadialGradientPipelineStates[gradient.colors.count]!) - - var startBytes: [Float] = [ - Float(gradient.start.x), - Float(gradient.start.y) - ] - var endBytes: [Float] = [ - Float(gradient.end.x), - Float(gradient.end.y) - ] - renderEncoder.setFragmentBytes(&startBytes, length: startBytes.count * 4, index: 1) - renderEncoder.setFragmentBytes(&endBytes, length: endBytes.count * 4, index: 2) - - var colors: [Float] = [] - for color in gradient.colors { - colors.append(color.r) - colors.append(color.g) - colors.append(color.b) - colors.append(color.a) - } - renderEncoder.setFragmentBytes(&colors, length: colors.count * 4, index: 3) - - var steps: [Float] = gradient.colorLocations - renderEncoder.setFragmentBytes(&steps, length: colors.count * 4, index: 4) - } - - var transformBytes = Array(repeating: 0.0, count: 4 * 4) - let transform = CATransform3DMakeAffineTransform(segment.transform) - transformBytes[0] = Float(transform.m11) - transformBytes[1] = Float(transform.m12) - transformBytes[2] = Float(transform.m13) - transformBytes[3] = Float(transform.m14) - transformBytes[4] = Float(transform.m21) - transformBytes[5] = Float(transform.m22) - transformBytes[6] = Float(transform.m23) - transformBytes[7] = Float(transform.m24) - transformBytes[8] = Float(transform.m31) - transformBytes[9] = Float(transform.m32) - transformBytes[10] = Float(transform.m33) - transformBytes[11] = Float(transform.m34) - transformBytes[12] = Float(transform.m41) - transformBytes[13] = Float(transform.m42) - transformBytes[14] = Float(transform.m43) - transformBytes[15] = Float(transform.m44) - - renderEncoder.setVertexBytes(&transformBytes, length: transformBytes.count * 4, index: 2) - var baseVertexIndexBytes: Int32 = Int32(baseVertexIndex) - renderEncoder.setVertexBytes(&baseVertexIndexBytes, length: 4, index: 3) - - renderEncoder.setVertexBuffer(mesh.vertexBuffer, offset: 0, index: 0) - renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: iCount, indexType: .uint32, indexBuffer: mesh.indexBuffer, indexBufferOffset: iStart * 4) - } - - let nextFrame = mesh.currentFrame + 1 - if nextFrame >= mesh.mesh.frames.count { - if mesh.loop { - mesh.currentFrame = 0 - } else { - removeMeshes.append(i) - } - } else { - mesh.currentFrame = nextFrame - } - } - - renderEncoder.endEncoding() - - commandBuffer.present(drawable) - commandBuffer.commit() - - if !removeMeshes.isEmpty { - for i in removeMeshes.reversed() { - self.meshes.remove(at: i) - } - if self.meshes.isEmpty { - self.allAnimationsCompleted?() - } - } - } -} - -private func generateSegments(writeBuffer: MeshWriteBuffer, segmentCount: inout Int, geometry: CapturedGeometryNode, superAlpha: CGFloat, superTransform: CGAffineTransform) { - if geometry.isHidden || geometry.alpha.isZero { - return - } - - for i in 0 ..< geometry.subnodes.count { - generateSegments(writeBuffer: writeBuffer, segmentCount: &segmentCount, geometry: geometry.subnodes[i], superAlpha: superAlpha * geometry.alpha, superTransform: CATransform3DGetAffineTransform(geometry.transform).concatenating(superTransform)) - } - - if let displayItem = geometry.displayItem { - var meshData: LottieMeshData? - let triangleFill: TriangleFill - - switch displayItem.display { - case let .fill(fill): - switch fill.style { - case let .color(color, alpha): - triangleFill = .color(TriangleFill.Color(color).multiplied(alpha: Float(geometry.alpha * alpha * superAlpha))) - case let .gradient(colors, positions, start, end, type): - triangleFill = .gradient(TriangleFill.Gradient(colors: colors.map { TriangleFill.Color($0).multiplied(alpha: Float(geometry.alpha * superAlpha)) }, colorLocations: positions.map(Float.init), start: start, end: end, isRadial: type == .radial)) - } - - let mappedFillRule: LottieMeshFillRule - switch fill.fillRule { - case .evenOdd: - mappedFillRule = .evenOdd - case .winding: - mappedFillRule = .nonZero - default: - mappedFillRule = .evenOdd - } - - meshData = LottieMeshData.generate(with: UIBezierPath(cgPath: displayItem.path), fill: LottieMeshFill(fillRule: mappedFillRule), stroke: nil) - case let .stroke(stroke): - switch stroke.style { - case let .color(color, alpha): - triangleFill = .color(TriangleFill.Color(color).multiplied(alpha: Float(geometry.alpha * alpha * superAlpha))) - case let .gradient(colors, positions, start, end, type): - triangleFill = .gradient(TriangleFill.Gradient(colors: colors.map(TriangleFill.Color.init), colorLocations: positions.map(Float.init), start: start, end: end, isRadial: type == .radial)) - } - - meshData = LottieMeshData.generate(with: UIBezierPath(cgPath: displayItem.path), fill: nil, stroke: LottieMeshStroke(lineWidth: stroke.lineWidth, lineJoin: stroke.lineJoin, lineCap: stroke.lineCap, miterLimit: stroke.miterLimit)) - } - if let meshData = meshData, meshData.triangleCount() != 0 { - let mappedVertices = WriteBuffer() - for i in 0 ..< meshData.vertexCount() { - var x: Float = 0.0 - var y: Float = 0.0 - meshData.getVertexAt(i, x: &x, y: &y) - mappedVertices.writeFloat(x) - mappedVertices.writeFloat(y) - } - - let trianglesData = Data(bytes: meshData.getTriangles(), count: meshData.triangleCount() * 3 * 4) - - let verticesData = mappedVertices.makeData() - - let segment = MeshAnimation.Frame.Segment(vertices: DataRange(data: verticesData, range: 0 ..< verticesData.count), triangles: DataRange(data: trianglesData, range: 0 ..< trianglesData.count), fill: triangleFill, transform: CATransform3DGetAffineTransform(geometry.transform).concatenating(superTransform)) - - segment.write(buffer: writeBuffer) - segmentCount += 1 - } - } -} - -public func generateMeshAnimation(data: Data) -> TempBoxFile? { - guard let animation = try? JSONDecoder().decode(Animation.self, from: data) else { - return nil - } - let container = MyAnimationContainer(animation: animation) - - let tempFile = TempBox.shared.tempFile(fileName: "data") - guard let file = ManagedFile(queue: nil, path: tempFile.path, mode: .readwrite) else { - return nil - } - let writeBuffer = MeshWriteBuffer(file: file) - - let frameCountOffset = writeBuffer.offset - writeBuffer.writeInt32(0) - - var frameCount: Int = 0 - - for i in 0 ..< Int(animation.endFrame) { - container.setFrame(frame: CGFloat(i)) - //#if DEBUG - print("Frame \(i) / \(Int(animation.endFrame))") - //#endif - - let segmentCountOffset = writeBuffer.offset - writeBuffer.writeInt32(0) - var segmentCount: Int = 0 - - let geometry = container.captureGeometry() - geometry.transform = CATransform3DMakeTranslation(256.0, 256.0, 0.0) - - generateSegments(writeBuffer: writeBuffer, segmentCount: &segmentCount, geometry: geometry, superAlpha: 1.0, superTransform: .identity) - - let currentOffset = writeBuffer.offset - writeBuffer.seek(offset: segmentCountOffset) - writeBuffer.writeInt32(Int32(segmentCount)) - - writeBuffer.seek(offset: currentOffset) - - frameCount += 1 - } - - let currentOffset = writeBuffer.offset - writeBuffer.seek(offset: frameCountOffset) - writeBuffer.writeInt32(Int32(frameCount)) - writeBuffer.seek(offset: currentOffset) - - return tempFile -} - -public final class MeshRenderingContext { - -} diff --git a/submodules/LottieMeshSwift/Sources/Render.swift b/submodules/LottieMeshSwift/Sources/Render.swift deleted file mode 100644 index 78d5989904..0000000000 --- a/submodules/LottieMeshSwift/Sources/Render.swift +++ /dev/null @@ -1,138 +0,0 @@ -import Foundation -import UIKit - -func initializeCompositionLayers( - layers: [LayerModel], - assetLibrary: AssetLibrary?, - frameRate: CGFloat -) -> [MyCompositionLayer] { - var compositionLayers = [MyCompositionLayer]() - var layerMap = [Int : MyCompositionLayer]() - - /// Organize the assets into a dictionary of [ID : ImageAsset] - var childLayers = [LayerModel]() - - for layer in layers { - if layer.hidden == true { - let genericLayer = MyNullCompositionLayer(layer: layer) - compositionLayers.append(genericLayer) - layerMap[layer.index] = genericLayer - } else if let shapeLayer = layer as? ShapeLayerModel { - let shapeContainer = MyShapeCompositionLayer(shapeLayer: shapeLayer) - compositionLayers.append(shapeContainer) - layerMap[layer.index] = shapeContainer - } else if let solidLayer = layer as? SolidLayerModel { - let solidContainer = MySolidCompositionLayer(solid: solidLayer) - compositionLayers.append(solidContainer) - layerMap[layer.index] = solidContainer - } else if let precompLayer = layer as? PreCompLayerModel, - let assetLibrary = assetLibrary, - let precompAsset = assetLibrary.precompAssets[precompLayer.referenceID] { - let precompContainer = MyPreCompositionLayer(precomp: precompLayer, - asset: precompAsset, - assetLibrary: assetLibrary, - frameRate: frameRate) - compositionLayers.append(precompContainer) - layerMap[layer.index] = precompContainer - } else if let imageLayer = layer as? ImageLayerModel, - let assetLibrary = assetLibrary, - let imageAsset = assetLibrary.imageAssets[imageLayer.referenceID] { - let imageContainer = MyImageCompositionLayer(imageLayer: imageLayer, size: CGSize(width: imageAsset.width, height: imageAsset.height)) - compositionLayers.append(imageContainer) - layerMap[layer.index] = imageContainer - } else if let _ = layer as? TextLayerModel { - let genericLayer = MyNullCompositionLayer(layer: layer) - compositionLayers.append(genericLayer) - layerMap[layer.index] = genericLayer - /*let textContainer = TextCompositionLayer(textLayer: textLayer, textProvider: textProvider, fontProvider: fontProvider) - compositionLayers.append(textContainer) - layerMap[layer.index] = textContainer*/ - } else { - let genericLayer = MyNullCompositionLayer(layer: layer) - compositionLayers.append(genericLayer) - layerMap[layer.index] = genericLayer - } - if layer.parent != nil { - childLayers.append(layer) - } - } - - /// Now link children with their parents - for layerModel in childLayers { - if let parentID = layerModel.parent { - let childLayer = layerMap[layerModel.index] - let parentLayer = layerMap[parentID] - childLayer?.transformNode.parentNode = parentLayer?.transformNode - } - } - - return compositionLayers -} - -final class MyAnimationContainer { - let bounds: CGRect - - var currentFrame: CGFloat = 0.0 - - /// Forces the view to update its drawing. - func forceDisplayUpdate() { - animationLayers.forEach( { $0.displayWithFrame(frame: currentFrame, forceUpdates: true) }) - } - - var animationLayers: [MyCompositionLayer] - - init(animation: Animation) { - self.animationLayers = [] - - self.bounds = CGRect(origin: CGPoint(), size: CGSize(width: animation.width, height: animation.height)) - let layers = initializeCompositionLayers(layers: animation.layers, assetLibrary: animation.assetLibrary, frameRate: CGFloat(animation.framerate)) - - var imageLayers = [MyImageCompositionLayer]() - - var mattedLayer: MyCompositionLayer? = nil - - for layer in layers.reversed() { - layer.bounds = bounds - animationLayers.append(layer) - if let imageLayer = layer as? MyImageCompositionLayer { - imageLayers.append(imageLayer) - } - if let matte = mattedLayer { - /// The previous layer requires this layer to be its matte - matte.matteLayer = layer - mattedLayer = nil - continue - } - if let matte = layer.matteType, - (matte == .add || matte == .invert) { - /// We have a layer that requires a matte. - mattedLayer = layer - } - - //NOTE - //addSublayer(layer) - } - } - - func setFrame(frame: CGFloat) { - self.currentFrame = frame - for animationLayer in self.animationLayers { - animationLayer.displayWithFrame(frame: frame, forceUpdates: false) - } - } - - func captureGeometry() -> CapturedGeometryNode { - var subnodes: [CapturedGeometryNode] = [] - for animationLayer in self.animationLayers { - let capturedSubnode = animationLayer.captureGeometry() - subnodes.append(capturedSubnode) - } - return CapturedGeometryNode( - transform: CATransform3DIdentity, - alpha: 1.0, - isHidden: false, - displayItem: nil, - subnodes: subnodes - ) - } -} diff --git a/submodules/LottieMeshSwift/libtess2/Include/tesselator.h b/submodules/LottieMeshSwift/libtess2/Include/tesselator.h deleted file mode 100755 index 3d431559a1..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Include/tesselator.h +++ /dev/null @@ -1,242 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Mikko Mononen, July 2009. -*/ - -#ifndef TESSELATOR_H -#define TESSELATOR_H - -#ifdef __cplusplus -extern "C" { -#endif - -// See OpenGL Red Book for description of the winding rules -// http://www.glprogramming.com/red/chapter11.html -enum TessWindingRule -{ - TESS_WINDING_ODD, - TESS_WINDING_NONZERO, - TESS_WINDING_POSITIVE, - TESS_WINDING_NEGATIVE, - TESS_WINDING_ABS_GEQ_TWO, -}; - -// The contents of the tessGetElements() depends on element type being passed to tessTesselate(). -// Tesselation result element types: -// TESS_POLYGONS -// Each element in the element array is polygon defined as 'polySize' number of vertex indices. -// If a polygon has than 'polySize' vertices, the remaining indices are stored as TESS_UNDEF. -// Example, drawing a polygon: -// const int nelems = tessGetElementCount(tess); -// const TESSindex* elems = tessGetElements(tess); -// for (int i = 0; i < nelems; i++) { -// const TESSindex* poly = &elems[i * polySize]; -// glBegin(GL_POLYGON); -// for (int j = 0; j < polySize; j++) { -// if (poly[j] == TESS_UNDEF) break; -// glVertex2fv(&verts[poly[j]*vertexSize]); -// } -// glEnd(); -// } -// -// TESS_CONNECTED_POLYGONS -// Each element in the element array is polygon defined as 'polySize' number of vertex indices, -// followed by 'polySize' indices to neighour polygons, that is each element is 'polySize' * 2 indices. -// If a polygon has than 'polySize' vertices, the remaining indices are stored as TESS_UNDEF. -// If a polygon edge is a boundary, that is, not connected to another polygon, the neighbour index is TESS_UNDEF. -// Example, flood fill based on seed polygon: -// const int nelems = tessGetElementCount(tess); -// const TESSindex* elems = tessGetElements(tess); -// unsigned char* visited = (unsigned char*)calloc(nelems); -// TESSindex stack[50]; -// int nstack = 0; -// stack[nstack++] = seedPoly; -// visited[startPoly] = 1; -// while (nstack > 0) { -// TESSindex idx = stack[--nstack]; -// const TESSindex* poly = &elems[idx * polySize * 2]; -// const TESSindex* nei = &poly[polySize]; -// for (int i = 0; i < polySize; i++) { -// if (poly[i] == TESS_UNDEF) break; -// if (nei[i] != TESS_UNDEF && !visited[nei[i]]) -// stack[nstack++] = nei[i]; -// visited[nei[i]] = 1; -// } -// } -// } -// -// TESS_BOUNDARY_CONTOURS -// Each element in the element array is [base index, count] pair defining a range of vertices for a contour. -// The first value is index to first vertex in contour and the second value is number of vertices in the contour. -// Example, drawing contours: -// const int nelems = tessGetElementCount(tess); -// const TESSindex* elems = tessGetElements(tess); -// for (int i = 0; i < nelems; i++) { -// const TESSindex base = elems[i * 2]; -// const TESSindex count = elems[i * 2 + 1]; -// glBegin(GL_LINE_LOOP); -// for (int j = 0; j < count; j++) { -// glVertex2fv(&verts[(base+j) * vertexSize]); -// } -// glEnd(); -// } - -enum TessElementType -{ - TESS_POLYGONS, - TESS_CONNECTED_POLYGONS, - TESS_BOUNDARY_CONTOURS, -}; - - -// TESS_CONSTRAINED_DELAUNAY_TRIANGULATION -// If enabled, the initial triagulation is improved with non-robust Constrained Delayney triangulation. -// Disable by default. -// -// TESS_REVERSE_CONTOURS -// If enabled, tessAddContour() will treat CW contours as CCW and vice versa -// Disabled by default. - -enum TessOption -{ - TESS_CONSTRAINED_DELAUNAY_TRIANGULATION, - TESS_REVERSE_CONTOURS -}; - -typedef float TESSreal; -typedef int TESSindex; -typedef struct TESStesselator TESStesselator; -typedef struct TESSalloc TESSalloc; - -#define TESS_UNDEF (~(TESSindex)0) - -#define TESS_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) - -// Custom memory allocator interface. -// The internal memory allocator allocates mesh edges, vertices and faces -// as well as dictionary nodes and active regions in buckets and uses simple -// freelist to speed up the allocation. The bucket size should roughly match your -// expected input data. For example if you process only hundreds of vertices, -// a bucket size of 128 might be ok, where as when processing thousands of vertices -// bucket size of 1024 might be approproate. The bucket size is a compromise between -// how often to allocate memory from the system versus how much extra space the system -// should allocate. Reasonable defaults are show in commects below, they will be used if -// the bucket sizes are zero. -// -// The use may left the memrealloc to be null. In that case, the tesselator will not try to -// dynamically grow int's internal arrays. The tesselator only needs the reallocation when it -// has found intersecting segments and needs to add new vertex. This defency can be cured by -// allocating some extra vertices beforehand. The 'extraVertices' variable allows to specify -// number of expected extra vertices. -struct TESSalloc -{ - void *(*memalloc)( void *userData, unsigned int size ); - void *(*memrealloc)( void *userData, void* ptr, unsigned int size ); - void (*memfree)( void *userData, void *ptr ); - void* userData; // User data passed to the allocator functions. - int meshEdgeBucketSize; // 512 - int meshVertexBucketSize; // 512 - int meshFaceBucketSize; // 256 - int dictNodeBucketSize; // 512 - int regionBucketSize; // 256 - int extraVertices; // Number of extra vertices allocated for the priority queue. -}; - - -// -// Example use: -// -// -// -// - -// tessNewTess() - Creates a new tesselator. -// Use tessDeleteTess() to delete the tesselator. -// Parameters: -// alloc - pointer to a filled TESSalloc struct or NULL to use default malloc based allocator. -// Returns: -// new tesselator object. -TESStesselator* tessNewTess( TESSalloc* alloc ); - -// tessDeleteTess() - Deletes a tesselator. -// Parameters: -// tess - pointer to tesselator object to be deleted. -void tessDeleteTess( TESStesselator *tess ); - -// tessAddContour() - Adds a contour to be tesselated. -// The type of the vertex coordinates is assumed to be TESSreal. -// Parameters: -// tess - pointer to tesselator object. -// size - number of coordinates per vertex. Must be 2 or 3. -// pointer - pointer to the first coordinate of the first vertex in the array. -// stride - defines offset in bytes between consecutive vertices. -// count - number of vertices in contour. -void tessAddContour( TESStesselator *tess, int size, const void* pointer, int stride, int count ); - -// tessSetOption() - Toggles optional tessellation parameters -// Parameters: -// option - one of TessOption -// value - 1 if enabled, 0 if disabled. -void tessSetOption( TESStesselator *tess, int option, int value ); - -// tessTesselate() - tesselate contours. -// Parameters: -// tess - pointer to tesselator object. -// windingRule - winding rules used for tesselation, must be one of TessWindingRule. -// elementType - defines the tesselation result element type, must be one of TessElementType. -// polySize - defines maximum vertices per polygons if output is polygons. -// vertexSize - defines the number of coordinates in tesselation result vertex, must be 2 or 3. -// normal - defines the normal of the input contours, of null the normal is calculated automatically. -// Returns: -// 1 if succeed, 0 if failed. -int tessTesselate( TESStesselator *tess, int windingRule, int elementType, int polySize, int vertexSize, const TESSreal* normal ); - -// tessGetVertexCount() - Returns number of vertices in the tesselated output. -int tessGetVertexCount( TESStesselator *tess ); - -// tessGetVertices() - Returns pointer to first coordinate of first vertex. -const TESSreal* tessGetVertices( TESStesselator *tess ); - -// tessGetVertexIndices() - Returns pointer to first vertex index. -// Vertex indices can be used to map the generated vertices to the original vertices. -// Every point added using tessAddContour() will get a new index starting at 0. -// New vertices generated at the intersections of segments are assigned value TESS_UNDEF. -const TESSindex* tessGetVertexIndices( TESStesselator *tess ); - -// tessGetElementCount() - Returns number of elements in the the tesselated output. -int tessGetElementCount( TESStesselator *tess ); - -// tessGetElements() - Returns pointer to the first element. -const TESSindex* tessGetElements( TESStesselator *tess ); - -#ifdef __cplusplus -}; -#endif - -#endif // TESSELATOR_H diff --git a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.c b/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.c deleted file mode 100755 index 420ebab5ea..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.c +++ /dev/null @@ -1,191 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Mikko Mononen, July 2009. -*/ - -#include -#include -#include "../Include/tesselator.h" - -//#define CHECK_BOUNDS - -typedef struct BucketAlloc BucketAlloc; -typedef struct Bucket Bucket; - -struct Bucket -{ - Bucket *next; -}; - -struct BucketAlloc -{ - void *freelist; - Bucket *buckets; - unsigned int itemSize; - unsigned int bucketSize; - const char *name; - TESSalloc* alloc; -}; - -static int CreateBucket( struct BucketAlloc* ba ) -{ - size_t size; - Bucket* bucket; - void* freelist; - unsigned char* head; - unsigned char* it; - - // Allocate memory for the bucket - size = sizeof(Bucket) + ba->itemSize * ba->bucketSize; - bucket = (Bucket*)ba->alloc->memalloc( ba->alloc->userData, size ); - if ( !bucket ) - return 0; - bucket->next = 0; - - // Add the bucket into the list of buckets. - bucket->next = ba->buckets; - ba->buckets = bucket; - - // Add new items to the free list. - freelist = ba->freelist; - head = (unsigned char*)bucket + sizeof(Bucket); - it = head + ba->itemSize * ba->bucketSize; - do - { - it -= ba->itemSize; - // Store pointer to next free item. - *((void**)it) = freelist; - // Pointer to next location containing a free item. - freelist = (void*)it; - } - while ( it != head ); - // Update pointer to next location containing a free item. - ba->freelist = (void*)it; - - return 1; -} - -static void *NextFreeItem( struct BucketAlloc *ba ) -{ - return *(void**)ba->freelist; -} - -struct BucketAlloc* createBucketAlloc( TESSalloc* alloc, const char* name, - unsigned int itemSize, unsigned int bucketSize ) -{ - BucketAlloc* ba = (BucketAlloc*)alloc->memalloc( alloc->userData, sizeof(BucketAlloc) ); - - ba->alloc = alloc; - ba->name = name; - ba->itemSize = itemSize; - if ( ba->itemSize < sizeof(void*) ) - ba->itemSize = sizeof(void*); - ba->bucketSize = bucketSize; - ba->freelist = 0; - ba->buckets = 0; - - if ( !CreateBucket( ba ) ) - { - alloc->memfree( alloc->userData, ba ); - return 0; - } - - return ba; -} - -void* bucketAlloc( struct BucketAlloc *ba ) -{ - void *it; - - // If running out of memory, allocate new bucket and update the freelist. - if ( !ba->freelist || !NextFreeItem( ba ) ) - { - if ( !CreateBucket( ba ) ) - return 0; - } - - // Pop item from in front of the free list. - it = ba->freelist; - ba->freelist = NextFreeItem( ba ); - - return it; -} - -void bucketFree( struct BucketAlloc *ba, void *ptr ) -{ -#ifdef CHECK_BOUNDS - int inBounds = 0; - Bucket *bucket; - - // Check that the pointer is allocated with this allocator. - bucket = ba->buckets; - while ( bucket ) - { - void *bucketMin = (void*)((unsigned char*)bucket + sizeof(Bucket)); - void *bucketMax = (void*)((unsigned char*)bucket + sizeof(Bucket) + ba->itemSize * ba->bucketSize); - if ( ptr >= bucketMin && ptr < bucketMax ) - { - inBounds = 1; - break; - } - bucket = bucket->next; - } - - if ( inBounds ) - { - // Add the node in front of the free list. - *(void**)ptr = ba->freelist; - ba->freelist = ptr; - } - else - { - printf("ERROR! pointer 0x%p does not belong to allocator '%s'\n", ba->name); - } -#else - // Add the node in front of the free list. - *(void**)ptr = ba->freelist; - ba->freelist = ptr; -#endif -} - -void deleteBucketAlloc( struct BucketAlloc *ba ) -{ - TESSalloc* alloc = ba->alloc; - Bucket *bucket = ba->buckets; - Bucket *next; - while ( bucket ) - { - next = bucket->next; - alloc->memfree( alloc->userData, bucket ); - bucket = next; - } - ba->freelist = 0; - ba->buckets = 0; - alloc->memfree( alloc->userData, ba ); -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.h b/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.h deleted file mode 100755 index c540951ea3..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/bucketalloc.h +++ /dev/null @@ -1,51 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Mikko Mononen, July 2009. -*/ - -#ifndef MEMALLOC_H -#define MEMALLOC_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "tesselator.h" - -struct BucketAlloc *createBucketAlloc( TESSalloc* alloc, const char *name, - unsigned int itemSize, unsigned int bucketSize ); -void *bucketAlloc( struct BucketAlloc *ba); -void bucketFree( struct BucketAlloc *ba, void *ptr ); -void deleteBucketAlloc( struct BucketAlloc *ba ); - -#ifdef __cplusplus -}; -#endif - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/dict.c b/submodules/LottieMeshSwift/libtess2/Sources/dict.c deleted file mode 100755 index 650adda21d..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/dict.c +++ /dev/null @@ -1,109 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#include -#include "../Include/tesselator.h" -#include "bucketalloc.h" -#include "dict.h" - -/* really tessDictListNewDict */ -Dict *dictNewDict( TESSalloc* alloc, void *frame, int (*leq)(void *frame, DictKey key1, DictKey key2) ) -{ - Dict *dict = (Dict *)alloc->memalloc( alloc->userData, sizeof( Dict )); - DictNode *head; - - if (dict == NULL) return NULL; - - head = &dict->head; - - head->key = NULL; - head->next = head; - head->prev = head; - - dict->frame = frame; - dict->leq = leq; - - if (alloc->dictNodeBucketSize < 16) - alloc->dictNodeBucketSize = 16; - if (alloc->dictNodeBucketSize > 4096) - alloc->dictNodeBucketSize = 4096; - dict->nodePool = createBucketAlloc( alloc, "Dict", sizeof(DictNode), alloc->dictNodeBucketSize ); - - return dict; -} - -/* really tessDictListDeleteDict */ -void dictDeleteDict( TESSalloc* alloc, Dict *dict ) -{ - deleteBucketAlloc( dict->nodePool ); - alloc->memfree( alloc->userData, dict ); -} - -/* really tessDictListInsertBefore */ -DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key ) -{ - DictNode *newNode; - - do { - node = node->prev; - } while( node->key != NULL && ! (*dict->leq)(dict->frame, node->key, key)); - - newNode = (DictNode *)bucketAlloc( dict->nodePool ); - if (newNode == NULL) return NULL; - - newNode->key = key; - newNode->next = node->next; - node->next->prev = newNode; - newNode->prev = node; - node->next = newNode; - - return newNode; -} - -/* really tessDictListDelete */ -void dictDelete( Dict *dict, DictNode *node ) /*ARGSUSED*/ -{ - node->next->prev = node->prev; - node->prev->next = node->next; - bucketFree( dict->nodePool, node ); -} - -/* really tessDictListSearch */ -DictNode *dictSearch( Dict *dict, DictKey key ) -{ - DictNode *node = &dict->head; - - do { - node = node->next; - } while( node->key != NULL && ! (*dict->leq)(dict->frame, key, node->key)); - - return node; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/dict.h b/submodules/LottieMeshSwift/libtess2/Sources/dict.h deleted file mode 100755 index 4cf322657b..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/dict.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef DICT_LIST_H -#define DICT_LIST_H - -typedef void *DictKey; -typedef struct Dict Dict; -typedef struct DictNode DictNode; - -Dict *dictNewDict( TESSalloc* alloc, void *frame, int (*leq)(void *frame, DictKey key1, DictKey key2) ); - -void dictDeleteDict( TESSalloc* alloc, Dict *dict ); - -/* Search returns the node with the smallest key greater than or equal -* to the given key. If there is no such key, returns a node whose -* key is NULL. Similarly, Succ(Max(d)) has a NULL key, etc. -*/ -DictNode *dictSearch( Dict *dict, DictKey key ); -DictNode *dictInsertBefore( Dict *dict, DictNode *node, DictKey key ); -void dictDelete( Dict *dict, DictNode *node ); - -#define dictKey(n) ((n)->key) -#define dictSucc(n) ((n)->next) -#define dictPred(n) ((n)->prev) -#define dictMin(d) ((d)->head.next) -#define dictMax(d) ((d)->head.prev) -#define dictInsert(d,k) (dictInsertBefore((d),&(d)->head,(k))) - - -/*** Private data structures ***/ - -struct DictNode { - DictKey key; - DictNode *next; - DictNode *prev; -}; - -struct Dict { - DictNode head; - void *frame; - struct BucketAlloc *nodePool; - int (*leq)(void *frame, DictKey key1, DictKey key2); -}; - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/geom.c b/submodules/LottieMeshSwift/libtess2/Sources/geom.c deleted file mode 100755 index 66005540d4..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/geom.c +++ /dev/null @@ -1,293 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -//#include "tesos.h" -#include -#include "mesh.h" -#include "geom.h" -#include - -int tesvertLeq( TESSvertex *u, TESSvertex *v ) -{ - /* Returns TRUE if u is lexicographically <= v. */ - - return VertLeq( u, v ); -} - -TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w), - * evaluates the t-coord of the edge uw at the s-coord of the vertex v. - * Returns v->t - (uw)(v->s), ie. the signed distance from uw to v. - * If uw is vertical (and thus passes thru v), the result is zero. - * - * The calculation is extremely accurate and stable, even when v - * is very close to u or w. In particular if we set v->t = 0 and - * let r be the negated result (this evaluates (uw)(v->s)), then - * r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t). - */ - TESSreal gapL, gapR; - - assert( VertLeq( u, v ) && VertLeq( v, w )); - - gapL = v->s - u->s; - gapR = w->s - v->s; - - if( gapL + gapR > 0 ) { - if( gapL < gapR ) { - return (v->t - u->t) + (u->t - w->t) * (gapL / (gapL + gapR)); - } else { - return (v->t - w->t) + (w->t - u->t) * (gapR / (gapL + gapR)); - } - } - /* vertical line */ - return 0; -} - -TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Returns a number whose sign matches EdgeEval(u,v,w) but which - * is cheaper to evaluate. Returns > 0, == 0 , or < 0 - * as v is above, on, or below the edge uw. - */ - TESSreal gapL, gapR; - - assert( VertLeq( u, v ) && VertLeq( v, w )); - - gapL = v->s - u->s; - gapR = w->s - v->s; - - if( gapL + gapR > 0 ) { - return (v->t - w->t) * gapL + (v->t - u->t) * gapR; - } - /* vertical line */ - return 0; -} - - -/*********************************************************************** -* Define versions of EdgeSign, EdgeEval with s and t transposed. -*/ - -TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Given three vertices u,v,w such that TransLeq(u,v) && TransLeq(v,w), - * evaluates the t-coord of the edge uw at the s-coord of the vertex v. - * Returns v->s - (uw)(v->t), ie. the signed distance from uw to v. - * If uw is vertical (and thus passes thru v), the result is zero. - * - * The calculation is extremely accurate and stable, even when v - * is very close to u or w. In particular if we set v->s = 0 and - * let r be the negated result (this evaluates (uw)(v->t)), then - * r is guaranteed to satisfy MIN(u->s,w->s) <= r <= MAX(u->s,w->s). - */ - TESSreal gapL, gapR; - - assert( TransLeq( u, v ) && TransLeq( v, w )); - - gapL = v->t - u->t; - gapR = w->t - v->t; - - if( gapL + gapR > 0 ) { - if( gapL < gapR ) { - return (v->s - u->s) + (u->s - w->s) * (gapL / (gapL + gapR)); - } else { - return (v->s - w->s) + (w->s - u->s) * (gapR / (gapL + gapR)); - } - } - /* vertical line */ - return 0; -} - -TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* Returns a number whose sign matches TransEval(u,v,w) but which - * is cheaper to evaluate. Returns > 0, == 0 , or < 0 - * as v is above, on, or below the edge uw. - */ - TESSreal gapL, gapR; - - assert( TransLeq( u, v ) && TransLeq( v, w )); - - gapL = v->t - u->t; - gapR = w->t - v->t; - - if( gapL + gapR > 0 ) { - return (v->s - w->s) * gapL + (v->s - u->s) * gapR; - } - /* vertical line */ - return 0; -} - - -int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w ) -{ - /* For almost-degenerate situations, the results are not reliable. - * Unless the floating-point arithmetic can be performed without - * rounding errors, *any* implementation will give incorrect results - * on some degenerate inputs, so the client must have some way to - * handle this situation. - */ - return (u->s*(v->t - w->t) + v->s*(w->t - u->t) + w->s*(u->t - v->t)) >= 0; -} - -/* Given parameters a,x,b,y returns the value (b*x+a*y)/(a+b), -* or (x+y)/2 if a==b==0. It requires that a,b >= 0, and enforces -* this in the rare case that one argument is slightly negative. -* The implementation is extremely stable numerically. -* In particular it guarantees that the result r satisfies -* MIN(x,y) <= r <= MAX(x,y), and the results are very accurate -* even when a and b differ greatly in magnitude. -*/ -#define RealInterpolate(a,x,b,y) \ - (a = (a < 0) ? 0 : a, b = (b < 0) ? 0 : b, \ - ((a <= b) ? ((b == 0) ? ((x+y) / 2) \ - : (x + (y-x) * (a/(a+b)))) \ - : (y + (x-y) * (b/(a+b))))) - -#ifndef FOR_TRITE_TEST_PROGRAM -#define Interpolate(a,x,b,y) RealInterpolate(a,x,b,y) -#else - -/* Claim: the ONLY property the sweep algorithm relies on is that -* MIN(x,y) <= r <= MAX(x,y). This is a nasty way to test that. -*/ -#include -extern int RandomInterpolate; - -double Interpolate( double a, double x, double b, double y) -{ - printf("*********************%d\n",RandomInterpolate); - if( RandomInterpolate ) { - a = 1.2 * drand48() - 0.1; - a = (a < 0) ? 0 : ((a > 1) ? 1 : a); - b = 1.0 - a; - } - return RealInterpolate(a,x,b,y); -} - -#endif - -#define Swap(a,b) if (1) { TESSvertex *t = a; a = b; b = t; } else - -void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1, - TESSvertex *o2, TESSvertex *d2, - TESSvertex *v ) - /* Given edges (o1,d1) and (o2,d2), compute their point of intersection. - * The computed point is guaranteed to lie in the intersection of the - * bounding rectangles defined by each edge. - */ -{ - TESSreal z1, z2; - - /* This is certainly not the most efficient way to find the intersection - * of two line segments, but it is very numerically stable. - * - * Strategy: find the two middle vertices in the VertLeq ordering, - * and interpolate the intersection s-value from these. Then repeat - * using the TransLeq ordering to find the intersection t-value. - */ - - if( ! VertLeq( o1, d1 )) { Swap( o1, d1 ); } - if( ! VertLeq( o2, d2 )) { Swap( o2, d2 ); } - if( ! VertLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); } - - if( ! VertLeq( o2, d1 )) { - /* Technically, no intersection -- do our best */ - v->s = (o2->s + d1->s) / 2; - } else if( VertLeq( d1, d2 )) { - /* Interpolate between o2 and d1 */ - z1 = EdgeEval( o1, o2, d1 ); - z2 = EdgeEval( o2, d1, d2 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->s = Interpolate( z1, o2->s, z2, d1->s ); - } else { - /* Interpolate between o2 and d2 */ - z1 = EdgeSign( o1, o2, d1 ); - z2 = -EdgeSign( o1, d2, d1 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->s = Interpolate( z1, o2->s, z2, d2->s ); - } - - /* Now repeat the process for t */ - - if( ! TransLeq( o1, d1 )) { Swap( o1, d1 ); } - if( ! TransLeq( o2, d2 )) { Swap( o2, d2 ); } - if( ! TransLeq( o1, o2 )) { Swap( o1, o2 ); Swap( d1, d2 ); } - - if( ! TransLeq( o2, d1 )) { - /* Technically, no intersection -- do our best */ - v->t = (o2->t + d1->t) / 2; - } else if( TransLeq( d1, d2 )) { - /* Interpolate between o2 and d1 */ - z1 = TransEval( o1, o2, d1 ); - z2 = TransEval( o2, d1, d2 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->t = Interpolate( z1, o2->t, z2, d1->t ); - } else { - /* Interpolate between o2 and d2 */ - z1 = TransSign( o1, o2, d1 ); - z2 = -TransSign( o1, d2, d1 ); - if( z1+z2 < 0 ) { z1 = -z1; z2 = -z2; } - v->t = Interpolate( z1, o2->t, z2, d2->t ); - } -} - -TESSreal inCircle( TESSvertex *v, TESSvertex *v0, TESSvertex *v1, TESSvertex *v2 ) { - TESSreal adx, ady, bdx, bdy, cdx, cdy; - TESSreal abdet, bcdet, cadet; - TESSreal alift, blift, clift; - - adx = v0->s - v->s; - ady = v0->t - v->t; - bdx = v1->s - v->s; - bdy = v1->t - v->t; - cdx = v2->s - v->s; - cdy = v2->t - v->t; - - abdet = adx * bdy - bdx * ady; - bcdet = bdx * cdy - cdx * bdy; - cadet = cdx * ady - adx * cdy; - - alift = adx * adx + ady * ady; - blift = bdx * bdx + bdy * bdy; - clift = cdx * cdx + cdy * cdy; - - return alift * bcdet + blift * cadet + clift * abdet; -} - -/* - Returns 1 is edge is locally delaunay - */ -int tesedgeIsLocallyDelaunay( TESShalfEdge *e ) -{ - return inCircle(e->Sym->Lnext->Lnext->Org, e->Lnext->Org, e->Lnext->Lnext->Org, e->Org) < 0; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/geom.h b/submodules/LottieMeshSwift/libtess2/Sources/geom.h deleted file mode 100755 index c29a932685..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/geom.h +++ /dev/null @@ -1,78 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef GEOM_H -#define GEOM_H - -#include "mesh.h" - -#ifdef NO_BRANCH_CONDITIONS -/* MIPS architecture has special instructions to evaluate boolean -* conditions -- more efficient than branching, IF you can get the -* compiler to generate the right instructions (SGI compiler doesn't) -*/ -#define VertEq(u,v) (((u)->s == (v)->s) & ((u)->t == (v)->t)) -#define VertLeq(u,v) (((u)->s < (v)->s) | \ - ((u)->s == (v)->s & (u)->t <= (v)->t)) -#else -#define VertEq(u,v) ((u)->s == (v)->s && (u)->t == (v)->t) -#define VertLeq(u,v) (((u)->s < (v)->s) || ((u)->s == (v)->s && (u)->t <= (v)->t)) -#endif - -#define EdgeEval(u,v,w) tesedgeEval(u,v,w) -#define EdgeSign(u,v,w) tesedgeSign(u,v,w) - -/* Versions of VertLeq, EdgeSign, EdgeEval with s and t transposed. */ - -#define TransLeq(u,v) (((u)->t < (v)->t) || ((u)->t == (v)->t && (u)->s <= (v)->s)) -#define TransEval(u,v,w) testransEval(u,v,w) -#define TransSign(u,v,w) testransSign(u,v,w) - - -#define EdgeGoesLeft(e) VertLeq( (e)->Dst, (e)->Org ) -#define EdgeGoesRight(e) VertLeq( (e)->Org, (e)->Dst ) -#define EdgeIsInternal(e) e->Rface && e->Rface->inside - -#define ABS(x) ((x) < 0 ? -(x) : (x)) -#define VertL1dist(u,v) (ABS(u->s - v->s) + ABS(u->t - v->t)) - -#define VertCCW(u,v,w) tesvertCCW(u,v,w) - -int tesvertLeq( TESSvertex *u, TESSvertex *v ); -TESSreal tesedgeEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -TESSreal tesedgeSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -TESSreal testransEval( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -TESSreal testransSign( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -int tesvertCCW( TESSvertex *u, TESSvertex *v, TESSvertex *w ); -void tesedgeIntersect( TESSvertex *o1, TESSvertex *d1, TESSvertex *o2, TESSvertex *d2, TESSvertex *v ); -int tesedgeIsLocallyDelaunay( TESShalfEdge *e ); - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/mesh.c b/submodules/LottieMeshSwift/libtess2/Sources/mesh.c deleted file mode 100755 index a0fa08e577..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/mesh.c +++ /dev/null @@ -1,917 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -//#include "tesos.h" -#include -#include -#include "mesh.h" -#include "geom.h" -#include "bucketalloc.h" - -#define TRUE 1 -#define FALSE 0 - -/************************ Utility Routines ************************/ - -/* Allocate and free half-edges in pairs for efficiency. -* The *only* place that should use this fact is allocation/free. -*/ -typedef struct { TESShalfEdge e, eSym; } EdgePair; - -/* MakeEdge creates a new pair of half-edges which form their own loop. -* No vertex or face structures are allocated, but these must be assigned -* before the current edge operation is completed. -*/ -static TESShalfEdge *MakeEdge( TESSmesh* mesh, TESShalfEdge *eNext ) -{ - TESShalfEdge *e; - TESShalfEdge *eSym; - TESShalfEdge *ePrev; - EdgePair *pair = (EdgePair *)bucketAlloc( mesh->edgeBucket ); - if (pair == NULL) return NULL; - - e = &pair->e; - eSym = &pair->eSym; - - /* Make sure eNext points to the first edge of the edge pair */ - if( eNext->Sym < eNext ) { eNext = eNext->Sym; } - - /* Insert in circular doubly-linked list before eNext. - * Note that the prev pointer is stored in Sym->next. - */ - ePrev = eNext->Sym->next; - eSym->next = ePrev; - ePrev->Sym->next = e; - e->next = eNext; - eNext->Sym->next = eSym; - - e->Sym = eSym; - e->Onext = e; - e->Lnext = eSym; - e->Org = NULL; - e->Lface = NULL; - e->winding = 0; - e->activeRegion = NULL; - e->mark = 0; - - eSym->Sym = e; - eSym->Onext = eSym; - eSym->Lnext = e; - eSym->Org = NULL; - eSym->Lface = NULL; - eSym->winding = 0; - eSym->activeRegion = NULL; - eSym->mark = 0; - - return e; -} - -/* Splice( a, b ) is best described by the Guibas/Stolfi paper or the -* CS348a notes (see mesh.h). Basically it modifies the mesh so that -* a->Onext and b->Onext are exchanged. This can have various effects -* depending on whether a and b belong to different face or vertex rings. -* For more explanation see tessMeshSplice() below. -*/ -static void Splice( TESShalfEdge *a, TESShalfEdge *b ) -{ - TESShalfEdge *aOnext = a->Onext; - TESShalfEdge *bOnext = b->Onext; - - aOnext->Sym->Lnext = b; - bOnext->Sym->Lnext = a; - a->Onext = bOnext; - b->Onext = aOnext; -} - -/* MakeVertex( newVertex, eOrig, vNext ) attaches a new vertex and makes it the -* origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives -* a place to insert the new vertex in the global vertex list. We insert -* the new vertex *before* vNext so that algorithms which walk the vertex -* list will not see the newly created vertices. -*/ -static void MakeVertex( TESSvertex *newVertex, - TESShalfEdge *eOrig, TESSvertex *vNext ) -{ - TESShalfEdge *e; - TESSvertex *vPrev; - TESSvertex *vNew = newVertex; - - assert(vNew != NULL); - - /* insert in circular doubly-linked list before vNext */ - vPrev = vNext->prev; - vNew->prev = vPrev; - vPrev->next = vNew; - vNew->next = vNext; - vNext->prev = vNew; - - vNew->anEdge = eOrig; - /* leave coords, s, t undefined */ - - /* fix other edges on this vertex loop */ - e = eOrig; - do { - e->Org = vNew; - e = e->Onext; - } while( e != eOrig ); -} - -/* MakeFace( newFace, eOrig, fNext ) attaches a new face and makes it the left -* face of all edges in the face loop to which eOrig belongs. "fNext" gives -* a place to insert the new face in the global face list. We insert -* the new face *before* fNext so that algorithms which walk the face -* list will not see the newly created faces. -*/ -static void MakeFace( TESSface *newFace, TESShalfEdge *eOrig, TESSface *fNext ) -{ - TESShalfEdge *e; - TESSface *fPrev; - TESSface *fNew = newFace; - - assert(fNew != NULL); - - /* insert in circular doubly-linked list before fNext */ - fPrev = fNext->prev; - fNew->prev = fPrev; - fPrev->next = fNew; - fNew->next = fNext; - fNext->prev = fNew; - - fNew->anEdge = eOrig; - fNew->trail = NULL; - fNew->marked = FALSE; - - /* The new face is marked "inside" if the old one was. This is a - * convenience for the common case where a face has been split in two. - */ - fNew->inside = fNext->inside; - - /* fix other edges on this face loop */ - e = eOrig; - do { - e->Lface = fNew; - e = e->Lnext; - } while( e != eOrig ); -} - -/* KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym), -* and removes from the global edge list. -*/ -static void KillEdge( TESSmesh *mesh, TESShalfEdge *eDel ) -{ - TESShalfEdge *ePrev, *eNext; - - /* Half-edges are allocated in pairs, see EdgePair above */ - if( eDel->Sym < eDel ) { eDel = eDel->Sym; } - - /* delete from circular doubly-linked list */ - eNext = eDel->next; - ePrev = eDel->Sym->next; - eNext->Sym->next = ePrev; - ePrev->Sym->next = eNext; - - bucketFree( mesh->edgeBucket, eDel ); -} - - -/* KillVertex( vDel ) destroys a vertex and removes it from the global -* vertex list. It updates the vertex loop to point to a given new vertex. -*/ -static void KillVertex( TESSmesh *mesh, TESSvertex *vDel, TESSvertex *newOrg ) -{ - TESShalfEdge *e, *eStart = vDel->anEdge; - TESSvertex *vPrev, *vNext; - - /* change the origin of all affected edges */ - e = eStart; - do { - e->Org = newOrg; - e = e->Onext; - } while( e != eStart ); - - /* delete from circular doubly-linked list */ - vPrev = vDel->prev; - vNext = vDel->next; - vNext->prev = vPrev; - vPrev->next = vNext; - - bucketFree( mesh->vertexBucket, vDel ); -} - -/* KillFace( fDel ) destroys a face and removes it from the global face -* list. It updates the face loop to point to a given new face. -*/ -static void KillFace( TESSmesh *mesh, TESSface *fDel, TESSface *newLface ) -{ - TESShalfEdge *e, *eStart = fDel->anEdge; - TESSface *fPrev, *fNext; - - /* change the left face of all affected edges */ - e = eStart; - do { - e->Lface = newLface; - e = e->Lnext; - } while( e != eStart ); - - /* delete from circular doubly-linked list */ - fPrev = fDel->prev; - fNext = fDel->next; - fNext->prev = fPrev; - fPrev->next = fNext; - - bucketFree( mesh->faceBucket, fDel ); -} - - -/****************** Basic Edge Operations **********************/ - -/* tessMeshMakeEdge creates one edge, two vertices, and a loop (face). -* The loop consists of the two new half-edges. -*/ -TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh ) -{ - TESSvertex *newVertex1 = (TESSvertex*)bucketAlloc(mesh->vertexBucket); - TESSvertex *newVertex2 = (TESSvertex*)bucketAlloc(mesh->vertexBucket); - TESSface *newFace = (TESSface*)bucketAlloc(mesh->faceBucket); - TESShalfEdge *e; - - /* if any one is null then all get freed */ - if (newVertex1 == NULL || newVertex2 == NULL || newFace == NULL) { - if (newVertex1 != NULL) bucketFree( mesh->vertexBucket, newVertex1 ); - if (newVertex2 != NULL) bucketFree( mesh->vertexBucket, newVertex2 ); - if (newFace != NULL) bucketFree( mesh->faceBucket, newFace ); - return NULL; - } - - e = MakeEdge( mesh, &mesh->eHead ); - if (e == NULL) return NULL; - - MakeVertex( newVertex1, e, &mesh->vHead ); - MakeVertex( newVertex2, e->Sym, &mesh->vHead ); - MakeFace( newFace, e, &mesh->fHead ); - return e; -} - - -/* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the -* mesh connectivity and topology. It changes the mesh so that -* eOrg->Onext <- OLD( eDst->Onext ) -* eDst->Onext <- OLD( eOrg->Onext ) -* where OLD(...) means the value before the meshSplice operation. -* -* This can have two effects on the vertex structure: -* - if eOrg->Org != eDst->Org, the two vertices are merged together -* - if eOrg->Org == eDst->Org, the origin is split into two vertices -* In both cases, eDst->Org is changed and eOrg->Org is untouched. -* -* Similarly (and independently) for the face structure, -* - if eOrg->Lface == eDst->Lface, one loop is split into two -* - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one -* In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. -* -* Some special cases: -* If eDst == eOrg, the operation has no effect. -* If eDst == eOrg->Lnext, the new face will have a single edge. -* If eDst == eOrg->Lprev, the old face will have a single edge. -* If eDst == eOrg->Onext, the new vertex will have a single edge. -* If eDst == eOrg->Oprev, the old vertex will have a single edge. -*/ -int tessMeshSplice( TESSmesh* mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ) -{ - int joiningLoops = FALSE; - int joiningVertices = FALSE; - - if( eOrg == eDst ) return 1; - - if( eDst->Org != eOrg->Org ) { - /* We are merging two disjoint vertices -- destroy eDst->Org */ - joiningVertices = TRUE; - KillVertex( mesh, eDst->Org, eOrg->Org ); - } - if( eDst->Lface != eOrg->Lface ) { - /* We are connecting two disjoint loops -- destroy eDst->Lface */ - joiningLoops = TRUE; - KillFace( mesh, eDst->Lface, eOrg->Lface ); - } - - /* Change the edge structure */ - Splice( eDst, eOrg ); - - if( ! joiningVertices ) { - TESSvertex *newVertex = (TESSvertex*)bucketAlloc( mesh->vertexBucket ); - if (newVertex == NULL) return 0; - - /* We split one vertex into two -- the new vertex is eDst->Org. - * Make sure the old vertex points to a valid half-edge. - */ - MakeVertex( newVertex, eDst, eOrg->Org ); - eOrg->Org->anEdge = eOrg; - } - if( ! joiningLoops ) { - TESSface *newFace = (TESSface*)bucketAlloc( mesh->faceBucket ); - if (newFace == NULL) return 0; - - /* We split one loop into two -- the new loop is eDst->Lface. - * Make sure the old face points to a valid half-edge. - */ - MakeFace( newFace, eDst, eOrg->Lface ); - eOrg->Lface->anEdge = eOrg; - } - - return 1; -} - - -/* tessMeshDelete( eDel ) removes the edge eDel. There are several cases: -* if (eDel->Lface != eDel->Rface), we join two loops into one; the loop -* eDel->Lface is deleted. Otherwise, we are splitting one loop into two; -* the newly created loop will contain eDel->Dst. If the deletion of eDel -* would create isolated vertices, those are deleted as well. -* -* This function could be implemented as two calls to tessMeshSplice -* plus a few calls to memFree, but this would allocate and delete -* unnecessary vertices and faces. -*/ -int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel ) -{ - TESShalfEdge *eDelSym = eDel->Sym; - int joiningLoops = FALSE; - - /* First step: disconnect the origin vertex eDel->Org. We make all - * changes to get a consistent mesh in this "intermediate" state. - */ - if( eDel->Lface != eDel->Rface ) { - /* We are joining two loops into one -- remove the left face */ - joiningLoops = TRUE; - KillFace( mesh, eDel->Lface, eDel->Rface ); - } - - if( eDel->Onext == eDel ) { - KillVertex( mesh, eDel->Org, NULL ); - } else { - /* Make sure that eDel->Org and eDel->Rface point to valid half-edges */ - eDel->Rface->anEdge = eDel->Oprev; - eDel->Org->anEdge = eDel->Onext; - - Splice( eDel, eDel->Oprev ); - if( ! joiningLoops ) { - TESSface *newFace= (TESSface*)bucketAlloc( mesh->faceBucket ); - if (newFace == NULL) return 0; - - /* We are splitting one loop into two -- create a new loop for eDel. */ - MakeFace( newFace, eDel, eDel->Lface ); - } - } - - /* Claim: the mesh is now in a consistent state, except that eDel->Org - * may have been deleted. Now we disconnect eDel->Dst. - */ - if( eDelSym->Onext == eDelSym ) { - KillVertex( mesh, eDelSym->Org, NULL ); - KillFace( mesh, eDelSym->Lface, NULL ); - } else { - /* Make sure that eDel->Dst and eDel->Lface point to valid half-edges */ - eDel->Lface->anEdge = eDelSym->Oprev; - eDelSym->Org->anEdge = eDelSym->Onext; - Splice( eDelSym, eDelSym->Oprev ); - } - - /* Any isolated vertices or faces have already been freed. */ - KillEdge( mesh, eDel ); - - return 1; -} - - -/******************** Other Edge Operations **********************/ - -/* All these routines can be implemented with the basic edge -* operations above. They are provided for convenience and efficiency. -*/ - - -/* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that -* eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. -* eOrg and eNew will have the same left face. -*/ -TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg ) -{ - TESShalfEdge *eNewSym; - TESShalfEdge *eNew = MakeEdge( mesh, eOrg ); - if (eNew == NULL) return NULL; - - eNewSym = eNew->Sym; - - /* Connect the new edge appropriately */ - Splice( eNew, eOrg->Lnext ); - - /* Set the vertex and face information */ - eNew->Org = eOrg->Dst; - { - TESSvertex *newVertex= (TESSvertex*)bucketAlloc( mesh->vertexBucket ); - if (newVertex == NULL) return NULL; - - MakeVertex( newVertex, eNewSym, eNew->Org ); - } - eNew->Lface = eNewSym->Lface = eOrg->Lface; - - return eNew; -} - - -/* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, -* such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. -* eOrg and eNew will have the same left face. -*/ -TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg ) -{ - TESShalfEdge *eNew; - TESShalfEdge *tempHalfEdge= tessMeshAddEdgeVertex( mesh, eOrg ); - if (tempHalfEdge == NULL) return NULL; - - eNew = tempHalfEdge->Sym; - - /* Disconnect eOrg from eOrg->Dst and connect it to eNew->Org */ - Splice( eOrg->Sym, eOrg->Sym->Oprev ); - Splice( eOrg->Sym, eNew ); - - /* Set the vertex and face information */ - eOrg->Dst = eNew->Org; - eNew->Dst->anEdge = eNew->Sym; /* may have pointed to eOrg->Sym */ - eNew->Rface = eOrg->Rface; - eNew->winding = eOrg->winding; /* copy old winding information */ - eNew->Sym->winding = eOrg->Sym->winding; - - return eNew; -} - - -/* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst -* to eDst->Org, and returns the corresponding half-edge eNew. -* If eOrg->Lface == eDst->Lface, this splits one loop into two, -* and the newly created loop is eNew->Lface. Otherwise, two disjoint -* loops are merged into one, and the loop eDst->Lface is destroyed. -* -* If (eOrg == eDst), the new face will have only two edges. -* If (eOrg->Lnext == eDst), the old face is reduced to a single edge. -* If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges. -*/ -TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ) -{ - TESShalfEdge *eNewSym; - int joiningLoops = FALSE; - TESShalfEdge *eNew = MakeEdge( mesh, eOrg ); - if (eNew == NULL) return NULL; - - eNewSym = eNew->Sym; - - if( eDst->Lface != eOrg->Lface ) { - /* We are connecting two disjoint loops -- destroy eDst->Lface */ - joiningLoops = TRUE; - KillFace( mesh, eDst->Lface, eOrg->Lface ); - } - - /* Connect the new edge appropriately */ - Splice( eNew, eOrg->Lnext ); - Splice( eNewSym, eDst ); - - /* Set the vertex and face information */ - eNew->Org = eOrg->Dst; - eNewSym->Org = eDst->Org; - eNew->Lface = eNewSym->Lface = eOrg->Lface; - - /* Make sure the old face points to a valid half-edge */ - eOrg->Lface->anEdge = eNewSym; - - if( ! joiningLoops ) { - TESSface *newFace= (TESSface*)bucketAlloc( mesh->faceBucket ); - if (newFace == NULL) return NULL; - - /* We split one loop into two -- the new loop is eNew->Lface */ - MakeFace( newFace, eNew, eOrg->Lface ); - } - return eNew; -} - - -/******************** Other Operations **********************/ - -/* tessMeshZapFace( fZap ) destroys a face and removes it from the -* global face list. All edges of fZap will have a NULL pointer as their -* left face. Any edges which also have a NULL pointer as their right face -* are deleted entirely (along with any isolated vertices this produces). -* An entire mesh can be deleted by zapping its faces, one at a time, -* in any order. Zapped faces cannot be used in further mesh operations! -*/ -void tessMeshZapFace( TESSmesh *mesh, TESSface *fZap ) -{ - TESShalfEdge *eStart = fZap->anEdge; - TESShalfEdge *e, *eNext, *eSym; - TESSface *fPrev, *fNext; - - /* walk around face, deleting edges whose right face is also NULL */ - eNext = eStart->Lnext; - do { - e = eNext; - eNext = e->Lnext; - - e->Lface = NULL; - if( e->Rface == NULL ) { - /* delete the edge -- see TESSmeshDelete above */ - - if( e->Onext == e ) { - KillVertex( mesh, e->Org, NULL ); - } else { - /* Make sure that e->Org points to a valid half-edge */ - e->Org->anEdge = e->Onext; - Splice( e, e->Oprev ); - } - eSym = e->Sym; - if( eSym->Onext == eSym ) { - KillVertex( mesh, eSym->Org, NULL ); - } else { - /* Make sure that eSym->Org points to a valid half-edge */ - eSym->Org->anEdge = eSym->Onext; - Splice( eSym, eSym->Oprev ); - } - KillEdge( mesh, e ); - } - } while( e != eStart ); - - /* delete from circular doubly-linked list */ - fPrev = fZap->prev; - fNext = fZap->next; - fNext->prev = fPrev; - fPrev->next = fNext; - - bucketFree( mesh->faceBucket, fZap ); -} - - -/* tessMeshNewMesh() creates a new mesh with no edges, no vertices, -* and no loops (what we usually call a "face"). -*/ -TESSmesh *tessMeshNewMesh( TESSalloc* alloc ) -{ - TESSvertex *v; - TESSface *f; - TESShalfEdge *e; - TESShalfEdge *eSym; - TESSmesh *mesh = (TESSmesh *)alloc->memalloc( alloc->userData, sizeof( TESSmesh )); - if (mesh == NULL) { - return NULL; - } - - if (alloc->meshEdgeBucketSize < 16) - alloc->meshEdgeBucketSize = 16; - if (alloc->meshEdgeBucketSize > 4096) - alloc->meshEdgeBucketSize = 4096; - - if (alloc->meshVertexBucketSize < 16) - alloc->meshVertexBucketSize = 16; - if (alloc->meshVertexBucketSize > 4096) - alloc->meshVertexBucketSize = 4096; - - if (alloc->meshFaceBucketSize < 16) - alloc->meshFaceBucketSize = 16; - if (alloc->meshFaceBucketSize > 4096) - alloc->meshFaceBucketSize = 4096; - - mesh->edgeBucket = createBucketAlloc( alloc, "Mesh Edges", sizeof(EdgePair), alloc->meshEdgeBucketSize ); - mesh->vertexBucket = createBucketAlloc( alloc, "Mesh Vertices", sizeof(TESSvertex), alloc->meshVertexBucketSize ); - mesh->faceBucket = createBucketAlloc( alloc, "Mesh Faces", sizeof(TESSface), alloc->meshFaceBucketSize ); - - v = &mesh->vHead; - f = &mesh->fHead; - e = &mesh->eHead; - eSym = &mesh->eHeadSym; - - v->next = v->prev = v; - v->anEdge = NULL; - - f->next = f->prev = f; - f->anEdge = NULL; - f->trail = NULL; - f->marked = FALSE; - f->inside = FALSE; - - e->next = e; - e->Sym = eSym; - e->Onext = NULL; - e->Lnext = NULL; - e->Org = NULL; - e->Lface = NULL; - e->winding = 0; - e->activeRegion = NULL; - - eSym->next = eSym; - eSym->Sym = e; - eSym->Onext = NULL; - eSym->Lnext = NULL; - eSym->Org = NULL; - eSym->Lface = NULL; - eSym->winding = 0; - eSym->activeRegion = NULL; - - return mesh; -} - - -/* tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in -* both meshes, and returns the new mesh (the old meshes are destroyed). -*/ -TESSmesh *tessMeshUnion( TESSalloc* alloc, TESSmesh *mesh1, TESSmesh *mesh2 ) -{ - TESSface *f1 = &mesh1->fHead; - TESSvertex *v1 = &mesh1->vHead; - TESShalfEdge *e1 = &mesh1->eHead; - TESSface *f2 = &mesh2->fHead; - TESSvertex *v2 = &mesh2->vHead; - TESShalfEdge *e2 = &mesh2->eHead; - - /* Add the faces, vertices, and edges of mesh2 to those of mesh1 */ - if( f2->next != f2 ) { - f1->prev->next = f2->next; - f2->next->prev = f1->prev; - f2->prev->next = f1; - f1->prev = f2->prev; - } - - if( v2->next != v2 ) { - v1->prev->next = v2->next; - v2->next->prev = v1->prev; - v2->prev->next = v1; - v1->prev = v2->prev; - } - - if( e2->next != e2 ) { - e1->Sym->next->Sym->next = e2->next; - e2->next->Sym->next = e1->Sym->next; - e2->Sym->next->Sym->next = e1; - e1->Sym->next = e2->Sym->next; - } - - alloc->memfree( alloc->userData, mesh2 ); - return mesh1; -} - - -static int CountFaceVerts( TESSface *f ) -{ - TESShalfEdge *eCur = f->anEdge; - int n = 0; - do - { - n++; - eCur = eCur->Lnext; - } - while (eCur != f->anEdge); - return n; -} - -int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace ) -{ - TESShalfEdge *e, *eNext, *eSym; - TESShalfEdge *eHead = &mesh->eHead; - TESSvertex *va, *vb, *vc, *vd, *ve, *vf; - int leftNv, rightNv; - - for( e = eHead->next; e != eHead; e = eNext ) - { - eNext = e->next; - eSym = e->Sym; - if( !eSym ) - continue; - - // Both faces must be inside - if( !e->Lface || !e->Lface->inside ) - continue; - if( !eSym->Lface || !eSym->Lface->inside ) - continue; - - leftNv = CountFaceVerts( e->Lface ); - rightNv = CountFaceVerts( eSym->Lface ); - if( (leftNv+rightNv-2) > maxVertsPerFace ) - continue; - - // Merge if the resulting poly is convex. - // - // vf--ve--vd - // ^| - // left e || right - // |v - // va--vb--vc - - va = e->Lprev->Org; - vb = e->Org; - vc = e->Sym->Lnext->Dst; - - vd = e->Sym->Lprev->Org; - ve = e->Sym->Org; - vf = e->Lnext->Dst; - - if( VertCCW( va, vb, vc ) && VertCCW( vd, ve, vf ) ) { - if( e == eNext || e == eNext->Sym ) { eNext = eNext->next; } - if( !tessMeshDelete( mesh, e ) ) - return 0; - } - } - - return 1; -} - -void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge ) -{ - TESShalfEdge *a0 = edge; - TESShalfEdge *a1 = a0->Lnext; - TESShalfEdge *a2 = a1->Lnext; - TESShalfEdge *b0 = edge->Sym; - TESShalfEdge *b1 = b0->Lnext; - TESShalfEdge *b2 = b1->Lnext; - - TESSvertex *aOrg = a0->Org; - TESSvertex *aOpp = a2->Org; - TESSvertex *bOrg = b0->Org; - TESSvertex *bOpp = b2->Org; - - TESSface *fa = a0->Lface; - TESSface *fb = b0->Lface; - - assert(EdgeIsInternal(edge)); - assert(a2->Lnext == a0); - assert(b2->Lnext == b0); - - a0->Org = bOpp; - a0->Onext = b1->Sym; - b0->Org = aOpp; - b0->Onext = a1->Sym; - a2->Onext = b0; - b2->Onext = a0; - b1->Onext = a2->Sym; - a1->Onext = b2->Sym; - - a0->Lnext = a2; - a2->Lnext = b1; - b1->Lnext = a0; - - b0->Lnext = b2; - b2->Lnext = a1; - a1->Lnext = b0; - - a1->Lface = fb; - b1->Lface = fa; - - fa->anEdge = a0; - fb->anEdge = b0; - - if (aOrg->anEdge == a0) aOrg->anEdge = b1; - if (bOrg->anEdge == b0) bOrg->anEdge = a1; - - assert( a0->Lnext->Onext->Sym == a0 ); - assert( a0->Onext->Sym->Lnext == a0 ); - assert( a0->Org->anEdge->Org == a0->Org ); - - - assert( a1->Lnext->Onext->Sym == a1 ); - assert( a1->Onext->Sym->Lnext == a1 ); - assert( a1->Org->anEdge->Org == a1->Org ); - - assert( a2->Lnext->Onext->Sym == a2 ); - assert( a2->Onext->Sym->Lnext == a2 ); - assert( a2->Org->anEdge->Org == a2->Org ); - - assert( b0->Lnext->Onext->Sym == b0 ); - assert( b0->Onext->Sym->Lnext == b0 ); - assert( b0->Org->anEdge->Org == b0->Org ); - - assert( b1->Lnext->Onext->Sym == b1 ); - assert( b1->Onext->Sym->Lnext == b1 ); - assert( b1->Org->anEdge->Org == b1->Org ); - - assert( b2->Lnext->Onext->Sym == b2 ); - assert( b2->Onext->Sym->Lnext == b2 ); - assert( b2->Org->anEdge->Org == b2->Org ); - - assert(aOrg->anEdge->Org == aOrg); - assert(bOrg->anEdge->Org == bOrg); - - assert(a0->Oprev->Onext->Org == a0->Org); -} - -#ifdef DELETE_BY_ZAPPING - -/* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh. -*/ -void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh ) -{ - TESSface *fHead = &mesh->fHead; - - while( fHead->next != fHead ) { - tessMeshZapFace( fHead->next ); - } - assert( mesh->vHead.next == &mesh->vHead ); - - alloc->memfree( alloc->userData, mesh ); -} - -#else - -/* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh. -*/ -void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh ) -{ - deleteBucketAlloc(mesh->edgeBucket); - deleteBucketAlloc(mesh->vertexBucket); - deleteBucketAlloc(mesh->faceBucket); - - alloc->memfree( alloc->userData, mesh ); -} - -#endif - -#ifndef NDEBUG - -/* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency. -*/ -void tessMeshCheckMesh( TESSmesh *mesh ) -{ - TESSface *fHead = &mesh->fHead; - TESSvertex *vHead = &mesh->vHead; - TESShalfEdge *eHead = &mesh->eHead; - TESSface *f, *fPrev; - TESSvertex *v, *vPrev; - TESShalfEdge *e, *ePrev; - - for( fPrev = fHead ; (f = fPrev->next) != fHead; fPrev = f) { - assert( f->prev == fPrev ); - e = f->anEdge; - do { - assert( e->Sym != e ); - assert( e->Sym->Sym == e ); - assert( e->Lnext->Onext->Sym == e ); - assert( e->Onext->Sym->Lnext == e ); - assert( e->Lface == f ); - e = e->Lnext; - } while( e != f->anEdge ); - } - assert( f->prev == fPrev && f->anEdge == NULL ); - - for( vPrev = vHead ; (v = vPrev->next) != vHead; vPrev = v) { - assert( v->prev == vPrev ); - e = v->anEdge; - do { - assert( e->Sym != e ); - assert( e->Sym->Sym == e ); - assert( e->Lnext->Onext->Sym == e ); - assert( e->Onext->Sym->Lnext == e ); - assert( e->Org == v ); - e = e->Onext; - } while( e != v->anEdge ); - } - assert( v->prev == vPrev && v->anEdge == NULL ); - - for( ePrev = eHead ; (e = ePrev->next) != eHead; ePrev = e) { - assert( e->Sym->next == ePrev->Sym ); - assert( e->Sym != e ); - assert( e->Sym->Sym == e ); - assert( e->Org != NULL ); - assert( e->Dst != NULL ); - assert( e->Lnext->Onext->Sym == e ); - assert( e->Onext->Sym->Lnext == e ); - } - assert( e->Sym->next == ePrev->Sym - && e->Sym == &mesh->eHeadSym - && e->Sym->Sym == e - && e->Org == NULL && e->Dst == NULL - && e->Lface == NULL && e->Rface == NULL ); -} - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/mesh.h b/submodules/LottieMeshSwift/libtess2/Sources/mesh.h deleted file mode 100755 index 479c66f369..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/mesh.h +++ /dev/null @@ -1,269 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef MESH_H -#define MESH_H - -#include "../Include/tesselator.h" - -typedef struct TESSmesh TESSmesh; -typedef struct TESSvertex TESSvertex; -typedef struct TESSface TESSface; -typedef struct TESShalfEdge TESShalfEdge; -typedef struct ActiveRegion ActiveRegion; - -/* The mesh structure is similar in spirit, notation, and operations -* to the "quad-edge" structure (see L. Guibas and J. Stolfi, Primitives -* for the manipulation of general subdivisions and the computation of -* Voronoi diagrams, ACM Transactions on Graphics, 4(2):74-123, April 1985). -* For a simplified description, see the course notes for CS348a, -* "Mathematical Foundations of Computer Graphics", available at the -* Stanford bookstore (and taught during the fall quarter). -* The implementation also borrows a tiny subset of the graph-based approach -* use in Mantyla's Geometric Work Bench (see M. Mantyla, An Introduction -* to Sold Modeling, Computer Science Press, Rockville, Maryland, 1988). -* -* The fundamental data structure is the "half-edge". Two half-edges -* go together to make an edge, but they point in opposite directions. -* Each half-edge has a pointer to its mate (the "symmetric" half-edge Sym), -* its origin vertex (Org), the face on its left side (Lface), and the -* adjacent half-edges in the CCW direction around the origin vertex -* (Onext) and around the left face (Lnext). There is also a "next" -* pointer for the global edge list (see below). -* -* The notation used for mesh navigation: -* Sym = the mate of a half-edge (same edge, but opposite direction) -* Onext = edge CCW around origin vertex (keep same origin) -* Dnext = edge CCW around destination vertex (keep same dest) -* Lnext = edge CCW around left face (dest becomes new origin) -* Rnext = edge CCW around right face (origin becomes new dest) -* -* "prev" means to substitute CW for CCW in the definitions above. -* -* The mesh keeps global lists of all vertices, faces, and edges, -* stored as doubly-linked circular lists with a dummy header node. -* The mesh stores pointers to these dummy headers (vHead, fHead, eHead). -* -* The circular edge list is special; since half-edges always occur -* in pairs (e and e->Sym), each half-edge stores a pointer in only -* one direction. Starting at eHead and following the e->next pointers -* will visit each *edge* once (ie. e or e->Sym, but not both). -* e->Sym stores a pointer in the opposite direction, thus it is -* always true that e->Sym->next->Sym->next == e. -* -* Each vertex has a pointer to next and previous vertices in the -* circular list, and a pointer to a half-edge with this vertex as -* the origin (NULL if this is the dummy header). There is also a -* field "data" for client data. -* -* Each face has a pointer to the next and previous faces in the -* circular list, and a pointer to a half-edge with this face as -* the left face (NULL if this is the dummy header). There is also -* a field "data" for client data. -* -* Note that what we call a "face" is really a loop; faces may consist -* of more than one loop (ie. not simply connected), but there is no -* record of this in the data structure. The mesh may consist of -* several disconnected regions, so it may not be possible to visit -* the entire mesh by starting at a half-edge and traversing the edge -* structure. -* -* The mesh does NOT support isolated vertices; a vertex is deleted along -* with its last edge. Similarly when two faces are merged, one of the -* faces is deleted (see tessMeshDelete below). For mesh operations, -* all face (loop) and vertex pointers must not be NULL. However, once -* mesh manipulation is finished, TESSmeshZapFace can be used to delete -* faces of the mesh, one at a time. All external faces can be "zapped" -* before the mesh is returned to the client; then a NULL face indicates -* a region which is not part of the output polygon. -*/ - -struct TESSvertex { - TESSvertex *next; /* next vertex (never NULL) */ - TESSvertex *prev; /* previous vertex (never NULL) */ - TESShalfEdge *anEdge; /* a half-edge with this origin */ - - /* Internal data (keep hidden) */ - TESSreal coords[3]; /* vertex location in 3D */ - TESSreal s, t; /* projection onto the sweep plane */ - int pqHandle; /* to allow deletion from priority queue */ - TESSindex n; /* to allow identify unique vertices */ - TESSindex idx; /* to allow map result to original verts */ -}; - -struct TESSface { - TESSface *next; /* next face (never NULL) */ - TESSface *prev; /* previous face (never NULL) */ - TESShalfEdge *anEdge; /* a half edge with this left face */ - - /* Internal data (keep hidden) */ - TESSface *trail; /* "stack" for conversion to strips */ - TESSindex n; /* to allow identiy unique faces */ - char marked; /* flag for conversion to strips */ - char inside; /* this face is in the polygon interior */ -}; - -struct TESShalfEdge { - TESShalfEdge *next; /* doubly-linked list (prev==Sym->next) */ - TESShalfEdge *Sym; /* same edge, opposite direction */ - TESShalfEdge *Onext; /* next edge CCW around origin */ - TESShalfEdge *Lnext; /* next edge CCW around left face */ - TESSvertex *Org; /* origin vertex (Overtex too long) */ - TESSface *Lface; /* left face */ - - /* Internal data (keep hidden) */ - ActiveRegion *activeRegion; /* a region with this upper edge (sweep.c) */ - int winding; /* change in winding number when crossing - from the right face to the left face */ - int mark; /* Used by the Edge Flip algorithm */ -}; - -#define Rface Sym->Lface -#define Dst Sym->Org - -#define Oprev Sym->Lnext -#define Lprev Onext->Sym -#define Dprev Lnext->Sym -#define Rprev Sym->Onext -#define Dnext Rprev->Sym /* 3 pointers */ -#define Rnext Oprev->Sym /* 3 pointers */ - -struct TESSmesh { - TESSvertex vHead; /* dummy header for vertex list */ - TESSface fHead; /* dummy header for face list */ - TESShalfEdge eHead; /* dummy header for edge list */ - TESShalfEdge eHeadSym; /* and its symmetric counterpart */ - - struct BucketAlloc* edgeBucket; - struct BucketAlloc* vertexBucket; - struct BucketAlloc* faceBucket; -}; - -/* The mesh operations below have three motivations: completeness, -* convenience, and efficiency. The basic mesh operations are MakeEdge, -* Splice, and Delete. All the other edge operations can be implemented -* in terms of these. The other operations are provided for convenience -* and/or efficiency. -* -* When a face is split or a vertex is added, they are inserted into the -* global list *before* the existing vertex or face (ie. e->Org or e->Lface). -* This makes it easier to process all vertices or faces in the global lists -* without worrying about processing the same data twice. As a convenience, -* when a face is split, the "inside" flag is copied from the old face. -* Other internal data (v->data, v->activeRegion, f->data, f->marked, -* f->trail, e->winding) is set to zero. -* -* ********************** Basic Edge Operations ************************** -* -* tessMeshMakeEdge( mesh ) creates one edge, two vertices, and a loop. -* The loop (face) consists of the two new half-edges. -* -* tessMeshSplice( eOrg, eDst ) is the basic operation for changing the -* mesh connectivity and topology. It changes the mesh so that -* eOrg->Onext <- OLD( eDst->Onext ) -* eDst->Onext <- OLD( eOrg->Onext ) -* where OLD(...) means the value before the meshSplice operation. -* -* This can have two effects on the vertex structure: -* - if eOrg->Org != eDst->Org, the two vertices are merged together -* - if eOrg->Org == eDst->Org, the origin is split into two vertices -* In both cases, eDst->Org is changed and eOrg->Org is untouched. -* -* Similarly (and independently) for the face structure, -* - if eOrg->Lface == eDst->Lface, one loop is split into two -* - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one -* In both cases, eDst->Lface is changed and eOrg->Lface is unaffected. -* -* tessMeshDelete( eDel ) removes the edge eDel. There are several cases: -* if (eDel->Lface != eDel->Rface), we join two loops into one; the loop -* eDel->Lface is deleted. Otherwise, we are splitting one loop into two; -* the newly created loop will contain eDel->Dst. If the deletion of eDel -* would create isolated vertices, those are deleted as well. -* -* ********************** Other Edge Operations ************************** -* -* tessMeshAddEdgeVertex( eOrg ) creates a new edge eNew such that -* eNew == eOrg->Lnext, and eNew->Dst is a newly created vertex. -* eOrg and eNew will have the same left face. -* -* tessMeshSplitEdge( eOrg ) splits eOrg into two edges eOrg and eNew, -* such that eNew == eOrg->Lnext. The new vertex is eOrg->Dst == eNew->Org. -* eOrg and eNew will have the same left face. -* -* tessMeshConnect( eOrg, eDst ) creates a new edge from eOrg->Dst -* to eDst->Org, and returns the corresponding half-edge eNew. -* If eOrg->Lface == eDst->Lface, this splits one loop into two, -* and the newly created loop is eNew->Lface. Otherwise, two disjoint -* loops are merged into one, and the loop eDst->Lface is destroyed. -* -* ************************ Other Operations ***************************** -* -* tessMeshNewMesh() creates a new mesh with no edges, no vertices, -* and no loops (what we usually call a "face"). -* -* tessMeshUnion( mesh1, mesh2 ) forms the union of all structures in -* both meshes, and returns the new mesh (the old meshes are destroyed). -* -* tessMeshDeleteMesh( mesh ) will free all storage for any valid mesh. -* -* tessMeshZapFace( fZap ) destroys a face and removes it from the -* global face list. All edges of fZap will have a NULL pointer as their -* left face. Any edges which also have a NULL pointer as their right face -* are deleted entirely (along with any isolated vertices this produces). -* An entire mesh can be deleted by zapping its faces, one at a time, -* in any order. Zapped faces cannot be used in further mesh operations! -* -* tessMeshCheckMesh( mesh ) checks a mesh for self-consistency. -*/ - -TESShalfEdge *tessMeshMakeEdge( TESSmesh *mesh ); -int tessMeshSplice( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ); -int tessMeshDelete( TESSmesh *mesh, TESShalfEdge *eDel ); - -TESShalfEdge *tessMeshAddEdgeVertex( TESSmesh *mesh, TESShalfEdge *eOrg ); -TESShalfEdge *tessMeshSplitEdge( TESSmesh *mesh, TESShalfEdge *eOrg ); -TESShalfEdge *tessMeshConnect( TESSmesh *mesh, TESShalfEdge *eOrg, TESShalfEdge *eDst ); - -TESSmesh *tessMeshNewMesh( TESSalloc* alloc ); -TESSmesh *tessMeshUnion( TESSalloc* alloc, TESSmesh *mesh1, TESSmesh *mesh2 ); -int tessMeshMergeConvexFaces( TESSmesh *mesh, int maxVertsPerFace ); -void tessMeshDeleteMesh( TESSalloc* alloc, TESSmesh *mesh ); -void tessMeshZapFace( TESSmesh *mesh, TESSface *fZap ); - -void tessMeshFlipEdge( TESSmesh *mesh, TESShalfEdge *edge ); - -#ifdef NDEBUG -#define tessMeshCheckMesh( mesh ) -#else -void tessMeshCheckMesh( TESSmesh *mesh ); -#endif - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.c b/submodules/LottieMeshSwift/libtess2/Sources/priorityq.c deleted file mode 100755 index 62a6654535..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.c +++ /dev/null @@ -1,514 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -//#include "tesos.h" -#include -#include -#include "../Include/tesselator.h" -#include "priorityq.h" - - -#define INIT_SIZE 32 - -#define TRUE 1 -#define FALSE 0 - -#ifdef FOR_TRITE_TEST_PROGRAM -#define LEQ(x,y) (*pq->leq)(x,y) -#else -/* Violates modularity, but a little faster */ -#include "geom.h" -#define LEQ(x,y) VertLeq((TESSvertex *)x, (TESSvertex *)y) -#endif - - -/* Include all the code for the regular heap-based queue here. */ - -/* The basic operations are insertion of a new key (pqInsert), -* and examination/extraction of a key whose value is minimum -* (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete); -* for this purpose pqInsert returns a "handle" which is supplied -* as the argument. -* -* An initial heap may be created efficiently by calling pqInsert -* repeatedly, then calling pqInit. In any case pqInit must be called -* before any operations other than pqInsert are used. -* -* If the heap is empty, pqMinimum/pqExtractMin will return a NULL key. -* This may also be tested with pqIsEmpty. -*/ - - -/* Since we support deletion the data structure is a little more -* complicated than an ordinary heap. "nodes" is the heap itself; -* active nodes are stored in the range 1..pq->size. When the -* heap exceeds its allocated size (pq->max), its size doubles. -* The children of node i are nodes 2i and 2i+1. -* -* Each node stores an index into an array "handles". Each handle -* stores a key, plus a pointer back to the node which currently -* represents that key (ie. nodes[handles[i].node].handle == i). -*/ - - -#define pqHeapMinimum(pq) ((pq)->handles[(pq)->nodes[1].handle].key) -#define pqHeapIsEmpty(pq) ((pq)->size == 0) - - - -/* really pqHeapNewPriorityQHeap */ -PriorityQHeap *pqHeapNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) ) -{ - PriorityQHeap *pq = (PriorityQHeap *)alloc->memalloc( alloc->userData, sizeof( PriorityQHeap )); - if (pq == NULL) return NULL; - - pq->size = 0; - pq->max = size; - pq->nodes = (PQnode *)alloc->memalloc( alloc->userData, (size + 1) * sizeof(pq->nodes[0]) ); - if (pq->nodes == NULL) { - alloc->memfree( alloc->userData, pq ); - return NULL; - } - - pq->handles = (PQhandleElem *)alloc->memalloc( alloc->userData, (size + 1) * sizeof(pq->handles[0]) ); - if (pq->handles == NULL) { - alloc->memfree( alloc->userData, pq->nodes ); - alloc->memfree( alloc->userData, pq ); - return NULL; - } - - pq->initialized = FALSE; - pq->freeList = 0; - pq->leq = leq; - - pq->nodes[1].handle = 1; /* so that Minimum() returns NULL */ - pq->handles[1].key = NULL; - return pq; -} - -/* really pqHeapDeletePriorityQHeap */ -void pqHeapDeletePriorityQ( TESSalloc* alloc, PriorityQHeap *pq ) -{ - alloc->memfree( alloc->userData, pq->handles ); - alloc->memfree( alloc->userData, pq->nodes ); - alloc->memfree( alloc->userData, pq ); -} - - -static void FloatDown( PriorityQHeap *pq, int curr ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - PQhandle hCurr, hChild; - int child; - - hCurr = n[curr].handle; - for( ;; ) { - child = curr << 1; - if( child < pq->size && LEQ( h[n[child+1].handle].key, - h[n[child].handle].key )) { - ++child; - } - - assert(child <= pq->max); - - hChild = n[child].handle; - if( child > pq->size || LEQ( h[hCurr].key, h[hChild].key )) { - n[curr].handle = hCurr; - h[hCurr].node = curr; - break; - } - n[curr].handle = hChild; - h[hChild].node = curr; - curr = child; - } -} - - -static void FloatUp( PriorityQHeap *pq, int curr ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - PQhandle hCurr, hParent; - int parent; - - hCurr = n[curr].handle; - for( ;; ) { - parent = curr >> 1; - hParent = n[parent].handle; - if( parent == 0 || LEQ( h[hParent].key, h[hCurr].key )) { - n[curr].handle = hCurr; - h[hCurr].node = curr; - break; - } - n[curr].handle = hParent; - h[hParent].node = curr; - curr = parent; - } -} - -/* really pqHeapInit */ -void pqHeapInit( PriorityQHeap *pq ) -{ - int i; - - /* This method of building a heap is O(n), rather than O(n lg n). */ - - for( i = pq->size; i >= 1; --i ) { - FloatDown( pq, i ); - } - pq->initialized = TRUE; -} - -/* really pqHeapInsert */ -/* returns INV_HANDLE iff out of memory */ -PQhandle pqHeapInsert( TESSalloc* alloc, PriorityQHeap *pq, PQkey keyNew ) -{ - int curr; - PQhandle free; - - curr = ++ pq->size; - if( (curr*2) > pq->max ) { - if (!alloc->memrealloc) - { - return INV_HANDLE; - } - else - { - PQnode *saveNodes= pq->nodes; - PQhandleElem *saveHandles= pq->handles; - - // If the heap overflows, double its size. - pq->max <<= 1; - pq->nodes = (PQnode *)alloc->memrealloc( alloc->userData, pq->nodes, - (size_t)((pq->max + 1) * sizeof( pq->nodes[0] ))); - if (pq->nodes == NULL) { - pq->nodes = saveNodes; // restore ptr to free upon return - return INV_HANDLE; - } - pq->handles = (PQhandleElem *)alloc->memrealloc( alloc->userData, pq->handles, - (size_t) ((pq->max + 1) * sizeof( pq->handles[0] ))); - if (pq->handles == NULL) { - pq->handles = saveHandles; // restore ptr to free upon return - return INV_HANDLE; - } - } - } - - if( pq->freeList == 0 ) { - free = curr; - } else { - free = pq->freeList; - pq->freeList = pq->handles[free].node; - } - - pq->nodes[curr].handle = free; - pq->handles[free].node = curr; - pq->handles[free].key = keyNew; - - if( pq->initialized ) { - FloatUp( pq, curr ); - } - assert(free != INV_HANDLE); - return free; -} - -/* really pqHeapExtractMin */ -PQkey pqHeapExtractMin( PriorityQHeap *pq ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - PQhandle hMin = n[1].handle; - PQkey min = h[hMin].key; - - if( pq->size > 0 ) { - n[1].handle = n[pq->size].handle; - h[n[1].handle].node = 1; - - h[hMin].key = NULL; - h[hMin].node = pq->freeList; - pq->freeList = hMin; - - if( -- pq->size > 0 ) { - FloatDown( pq, 1 ); - } - } - return min; -} - -/* really pqHeapDelete */ -void pqHeapDelete( PriorityQHeap *pq, PQhandle hCurr ) -{ - PQnode *n = pq->nodes; - PQhandleElem *h = pq->handles; - int curr; - - assert( hCurr >= 1 && hCurr <= pq->max && h[hCurr].key != NULL ); - - curr = h[hCurr].node; - n[curr].handle = n[pq->size].handle; - h[n[curr].handle].node = curr; - - if( curr <= -- pq->size ) { - if( curr <= 1 || LEQ( h[n[curr>>1].handle].key, h[n[curr].handle].key )) { - FloatDown( pq, curr ); - } else { - FloatUp( pq, curr ); - } - } - h[hCurr].key = NULL; - h[hCurr].node = pq->freeList; - pq->freeList = hCurr; -} - - - -/* Now redefine all the function names to map to their "Sort" versions. */ - -/* really tessPqSortNewPriorityQ */ -PriorityQ *pqNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) ) -{ - PriorityQ *pq = (PriorityQ *)alloc->memalloc( alloc->userData, sizeof( PriorityQ )); - if (pq == NULL) return NULL; - - pq->heap = pqHeapNewPriorityQ( alloc, size, leq ); - if (pq->heap == NULL) { - alloc->memfree( alloc->userData, pq ); - return NULL; - } - -// pq->keys = (PQkey *)memAlloc( INIT_SIZE * sizeof(pq->keys[0]) ); - pq->keys = (PQkey *)alloc->memalloc( alloc->userData, size * sizeof(pq->keys[0]) ); - if (pq->keys == NULL) { - pqHeapDeletePriorityQ( alloc, pq->heap ); - alloc->memfree( alloc->userData, pq ); - return NULL; - } - - pq->size = 0; - pq->max = size; //INIT_SIZE; - pq->initialized = FALSE; - pq->leq = leq; - - return pq; -} - -/* really tessPqSortDeletePriorityQ */ -void pqDeletePriorityQ( TESSalloc* alloc, PriorityQ *pq ) -{ - assert(pq != NULL); - if (pq->heap != NULL) pqHeapDeletePriorityQ( alloc, pq->heap ); - if (pq->order != NULL) alloc->memfree( alloc->userData, pq->order ); - if (pq->keys != NULL) alloc->memfree( alloc->userData, pq->keys ); - alloc->memfree( alloc->userData, pq ); -} - - -#define LT(x,y) (! LEQ(y,x)) -#define GT(x,y) (! LEQ(x,y)) -#define Swap(a,b) if(1){PQkey *tmp = *a; *a = *b; *b = tmp;}else - -/* really tessPqSortInit */ -int pqInit( TESSalloc* alloc, PriorityQ *pq ) -{ - PQkey **p, **r, **i, **j, *piv; - struct { PQkey **p, **r; } Stack[50], *top = Stack; - unsigned int seed = 2016473283; - - /* Create an array of indirect pointers to the keys, so that we - * the handles we have returned are still valid. - */ - /* - pq->order = (PQkey **)memAlloc( (size_t) - (pq->size * sizeof(pq->order[0])) ); - */ - pq->order = (PQkey **)alloc->memalloc( alloc->userData, - (size_t)((pq->size+1) * sizeof(pq->order[0])) ); - /* the previous line is a patch to compensate for the fact that IBM */ - /* machines return a null on a malloc of zero bytes (unlike SGI), */ - /* so we have to put in this defense to guard against a memory */ - /* fault four lines down. from fossum@austin.ibm.com. */ - if (pq->order == NULL) return 0; - - p = pq->order; - r = p + pq->size - 1; - for( piv = pq->keys, i = p; i <= r; ++piv, ++i ) { - *i = piv; - } - - /* Sort the indirect pointers in descending order, - * using randomized Quicksort - */ - top->p = p; top->r = r; ++top; - while( --top >= Stack ) { - p = top->p; - r = top->r; - while( r > p + 10 ) { - seed = seed * 1539415821 + 1; - i = p + seed % (r - p + 1); - piv = *i; - *i = *p; - *p = piv; - i = p - 1; - j = r + 1; - do { - do { ++i; } while( GT( **i, *piv )); - do { --j; } while( LT( **j, *piv )); - Swap( i, j ); - } while( i < j ); - Swap( i, j ); /* Undo last swap */ - if( i - p < r - j ) { - top->p = j+1; top->r = r; ++top; - r = i-1; - } else { - top->p = p; top->r = i-1; ++top; - p = j+1; - } - } - /* Insertion sort small lists */ - for( i = p+1; i <= r; ++i ) { - piv = *i; - for( j = i; j > p && LT( **(j-1), *piv ); --j ) { - *j = *(j-1); - } - *j = piv; - } - } - pq->max = pq->size; - pq->initialized = TRUE; - pqHeapInit( pq->heap ); /* always succeeds */ - -#ifndef NDEBUG - p = pq->order; - r = p + pq->size - 1; - for( i = p; i < r; ++i ) { - assert( LEQ( **(i+1), **i )); - } -#endif - - return 1; -} - -/* really tessPqSortInsert */ -/* returns INV_HANDLE iff out of memory */ -PQhandle pqInsert( TESSalloc* alloc, PriorityQ *pq, PQkey keyNew ) -{ - int curr; - - if( pq->initialized ) { - return pqHeapInsert( alloc, pq->heap, keyNew ); - } - curr = pq->size; - if( ++ pq->size >= pq->max ) { - if (!alloc->memrealloc) - { - return INV_HANDLE; - } - else - { - PQkey *saveKey= pq->keys; - // If the heap overflows, double its size. - pq->max <<= 1; - pq->keys = (PQkey *)alloc->memrealloc( alloc->userData, pq->keys, - (size_t)(pq->max * sizeof( pq->keys[0] ))); - if (pq->keys == NULL) { - pq->keys = saveKey; // restore ptr to free upon return - return INV_HANDLE; - } - } - } - assert(curr != INV_HANDLE); - pq->keys[curr] = keyNew; - - /* Negative handles index the sorted array. */ - return -(curr+1); -} - -/* really tessPqSortExtractMin */ -PQkey pqExtractMin( PriorityQ *pq ) -{ - PQkey sortMin, heapMin; - - if( pq->size == 0 ) { - return pqHeapExtractMin( pq->heap ); - } - sortMin = *(pq->order[pq->size-1]); - if( ! pqHeapIsEmpty( pq->heap )) { - heapMin = pqHeapMinimum( pq->heap ); - if( LEQ( heapMin, sortMin )) { - return pqHeapExtractMin( pq->heap ); - } - } - do { - -- pq->size; - } while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ); - return sortMin; -} - -/* really tessPqSortMinimum */ -PQkey pqMinimum( PriorityQ *pq ) -{ - PQkey sortMin, heapMin; - - if( pq->size == 0 ) { - return pqHeapMinimum( pq->heap ); - } - sortMin = *(pq->order[pq->size-1]); - if( ! pqHeapIsEmpty( pq->heap )) { - heapMin = pqHeapMinimum( pq->heap ); - if( LEQ( heapMin, sortMin )) { - return heapMin; - } - } - return sortMin; -} - -/* really tessPqSortIsEmpty */ -int pqIsEmpty( PriorityQ *pq ) -{ - return (pq->size == 0) && pqHeapIsEmpty( pq->heap ); -} - -/* really tessPqSortDelete */ -void pqDelete( PriorityQ *pq, PQhandle curr ) -{ - if( curr >= 0 ) { - pqHeapDelete( pq->heap, curr ); - return; - } - curr = -(curr+1); - assert( curr < pq->max && pq->keys[curr] != NULL ); - - pq->keys[curr] = NULL; - while( pq->size > 0 && *(pq->order[pq->size-1]) == NULL ) { - -- pq->size; - } -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.h b/submodules/LottieMeshSwift/libtess2/Sources/priorityq.h deleted file mode 100755 index 6ef1c05f56..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/priorityq.h +++ /dev/null @@ -1,104 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef PRIORITYQ_H -#define PRIORITYQ_H - -/* The basic operations are insertion of a new key (pqInsert), -* and examination/extraction of a key whose value is minimum -* (pqMinimum/pqExtractMin). Deletion is also allowed (pqDelete); -* for this purpose pqInsert returns a "handle" which is supplied -* as the argument. -* -* An initial heap may be created efficiently by calling pqInsert -* repeatedly, then calling pqInit. In any case pqInit must be called -* before any operations other than pqInsert are used. -* -* If the heap is empty, pqMinimum/pqExtractMin will return a NULL key. -* This may also be tested with pqIsEmpty. -*/ - -/* Since we support deletion the data structure is a little more -* complicated than an ordinary heap. "nodes" is the heap itself; -* active nodes are stored in the range 1..pq->size. When the -* heap exceeds its allocated size (pq->max), its size doubles. -* The children of node i are nodes 2i and 2i+1. -* -* Each node stores an index into an array "handles". Each handle -* stores a key, plus a pointer back to the node which currently -* represents that key (ie. nodes[handles[i].node].handle == i). -*/ - -typedef void *PQkey; -typedef int PQhandle; -typedef struct PriorityQHeap PriorityQHeap; - -#define INV_HANDLE 0x0fffffff - -typedef struct { PQhandle handle; } PQnode; -typedef struct { PQkey key; PQhandle node; } PQhandleElem; - -struct PriorityQHeap { - - PQnode *nodes; - PQhandleElem *handles; - int size, max; - PQhandle freeList; - int initialized; - - int (*leq)(PQkey key1, PQkey key2); -}; - -typedef struct PriorityQ PriorityQ; - -struct PriorityQ { - PriorityQHeap *heap; - - PQkey *keys; - PQkey **order; - PQhandle size, max; - int initialized; - - int (*leq)(PQkey key1, PQkey key2); -}; - -PriorityQ *pqNewPriorityQ( TESSalloc* alloc, int size, int (*leq)(PQkey key1, PQkey key2) ); -void pqDeletePriorityQ( TESSalloc* alloc, PriorityQ *pq ); - -int pqInit( TESSalloc* alloc, PriorityQ *pq ); -PQhandle pqInsert( TESSalloc* alloc, PriorityQ *pq, PQkey key ); -PQkey pqExtractMin( PriorityQ *pq ); -void pqDelete( PriorityQ *pq, PQhandle handle ); - -PQkey pqMinimum( PriorityQ *pq ); -int pqIsEmpty( PriorityQ *pq ); - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/sweep.c b/submodules/LottieMeshSwift/libtess2/Sources/sweep.c deleted file mode 100755 index 32a56bf406..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/sweep.c +++ /dev/null @@ -1,1324 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#include -#include -#include /* longjmp */ - -#include "mesh.h" -#include "geom.h" -#include "tess.h" -#include "dict.h" -#include "priorityq.h" -#include "bucketalloc.h" -#include "sweep.h" - -#define TRUE 1 -#define FALSE 0 - -#ifdef FOR_TRITE_TEST_PROGRAM -extern void DebugEvent( TESStesselator *tess ); -#else -#define DebugEvent( tess ) -#endif - -/* -* Invariants for the Edge Dictionary. -* - each pair of adjacent edges e2=Succ(e1) satisfies EdgeLeq(e1,e2) -* at any valid location of the sweep event -* - if EdgeLeq(e2,e1) as well (at any valid sweep event), then e1 and e2 -* share a common endpoint -* - for each e, e->Dst has been processed, but not e->Org -* - each edge e satisfies VertLeq(e->Dst,event) && VertLeq(event,e->Org) -* where "event" is the current sweep line event. -* - no edge e has zero length -* -* Invariants for the Mesh (the processed portion). -* - the portion of the mesh left of the sweep line is a planar graph, -* ie. there is *some* way to embed it in the plane -* - no processed edge has zero length -* - no two processed vertices have identical coordinates -* - each "inside" region is monotone, ie. can be broken into two chains -* of monotonically increasing vertices according to VertLeq(v1,v2) -* - a non-invariant: these chains may intersect (very slightly) -* -* Invariants for the Sweep. -* - if none of the edges incident to the event vertex have an activeRegion -* (ie. none of these edges are in the edge dictionary), then the vertex -* has only right-going edges. -* - if an edge is marked "fixUpperEdge" (it is a temporary edge introduced -* by ConnectRightVertex), then it is the only right-going edge from -* its associated vertex. (This says that these edges exist only -* when it is necessary.) -*/ - -#define MAX(x,y) ((x) >= (y) ? (x) : (y)) -#define MIN(x,y) ((x) <= (y) ? (x) : (y)) - -/* When we merge two edges into one, we need to compute the combined -* winding of the new edge. -*/ -#define AddWinding(eDst,eSrc) (eDst->winding += eSrc->winding, \ - eDst->Sym->winding += eSrc->Sym->winding) - -static void SweepEvent( TESStesselator *tess, TESSvertex *vEvent ); -static void WalkDirtyRegions( TESStesselator *tess, ActiveRegion *regUp ); -static int CheckForRightSplice( TESStesselator *tess, ActiveRegion *regUp ); - -static int EdgeLeq( TESStesselator *tess, ActiveRegion *reg1, ActiveRegion *reg2 ) -/* -* Both edges must be directed from right to left (this is the canonical -* direction for the upper edge of each region). -* -* The strategy is to evaluate a "t" value for each edge at the -* current sweep line position, given by tess->event. The calculations -* are designed to be very stable, but of course they are not perfect. -* -* Special case: if both edge destinations are at the sweep event, -* we sort the edges by slope (they would otherwise compare equally). -*/ -{ - TESSvertex *event = tess->event; - TESShalfEdge *e1, *e2; - TESSreal t1, t2; - - e1 = reg1->eUp; - e2 = reg2->eUp; - - if( e1->Dst == event ) { - if( e2->Dst == event ) { - /* Two edges right of the sweep line which meet at the sweep event. - * Sort them by slope. - */ - if( VertLeq( e1->Org, e2->Org )) { - return EdgeSign( e2->Dst, e1->Org, e2->Org ) <= 0; - } - return EdgeSign( e1->Dst, e2->Org, e1->Org ) >= 0; - } - return EdgeSign( e2->Dst, event, e2->Org ) <= 0; - } - if( e2->Dst == event ) { - return EdgeSign( e1->Dst, event, e1->Org ) >= 0; - } - - /* General case - compute signed distance *from* e1, e2 to event */ - t1 = EdgeEval( e1->Dst, event, e1->Org ); - t2 = EdgeEval( e2->Dst, event, e2->Org ); - return (t1 >= t2); -} - - -static void DeleteRegion( TESStesselator *tess, ActiveRegion *reg ) -{ - if( reg->fixUpperEdge ) { - /* It was created with zero winding number, so it better be - * deleted with zero winding number (ie. it better not get merged - * with a real edge). - */ - assert( reg->eUp->winding == 0 ); - } - reg->eUp->activeRegion = NULL; - dictDelete( tess->dict, reg->nodeUp ); - bucketFree( tess->regionPool, reg ); -} - - -static int FixUpperEdge( TESStesselator *tess, ActiveRegion *reg, TESShalfEdge *newEdge ) -/* -* Replace an upper edge which needs fixing (see ConnectRightVertex). -*/ -{ - assert( reg->fixUpperEdge ); - if ( !tessMeshDelete( tess->mesh, reg->eUp ) ) return 0; - reg->fixUpperEdge = FALSE; - reg->eUp = newEdge; - newEdge->activeRegion = reg; - - return 1; -} - -static ActiveRegion *TopLeftRegion( TESStesselator *tess, ActiveRegion *reg ) -{ - TESSvertex *org = reg->eUp->Org; - TESShalfEdge *e; - - /* Find the region above the uppermost edge with the same origin */ - do { - reg = RegionAbove( reg ); - } while( reg->eUp->Org == org ); - - /* If the edge above was a temporary edge introduced by ConnectRightVertex, - * now is the time to fix it. - */ - if( reg->fixUpperEdge ) { - e = tessMeshConnect( tess->mesh, RegionBelow(reg)->eUp->Sym, reg->eUp->Lnext ); - if (e == NULL) return NULL; - if ( !FixUpperEdge( tess, reg, e ) ) return NULL; - reg = RegionAbove( reg ); - } - return reg; -} - -static ActiveRegion *TopRightRegion( ActiveRegion *reg ) -{ - TESSvertex *dst = reg->eUp->Dst; - - /* Find the region above the uppermost edge with the same destination */ - do { - reg = RegionAbove( reg ); - } while( reg->eUp->Dst == dst ); - return reg; -} - -static ActiveRegion *AddRegionBelow( TESStesselator *tess, - ActiveRegion *regAbove, - TESShalfEdge *eNewUp ) -/* -* Add a new active region to the sweep line, *somewhere* below "regAbove" -* (according to where the new edge belongs in the sweep-line dictionary). -* The upper edge of the new region will be "eNewUp". -* Winding number and "inside" flag are not updated. -*/ -{ - ActiveRegion *regNew = (ActiveRegion *)bucketAlloc( tess->regionPool ); - if (regNew == NULL) longjmp(tess->env,1); - - regNew->eUp = eNewUp; - regNew->nodeUp = dictInsertBefore( tess->dict, regAbove->nodeUp, regNew ); - if (regNew->nodeUp == NULL) longjmp(tess->env,1); - regNew->fixUpperEdge = FALSE; - regNew->sentinel = FALSE; - regNew->dirty = FALSE; - - eNewUp->activeRegion = regNew; - return regNew; -} - -static int IsWindingInside( TESStesselator *tess, int n ) -{ - switch( tess->windingRule ) { - case TESS_WINDING_ODD: - return (n & 1); - case TESS_WINDING_NONZERO: - return (n != 0); - case TESS_WINDING_POSITIVE: - return (n > 0); - case TESS_WINDING_NEGATIVE: - return (n < 0); - case TESS_WINDING_ABS_GEQ_TWO: - return (n >= 2) || (n <= -2); - } - /*LINTED*/ - assert( FALSE ); - /*NOTREACHED*/ - - return( FALSE ); -} - - -static void ComputeWinding( TESStesselator *tess, ActiveRegion *reg ) -{ - reg->windingNumber = RegionAbove(reg)->windingNumber + reg->eUp->winding; - reg->inside = IsWindingInside( tess, reg->windingNumber ); -} - - -static void FinishRegion( TESStesselator *tess, ActiveRegion *reg ) -/* -* Delete a region from the sweep line. This happens when the upper -* and lower chains of a region meet (at a vertex on the sweep line). -* The "inside" flag is copied to the appropriate mesh face (we could -* not do this before -- since the structure of the mesh is always -* changing, this face may not have even existed until now). -*/ -{ - TESShalfEdge *e = reg->eUp; - TESSface *f = e->Lface; - - f->inside = reg->inside; - f->anEdge = e; /* optimization for tessMeshTessellateMonoRegion() */ - DeleteRegion( tess, reg ); -} - - -static TESShalfEdge *FinishLeftRegions( TESStesselator *tess, - ActiveRegion *regFirst, ActiveRegion *regLast ) -/* -* We are given a vertex with one or more left-going edges. All affected -* edges should be in the edge dictionary. Starting at regFirst->eUp, -* we walk down deleting all regions where both edges have the same -* origin vOrg. At the same time we copy the "inside" flag from the -* active region to the face, since at this point each face will belong -* to at most one region (this was not necessarily true until this point -* in the sweep). The walk stops at the region above regLast; if regLast -* is NULL we walk as far as possible. At the same time we relink the -* mesh if necessary, so that the ordering of edges around vOrg is the -* same as in the dictionary. -*/ -{ - ActiveRegion *reg, *regPrev; - TESShalfEdge *e, *ePrev; - - regPrev = regFirst; - ePrev = regFirst->eUp; - while( regPrev != regLast ) { - regPrev->fixUpperEdge = FALSE; /* placement was OK */ - reg = RegionBelow( regPrev ); - e = reg->eUp; - if( e->Org != ePrev->Org ) { - if( ! reg->fixUpperEdge ) { - /* Remove the last left-going edge. Even though there are no further - * edges in the dictionary with this origin, there may be further - * such edges in the mesh (if we are adding left edges to a vertex - * that has already been processed). Thus it is important to call - * FinishRegion rather than just DeleteRegion. - */ - FinishRegion( tess, regPrev ); - break; - } - /* If the edge below was a temporary edge introduced by - * ConnectRightVertex, now is the time to fix it. - */ - e = tessMeshConnect( tess->mesh, ePrev->Lprev, e->Sym ); - if (e == NULL) longjmp(tess->env,1); - if ( !FixUpperEdge( tess, reg, e ) ) longjmp(tess->env,1); - } - - /* Relink edges so that ePrev->Onext == e */ - if( ePrev->Onext != e ) { - if ( !tessMeshSplice( tess->mesh, e->Oprev, e ) ) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, ePrev, e ) ) longjmp(tess->env,1); - } - FinishRegion( tess, regPrev ); /* may change reg->eUp */ - ePrev = reg->eUp; - regPrev = reg; - } - return ePrev; -} - - -static void AddRightEdges( TESStesselator *tess, ActiveRegion *regUp, - TESShalfEdge *eFirst, TESShalfEdge *eLast, TESShalfEdge *eTopLeft, - int cleanUp ) -/* -* Purpose: insert right-going edges into the edge dictionary, and update -* winding numbers and mesh connectivity appropriately. All right-going -* edges share a common origin vOrg. Edges are inserted CCW starting at -* eFirst; the last edge inserted is eLast->Oprev. If vOrg has any -* left-going edges already processed, then eTopLeft must be the edge -* such that an imaginary upward vertical segment from vOrg would be -* contained between eTopLeft->Oprev and eTopLeft; otherwise eTopLeft -* should be NULL. -*/ -{ - ActiveRegion *reg, *regPrev; - TESShalfEdge *e, *ePrev; - int firstTime = TRUE; - - /* Insert the new right-going edges in the dictionary */ - e = eFirst; - do { - assert( VertLeq( e->Org, e->Dst )); - AddRegionBelow( tess, regUp, e->Sym ); - e = e->Onext; - } while ( e != eLast ); - - /* Walk *all* right-going edges from e->Org, in the dictionary order, - * updating the winding numbers of each region, and re-linking the mesh - * edges to match the dictionary ordering (if necessary). - */ - if( eTopLeft == NULL ) { - eTopLeft = RegionBelow( regUp )->eUp->Rprev; - } - regPrev = regUp; - ePrev = eTopLeft; - for( ;; ) { - reg = RegionBelow( regPrev ); - e = reg->eUp->Sym; - if( e->Org != ePrev->Org ) break; - - if( e->Onext != ePrev ) { - /* Unlink e from its current position, and relink below ePrev */ - if ( !tessMeshSplice( tess->mesh, e->Oprev, e ) ) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, ePrev->Oprev, e ) ) longjmp(tess->env,1); - } - /* Compute the winding number and "inside" flag for the new regions */ - reg->windingNumber = regPrev->windingNumber - e->winding; - reg->inside = IsWindingInside( tess, reg->windingNumber ); - - /* Check for two outgoing edges with same slope -- process these - * before any intersection tests (see example in tessComputeInterior). - */ - regPrev->dirty = TRUE; - if( ! firstTime && CheckForRightSplice( tess, regPrev )) { - AddWinding( e, ePrev ); - DeleteRegion( tess, regPrev ); - if ( !tessMeshDelete( tess->mesh, ePrev ) ) longjmp(tess->env,1); - } - firstTime = FALSE; - regPrev = reg; - ePrev = e; - } - regPrev->dirty = TRUE; - assert( regPrev->windingNumber - e->winding == reg->windingNumber ); - - if( cleanUp ) { - /* Check for intersections between newly adjacent edges. */ - WalkDirtyRegions( tess, regPrev ); - } -} - - -static void SpliceMergeVertices( TESStesselator *tess, TESShalfEdge *e1, - TESShalfEdge *e2 ) -/* -* Two vertices with idential coordinates are combined into one. -* e1->Org is kept, while e2->Org is discarded. -*/ -{ - if ( !tessMeshSplice( tess->mesh, e1, e2 ) ) longjmp(tess->env,1); -} - -static void VertexWeights( TESSvertex *isect, TESSvertex *org, TESSvertex *dst, - TESSreal *weights ) -/* -* Find some weights which describe how the intersection vertex is -* a linear combination of "org" and "dest". Each of the two edges -* which generated "isect" is allocated 50% of the weight; each edge -* splits the weight between its org and dst according to the -* relative distance to "isect". -*/ -{ - TESSreal t1 = VertL1dist( org, isect ); - TESSreal t2 = VertL1dist( dst, isect ); - - weights[0] = (TESSreal)0.5 * t2 / (t1 + t2); - weights[1] = (TESSreal)0.5 * t1 / (t1 + t2); - isect->coords[0] += weights[0]*org->coords[0] + weights[1]*dst->coords[0]; - isect->coords[1] += weights[0]*org->coords[1] + weights[1]*dst->coords[1]; - isect->coords[2] += weights[0]*org->coords[2] + weights[1]*dst->coords[2]; -} - - -static void GetIntersectData( TESStesselator *tess, TESSvertex *isect, - TESSvertex *orgUp, TESSvertex *dstUp, - TESSvertex *orgLo, TESSvertex *dstLo ) - /* - * We've computed a new intersection point, now we need a "data" pointer - * from the user so that we can refer to this new vertex in the - * rendering callbacks. - */ -{ - TESSreal weights[4]; - TESS_NOTUSED( tess ); - - isect->coords[0] = isect->coords[1] = isect->coords[2] = 0; - isect->idx = TESS_UNDEF; - VertexWeights( isect, orgUp, dstUp, &weights[0] ); - VertexWeights( isect, orgLo, dstLo, &weights[2] ); -} - -static int CheckForRightSplice( TESStesselator *tess, ActiveRegion *regUp ) -/* -* Check the upper and lower edge of "regUp", to make sure that the -* eUp->Org is above eLo, or eLo->Org is below eUp (depending on which -* origin is leftmost). -* -* The main purpose is to splice right-going edges with the same -* dest vertex and nearly identical slopes (ie. we can't distinguish -* the slopes numerically). However the splicing can also help us -* to recover from numerical errors. For example, suppose at one -* point we checked eUp and eLo, and decided that eUp->Org is barely -* above eLo. Then later, we split eLo into two edges (eg. from -* a splice operation like this one). This can change the result of -* our test so that now eUp->Org is incident to eLo, or barely below it. -* We must correct this condition to maintain the dictionary invariants. -* -* One possibility is to check these edges for intersection again -* (ie. CheckForIntersect). This is what we do if possible. However -* CheckForIntersect requires that tess->event lies between eUp and eLo, -* so that it has something to fall back on when the intersection -* calculation gives us an unusable answer. So, for those cases where -* we can't check for intersection, this routine fixes the problem -* by just splicing the offending vertex into the other edge. -* This is a guaranteed solution, no matter how degenerate things get. -* Basically this is a combinatorial solution to a numerical problem. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - - if( VertLeq( eUp->Org, eLo->Org )) { - if( EdgeSign( eLo->Dst, eUp->Org, eLo->Org ) > 0 ) return FALSE; - - /* eUp->Org appears to be below eLo */ - if( ! VertEq( eUp->Org, eLo->Org )) { - /* Splice eUp->Org into eLo */ - if ( tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eUp, eLo->Oprev ) ) longjmp(tess->env,1); - regUp->dirty = regLo->dirty = TRUE; - - } else if( eUp->Org != eLo->Org ) { - /* merge the two vertices, discarding eUp->Org */ - pqDelete( tess->pq, eUp->Org->pqHandle ); - SpliceMergeVertices( tess, eLo->Oprev, eUp ); - } - } else { - if( EdgeSign( eUp->Dst, eLo->Org, eUp->Org ) <= 0 ) return FALSE; - - /* eLo->Org appears to be above eUp, so splice eLo->Org into eUp */ - RegionAbove(regUp)->dirty = regUp->dirty = TRUE; - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Oprev, eUp ) ) longjmp(tess->env,1); - } - return TRUE; -} - -static int CheckForLeftSplice( TESStesselator *tess, ActiveRegion *regUp ) -/* -* Check the upper and lower edge of "regUp", to make sure that the -* eUp->Dst is above eLo, or eLo->Dst is below eUp (depending on which -* destination is rightmost). -* -* Theoretically, this should always be true. However, splitting an edge -* into two pieces can change the results of previous tests. For example, -* suppose at one point we checked eUp and eLo, and decided that eUp->Dst -* is barely above eLo. Then later, we split eLo into two edges (eg. from -* a splice operation like this one). This can change the result of -* the test so that now eUp->Dst is incident to eLo, or barely below it. -* We must correct this condition to maintain the dictionary invariants -* (otherwise new edges might get inserted in the wrong place in the -* dictionary, and bad stuff will happen). -* -* We fix the problem by just splicing the offending vertex into the -* other edge. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - TESShalfEdge *e; - - assert( ! VertEq( eUp->Dst, eLo->Dst )); - - if( VertLeq( eUp->Dst, eLo->Dst )) { - if( EdgeSign( eUp->Dst, eLo->Dst, eUp->Org ) < 0 ) return FALSE; - - /* eLo->Dst is above eUp, so splice eLo->Dst into eUp */ - RegionAbove(regUp)->dirty = regUp->dirty = TRUE; - e = tessMeshSplitEdge( tess->mesh, eUp ); - if (e == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Sym, e ) ) longjmp(tess->env,1); - e->Lface->inside = regUp->inside; - } else { - if( EdgeSign( eLo->Dst, eUp->Dst, eLo->Org ) > 0 ) return FALSE; - - /* eUp->Dst is below eLo, so splice eUp->Dst into eLo */ - regUp->dirty = regLo->dirty = TRUE; - e = tessMeshSplitEdge( tess->mesh, eLo ); - if (e == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eUp->Lnext, eLo->Sym ) ) longjmp(tess->env,1); - e->Rface->inside = regUp->inside; - } - return TRUE; -} - - -static int CheckForIntersect( TESStesselator *tess, ActiveRegion *regUp ) -/* -* Check the upper and lower edges of the given region to see if -* they intersect. If so, create the intersection and add it -* to the data structures. -* -* Returns TRUE if adding the new intersection resulted in a recursive -* call to AddRightEdges(); in this case all "dirty" regions have been -* checked for intersections, and possibly regUp has been deleted. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - TESSvertex *orgUp = eUp->Org; - TESSvertex *orgLo = eLo->Org; - TESSvertex *dstUp = eUp->Dst; - TESSvertex *dstLo = eLo->Dst; - TESSreal tMinUp, tMaxLo; - TESSvertex isect, *orgMin; - TESShalfEdge *e; - - assert( ! VertEq( dstLo, dstUp )); - assert( EdgeSign( dstUp, tess->event, orgUp ) <= 0 ); - assert( EdgeSign( dstLo, tess->event, orgLo ) >= 0 ); - assert( orgUp != tess->event && orgLo != tess->event ); - assert( ! regUp->fixUpperEdge && ! regLo->fixUpperEdge ); - - if( orgUp == orgLo ) return FALSE; /* right endpoints are the same */ - - tMinUp = MIN( orgUp->t, dstUp->t ); - tMaxLo = MAX( orgLo->t, dstLo->t ); - if( tMinUp > tMaxLo ) return FALSE; /* t ranges do not overlap */ - - if( VertLeq( orgUp, orgLo )) { - if( EdgeSign( dstLo, orgUp, orgLo ) > 0 ) return FALSE; - } else { - if( EdgeSign( dstUp, orgLo, orgUp ) < 0 ) return FALSE; - } - - /* At this point the edges intersect, at least marginally */ - DebugEvent( tess ); - - tesedgeIntersect( dstUp, orgUp, dstLo, orgLo, &isect ); - /* The following properties are guaranteed: */ - assert( MIN( orgUp->t, dstUp->t ) <= isect.t ); - assert( isect.t <= MAX( orgLo->t, dstLo->t )); - assert( MIN( dstLo->s, dstUp->s ) <= isect.s ); - assert( isect.s <= MAX( orgLo->s, orgUp->s )); - - if( VertLeq( &isect, tess->event )) { - /* The intersection point lies slightly to the left of the sweep line, - * so move it until it''s slightly to the right of the sweep line. - * (If we had perfect numerical precision, this would never happen - * in the first place). The easiest and safest thing to do is - * replace the intersection by tess->event. - */ - isect.s = tess->event->s; - isect.t = tess->event->t; - } - /* Similarly, if the computed intersection lies to the right of the - * rightmost origin (which should rarely happen), it can cause - * unbelievable inefficiency on sufficiently degenerate inputs. - * (If you have the test program, try running test54.d with the - * "X zoom" option turned on). - */ - orgMin = VertLeq( orgUp, orgLo ) ? orgUp : orgLo; - if( VertLeq( orgMin, &isect )) { - isect.s = orgMin->s; - isect.t = orgMin->t; - } - - if( VertEq( &isect, orgUp ) || VertEq( &isect, orgLo )) { - /* Easy case -- intersection at one of the right endpoints */ - (void) CheckForRightSplice( tess, regUp ); - return FALSE; - } - - if( (! VertEq( dstUp, tess->event ) - && EdgeSign( dstUp, tess->event, &isect ) >= 0) - || (! VertEq( dstLo, tess->event ) - && EdgeSign( dstLo, tess->event, &isect ) <= 0 )) - { - /* Very unusual -- the new upper or lower edge would pass on the - * wrong side of the sweep event, or through it. This can happen - * due to very small numerical errors in the intersection calculation. - */ - if( dstLo == tess->event ) { - /* Splice dstLo into eUp, and process the new region(s) */ - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Sym, eUp ) ) longjmp(tess->env,1); - regUp = TopLeftRegion( tess, regUp ); - if (regUp == NULL) longjmp(tess->env,1); - eUp = RegionBelow(regUp)->eUp; - FinishLeftRegions( tess, RegionBelow(regUp), regLo ); - AddRightEdges( tess, regUp, eUp->Oprev, eUp, eUp, TRUE ); - return TRUE; - } - if( dstUp == tess->event ) { - /* Splice dstUp into eLo, and process the new region(s) */ - if (tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eUp->Lnext, eLo->Oprev ) ) longjmp(tess->env,1); - regLo = regUp; - regUp = TopRightRegion( regUp ); - e = RegionBelow(regUp)->eUp->Rprev; - regLo->eUp = eLo->Oprev; - eLo = FinishLeftRegions( tess, regLo, NULL ); - AddRightEdges( tess, regUp, eLo->Onext, eUp->Rprev, e, TRUE ); - return TRUE; - } - /* Special case: called from ConnectRightVertex. If either - * edge passes on the wrong side of tess->event, split it - * (and wait for ConnectRightVertex to splice it appropriately). - */ - if( EdgeSign( dstUp, tess->event, &isect ) >= 0 ) { - RegionAbove(regUp)->dirty = regUp->dirty = TRUE; - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - eUp->Org->s = tess->event->s; - eUp->Org->t = tess->event->t; - } - if( EdgeSign( dstLo, tess->event, &isect ) <= 0 ) { - regUp->dirty = regLo->dirty = TRUE; - if (tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - eLo->Org->s = tess->event->s; - eLo->Org->t = tess->event->t; - } - /* leave the rest for ConnectRightVertex */ - return FALSE; - } - - /* General case -- split both edges, splice into new vertex. - * When we do the splice operation, the order of the arguments is - * arbitrary as far as correctness goes. However, when the operation - * creates a new face, the work done is proportional to the size of - * the new face. We expect the faces in the processed part of - * the mesh (ie. eUp->Lface) to be smaller than the faces in the - * unprocessed original contours (which will be eLo->Oprev->Lface). - */ - if (tessMeshSplitEdge( tess->mesh, eUp->Sym ) == NULL) longjmp(tess->env,1); - if (tessMeshSplitEdge( tess->mesh, eLo->Sym ) == NULL) longjmp(tess->env,1); - if ( !tessMeshSplice( tess->mesh, eLo->Oprev, eUp ) ) longjmp(tess->env,1); - eUp->Org->s = isect.s; - eUp->Org->t = isect.t; - eUp->Org->pqHandle = pqInsert( &tess->alloc, tess->pq, eUp->Org ); - if (eUp->Org->pqHandle == INV_HANDLE) { - pqDeletePriorityQ( &tess->alloc, tess->pq ); - tess->pq = NULL; - longjmp(tess->env,1); - } - GetIntersectData( tess, eUp->Org, orgUp, dstUp, orgLo, dstLo ); - RegionAbove(regUp)->dirty = regUp->dirty = regLo->dirty = TRUE; - return FALSE; -} - -static void WalkDirtyRegions( TESStesselator *tess, ActiveRegion *regUp ) -/* -* When the upper or lower edge of any region changes, the region is -* marked "dirty". This routine walks through all the dirty regions -* and makes sure that the dictionary invariants are satisfied -* (see the comments at the beginning of this file). Of course -* new dirty regions can be created as we make changes to restore -* the invariants. -*/ -{ - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp, *eLo; - - for( ;; ) { - /* Find the lowest dirty region (we walk from the bottom up). */ - while( regLo->dirty ) { - regUp = regLo; - regLo = RegionBelow(regLo); - } - if( ! regUp->dirty ) { - regLo = regUp; - regUp = RegionAbove( regUp ); - if( regUp == NULL || ! regUp->dirty ) { - /* We've walked all the dirty regions */ - return; - } - } - regUp->dirty = FALSE; - eUp = regUp->eUp; - eLo = regLo->eUp; - - if( eUp->Dst != eLo->Dst ) { - /* Check that the edge ordering is obeyed at the Dst vertices. */ - if( CheckForLeftSplice( tess, regUp )) { - - /* If the upper or lower edge was marked fixUpperEdge, then - * we no longer need it (since these edges are needed only for - * vertices which otherwise have no right-going edges). - */ - if( regLo->fixUpperEdge ) { - DeleteRegion( tess, regLo ); - if ( !tessMeshDelete( tess->mesh, eLo ) ) longjmp(tess->env,1); - regLo = RegionBelow( regUp ); - eLo = regLo->eUp; - } else if( regUp->fixUpperEdge ) { - DeleteRegion( tess, regUp ); - if ( !tessMeshDelete( tess->mesh, eUp ) ) longjmp(tess->env,1); - regUp = RegionAbove( regLo ); - eUp = regUp->eUp; - } - } - } - if( eUp->Org != eLo->Org ) { - if( eUp->Dst != eLo->Dst - && ! regUp->fixUpperEdge && ! regLo->fixUpperEdge - && (eUp->Dst == tess->event || eLo->Dst == tess->event) ) - { - /* When all else fails in CheckForIntersect(), it uses tess->event - * as the intersection location. To make this possible, it requires - * that tess->event lie between the upper and lower edges, and also - * that neither of these is marked fixUpperEdge (since in the worst - * case it might splice one of these edges into tess->event, and - * violate the invariant that fixable edges are the only right-going - * edge from their associated vertex). - */ - if( CheckForIntersect( tess, regUp )) { - /* WalkDirtyRegions() was called recursively; we're done */ - return; - } - } else { - /* Even though we can't use CheckForIntersect(), the Org vertices - * may violate the dictionary edge ordering. Check and correct this. - */ - (void) CheckForRightSplice( tess, regUp ); - } - } - if( eUp->Org == eLo->Org && eUp->Dst == eLo->Dst ) { - /* A degenerate loop consisting of only two edges -- delete it. */ - AddWinding( eLo, eUp ); - DeleteRegion( tess, regUp ); - if ( !tessMeshDelete( tess->mesh, eUp ) ) longjmp(tess->env,1); - regUp = RegionAbove( regLo ); - } - } -} - - -static void ConnectRightVertex( TESStesselator *tess, ActiveRegion *regUp, - TESShalfEdge *eBottomLeft ) -/* -* Purpose: connect a "right" vertex vEvent (one where all edges go left) -* to the unprocessed portion of the mesh. Since there are no right-going -* edges, two regions (one above vEvent and one below) are being merged -* into one. "regUp" is the upper of these two regions. -* -* There are two reasons for doing this (adding a right-going edge): -* - if the two regions being merged are "inside", we must add an edge -* to keep them separated (the combined region would not be monotone). -* - in any case, we must leave some record of vEvent in the dictionary, -* so that we can merge vEvent with features that we have not seen yet. -* For example, maybe there is a vertical edge which passes just to -* the right of vEvent; we would like to splice vEvent into this edge. -* -* However, we don't want to connect vEvent to just any vertex. We don''t -* want the new edge to cross any other edges; otherwise we will create -* intersection vertices even when the input data had no self-intersections. -* (This is a bad thing; if the user's input data has no intersections, -* we don't want to generate any false intersections ourselves.) -* -* Our eventual goal is to connect vEvent to the leftmost unprocessed -* vertex of the combined region (the union of regUp and regLo). -* But because of unseen vertices with all right-going edges, and also -* new vertices which may be created by edge intersections, we don''t -* know where that leftmost unprocessed vertex is. In the meantime, we -* connect vEvent to the closest vertex of either chain, and mark the region -* as "fixUpperEdge". This flag says to delete and reconnect this edge -* to the next processed vertex on the boundary of the combined region. -* Quite possibly the vertex we connected to will turn out to be the -* closest one, in which case we won''t need to make any changes. -*/ -{ - TESShalfEdge *eNew; - TESShalfEdge *eTopLeft = eBottomLeft->Onext; - ActiveRegion *regLo = RegionBelow(regUp); - TESShalfEdge *eUp = regUp->eUp; - TESShalfEdge *eLo = regLo->eUp; - int degenerate = FALSE; - - if( eUp->Dst != eLo->Dst ) { - (void) CheckForIntersect( tess, regUp ); - } - - /* Possible new degeneracies: upper or lower edge of regUp may pass - * through vEvent, or may coincide with new intersection vertex - */ - if( VertEq( eUp->Org, tess->event )) { - if ( !tessMeshSplice( tess->mesh, eTopLeft->Oprev, eUp ) ) longjmp(tess->env,1); - regUp = TopLeftRegion( tess, regUp ); - if (regUp == NULL) longjmp(tess->env,1); - eTopLeft = RegionBelow( regUp )->eUp; - FinishLeftRegions( tess, RegionBelow(regUp), regLo ); - degenerate = TRUE; - } - if( VertEq( eLo->Org, tess->event )) { - if ( !tessMeshSplice( tess->mesh, eBottomLeft, eLo->Oprev ) ) longjmp(tess->env,1); - eBottomLeft = FinishLeftRegions( tess, regLo, NULL ); - degenerate = TRUE; - } - if( degenerate ) { - AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE ); - return; - } - - /* Non-degenerate situation -- need to add a temporary, fixable edge. - * Connect to the closer of eLo->Org, eUp->Org. - */ - if( VertLeq( eLo->Org, eUp->Org )) { - eNew = eLo->Oprev; - } else { - eNew = eUp; - } - eNew = tessMeshConnect( tess->mesh, eBottomLeft->Lprev, eNew ); - if (eNew == NULL) longjmp(tess->env,1); - - /* Prevent cleanup, otherwise eNew might disappear before we've even - * had a chance to mark it as a temporary edge. - */ - AddRightEdges( tess, regUp, eNew, eNew->Onext, eNew->Onext, FALSE ); - eNew->Sym->activeRegion->fixUpperEdge = TRUE; - WalkDirtyRegions( tess, regUp ); -} - -/* Because vertices at exactly the same location are merged together -* before we process the sweep event, some degenerate cases can't occur. -* However if someone eventually makes the modifications required to -* merge features which are close together, the cases below marked -* TOLERANCE_NONZERO will be useful. They were debugged before the -* code to merge identical vertices in the main loop was added. -*/ -#define TOLERANCE_NONZERO FALSE - -static void ConnectLeftDegenerate( TESStesselator *tess, - ActiveRegion *regUp, TESSvertex *vEvent ) -/* -* The event vertex lies exacty on an already-processed edge or vertex. -* Adding the new vertex involves splicing it into the already-processed -* part of the mesh. -*/ -{ - TESShalfEdge *e, *eTopLeft, *eTopRight, *eLast; - ActiveRegion *reg; - - e = regUp->eUp; - if( VertEq( e->Org, vEvent )) { - /* e->Org is an unprocessed vertex - just combine them, and wait - * for e->Org to be pulled from the queue - */ - assert( TOLERANCE_NONZERO ); - SpliceMergeVertices( tess, e, vEvent->anEdge ); - return; - } - - if( ! VertEq( e->Dst, vEvent )) { - /* General case -- splice vEvent into edge e which passes through it */ - if (tessMeshSplitEdge( tess->mesh, e->Sym ) == NULL) longjmp(tess->env,1); - if( regUp->fixUpperEdge ) { - /* This edge was fixable -- delete unused portion of original edge */ - if ( !tessMeshDelete( tess->mesh, e->Onext ) ) longjmp(tess->env,1); - regUp->fixUpperEdge = FALSE; - } - if ( !tessMeshSplice( tess->mesh, vEvent->anEdge, e ) ) longjmp(tess->env,1); - SweepEvent( tess, vEvent ); /* recurse */ - return; - } - - /* vEvent coincides with e->Dst, which has already been processed. - * Splice in the additional right-going edges. - */ - assert( TOLERANCE_NONZERO ); - regUp = TopRightRegion( regUp ); - reg = RegionBelow( regUp ); - eTopRight = reg->eUp->Sym; - eTopLeft = eLast = eTopRight->Onext; - if( reg->fixUpperEdge ) { - /* Here e->Dst has only a single fixable edge going right. - * We can delete it since now we have some real right-going edges. - */ - assert( eTopLeft != eTopRight ); /* there are some left edges too */ - DeleteRegion( tess, reg ); - if ( !tessMeshDelete( tess->mesh, eTopRight ) ) longjmp(tess->env,1); - eTopRight = eTopLeft->Oprev; - } - if ( !tessMeshSplice( tess->mesh, vEvent->anEdge, eTopRight ) ) longjmp(tess->env,1); - if( ! EdgeGoesLeft( eTopLeft )) { - /* e->Dst had no left-going edges -- indicate this to AddRightEdges() */ - eTopLeft = NULL; - } - AddRightEdges( tess, regUp, eTopRight->Onext, eLast, eTopLeft, TRUE ); -} - - -static void ConnectLeftVertex( TESStesselator *tess, TESSvertex *vEvent ) -/* -* Purpose: connect a "left" vertex (one where both edges go right) -* to the processed portion of the mesh. Let R be the active region -* containing vEvent, and let U and L be the upper and lower edge -* chains of R. There are two possibilities: -* -* - the normal case: split R into two regions, by connecting vEvent to -* the rightmost vertex of U or L lying to the left of the sweep line -* -* - the degenerate case: if vEvent is close enough to U or L, we -* merge vEvent into that edge chain. The subcases are: -* - merging with the rightmost vertex of U or L -* - merging with the active edge of U or L -* - merging with an already-processed portion of U or L -*/ -{ - ActiveRegion *regUp, *regLo, *reg; - TESShalfEdge *eUp, *eLo, *eNew; - ActiveRegion tmp; - - /* assert( vEvent->anEdge->Onext->Onext == vEvent->anEdge ); */ - - /* Get a pointer to the active region containing vEvent */ - tmp.eUp = vEvent->anEdge->Sym; - /* __GL_DICTLISTKEY */ /* tessDictListSearch */ - regUp = (ActiveRegion *)dictKey( dictSearch( tess->dict, &tmp )); - regLo = RegionBelow( regUp ); - if( !regLo ) { - // This may happen if the input polygon is coplanar. - return; - } - eUp = regUp->eUp; - eLo = regLo->eUp; - - /* Try merging with U or L first */ - if( EdgeSign( eUp->Dst, vEvent, eUp->Org ) == 0 ) { - ConnectLeftDegenerate( tess, regUp, vEvent ); - return; - } - - /* Connect vEvent to rightmost processed vertex of either chain. - * e->Dst is the vertex that we will connect to vEvent. - */ - reg = VertLeq( eLo->Dst, eUp->Dst ) ? regUp : regLo; - - if( regUp->inside || reg->fixUpperEdge) { - if( reg == regUp ) { - eNew = tessMeshConnect( tess->mesh, vEvent->anEdge->Sym, eUp->Lnext ); - if (eNew == NULL) longjmp(tess->env,1); - } else { - TESShalfEdge *tempHalfEdge= tessMeshConnect( tess->mesh, eLo->Dnext, vEvent->anEdge); - if (tempHalfEdge == NULL) longjmp(tess->env,1); - - eNew = tempHalfEdge->Sym; - } - if( reg->fixUpperEdge ) { - if ( !FixUpperEdge( tess, reg, eNew ) ) longjmp(tess->env,1); - } else { - ComputeWinding( tess, AddRegionBelow( tess, regUp, eNew )); - } - SweepEvent( tess, vEvent ); - } else { - /* The new vertex is in a region which does not belong to the polygon. - * We don''t need to connect this vertex to the rest of the mesh. - */ - AddRightEdges( tess, regUp, vEvent->anEdge, vEvent->anEdge, NULL, TRUE ); - } -} - - -static void SweepEvent( TESStesselator *tess, TESSvertex *vEvent ) -/* -* Does everything necessary when the sweep line crosses a vertex. -* Updates the mesh and the edge dictionary. -*/ -{ - ActiveRegion *regUp, *reg; - TESShalfEdge *e, *eTopLeft, *eBottomLeft; - - tess->event = vEvent; /* for access in EdgeLeq() */ - DebugEvent( tess ); - - /* Check if this vertex is the right endpoint of an edge that is - * already in the dictionary. In this case we don't need to waste - * time searching for the location to insert new edges. - */ - e = vEvent->anEdge; - while( e->activeRegion == NULL ) { - e = e->Onext; - if( e == vEvent->anEdge ) { - /* All edges go right -- not incident to any processed edges */ - ConnectLeftVertex( tess, vEvent ); - return; - } - } - - /* Processing consists of two phases: first we "finish" all the - * active regions where both the upper and lower edges terminate - * at vEvent (ie. vEvent is closing off these regions). - * We mark these faces "inside" or "outside" the polygon according - * to their winding number, and delete the edges from the dictionary. - * This takes care of all the left-going edges from vEvent. - */ - regUp = TopLeftRegion( tess, e->activeRegion ); - if (regUp == NULL) longjmp(tess->env,1); - reg = RegionBelow( regUp ); - eTopLeft = reg->eUp; - eBottomLeft = FinishLeftRegions( tess, reg, NULL ); - - /* Next we process all the right-going edges from vEvent. This - * involves adding the edges to the dictionary, and creating the - * associated "active regions" which record information about the - * regions between adjacent dictionary edges. - */ - if( eBottomLeft->Onext == eTopLeft ) { - /* No right-going edges -- add a temporary "fixable" edge */ - ConnectRightVertex( tess, regUp, eBottomLeft ); - } else { - AddRightEdges( tess, regUp, eBottomLeft->Onext, eTopLeft, eTopLeft, TRUE ); - } -} - - -/* Make the sentinel coordinates big enough that they will never be -* merged with real input features. -*/ - -static void AddSentinel( TESStesselator *tess, TESSreal smin, TESSreal smax, TESSreal t ) -/* -* We add two sentinel edges above and below all other edges, -* to avoid special cases at the top and bottom. -*/ -{ - TESShalfEdge *e; - ActiveRegion *reg = (ActiveRegion *)bucketAlloc( tess->regionPool ); - if (reg == NULL) longjmp(tess->env,1); - - e = tessMeshMakeEdge( tess->mesh ); - if (e == NULL) longjmp(tess->env,1); - - e->Org->s = smax; - e->Org->t = t; - e->Dst->s = smin; - e->Dst->t = t; - tess->event = e->Dst; /* initialize it */ - - reg->eUp = e; - reg->windingNumber = 0; - reg->inside = FALSE; - reg->fixUpperEdge = FALSE; - reg->sentinel = TRUE; - reg->dirty = FALSE; - reg->nodeUp = dictInsert( tess->dict, reg ); - if (reg->nodeUp == NULL) longjmp(tess->env,1); -} - - -static void InitEdgeDict( TESStesselator *tess ) -/* -* We maintain an ordering of edge intersections with the sweep line. -* This order is maintained in a dynamic dictionary. -*/ -{ - TESSreal w, h; - TESSreal smin, smax, tmin, tmax; - - tess->dict = dictNewDict( &tess->alloc, tess, (int (*)(void *, DictKey, DictKey)) EdgeLeq ); - if (tess->dict == NULL) longjmp(tess->env,1); - - /* If the bbox is empty, ensure that sentinels are not coincident by slightly enlarging it. */ - w = (tess->bmax[0] - tess->bmin[0]) + (TESSreal)0.01; - h = (tess->bmax[1] - tess->bmin[1]) + (TESSreal)0.01; - - smin = tess->bmin[0] - w; - smax = tess->bmax[0] + w; - tmin = tess->bmin[1] - h; - tmax = tess->bmax[1] + h; - - AddSentinel( tess, smin, smax, tmin ); - AddSentinel( tess, smin, smax, tmax ); -} - - -static void DoneEdgeDict( TESStesselator *tess ) -{ - ActiveRegion *reg; - int fixedEdges = 0; - - while( (reg = (ActiveRegion *)dictKey( dictMin( tess->dict ))) != NULL ) { - /* - * At the end of all processing, the dictionary should contain - * only the two sentinel edges, plus at most one "fixable" edge - * created by ConnectRightVertex(). - */ - if( ! reg->sentinel ) { - assert( reg->fixUpperEdge ); - assert( ++fixedEdges == 1 ); - } - assert( reg->windingNumber == 0 ); - DeleteRegion( tess, reg ); - /* tessMeshDelete( reg->eUp );*/ - } - dictDeleteDict( &tess->alloc, tess->dict ); -} - - -static void RemoveDegenerateEdges( TESStesselator *tess ) -/* -* Remove zero-length edges, and contours with fewer than 3 vertices. -*/ -{ - TESShalfEdge *e, *eNext, *eLnext; - TESShalfEdge *eHead = &tess->mesh->eHead; - - /*LINTED*/ - for( e = eHead->next; e != eHead; e = eNext ) { - eNext = e->next; - eLnext = e->Lnext; - - if( VertEq( e->Org, e->Dst ) && e->Lnext->Lnext != e ) { - /* Zero-length edge, contour has at least 3 edges */ - - SpliceMergeVertices( tess, eLnext, e ); /* deletes e->Org */ - if ( !tessMeshDelete( tess->mesh, e ) ) longjmp(tess->env,1); /* e is a self-loop */ - e = eLnext; - eLnext = e->Lnext; - } - if( eLnext->Lnext == e ) { - /* Degenerate contour (one or two edges) */ - - if( eLnext != e ) { - if( eLnext == eNext || eLnext == eNext->Sym ) { eNext = eNext->next; } - if ( !tessMeshDelete( tess->mesh, eLnext ) ) longjmp(tess->env,1); - } - if( e == eNext || e == eNext->Sym ) { eNext = eNext->next; } - if ( !tessMeshDelete( tess->mesh, e ) ) longjmp(tess->env,1); - } - } -} - -static int InitPriorityQ( TESStesselator *tess ) -/* -* Insert all vertices into the priority queue which determines the -* order in which vertices cross the sweep line. -*/ -{ - PriorityQ *pq; - TESSvertex *v, *vHead; - int vertexCount = 0; - - vHead = &tess->mesh->vHead; - for( v = vHead->next; v != vHead; v = v->next ) { - vertexCount++; - } - /* Make sure there is enough space for sentinels. */ - vertexCount += MAX( 8, tess->alloc.extraVertices ); - - pq = tess->pq = pqNewPriorityQ( &tess->alloc, vertexCount, (int (*)(PQkey, PQkey)) tesvertLeq ); - if (pq == NULL) return 0; - - vHead = &tess->mesh->vHead; - for( v = vHead->next; v != vHead; v = v->next ) { - v->pqHandle = pqInsert( &tess->alloc, pq, v ); - if (v->pqHandle == INV_HANDLE) - break; - } - if (v != vHead || !pqInit( &tess->alloc, pq ) ) { - pqDeletePriorityQ( &tess->alloc, tess->pq ); - tess->pq = NULL; - return 0; - } - - return 1; -} - - -static void DonePriorityQ( TESStesselator *tess ) -{ - pqDeletePriorityQ( &tess->alloc, tess->pq ); -} - - -static int RemoveDegenerateFaces( TESStesselator *tess, TESSmesh *mesh ) -/* -* Delete any degenerate faces with only two edges. WalkDirtyRegions() -* will catch almost all of these, but it won't catch degenerate faces -* produced by splice operations on already-processed edges. -* The two places this can happen are in FinishLeftRegions(), when -* we splice in a "temporary" edge produced by ConnectRightVertex(), -* and in CheckForLeftSplice(), where we splice already-processed -* edges to ensure that our dictionary invariants are not violated -* by numerical errors. -* -* In both these cases it is *very* dangerous to delete the offending -* edge at the time, since one of the routines further up the stack -* will sometimes be keeping a pointer to that edge. -*/ -{ - TESSface *f, *fNext; - TESShalfEdge *e; - - /*LINTED*/ - for( f = mesh->fHead.next; f != &mesh->fHead; f = fNext ) { - fNext = f->next; - e = f->anEdge; - assert( e->Lnext != e ); - - if( e->Lnext->Lnext == e ) { - /* A face with only two edges */ - AddWinding( e->Onext, e ); - if ( !tessMeshDelete( tess->mesh, e ) ) return 0; - } - } - return 1; -} - -int tessComputeInterior( TESStesselator *tess ) -/* -* tessComputeInterior( tess ) computes the planar arrangement specified -* by the given contours, and further subdivides this arrangement -* into regions. Each region is marked "inside" if it belongs -* to the polygon, according to the rule given by tess->windingRule. -* Each interior region is guaranteed be monotone. -*/ -{ - TESSvertex *v, *vNext; - - /* Each vertex defines an event for our sweep line. Start by inserting - * all the vertices in a priority queue. Events are processed in - * lexicographic order, ie. - * - * e1 < e2 iff e1.x < e2.x || (e1.x == e2.x && e1.y < e2.y) - */ - RemoveDegenerateEdges( tess ); - if ( !InitPriorityQ( tess ) ) return 0; /* if error */ - InitEdgeDict( tess ); - - while( (v = (TESSvertex *)pqExtractMin( tess->pq )) != NULL ) { - for( ;; ) { - vNext = (TESSvertex *)pqMinimum( tess->pq ); - if( vNext == NULL || ! VertEq( vNext, v )) break; - - /* Merge together all vertices at exactly the same location. - * This is more efficient than processing them one at a time, - * simplifies the code (see ConnectLeftDegenerate), and is also - * important for correct handling of certain degenerate cases. - * For example, suppose there are two identical edges A and B - * that belong to different contours (so without this code they would - * be processed by separate sweep events). Suppose another edge C - * crosses A and B from above. When A is processed, we split it - * at its intersection point with C. However this also splits C, - * so when we insert B we may compute a slightly different - * intersection point. This might leave two edges with a small - * gap between them. This kind of error is especially obvious - * when using boundary extraction (TESS_BOUNDARY_ONLY). - */ - vNext = (TESSvertex *)pqExtractMin( tess->pq ); - SpliceMergeVertices( tess, v->anEdge, vNext->anEdge ); - } - SweepEvent( tess, v ); - } - - /* Set tess->event for debugging purposes */ - tess->event = ((ActiveRegion *) dictKey( dictMin( tess->dict )))->eUp->Org; - DebugEvent( tess ); - DoneEdgeDict( tess ); - DonePriorityQ( tess ); - - if ( !RemoveDegenerateFaces( tess, tess->mesh ) ) return 0; - tessMeshCheckMesh( tess->mesh ); - - return 1; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/sweep.h b/submodules/LottieMeshSwift/libtess2/Sources/sweep.h deleted file mode 100755 index 32f0f86dca..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/sweep.h +++ /dev/null @@ -1,74 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef SWEEP_H -#define SWEEP_H - -#include "mesh.h" - -/* tessComputeInterior( tess ) computes the planar arrangement specified -* by the given contours, and further subdivides this arrangement -* into regions. Each region is marked "inside" if it belongs -* to the polygon, according to the rule given by tess->windingRule. -* Each interior region is guaranteed be monotone. -*/ -int tessComputeInterior( TESStesselator *tess ); - - -/* The following is here *only* for access by debugging routines */ - -#include "dict.h" - -/* For each pair of adjacent edges crossing the sweep line, there is -* an ActiveRegion to represent the region between them. The active -* regions are kept in sorted order in a dynamic dictionary. As the -* sweep line crosses each vertex, we update the affected regions. -*/ - -struct ActiveRegion { - TESShalfEdge *eUp; /* upper edge, directed right to left */ - DictNode *nodeUp; /* dictionary node corresponding to eUp */ - int windingNumber; /* used to determine which regions are - * inside the polygon */ - int inside; /* is this region inside the polygon? */ - int sentinel; /* marks fake edges at t = +/-infinity */ - int dirty; /* marks regions where the upper or lower - * edge has changed, but we haven't checked - * whether they intersect yet */ - int fixUpperEdge; /* marks temporary edges introduced when - * we process a "right vertex" (one without - * any edges leaving to the right) */ -}; - -#define RegionBelow(r) ((ActiveRegion *) dictKey(dictPred((r)->nodeUp))) -#define RegionAbove(r) ((ActiveRegion *) dictKey(dictSucc((r)->nodeUp))) - -#endif diff --git a/submodules/LottieMeshSwift/libtess2/Sources/tess.c b/submodules/LottieMeshSwift/libtess2/Sources/tess.c deleted file mode 100755 index 5f47f8de06..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/tess.c +++ /dev/null @@ -1,1114 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#include -#include -#include -#include "bucketalloc.h" -#include "tess.h" -#include "mesh.h" -#include "sweep.h" -#include "geom.h" -#include -#include -#include - -#define TRUE 1 -#define FALSE 0 - -#define Dot(u,v) (u[0]*v[0] + u[1]*v[1] + u[2]*v[2]) - -#if defined(FOR_TRITE_TEST_PROGRAM) || defined(TRUE_PROJECT) -static void Normalize( TESSreal v[3] ) -{ - TESSreal len = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; - - assert( len > 0 ); - len = sqrtf( len ); - v[0] /= len; - v[1] /= len; - v[2] /= len; -} -#endif - -#define ABS(x) ((x) < 0 ? -(x) : (x)) - -static int LongAxis( TESSreal v[3] ) -{ - int i = 0; - - if( ABS(v[1]) > ABS(v[0]) ) { i = 1; } - if( ABS(v[2]) > ABS(v[i]) ) { i = 2; } - return i; -} - -static int ShortAxis( TESSreal v[3] ) -{ - int i = 0; - - if( ABS(v[1]) < ABS(v[0]) ) { i = 1; } - if( ABS(v[2]) < ABS(v[i]) ) { i = 2; } - return i; -} - -static void ComputeNormal( TESStesselator *tess, TESSreal norm[3] ) -{ - TESSvertex *v, *v1, *v2; - TESSreal c, tLen2, maxLen2; - TESSreal maxVal[3], minVal[3], d1[3], d2[3], tNorm[3]; - TESSvertex *maxVert[3], *minVert[3]; - TESSvertex *vHead = &tess->mesh->vHead; - int i; - - v = vHead->next; - for( i = 0; i < 3; ++i ) { - c = v->coords[i]; - minVal[i] = c; - minVert[i] = v; - maxVal[i] = c; - maxVert[i] = v; - } - - for( v = vHead->next; v != vHead; v = v->next ) { - for( i = 0; i < 3; ++i ) { - c = v->coords[i]; - if( c < minVal[i] ) { minVal[i] = c; minVert[i] = v; } - if( c > maxVal[i] ) { maxVal[i] = c; maxVert[i] = v; } - } - } - - /* Find two vertices separated by at least 1/sqrt(3) of the maximum - * distance between any two vertices - */ - i = 0; - if( maxVal[1] - minVal[1] > maxVal[0] - minVal[0] ) { i = 1; } - if( maxVal[2] - minVal[2] > maxVal[i] - minVal[i] ) { i = 2; } - if( minVal[i] >= maxVal[i] ) { - /* All vertices are the same -- normal doesn't matter */ - norm[0] = 0; norm[1] = 0; norm[2] = 1; - return; - } - - /* Look for a third vertex which forms the triangle with maximum area - * (Length of normal == twice the triangle area) - */ - maxLen2 = 0; - v1 = minVert[i]; - v2 = maxVert[i]; - d1[0] = v1->coords[0] - v2->coords[0]; - d1[1] = v1->coords[1] - v2->coords[1]; - d1[2] = v1->coords[2] - v2->coords[2]; - for( v = vHead->next; v != vHead; v = v->next ) { - d2[0] = v->coords[0] - v2->coords[0]; - d2[1] = v->coords[1] - v2->coords[1]; - d2[2] = v->coords[2] - v2->coords[2]; - tNorm[0] = d1[1]*d2[2] - d1[2]*d2[1]; - tNorm[1] = d1[2]*d2[0] - d1[0]*d2[2]; - tNorm[2] = d1[0]*d2[1] - d1[1]*d2[0]; - tLen2 = tNorm[0]*tNorm[0] + tNorm[1]*tNorm[1] + tNorm[2]*tNorm[2]; - if( tLen2 > maxLen2 ) { - maxLen2 = tLen2; - norm[0] = tNorm[0]; - norm[1] = tNorm[1]; - norm[2] = tNorm[2]; - } - } - - if( maxLen2 <= 0 ) { - /* All points lie on a single line -- any decent normal will do */ - norm[0] = norm[1] = norm[2] = 0; - norm[ShortAxis(d1)] = 1; - } -} - - -static void CheckOrientation( TESStesselator *tess ) -{ - TESSreal area; - TESSface *f, *fHead = &tess->mesh->fHead; - TESSvertex *v, *vHead = &tess->mesh->vHead; - TESShalfEdge *e; - - /* When we compute the normal automatically, we choose the orientation - * so that the the sum of the signed areas of all contours is non-negative. - */ - area = 0; - for( f = fHead->next; f != fHead; f = f->next ) { - e = f->anEdge; - if( e->winding <= 0 ) continue; - do { - area += (e->Org->s - e->Dst->s) * (e->Org->t + e->Dst->t); - e = e->Lnext; - } while( e != f->anEdge ); - } - if( area < 0 ) { - /* Reverse the orientation by flipping all the t-coordinates */ - for( v = vHead->next; v != vHead; v = v->next ) { - v->t = - v->t; - } - tess->tUnit[0] = - tess->tUnit[0]; - tess->tUnit[1] = - tess->tUnit[1]; - tess->tUnit[2] = - tess->tUnit[2]; - } -} - -#ifdef FOR_TRITE_TEST_PROGRAM -#include -extern int RandomSweep; -#define S_UNIT_X (RandomSweep ? (2*drand48()-1) : 1.0) -#define S_UNIT_Y (RandomSweep ? (2*drand48()-1) : 0.0) -#else -#if defined(SLANTED_SWEEP) -/* The "feature merging" is not intended to be complete. There are -* special cases where edges are nearly parallel to the sweep line -* which are not implemented. The algorithm should still behave -* robustly (ie. produce a reasonable tesselation) in the presence -* of such edges, however it may miss features which could have been -* merged. We could minimize this effect by choosing the sweep line -* direction to be something unusual (ie. not parallel to one of the -* coordinate axes). -*/ -#define S_UNIT_X (TESSreal)0.50941539564955385 /* Pre-normalized */ -#define S_UNIT_Y (TESSreal)0.86052074622010633 -#else -#define S_UNIT_X (TESSreal)1.0 -#define S_UNIT_Y (TESSreal)0.0 -#endif -#endif - -/* Determine the polygon normal and project vertices onto the plane -* of the polygon. -*/ -void tessProjectPolygon( TESStesselator *tess ) -{ - TESSvertex *v, *vHead = &tess->mesh->vHead; - TESSreal norm[3]; - TESSreal *sUnit, *tUnit; - int i, first, computedNormal = FALSE; - - norm[0] = tess->normal[0]; - norm[1] = tess->normal[1]; - norm[2] = tess->normal[2]; - if( norm[0] == 0 && norm[1] == 0 && norm[2] == 0 ) { - ComputeNormal( tess, norm ); - computedNormal = TRUE; - } - sUnit = tess->sUnit; - tUnit = tess->tUnit; - i = LongAxis( norm ); - -#if defined(FOR_TRITE_TEST_PROGRAM) || defined(TRUE_PROJECT) - /* Choose the initial sUnit vector to be approximately perpendicular - * to the normal. - */ - Normalize( norm ); - - sUnit[i] = 0; - sUnit[(i+1)%3] = S_UNIT_X; - sUnit[(i+2)%3] = S_UNIT_Y; - - /* Now make it exactly perpendicular */ - w = Dot( sUnit, norm ); - sUnit[0] -= w * norm[0]; - sUnit[1] -= w * norm[1]; - sUnit[2] -= w * norm[2]; - Normalize( sUnit ); - - /* Choose tUnit so that (sUnit,tUnit,norm) form a right-handed frame */ - tUnit[0] = norm[1]*sUnit[2] - norm[2]*sUnit[1]; - tUnit[1] = norm[2]*sUnit[0] - norm[0]*sUnit[2]; - tUnit[2] = norm[0]*sUnit[1] - norm[1]*sUnit[0]; - Normalize( tUnit ); -#else - /* Project perpendicular to a coordinate axis -- better numerically */ - sUnit[i] = 0; - sUnit[(i+1)%3] = S_UNIT_X; - sUnit[(i+2)%3] = S_UNIT_Y; - - tUnit[i] = 0; - tUnit[(i+1)%3] = (norm[i] > 0) ? -S_UNIT_Y : S_UNIT_Y; - tUnit[(i+2)%3] = (norm[i] > 0) ? S_UNIT_X : -S_UNIT_X; -#endif - - /* Project the vertices onto the sweep plane */ - for( v = vHead->next; v != vHead; v = v->next ) - { - v->s = Dot( v->coords, sUnit ); - v->t = Dot( v->coords, tUnit ); - } - if( computedNormal ) { - CheckOrientation( tess ); - } - - /* Compute ST bounds. */ - first = 1; - for( v = vHead->next; v != vHead; v = v->next ) - { - if (first) - { - tess->bmin[0] = tess->bmax[0] = v->s; - tess->bmin[1] = tess->bmax[1] = v->t; - first = 0; - } - else - { - if (v->s < tess->bmin[0]) tess->bmin[0] = v->s; - if (v->s > tess->bmax[0]) tess->bmax[0] = v->s; - if (v->t < tess->bmin[1]) tess->bmin[1] = v->t; - if (v->t > tess->bmax[1]) tess->bmax[1] = v->t; - } - } -} - -#define AddWinding(eDst,eSrc) (eDst->winding += eSrc->winding, \ - eDst->Sym->winding += eSrc->Sym->winding) - -/* tessMeshTessellateMonoRegion( face ) tessellates a monotone region -* (what else would it do??) The region must consist of a single -* loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this -* case means that any vertical line intersects the interior of the -* region in a single interval. -* -* Tessellation consists of adding interior edges (actually pairs of -* half-edges), to split the region into non-overlapping triangles. -* -* The basic idea is explained in Preparata and Shamos (which I don''t -* have handy right now), although their implementation is more -* complicated than this one. The are two edge chains, an upper chain -* and a lower chain. We process all vertices from both chains in order, -* from right to left. -* -* The algorithm ensures that the following invariant holds after each -* vertex is processed: the untessellated region consists of two -* chains, where one chain (say the upper) is a single edge, and -* the other chain is concave. The left vertex of the single edge -* is always to the left of all vertices in the concave chain. -* -* Each step consists of adding the rightmost unprocessed vertex to one -* of the two chains, and forming a fan of triangles from the rightmost -* of two chain endpoints. Determining whether we can add each triangle -* to the fan is a simple orientation test. By making the fan as large -* as possible, we restore the invariant (check it yourself). -*/ -int tessMeshTessellateMonoRegion( TESSmesh *mesh, TESSface *face ) -{ - TESShalfEdge *up, *lo; - - /* All edges are oriented CCW around the boundary of the region. - * First, find the half-edge whose origin vertex is rightmost. - * Since the sweep goes from left to right, face->anEdge should - * be close to the edge we want. - */ - up = face->anEdge; - assert( up->Lnext != up && up->Lnext->Lnext != up ); - - for( ; VertLeq( up->Dst, up->Org ); up = up->Lprev ) - ; - for( ; VertLeq( up->Org, up->Dst ); up = up->Lnext ) - ; - lo = up->Lprev; - - while( up->Lnext != lo ) { - if( VertLeq( up->Dst, lo->Org )) { - /* up->Dst is on the left. It is safe to form triangles from lo->Org. - * The EdgeGoesLeft test guarantees progress even when some triangles - * are CW, given that the upper and lower chains are truly monotone. - */ - while( lo->Lnext != up && (EdgeGoesLeft( lo->Lnext ) - || EdgeSign( lo->Org, lo->Dst, lo->Lnext->Dst ) <= 0 )) { - TESShalfEdge *tempHalfEdge= tessMeshConnect( mesh, lo->Lnext, lo ); - if (tempHalfEdge == NULL) return 0; - lo = tempHalfEdge->Sym; - } - lo = lo->Lprev; - } else { - /* lo->Org is on the left. We can make CCW triangles from up->Dst. */ - while( lo->Lnext != up && (EdgeGoesRight( up->Lprev ) - || EdgeSign( up->Dst, up->Org, up->Lprev->Org ) >= 0 )) { - TESShalfEdge *tempHalfEdge= tessMeshConnect( mesh, up, up->Lprev ); - if (tempHalfEdge == NULL) return 0; - up = tempHalfEdge->Sym; - } - up = up->Lnext; - } - } - - /* Now lo->Org == up->Dst == the leftmost vertex. The remaining region - * can be tessellated in a fan from this leftmost vertex. - */ - assert( lo->Lnext != up ); - while( lo->Lnext->Lnext != up ) { - TESShalfEdge *tempHalfEdge= tessMeshConnect( mesh, lo->Lnext, lo ); - if (tempHalfEdge == NULL) return 0; - lo = tempHalfEdge->Sym; - } - - return 1; -} - -/* tessMeshTessellateInterior( mesh ) tessellates each region of -* the mesh which is marked "inside" the polygon. Each such region -* must be monotone. -*/ -int tessMeshTessellateInterior( TESSmesh *mesh ) -{ - TESSface *f, *next; - - /*LINTED*/ - for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) { - /* Make sure we don''t try to tessellate the new triangles. */ - next = f->next; - if( f->inside ) { - if ( !tessMeshTessellateMonoRegion( mesh, f ) ) return 0; - } - } - return 1; -} - - -typedef struct EdgeStackNode EdgeStackNode; -typedef struct EdgeStack EdgeStack; - -struct EdgeStackNode { - TESShalfEdge *edge; - EdgeStackNode *next; -}; - -struct EdgeStack { - EdgeStackNode *top; - struct BucketAlloc *nodeBucket; -}; - -int stackInit( EdgeStack *stack, TESSalloc *alloc ) -{ - stack->top = NULL; - stack->nodeBucket = createBucketAlloc( alloc, "CDT nodes", sizeof(EdgeStackNode), 512 ); - return stack->nodeBucket != NULL; -} - -void stackDelete( EdgeStack *stack ) -{ - deleteBucketAlloc( stack->nodeBucket ); -} - -int stackEmpty( EdgeStack *stack ) -{ - return stack->top == NULL; -} - -void stackPush( EdgeStack *stack, TESShalfEdge *e ) -{ - EdgeStackNode *node = (EdgeStackNode *)bucketAlloc( stack->nodeBucket ); - if ( ! node ) return; - node->edge = e; - node->next = stack->top; - stack->top = node; -} - -TESShalfEdge *stackPop( EdgeStack *stack ) -{ - TESShalfEdge *e = NULL; - EdgeStackNode *node = stack->top; - if (node) { - stack->top = node->next; - e = node->edge; - bucketFree( stack->nodeBucket, node ); - } - return e; -} - - -// Starting with a valid triangulation, uses the Edge Flip algorithm to -// refine the triangulation into a Constrained Delaunay Triangulation. -void tessMeshRefineDelaunay( TESSmesh *mesh, TESSalloc *alloc ) -{ - // At this point, we have a valid, but not optimal, triangulation. - // We refine the triangulation using the Edge Flip algorithm - // - // 1) Find all internal edges - // 2) Mark all dual edges - // 3) insert all dual edges into a queue - - TESSface *f; - EdgeStack stack; - TESShalfEdge *e; - int maxFaces = 0, maxIter = 0, iter = 0; - - stackInit(&stack, alloc); - - for( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) { - if ( f->inside) { - e = f->anEdge; - do { - e->mark = EdgeIsInternal(e); // Mark internal edges - if (e->mark && !e->Sym->mark) stackPush(&stack, e); // Insert into queue - e = e->Lnext; - } while (e != f->anEdge); - maxFaces++; - } - } - - // The algorithm should converge on O(n^2), since the predicate is not robust, - // we'll save guard against infinite loop. - maxIter = maxFaces * maxFaces; - - // Pop stack until we find a reversed edge - // Flip the reversed edge, and insert any of the four opposite edges - // which are internal and not already in the stack (!marked) - while (!stackEmpty(&stack) && iter < maxIter) { - e = stackPop(&stack); - e->mark = e->Sym->mark = 0; - if (!tesedgeIsLocallyDelaunay(e)) { - TESShalfEdge *edges[4]; - int i; - tessMeshFlipEdge(mesh, e); - // for each opposite edge - edges[0] = e->Lnext; - edges[1] = e->Lprev; - edges[2] = e->Sym->Lnext; - edges[3] = e->Sym->Lprev; - for (i = 0; i < 4; i++) { - if (!edges[i]->mark && EdgeIsInternal(edges[i])) { - edges[i]->mark = edges[i]->Sym->mark = 1; - stackPush(&stack, edges[i]); - } - } - } - iter++; - } - - stackDelete(&stack); -} - - -/* tessMeshDiscardExterior( mesh ) zaps (ie. sets to NULL) all faces -* which are not marked "inside" the polygon. Since further mesh operations -* on NULL faces are not allowed, the main purpose is to clean up the -* mesh so that exterior loops are not represented in the data structure. -*/ -void tessMeshDiscardExterior( TESSmesh *mesh ) -{ - TESSface *f, *next; - - /*LINTED*/ - for( f = mesh->fHead.next; f != &mesh->fHead; f = next ) { - /* Since f will be destroyed, save its next pointer. */ - next = f->next; - if( ! f->inside ) { - tessMeshZapFace( mesh, f ); - } - } -} - -/* tessMeshSetWindingNumber( mesh, value, keepOnlyBoundary ) resets the -* winding numbers on all edges so that regions marked "inside" the -* polygon have a winding number of "value", and regions outside -* have a winding number of 0. -* -* If keepOnlyBoundary is TRUE, it also deletes all edges which do not -* separate an interior region from an exterior one. -*/ -int tessMeshSetWindingNumber( TESSmesh *mesh, int value, - int keepOnlyBoundary ) -{ - TESShalfEdge *e, *eNext; - - for( e = mesh->eHead.next; e != &mesh->eHead; e = eNext ) { - eNext = e->next; - if( e->Rface->inside != e->Lface->inside ) { - - /* This is a boundary edge (one side is interior, one is exterior). */ - e->winding = (e->Lface->inside) ? value : -value; - } else { - - /* Both regions are interior, or both are exterior. */ - if( ! keepOnlyBoundary ) { - e->winding = 0; - } else { - if ( !tessMeshDelete( mesh, e ) ) return 0; - } - } - } - return 1; -} - -void* heapAlloc( void* userData, unsigned int size ) -{ - TESS_NOTUSED( userData ); - return malloc( size ); -} - -void* heapRealloc( void *userData, void* ptr, unsigned int size ) -{ - TESS_NOTUSED( userData ); - return realloc( ptr, size ); -} - -void heapFree( void* userData, void* ptr ) -{ - TESS_NOTUSED( userData ); - free( ptr ); -} - -static TESSalloc defaulAlloc = -{ - heapAlloc, - heapRealloc, - heapFree, - 0, - 0, - 0, - 0, - 0, - 0, - 0, -}; - -TESStesselator* tessNewTess( TESSalloc* alloc ) -{ - TESStesselator* tess; - - if (alloc == NULL) - alloc = &defaulAlloc; - - /* Only initialize fields which can be changed by the api. Other fields - * are initialized where they are used. - */ - - tess = (TESStesselator *)alloc->memalloc( alloc->userData, sizeof( TESStesselator )); - if ( tess == NULL ) { - return 0; /* out of memory */ - } - tess->alloc = *alloc; - /* Check and set defaults. */ - if (tess->alloc.meshEdgeBucketSize == 0) - tess->alloc.meshEdgeBucketSize = 512; - if (tess->alloc.meshVertexBucketSize == 0) - tess->alloc.meshVertexBucketSize = 512; - if (tess->alloc.meshFaceBucketSize == 0) - tess->alloc.meshFaceBucketSize = 256; - if (tess->alloc.dictNodeBucketSize == 0) - tess->alloc.dictNodeBucketSize = 512; - if (tess->alloc.regionBucketSize == 0) - tess->alloc.regionBucketSize = 256; - - tess->normal[0] = 0; - tess->normal[1] = 0; - tess->normal[2] = 0; - - tess->bmin[0] = 0; - tess->bmin[1] = 0; - tess->bmax[0] = 0; - tess->bmax[1] = 0; - - tess->reverseContours = 0; - - tess->windingRule = TESS_WINDING_ODD; - tess->processCDT = 0; - - if (tess->alloc.regionBucketSize < 16) - tess->alloc.regionBucketSize = 16; - if (tess->alloc.regionBucketSize > 4096) - tess->alloc.regionBucketSize = 4096; - tess->regionPool = createBucketAlloc( &tess->alloc, "Regions", - sizeof(ActiveRegion), tess->alloc.regionBucketSize ); - - // Initialize to begin polygon. - tess->mesh = NULL; - - tess->outOfMemory = 0; - tess->vertexIndexCounter = 0; - - tess->vertices = 0; - tess->vertexIndices = 0; - tess->vertexCount = 0; - tess->elements = 0; - tess->elementCount = 0; - - return tess; -} - -void tessDeleteTess( TESStesselator *tess ) -{ - - struct TESSalloc alloc = tess->alloc; - - deleteBucketAlloc( tess->regionPool ); - - if( tess->mesh != NULL ) { - tessMeshDeleteMesh( &alloc, tess->mesh ); - tess->mesh = NULL; - } - if (tess->vertices != NULL) { - alloc.memfree( alloc.userData, tess->vertices ); - tess->vertices = 0; - } - if (tess->vertexIndices != NULL) { - alloc.memfree( alloc.userData, tess->vertexIndices ); - tess->vertexIndices = 0; - } - if (tess->elements != NULL) { - alloc.memfree( alloc.userData, tess->elements ); - tess->elements = 0; - } - - alloc.memfree( alloc.userData, tess ); -} - - -static TESSindex GetNeighbourFace(TESShalfEdge* edge) -{ - if (!edge->Rface) - return TESS_UNDEF; - if (!edge->Rface->inside) - return TESS_UNDEF; - return edge->Rface->n; -} - -void OutputPolymesh( TESStesselator *tess, TESSmesh *mesh, int elementType, int polySize, int vertexSize ) -{ - TESSvertex* v = 0; - TESSface* f = 0; - TESShalfEdge* edge = 0; - int maxFaceCount = 0; - int maxVertexCount = 0; - int faceVerts, i; - TESSindex *elements = 0; - TESSreal *vert; - - // Assume that the input data is triangles now. - // Try to merge as many polygons as possible - if (polySize > 3) - { - if (!tessMeshMergeConvexFaces( mesh, polySize )) - { - tess->outOfMemory = 1; - return; - } - } - - // Mark unused - for ( v = mesh->vHead.next; v != &mesh->vHead; v = v->next ) - v->n = TESS_UNDEF; - - // Create unique IDs for all vertices and faces. - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - f->n = TESS_UNDEF; - if( !f->inside ) continue; - - edge = f->anEdge; - faceVerts = 0; - do - { - v = edge->Org; - if ( v->n == TESS_UNDEF ) - { - v->n = maxVertexCount; - maxVertexCount++; - } - faceVerts++; - edge = edge->Lnext; - } - while (edge != f->anEdge); - - assert( faceVerts <= polySize ); - - f->n = maxFaceCount; - ++maxFaceCount; - } - - tess->elementCount = maxFaceCount; - if (elementType == TESS_CONNECTED_POLYGONS) - maxFaceCount *= 2; - tess->elements = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * maxFaceCount * polySize ); - if (!tess->elements) - { - tess->outOfMemory = 1; - return; - } - - tess->vertexCount = maxVertexCount; - tess->vertices = (TESSreal*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSreal) * tess->vertexCount * vertexSize ); - if (!tess->vertices) - { - tess->outOfMemory = 1; - return; - } - - tess->vertexIndices = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * tess->vertexCount ); - if (!tess->vertexIndices) - { - tess->outOfMemory = 1; - return; - } - - // Output vertices. - for ( v = mesh->vHead.next; v != &mesh->vHead; v = v->next ) - { - if ( v->n != TESS_UNDEF ) - { - // Store coordinate - vert = &tess->vertices[v->n*vertexSize]; - vert[0] = v->coords[0]; - vert[1] = v->coords[1]; - if ( vertexSize > 2 ) - vert[2] = v->coords[2]; - // Store vertex index. - tess->vertexIndices[v->n] = v->idx; - } - } - - // Output indices. - elements = tess->elements; - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - if ( !f->inside ) continue; - - // Store polygon - edge = f->anEdge; - faceVerts = 0; - do - { - v = edge->Org; - *elements++ = v->n; - faceVerts++; - edge = edge->Lnext; - } - while (edge != f->anEdge); - // Fill unused. - for (i = faceVerts; i < polySize; ++i) - *elements++ = TESS_UNDEF; - - // Store polygon connectivity - if ( elementType == TESS_CONNECTED_POLYGONS ) - { - edge = f->anEdge; - do - { - *elements++ = GetNeighbourFace( edge ); - edge = edge->Lnext; - } - while (edge != f->anEdge); - // Fill unused. - for (i = faceVerts; i < polySize; ++i) - *elements++ = TESS_UNDEF; - } - } -} - -void OutputContours( TESStesselator *tess, TESSmesh *mesh, int vertexSize ) -{ - TESSface *f = 0; - TESShalfEdge *edge = 0; - TESShalfEdge *start = 0; - TESSreal *verts = 0; - TESSindex *elements = 0; - TESSindex *vertInds = 0; - int startVert = 0; - int vertCount = 0; - - tess->vertexCount = 0; - tess->elementCount = 0; - - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - if ( !f->inside ) continue; - - start = edge = f->anEdge; - do - { - ++tess->vertexCount; - edge = edge->Lnext; - } - while ( edge != start ); - - ++tess->elementCount; - } - - tess->elements = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * tess->elementCount * 2 ); - if (!tess->elements) - { - tess->outOfMemory = 1; - return; - } - - tess->vertices = (TESSreal*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSreal) * tess->vertexCount * vertexSize ); - if (!tess->vertices) - { - tess->outOfMemory = 1; - return; - } - - tess->vertexIndices = (TESSindex*)tess->alloc.memalloc( tess->alloc.userData, - sizeof(TESSindex) * tess->vertexCount ); - if (!tess->vertexIndices) - { - tess->outOfMemory = 1; - return; - } - - verts = tess->vertices; - elements = tess->elements; - vertInds = tess->vertexIndices; - - startVert = 0; - - for ( f = mesh->fHead.next; f != &mesh->fHead; f = f->next ) - { - if ( !f->inside ) continue; - - vertCount = 0; - start = edge = f->anEdge; - do - { - *verts++ = edge->Org->coords[0]; - *verts++ = edge->Org->coords[1]; - if ( vertexSize > 2 ) - *verts++ = edge->Org->coords[2]; - *vertInds++ = edge->Org->idx; - ++vertCount; - edge = edge->Lnext; - } - while ( edge != start ); - - elements[0] = startVert; - elements[1] = vertCount; - elements += 2; - - startVert += vertCount; - } -} - -void tessAddContour( TESStesselator *tess, int size, const void* vertices, - int stride, int numVertices ) -{ - const unsigned char *src = (const unsigned char*)vertices; - TESShalfEdge *e; - int i; - - if ( tess->mesh == NULL ) - tess->mesh = tessMeshNewMesh( &tess->alloc ); - if ( tess->mesh == NULL ) { - tess->outOfMemory = 1; - return; - } - - if ( size < 2 ) - size = 2; - if ( size > 3 ) - size = 3; - - e = NULL; - - for( i = 0; i < numVertices; ++i ) - { - const TESSreal* coords = (const TESSreal*)src; - src += stride; - - if( e == NULL ) { - /* Make a self-loop (one vertex, one edge). */ - e = tessMeshMakeEdge( tess->mesh ); - if ( e == NULL ) { - tess->outOfMemory = 1; - return; - } - if ( !tessMeshSplice( tess->mesh, e, e->Sym ) ) { - tess->outOfMemory = 1; - return; - } - } else { - /* Create a new vertex and edge which immediately follow e - * in the ordering around the left face. - */ - if ( tessMeshSplitEdge( tess->mesh, e ) == NULL ) { - tess->outOfMemory = 1; - return; - } - e = e->Lnext; - } - - /* The new vertex is now e->Org. */ - e->Org->coords[0] = coords[0]; - e->Org->coords[1] = coords[1]; - if ( size > 2 ) - e->Org->coords[2] = coords[2]; - else - e->Org->coords[2] = 0; - /* Store the insertion number so that the vertex can be later recognized. */ - e->Org->idx = tess->vertexIndexCounter++; - - /* The winding of an edge says how the winding number changes as we - * cross from the edge''s right face to its left face. We add the - * vertices in such an order that a CCW contour will add +1 to - * the winding number of the region inside the contour. - */ - e->winding = tess->reverseContours ? -1 : 1; - e->Sym->winding = tess->reverseContours ? 1 : -1; - } -} - -void tessSetOption( TESStesselator *tess, int option, int value ) -{ - switch(option) - { - case TESS_CONSTRAINED_DELAUNAY_TRIANGULATION: - tess->processCDT = value > 0 ? 1 : 0; - break; - case TESS_REVERSE_CONTOURS: - tess->reverseContours = value > 0 ? 1 : 0; - break; - } -} - - -int tessTesselate( TESStesselator *tess, int windingRule, int elementType, - int polySize, int vertexSize, const TESSreal* normal ) -{ - TESSmesh *mesh; - int rc = 1; - - if (tess->vertices != NULL) { - tess->alloc.memfree( tess->alloc.userData, tess->vertices ); - tess->vertices = 0; - } - if (tess->elements != NULL) { - tess->alloc.memfree( tess->alloc.userData, tess->elements ); - tess->elements = 0; - } - if (tess->vertexIndices != NULL) { - tess->alloc.memfree( tess->alloc.userData, tess->vertexIndices ); - tess->vertexIndices = 0; - } - - tess->vertexIndexCounter = 0; - - if (normal) - { - tess->normal[0] = normal[0]; - tess->normal[1] = normal[1]; - tess->normal[2] = normal[2]; - } - - tess->windingRule = windingRule; - - if (vertexSize < 2) - vertexSize = 2; - if (vertexSize > 3) - vertexSize = 3; - - if (setjmp(tess->env) != 0) { - /* come back here if out of memory */ - return 0; - } - - if (!tess->mesh) - { - return 0; - } - - /* Determine the polygon normal and project vertices onto the plane - * of the polygon. - */ - tessProjectPolygon( tess ); - - /* tessComputeInterior( tess ) computes the planar arrangement specified - * by the given contours, and further subdivides this arrangement - * into regions. Each region is marked "inside" if it belongs - * to the polygon, according to the rule given by tess->windingRule. - * Each interior region is guaranteed be monotone. - */ - if ( !tessComputeInterior( tess ) ) { - longjmp(tess->env,1); /* could've used a label */ - } - - mesh = tess->mesh; - - /* If the user wants only the boundary contours, we throw away all edges - * except those which separate the interior from the exterior. - * Otherwise we tessellate all the regions marked "inside". - */ - if (elementType == TESS_BOUNDARY_CONTOURS) { - rc = tessMeshSetWindingNumber( mesh, 1, TRUE ); - } else { - rc = tessMeshTessellateInterior( mesh ); - if (rc != 0 && tess->processCDT != 0) - tessMeshRefineDelaunay( mesh, &tess->alloc ); - } - if (rc == 0) longjmp(tess->env,1); /* could've used a label */ - - tessMeshCheckMesh( mesh ); - - if (elementType == TESS_BOUNDARY_CONTOURS) { - OutputContours( tess, mesh, vertexSize ); /* output contours */ - } - else - { - OutputPolymesh( tess, mesh, elementType, polySize, vertexSize ); /* output polygons */ - } - - tessMeshDeleteMesh( &tess->alloc, mesh ); - tess->mesh = NULL; - - if (tess->outOfMemory) - return 0; - return 1; -} - -int tessGetVertexCount( TESStesselator *tess ) -{ - return tess->vertexCount; -} - -const TESSreal* tessGetVertices( TESStesselator *tess ) -{ - return tess->vertices; -} - -const TESSindex* tessGetVertexIndices( TESStesselator *tess ) -{ - return tess->vertexIndices; -} - -int tessGetElementCount( TESStesselator *tess ) -{ - return tess->elementCount; -} - -const int* tessGetElements( TESStesselator *tess ) -{ - return tess->elements; -} diff --git a/submodules/LottieMeshSwift/libtess2/Sources/tess.h b/submodules/LottieMeshSwift/libtess2/Sources/tess.h deleted file mode 100755 index 30fda27bc2..0000000000 --- a/submodules/LottieMeshSwift/libtess2/Sources/tess.h +++ /dev/null @@ -1,93 +0,0 @@ -/* -** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008) -** Copyright (C) [dates of first publication] Silicon Graphics, Inc. -** All Rights Reserved. -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -** of the Software, and to permit persons to whom the Software is furnished to do so, -** subject to the following conditions: -** -** The above copyright notice including the dates of first publication and either this -** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be -** included in all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC. -** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -** OR OTHER DEALINGS IN THE SOFTWARE. -** -** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not -** be used in advertising or otherwise to promote the sale, use or other dealings in -** this Software without prior written authorization from Silicon Graphics, Inc. -*/ -/* -** Author: Eric Veach, July 1994. -*/ - -#ifndef TESS_H -#define TESS_H - -#include -#include "bucketalloc.h" -#include "mesh.h" -#include "dict.h" -#include "priorityq.h" -#include "../Include/tesselator.h" - -#ifdef __cplusplus -extern "C" { -#endif - -//typedef struct TESStesselator TESStesselator; - -struct TESStesselator { - - /*** state needed for collecting the input data ***/ - TESSmesh *mesh; /* stores the input contours, and eventually - the tessellation itself */ - int outOfMemory; - - /*** state needed for projecting onto the sweep plane ***/ - - TESSreal normal[3]; /* user-specified normal (if provided) */ - TESSreal sUnit[3]; /* unit vector in s-direction (debugging) */ - TESSreal tUnit[3]; /* unit vector in t-direction (debugging) */ - - TESSreal bmin[2]; - TESSreal bmax[2]; - - int processCDT; /* option to run Constrained Delayney pass. */ - int reverseContours; /* tessAddContour() will treat CCW contours as CW and vice versa */ - - /*** state needed for the line sweep ***/ - int windingRule; /* rule for determining polygon interior */ - - Dict *dict; /* edge dictionary for sweep line */ - PriorityQ *pq; /* priority queue of vertex events */ - TESSvertex *event; /* current sweep event being processed */ - - struct BucketAlloc* regionPool; - - TESSindex vertexIndexCounter; - - TESSreal *vertices; - TESSindex *vertexIndices; - int vertexCount; - TESSindex *elements; - int elementCount; - - TESSalloc alloc; - - jmp_buf env; /* place to jump to when memAllocs fail */ -}; - -#ifdef __cplusplus -}; -#endif - -#endif diff --git a/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift b/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift index 99360ec901..69ec0dcfc3 100644 --- a/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift +++ b/submodules/MediaResources/Sources/MediaPlaybackStoredState.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences @@ -29,8 +28,8 @@ public final class MediaPlaybackStoredState: Codable { } } -public func mediaPlaybackStoredState(engine: TelegramEngine, messageId: MessageId) -> Signal { - let key = ValueBoxKey(length: 20) +public func mediaPlaybackStoredState(engine: TelegramEngine, messageId: EngineMessage.Id) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) @@ -42,8 +41,8 @@ public func mediaPlaybackStoredState(engine: TelegramEngine, messageId: MessageI } } -public func updateMediaPlaybackStoredStateInteractively(engine: TelegramEngine, messageId: MessageId, state: MediaPlaybackStoredState?) -> Signal { - let key = ValueBoxKey(length: 20) +public func updateMediaPlaybackStoredStateInteractively(engine: TelegramEngine, messageId: EngineMessage.Id, state: MediaPlaybackStoredState?) -> Signal { + let key = EngineDataBuffer(length: 20) key.setInt32(0, value: messageId.namespace) key.setInt32(4, value: messageId.peerId.namespace._internalGetInt32Value()) key.setInt64(8, value: messageId.peerId.id._internalGetInt64Value()) diff --git a/submodules/MeshAnimationCache/BUILD b/submodules/MeshAnimationCache/BUILD deleted file mode 100644 index e3c8ebcdef..0000000000 --- a/submodules/MeshAnimationCache/BUILD +++ /dev/null @@ -1,22 +0,0 @@ -load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") - -swift_library( - name = "MeshAnimationCache", - module_name = "MeshAnimationCache", - srcs = glob([ - "Sources/**/*.swift", - ]), - copts = [ - "-warnings-as-errors", - ], - deps = [ - "//submodules/LottieMeshSwift:LottieMeshSwift", - "//submodules/Postbox:Postbox", - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/GZip:GZip", - "//submodules/AppBundle:AppBundle", - ], - visibility = [ - "//visibility:public", - ], -) diff --git a/submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift b/submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift deleted file mode 100644 index 403bf31727..0000000000 --- a/submodules/MeshAnimationCache/Sources/MeshAnimationCache.swift +++ /dev/null @@ -1,182 +0,0 @@ -import Foundation -import LottieMeshSwift -import Postbox -import SwiftSignalKit -import GZip -import AppBundle - -public final class MeshAnimationCache { - private final class Item { - var isPending: Bool = false - let disposable = MetaDisposable() - var readyPath: String? - var animation: MeshAnimation? - } - - private let mediaBox: MediaBox - - private var items: [String: Item] = [:] - - public init(mediaBox: MediaBox) { - self.mediaBox = mediaBox - } - - public func get(resource: MediaResource) -> MeshAnimation? { - if let item = self.items[resource.id.stringRepresentation] { - if let animation = item.animation { - return animation - } else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath), options: [.alwaysMapped]) { - let buffer = MeshReadBuffer(data: data) - let animation = MeshAnimation.read(buffer: buffer) - item.animation = animation - return animation - } else { - return nil - } - } else { - let item = Item() - self.items[resource.id.stringRepresentation] = item - - let path = self.mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general) - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - item.readyPath = path - item.animation = animation - return animation - } else { - self.cache(item: item, resource: resource) - return nil - } - } - } - - public func get(bundleName: String) -> MeshAnimation? { - if let item = self.items[bundleName] { - if let animation = item.animation { - return animation - } else if let readyPath = item.readyPath, let data = try? Data(contentsOf: URL(fileURLWithPath: readyPath), options: [.alwaysMapped]) { - let buffer = MeshReadBuffer(data: data) - let animation = MeshAnimation.read(buffer: buffer) - item.animation = animation - return animation - } else { - return nil - } - } else { - let item = Item() - self.items[bundleName] = item - - let path = self.mediaBox.cachedRepresentationPathForId(bundleName, representationId: "mesh-animation", keepDuration: .general) - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - item.readyPath = path - item.animation = animation - return animation - } else { - self.cache(item: item, bundleName: bundleName) - return nil - } - } - } - - private func cache(item: Item, resource: MediaResource) { - let mediaBox = self.mediaBox - item.isPending = true - item.disposable.set((self.mediaBox.resourceData(resource) - |> filter { data in - return data.complete - } - |> take(1) - |> mapToSignal { data -> Signal<(MeshAnimation, String)?, NoError> in - return Signal { subscriber in - guard let zippedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.alwaysMapped]) else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - let jsonData = TGGUnzipData(zippedData, 1 * 1024 * 1024) ?? zippedData - if let tempFile = generateMeshAnimation(data: jsonData) { - mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general, tempFile: tempFile, completion: { path in - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - subscriber.putNext((animation, path)) - subscriber.putCompletion() - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - } - }) - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - - return EmptyDisposable - } - |> runOn(Queue.concurrentBackgroundQueue()) - } - |> deliverOnMainQueue).start(next: { [weak self] animationAndPath in - guard let strongSelf = self else { - return - } - if let animationAndPath = animationAndPath { - if let item = strongSelf.items[resource.id.stringRepresentation] { - item.isPending = false - item.animation = animationAndPath.0 - item.readyPath = animationAndPath.1 - } - } - })) - } - - private func cache(item: Item, bundleName: String) { - let mediaBox = self.mediaBox - item.isPending = true - item.disposable.set((Signal<(MeshAnimation, String)?, NoError> { subscriber in - guard let path = getAppBundle().path(forResource: bundleName, ofType: "tgs") else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - - guard let zippedData = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - let jsonData = TGGUnzipData(zippedData, 1 * 1024 * 1024) ?? zippedData - if let tempFile = generateMeshAnimation(data: jsonData) { - mediaBox.storeCachedResourceRepresentation(bundleName, representationId: "mesh-animation", keepDuration: .general, tempFile: tempFile, completion: { path in - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.alwaysMapped]) { - let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data)) - subscriber.putNext((animation, path)) - subscriber.putCompletion() - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - } - }) - } else { - subscriber.putNext(nil) - subscriber.putCompletion() - return EmptyDisposable - } - - return EmptyDisposable - } - |> runOn(Queue.concurrentDefaultQueue()) - |> deliverOnMainQueue).start(next: { [weak self] animationAndPath in - guard let strongSelf = self else { - return - } - if let animationAndPath = animationAndPath { - if let item = strongSelf.items[bundleName] { - item.isPending = false - item.animation = animationAndPath.0 - item.readyPath = animationAndPath.1 - } - } - })) - } -} diff --git a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift index 85197ec7af..ea61bbd1a4 100644 --- a/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift +++ b/submodules/NotificationSoundSelectionUI/Sources/NotificationSoundSelection.swift @@ -13,7 +13,6 @@ import AppBundle import LegacyMediaPickerUI import AVFoundation import UndoUI -import Postbox private struct NotificationSoundSelectionArguments { let account: Account @@ -516,20 +515,20 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co let fileName = url.lastPathComponent var maybeUrl: URL? - let tempFile = TempBox.shared.tempFile(fileName: "file.mp3") + let tempFile = EngineTempBox.shared.tempFile(fileName: "file.mp3") do { try FileManager.default.copyItem(at: url, to: URL(fileURLWithPath: tempFile.path)) maybeUrl = URL(fileURLWithPath: tempFile.path) } catch let e { Logger.shared.log("NotificationSoundSelection", "copy file error \(e)") - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) souceUrl.stopAccessingSecurityScopedResource() return } guard let url = maybeUrl else { Logger.shared.log("NotificationSoundSelection", "temp url is nil") - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) souceUrl.stopAccessingSecurityScopedResource() return } @@ -544,7 +543,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co presentUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLarge_Title, text: presentationData.strings.Notifications_UploadError_TooLarge_Text(dataSizeString(Int64(settings.maxSize), formatting: DataSizeStringFormatting(presentationData: presentationData))).string, timeout: nil)) souceUrl.stopAccessingSecurityScopedResource() - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return } @@ -572,7 +571,7 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co Logger.shared.log("NotificationSoundSelection", "track is nil") url.stopAccessingSecurityScopedResource() - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return } @@ -583,12 +582,12 @@ public func presentCustomNotificationSoundFilePicker(context: AccountContext, co Logger.shared.log("NotificationSoundSelection", "duration is zero") souceUrl.stopAccessingSecurityScopedResource() - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) return } - TempBox.shared.dispose(tempFile) + EngineTempBox.shared.dispose(tempFile) Queue.mainQueue().async { if duration > Double(settings.maxDuration) { diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift index 41f3518fab..9eedf8738b 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryController.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift index 113573f540..4f6e313750 100644 --- a/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift +++ b/submodules/PasscodeUI/Sources/PasscodeEntryControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/PasscodeUI/Sources/PasscodeSetupController.swift b/submodules/PasscodeUI/Sources/PasscodeSetupController.swift index 6eb37915ec..a4592b8085 100644 --- a/submodules/PasscodeUI/Sources/PasscodeSetupController.swift +++ b/submodules/PasscodeUI/Sources/PasscodeSetupController.swift @@ -4,7 +4,6 @@ import Display import AsyncDisplayKit import TelegramCore import SwiftSignalKit -import Postbox import TelegramPresentationData import AccountContext diff --git a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift index 4bb391d32d..f42594e4f0 100644 --- a/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift +++ b/submodules/PasswordSetupUI/Sources/ResetPasswordController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift index 33768b8952..9427aa0c22 100644 --- a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift +++ b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import SwiftSignalKit import TelegramCore import TelegramPresentationData diff --git a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift index e3cda020c3..692d1e07cf 100644 --- a/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift +++ b/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 6a2e0c8a76..3be80120ed 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import QuickLook -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore @@ -17,7 +16,7 @@ import PresentationDataUtils public enum AvatarGalleryEntryId: Hashable { case topImage - case image(MediaId) + case image(EngineMedia.Id) case resource(String) } @@ -27,7 +26,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id guard let peer = peer else { return .single(nil) } - return initialAvatarGalleryEntries(account: context.account, engine: context.engine, peer: peer._asPeer()) + return initialAvatarGalleryEntries(account: context.account, engine: context.engine, peer: peer) } |> distinctUntilChanged |> mapToSignal { entries -> Signal<(Bool, [AvatarGalleryEntry])?, NoError> in @@ -51,7 +50,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id lastEntry = photo } } - return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer, firstEntry: firstEntry, secondEntry: secondEntry, lastEntry: lastEntry) + return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: EnginePeer(peer), firstEntry: firstEntry, secondEntry: secondEntry, lastEntry: lastEntry) |> map(Optional.init) } else { return .single(nil) @@ -76,7 +75,7 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id } } -public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: PeerId) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { +public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: EnginePeer.Id) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { return context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> map { items -> (Bool, [AvatarGalleryEntry]) in return items as? (Bool, [AvatarGalleryEntry]) ?? (true, []) @@ -84,10 +83,10 @@ public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: Peer } public enum AvatarGalleryEntry: Equatable { - case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, GalleryItemIndexData?, Data?, String?) - case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, Int32?, GalleryItemIndexData?, MessageId?, Data?, String?, Bool, TelegramMediaImage.EmojiMarkup?) + case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], EnginePeer?, GalleryItemIndexData?, Data?, String?) + case image(EngineMedia.Id, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], EnginePeer?, Int32?, GalleryItemIndexData?, EngineMessage.Id?, Data?, String?, Bool, TelegramMediaImage.EmojiMarkup?) - public init(representation: TelegramMediaImageRepresentation, peer: Peer) { + public init(representation: TelegramMediaImageRepresentation, peer: EnginePeer) { self = .topImage([ImageRepresentationWithReference(representation: representation, reference: MediaResourceReference.standalone(resource: representation.resource))], [], peer, nil, nil, nil) } @@ -106,7 +105,7 @@ public enum AvatarGalleryEntry: Equatable { } } - public var peer: Peer? { + public var peer: EnginePeer? { switch self { case let .topImage(_, _, peer, _, _, _): return peer @@ -163,13 +162,13 @@ public enum AvatarGalleryEntry: Equatable { public static func ==(lhs: AvatarGalleryEntry, rhs: AvatarGalleryEntry) -> Bool { switch lhs { case let .topImage(lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsIndexData, lhsImmediateThumbnailData, lhsCategory): - if case let .topImage(rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsIndexData, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsIndexData == rhsIndexData, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory { + if case let .topImage(rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsIndexData, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, lhsPeer == rhsPeer, lhsIndexData == rhsIndexData, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory { return true } else { return false } case let .image(lhsId, lhsImageReference, lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsDate, lhsIndexData, lhsMessageId, lhsImmediateThumbnailData, lhsCategory, lhsIsFallback, lhsEmojiMarkup): - if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId, rhsImmediateThumbnailData, rhsCategory, rhsIsFallback, rhsEmojiMarkup) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory, lhsIsFallback == rhsIsFallback, lhsEmojiMarkup == rhsEmojiMarkup { + if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId, rhsImmediateThumbnailData, rhsCategory, rhsIsFallback, rhsEmojiMarkup) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory, lhsIsFallback == rhsIsFallback, lhsEmojiMarkup == rhsEmojiMarkup { return true } else { return false @@ -204,40 +203,46 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE return updatedEntries } -public func initialAvatarGalleryEntries(account: Account, engine: TelegramEngine, peer: Peer) -> Signal<[AvatarGalleryEntry]?, NoError> { +public func initialAvatarGalleryEntries(account: Account, engine: TelegramEngine, peer: EnginePeer) -> Signal<[AvatarGalleryEntry]?, NoError> { var initialEntries: [AvatarGalleryEntry] = [] - if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) { + if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer._asPeer()) { initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), [], peer, nil, nil, nil)) } - if peer is TelegramChannel || peer is TelegramGroup, let peerReference = PeerReference(peer) { - return engine.data.get(TelegramEngine.EngineData.Item.Peer.Photo(id: peer.id)) - |> map { peerPhoto in - var initialPhoto: TelegramMediaImage? - if case let .known(value) = peerPhoto { - initialPhoto = value + guard let peerReference = PeerReference(peer._asPeer()) else { + return .single(initialEntries) + } + switch peer { + case .channel, .legacyGroup: + break + default: + return .single(initialEntries) + } + + return engine.data.get(TelegramEngine.EngineData.Item.Peer.Photo(id: peer.id)) + |> map { peerPhoto in + var initialPhoto: TelegramMediaImage? + if case let .known(value) = peerPhoto { + initialPhoto = value + } + + if let photo = initialPhoto { + var representations = photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }) + if photo.immediateThumbnailData == nil, let firstEntry = initialEntries.first, let firstRepresentation = firstEntry.representations.first { + representations.insert(firstRepresentation, at: 0) } - - if let photo = initialPhoto { - var representations = photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }) - if photo.immediateThumbnailData == nil, let firstEntry = initialEntries.first, let firstRepresentation = firstEntry.representations.first { - representations.insert(firstRepresentation, at: 0) - } - return [.image(photo.imageId, photo.reference, representations, photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil, photo.immediateThumbnailData, nil, false, photo.emojiMarkup)] + return [.image(photo.imageId, photo.reference, representations, photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil, photo.immediateThumbnailData, nil, false, photo.emojiMarkup)] + } else { + if case .known = peerPhoto { + return [] } else { - if case .known = peerPhoto { - return [] - } else { - return nil - } + return nil } } - } else { - return .single(initialEntries) } } -public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> { +public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: EnginePeer) -> Signal<[AvatarGalleryEntry], NoError> { return initialAvatarGalleryEntries(account: account, engine: engine, peer: peer) |> map { entries -> [AvatarGalleryEntry] in return entries ?? [] @@ -250,10 +255,10 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account var result: [AvatarGalleryEntry] = [] if photos.isEmpty { result = initialEntries - } else if let peerReference = PeerReference(peer) { + } else if let peerReference = PeerReference(peer._asPeer()) { var index: Int32 = 0 if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { - var initialMediaIds = Set() + var initialMediaIds = Set() for entry in initialEntries { if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _, _) = entry { initialMediaIds.insert(mediaId) @@ -296,7 +301,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account } } -public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer, firstEntry: AvatarGalleryEntry, secondEntry: TelegramMediaImage?, lastEntry: TelegramMediaImage?) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { +public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: EnginePeer, firstEntry: AvatarGalleryEntry, secondEntry: TelegramMediaImage?, lastEntry: TelegramMediaImage?) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { let initialEntries = [firstEntry] return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries)) |> then( @@ -306,11 +311,11 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account let initialEntries = [firstEntry] if photos.isEmpty { result = initialEntries - } else if let peerReference = PeerReference(peer) { + } else if let peerReference = PeerReference(peer._asPeer()) { var index: Int32 = 0 if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { - var initialMediaIds = Set() + var initialMediaIds = Set() for entry in initialEntries { if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _, _) = entry { initialMediaIds.insert(mediaId) @@ -386,7 +391,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } private let context: AccountContext - private let peer: Peer + private let peer: EnginePeer private let sourceCorners: SourceCorners private let isSuggested: Bool @@ -428,7 +433,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr private let editDisposable = MetaDisposable () - public init(context: AccountContext, peer: Peer, sourceCorners: SourceCorners = .round, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, isSuggested: Bool = false, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { + public init(context: AccountContext, peer: EnginePeer, sourceCorners: SourceCorners = .round, remoteEntries: Promise<[AvatarGalleryEntry]>? = nil, isSuggested: Bool = false, skipInitial: Bool = false, centralEntryIndex: Int? = nil, replaceRootController: @escaping (ViewController, Promise?) -> Void, synchronousLoad: Bool = false) { self.context = context self.peer = peer self.sourceCorners = sourceCorners @@ -759,14 +764,14 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr let canDelete: Bool if self.peer.id == self.context.account.peerId { canDelete = true - } else if let group = self.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = self.peer { switch group.role { case .creator, .admin: canDelete = true case .member: canDelete = false } - } else if let channel = self.peer as? TelegramChannel { + } else if case let .channel(channel) = self.peer { canDelete = channel.hasPermission(.changeInfo) } else { canDelete = false @@ -799,7 +804,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr } else { } case let .image(_, reference, _, _, _, _, _, _, _, _, _, _): - if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) { + if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer._asPeer()) { if let reference = reference { let _ = (self.context.engine.accountData.updatePeerPhotoExisting(reference: reference) |> deliverOnMainQueue).start(next: { [weak self] photo in diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift index f744ae4558..ab03807855 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import Photos @@ -109,7 +108,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { switch entry { case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, isFallback, _): if date != 0 || isFallback { - nameText = peer.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" + nameText = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" } if let date = date, date != 0 { dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string @@ -126,7 +125,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { } if let peer = peer { - canShare = !peer.isCopyProtectionEnabled + canShare = !peer._asPeer().isCopyProtectionEnabled } default: break diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index ad32448449..85e5c5f39d 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import AccountContext @@ -16,10 +15,10 @@ import UndoUI private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem { let account: Account - let peer: Peer + let peer: EnginePeer let content: [ImageRepresentationWithReference] - init(account: Account, peer: Peer, content: [ImageRepresentationWithReference]) { + init(account: Account, peer: EnginePeer, content: [ImageRepresentationWithReference]) { self.account = account self.peer = peer self.content = content @@ -48,7 +47,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { } let context: AccountContext - let peer: Peer + let peer: EnginePeer let presentationData: PresentationData let entry: AvatarGalleryEntry let sourceCorners: AvatarGalleryController.SourceCorners @@ -56,7 +55,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { let setMain: (() -> Void)? let edit: (() -> Void)? - init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) { + init(context: AccountContext, peer: EnginePeer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceCorners: AvatarGalleryController.SourceCorners, delete: (() -> Void)?, setMain: (() -> Void)?, edit: (() -> Void)?) { self.context = context self.peer = peer self.presentationData = presentationData @@ -129,7 +128,7 @@ private class PeerAvatarImageGalleryContentNode: ASDisplayNode { final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private let context: AccountContext private let presentationData: PresentationData - private let peer: Peer + private let peer: EnginePeer private let sourceCorners: AvatarGalleryController.SourceCorners private var entry: AvatarGalleryEntry? @@ -150,12 +149,12 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { private let fetchDisposable = MetaDisposable() private let statusDisposable = MetaDisposable() - private var status: MediaResourceStatus? + private var status: EngineMediaResource.FetchStatus? private let playbackStatusDisposable = MetaDisposable() fileprivate var edit: (() -> Void)? - init(context: AccountContext, presentationData: PresentationData, peer: Peer, sourceCorners: AvatarGalleryController.SourceCorners) { + init(context: AccountContext, presentationData: PresentationData, peer: EnginePeer, sourceCorners: AvatarGalleryController.SourceCorners) { self.context = context self.presentationData = presentationData self.peer = peer @@ -187,8 +186,8 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if let strongSelf = self, let entry = strongSelf.entry, !entry.representations.isEmpty { let subject: ShareControllerSubject var actionCompletionText: String? - if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) { + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) subject = .media(videoFileReference.abstract) actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved } else { @@ -277,10 +276,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { id = id &+ resource.photoId } } - if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer) { + if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { if video != previousVideoRepresentations?.last { let mediaManager = self.context.sharedContext.mediaManager - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(id, category), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, useLargeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) videoNode.isUserInteractionEnabled = false diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index 8e2337c059..ad7b767433 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -94,16 +93,16 @@ public enum PeerInfoAvatarListItem: Equatable { case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?) case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?, Bool, TelegramMediaImage.EmojiMarkup?) - var id: MediaResourceId { + var id: EngineMediaResource.Id { switch self { case .custom: - return MediaResourceId(CustomListItemResourceId().uniqueId) + return EngineMediaResource.Id(CustomListItemResourceId().uniqueId) case let .topImage(representations, _, _): let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation - return representation.resource.id + return EngineMediaResource.Id(representation.resource.id) case let .image(_, representations, _, _, _, _): let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation - return representation.resource.id + return EngineMediaResource.Id(representation.resource.id) } } @@ -205,7 +204,7 @@ public enum PeerInfoAvatarListItem: Equatable { public final class PeerInfoAvatarListItemNode: ASDisplayNode { private let context: AccountContext - private let peer: Peer + private let peer: EnginePeer public let imageNode: TransformImageNode private var videoNode: UniversalVideoNode? private var videoContent: NativeVideoContent? @@ -266,7 +265,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { } } - init(context: AccountContext, peer: Peer) { + init(context: AccountContext, peer: EnginePeer) { self.context = context self.peer = peer self.imageNode = TransformImageNode() @@ -511,8 +510,8 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { self.didSetReady = true self.isReady.set(.single(true)) } - } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) + } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])])) let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { @@ -570,7 +569,7 @@ private let fadeWidth: CGFloat = 70.0 public final class PeerInfoAvatarListContainerNode: ASDisplayNode { private let context: AccountContext private let isSettings: Bool - public var peer: Peer? + public var peer: EnginePeer? public let controlsContainerNode: ASDisplayNode public let controlsClippingNode: ASDisplayNode @@ -590,7 +589,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { public private(set) var galleryEntries: [AvatarGalleryEntry] = [] private var items: [PeerInfoAvatarListItem] = [] - private var itemNodes: [MediaResourceId: PeerInfoAvatarListItemNode] = [:] + private var itemNodes: [EngineMediaResource.Id: PeerInfoAvatarListItemNode] = [:] private var stripNodes: [ASImageNode] = [] private var activeStripNode: ASImageNode private var loadingStripNode: PeerInfoAvatarListLoadingStripNode @@ -1118,16 +1117,20 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { index += 1 } - - if let peer = self.peer, peer is TelegramGroup || peer is TelegramChannel, deletedIndex == 0 { - self.galleryEntries = [] - self.items = [] - self.itemsUpdated?([]) - self.currentIndex = 0 - if let size = self.validLayout { - self.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: true) + switch self.peer { + case .legacyGroup, .channel: + if deletedIndex == 0 { + self.galleryEntries = [] + self.items = [] + self.itemsUpdated?([]) + self.currentIndex = 0 + if let size = self.validLayout { + self.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: true) + } + return true } - return true + default: + break } self.galleryEntries = normalizeEntries(entries) @@ -1146,7 +1149,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { } private var additionalEntryProgress: Signal? = nil - public func update(size: CGSize, peer: Peer?, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError> = .single(nil), isExpanded: Bool, transition: ContainedViewLayoutTransition) { + public func update(size: CGSize, peer: EnginePeer?, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError> = .single(nil), isExpanded: Bool, transition: ContainedViewLayoutTransition) { self.validLayout = size let previousExpanded = self.isExpanded self.isExpanded = isExpanded @@ -1304,7 +1307,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { public var updateCustomItemsOnlySynchronously = false private func updateItems(size: CGSize, update: Bool = false, transition: ContainedViewLayoutTransition, stripTransition: ContainedViewLayoutTransition, synchronous: Bool = false) { - var validIds: [MediaResourceId] = [] + var validIds: [EngineMediaResource.Id] = [] var addedItemNodesForAdditiveTransition: [PeerInfoAvatarListItemNode] = [] var additiveTransitionOffset: CGFloat = 0.0 var itemsAdded = false @@ -1371,7 +1374,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { photoTitle = representation.hasVideo ? presentationData.strings.UserInfo_PublicVideo : presentationData.strings.UserInfo_PublicPhoto hasLink = true if let peer = self.peer { - fallbackImageSignal = peerAvatarCompleteImage(account: self.context.account, peer: EnginePeer(peer), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)) + fallbackImageSignal = peerAvatarCompleteImage(account: self.context.account, peer: peer, forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0)) } } @@ -1398,7 +1401,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { for itemNode in addedItemNodesForAdditiveTransition { transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: additiveTransitionOffset, y: 0.0)) } - var removeIds: [MediaResourceId] = [] + var removeIds: [EngineMediaResource.Id] = [] for (id, _) in self.itemNodes { if !validIds.contains(id) { removeIds.append(id) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index f0fc00b5b7..05fe16fdbb 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -483,8 +482,8 @@ private func rightDependencies(_ right: TelegramChatAdminRightsFlags) -> [Telegr } } -private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initialParticipant: ChannelParticipant?) -> Bool { - if let channel = channelPeer as? TelegramChannel { +private func canEditAdminRights(accountPeerId: EnginePeer.Id, channelPeer: EnginePeer, initialParticipant: ChannelParticipant?) -> Bool { + if case let .channel(channel) = channelPeer { if channel.flags.contains(.isCreator) { return true } else if let initialParticipant = initialParticipant { @@ -501,7 +500,7 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } else { return channel.hasPermission(.addAdmins) } - } else if let group = channelPeer as? TelegramGroup { + } else if case let .legacyGroup(group) = channelPeer { if case .creator = group.role { return true } else { @@ -512,8 +511,8 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } } -private func rightEnabledByDefault(channelPeer: Peer, right: TelegramChatAdminRightsFlags) -> Bool { - if let channel = channelPeer as? TelegramChannel { +private func rightEnabledByDefault(channelPeer: EnginePeer, right: TelegramChatAdminRightsFlags) -> Bool { + if case let .channel(channel) = channelPeer { guard let defaultBannedRights = channel.defaultBannedRights else { return false } @@ -533,7 +532,7 @@ private func areAllAdminRightsEnabled(_ flags: TelegramChatAdminRightsFlags, pee return TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except).intersection(flags) == TelegramChatAdminRightsFlags.peerSpecific(peer: peer).subtracting(except) } -private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: PeerId, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] { +private func channelAdminControllerEntries(presentationData: PresentationData, state: ChannelAdminControllerState, accountPeerId: EnginePeer.Id, channelPeer: EnginePeer?, adminPeer: EnginePeer?, adminPresence: EnginePeer.Presence?, initialParticipant: ChannelParticipant?, invite: Bool, canEdit: Bool) -> [ChannelAdminEntry] { var entries: [ChannelAdminEntry] = [] if case let .channel(channel) = channelPeer, let admin = adminPeer { @@ -630,7 +629,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s } } } else { - if case let .user(adminPeer) = adminPeer, adminPeer.botInfo != nil, case .group = channel.info, invite, let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer._asPeer(), initialParticipant: initialParticipant) { + if case let .user(adminPeer) = adminPeer, adminPeer.botInfo != nil, case .group = channel.info, invite, let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer, initialParticipant: initialParticipant) { if let initialParticipant = initialParticipant, case let .member(_, _, adminInfo, _, _) = initialParticipant, adminInfo != nil { } else { @@ -641,7 +640,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s if !invite || state.adminRights { entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader)) - if let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer._asPeer(), initialParticipant: initialParticipant) { + if let channelPeer = channelPeer, canEditAdminRights(accountPeerId: accountPeerId, channelPeer: channelPeer, initialParticipant: initialParticipant) { let accountUserRightsFlags: TelegramChatAdminRightsFlags if channel.flags.contains(.isCreator) { accountUserRightsFlags = maskRightsFlags @@ -663,7 +662,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s var index = 0 for right in rightsOrder { if accountUserRightsFlags.contains(right) { - entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: channel, right: right))) + entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, isChannel: isChannel, isForum: channel.flags.contains(.isForum), defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), !state.updating && admin.id != accountPeerId && !rightEnabledByDefault(channelPeer: .channel(channel), right: right))) index += 1 } } @@ -829,7 +828,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s return entries } -public func channelAdminController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant?, invite: Bool = false, initialAdminRights: TelegramChatAdminRightsFlags? = nil, updated: @escaping (TelegramChatAdminRights?) -> Void, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, transferedOwnership: @escaping (PeerId) -> Void) -> ViewController { +public func channelAdminController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, adminId: EnginePeer.Id, initialParticipant: ChannelParticipant?, invite: Bool = false, initialAdminRights: TelegramChatAdminRightsFlags? = nil, updated: @escaping (TelegramChatAdminRights?) -> Void, upgradedToSupergroup: @escaping (EnginePeer.Id, @escaping () -> Void) -> Void, transferedOwnership: @escaping (EnginePeer.Id) -> Void) -> ViewController { let statePromise = ValuePromise(ChannelAdminControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelAdminControllerState()) let updateState: ((ChannelAdminControllerState) -> ChannelAdminControllerState) -> Void = { f in @@ -857,8 +856,8 @@ public func channelAdminController(context: AccountContext, updatedPresentationD var errorImpl: (() -> Void)? var scrollToRankImpl: (() -> Void)? - let actualPeerId = Atomic(value: peerId) - let upgradedToSupergroupImpl: (PeerId, @escaping () -> Void) -> Void = { peerId, completion in + let actualPeerId = Atomic(value: peerId) + let upgradedToSupergroupImpl: (EnginePeer.Id, @escaping () -> Void) -> Void = { peerId, completion in let _ = actualPeerId.swap(peerId) upgradedToSupergroup(peerId, completion) } @@ -889,9 +888,9 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let presentationData = context.sharedContext.currentPresentationData.with { $0 } let text: String - if !canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: peer._asPeer(), initialParticipant: initialParticipant) { + if !canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: peer, initialParticipant: initialParticipant) { text = presentationData.strings.Channel_EditAdmin_CannotEdit - } else if rightEnabledByDefault(channelPeer: peer._asPeer(), right: right) { + } else if rightEnabledByDefault(channelPeer: peer, right: right) { text = presentationData.strings.Channel_EditAdmin_PermissionEnabledByDefault } else { text = presentationData.strings.Channel_EditAdmin_CannotEdit @@ -993,7 +992,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let channelPeer = peerInfoData.0.flatMap { $0 } let adminPeer = peerInfoData.1.flatMap { $0 } let adminPresence = peerInfoData.2 - let canEdit = canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: channelPeer!._asPeer(), initialParticipant: initialParticipant) + let canEdit = canEditAdminRights(accountPeerId: context.account.peerId, channelPeer: channelPeer!, initialParticipant: initialParticipant) let leftNavigationButton: ItemListNavigationButton if canEdit { @@ -1308,7 +1307,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD let signal = context.engine.peers.convertGroupToSupergroup(peerId: peerId) |> map(Optional.init) - |> `catch` { error -> Signal in + |> `catch` { error -> Signal in switch error { case .tooManyChannels: return .fail(.conversionTooManyChannels) @@ -1316,7 +1315,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD return .fail(.conversionFailed) } } - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in guard let upgradedPeerId = upgradedPeerId else { return .fail(.conversionFailed) } @@ -1324,7 +1323,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD |> mapError { error -> WrappedUpdateChannelAdminRightsError in return .direct(error) } - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(upgradedPeerId)) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index c1b8399f12..e00c4d8015 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -120,7 +120,7 @@ public final class ChannelMembersSearchController: ViewController { self?.deactivateSearch(animated: true) } self.controllerNode.requestOpenPeerFromSearch = { [weak self] peer, participant in - self?.openPeer(peer, participant) + self?.openPeer(peer._asPeer(), participant) } self.controllerNode.requestCopyInviteLink = { [weak self] in self?.copyInviteLink?() diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index e77c6778e6..b48c6fe0c2 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -18,11 +17,11 @@ import ContactListUI import ChatListSearchItemHeader private final class ChannelMembersSearchInteraction { - let openPeer: (Peer, RenderedChannelParticipant?) -> Void + let openPeer: (EnginePeer, RenderedChannelParticipant?) -> Void let copyInviteLink: () -> Void init( - openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, + openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void, copyInviteLink: @escaping () -> Void ) { self.openPeer = openPeer @@ -32,14 +31,14 @@ private final class ChannelMembersSearchInteraction { private enum ChannelMembersSearchEntryId: Hashable { case copyInviteLink - case peer(PeerId) - case contact(PeerId) + case peer(EnginePeer.Id) + case contact(EnginePeer.Id) } private enum ChannelMembersSearchEntry: Comparable, Identifiable { case copyInviteLink case peer(Int, RenderedChannelParticipant, ContactsPeerItemEditing, String?, Bool, Bool, Bool) - case contact(Int, Peer, TelegramUserPresence?) + case contact(Int, EnginePeer, TelegramUserPresence?) var stableId: ChannelMembersSearchEntryId { switch self { @@ -71,7 +70,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { if lhsIndex != rhsIndex { return false } - if !lhsPeer.isEqual(rhsPeer) { + if lhsPeer != rhsPeer { return false } if lhsPresence != rhsPresence { @@ -146,7 +145,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { } return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(participant.peer), chatPeer: nil), status: status, enabled: enabled, selection: .none, editing: editing, index: nil, header: ChatListSearchItemHeader(type: headerType, theme: presentationData.theme, strings: presentationData.strings), action: { _ in - interaction.openPeer(participant.peer, participant) + interaction.openPeer(EnginePeer(participant.peer), participant) }) case let .contact(_, peer, presence): let status: ContactsPeerItemStatus @@ -156,7 +155,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable { status = .none } - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer), chatPeer: nil), status: status, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: presentationData.theme, strings: presentationData.strings), action: { _ in + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: nil), status: status, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: ChatListSearchItemHeader(type: .contacts, theme: presentationData.theme, strings: presentationData.strings), action: { _ in interaction.openPeer(peer, nil) }) } @@ -182,7 +181,7 @@ private func preparedTransition(from fromEntries: [ChannelMembersSearchEntry]?, class ChannelMembersSearchControllerNode: ASDisplayNode { private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let mode: ChannelMembersSearchControllerMode private let filters: [ChannelMembersSearchFilter] let listNode: ListView @@ -196,7 +195,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { var requestActivateSearch: (() -> Void)? var requestDeactivateSearch: (() -> Void)? - var requestOpenPeerFromSearch: ((Peer, RenderedChannelParticipant?) -> Void)? + var requestOpenPeerFromSearch: ((EnginePeer, RenderedChannelParticipant?) -> Void)? var requestCopyInviteLink: (() -> Void)? var pushController: ((ViewController) -> Void)? @@ -206,7 +205,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { private var disposable: Disposable? private var listControl: PeerChannelMemberCategoryControl? - init(context: AccountContext, presentationData: PresentationData, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) { + init(context: AccountContext, presentationData: PresentationData, forceTheme: PresentationTheme?, peerId: EnginePeer.Id, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter]) { self.context = context self.listNode = ListView() self.peerId = peerId @@ -265,12 +264,12 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { guard let cachedData = peerView.cachedData as? CachedGroupData, let participants = cachedData.participants else { return } - var creatorPeer: Peer? + var creatorPeer: EnginePeer? for participant in participants.participants { if let peer = peerView.peers[participant.peerId] { switch participant { case .creator: - creatorPeer = peer + creatorPeer = EnginePeer(peer) default: break } @@ -397,14 +396,14 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { case .creator: renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer, presences: peerView.peerPresences) case .admin: - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] peers[creator.id] = creator - peers[peer.id] = peer - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers, presences: peerView.peerPresences) + peers[peer.id] = EnginePeer(peer) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(mainPeer))), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences) case .member: - var peers: [PeerId: Peer] = [:] - peers[peer.id] = peer - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers, presences: peerView.peerPresences) + var peers: [EnginePeer.Id: EnginePeer] = [:] + peers[peer.id] = EnginePeer(peer) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() }), presences: peerView.peerPresences) } entries.append(.peer(index, renderedParticipant, ContactsPeerItemEditing(editable: false, editing: false, revealed: false), label, enabled, false, false)) @@ -419,7 +418,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } }) { for peer in contactsView.peers { - entries.append(ChannelMembersSearchEntry.contact(index, peer._asPeer(), contactsView.presences[peer.id]?._asPresence())) + entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.presences[peer.id]?._asPresence())) index += 1 } } @@ -477,7 +476,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } var index = 0 - var existingPeersIds = Set() + var existingPeersIds = Set() if case .inviteToCall = mode, canInviteByLink, !filters.contains(where: { filter in if case .excludeNonMembers = filter { return true @@ -596,7 +595,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } }) { for peer in contactsView.peers { - entries.append(ChannelMembersSearchEntry.contact(index, peer._asPeer(), contactsView.presences[peer.id]?._asPresence())) + entries.append(ChannelMembersSearchEntry.contact(index, peer, contactsView.presences[peer.id]?._asPresence())) index += 1 } } @@ -677,7 +676,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: self.forceTheme, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, searchContext: nil, openPeer: { [weak self] peer, participant in - self?.requestOpenPeerFromSearch?(peer, participant) + self?.requestOpenPeerFromSearch?(EnginePeer(peer), participant) }, updateActivity: { value in }, pushController: { [weak self] c in diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index ff47699cce..69618abfd2 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1349,7 +1349,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } openAvatarImpl = { [weak controller] peer in - let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in + let avatarController = AvatarGalleryController(context: context, peer: EnginePeer(peer), replaceRootController: { _, _ in }) hiddenAvatarPromise.set( avatarController.hiddenMedia diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index 99364741a6..2922a37a5c 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -682,9 +682,9 @@ public final class MediaBox { messageIdValue = messageId.id } - self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType.rawValue) + self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType) } else { - self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType.rawValue ?? 0) + self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType ?? 0) } guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else { @@ -857,9 +857,9 @@ public final class MediaBox { messageIdValue = messageId.id } - self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType.rawValue) + self.storageBox.add(reference: StorageBox.Reference(peerId: location.peerId.toInt64(), messageNamespace: UInt8(clamping: messageNamespace), messageId: messageIdValue), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters.contentType) } else { - self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType.rawValue ?? 0) + self.storageBox.add(reference: StorageBox.Reference(peerId: 0, messageNamespace: 0, messageId: 0), to: resource.id.stringRepresentation.data(using: .utf8)!, contentType: parameters?.contentType ?? 0) } if let _ = fileSize(paths.complete) { @@ -1387,7 +1387,7 @@ public final class MediaBox { } } - private func updateGeneralResourceIndex(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { + private func updateGeneralResourceIndex(otherResourceContentType: UInt8, lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { let basePath = self.basePath let storageBox = self.storageBox @@ -1456,7 +1456,7 @@ public final class MediaBox { size = value } return (resourceId.data(using: .utf8)!, size) - }, contentType: MediaResourceUserContentType.other.rawValue, completion: { addedCount in + }, contentType: otherResourceContentType, completion: { addedCount in if addedCount != 0 { postboxLog("UpdateResourceIndex: added \(addedCount) unreferenced ids") } @@ -1572,8 +1572,8 @@ public final class MediaBox { } }*/ - public func updateResourceIndex(lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { - return self.updateGeneralResourceIndex(lowImpact: lowImpact, completion: { + public func updateResourceIndex(otherResourceContentType: UInt8, lowImpact: Bool, completion: @escaping () -> Void) -> Disposable { + return self.updateGeneralResourceIndex(otherResourceContentType: otherResourceContentType, lowImpact: lowImpact, completion: { completion() }) } diff --git a/submodules/Postbox/Sources/MediaResource.swift b/submodules/Postbox/Sources/MediaResource.swift index 1811c565ed..aafeb87f9b 100644 --- a/submodules/Postbox/Sources/MediaResource.swift +++ b/submodules/Postbox/Sources/MediaResource.swift @@ -49,25 +49,14 @@ public final class MediaResourceStorageLocation { } } -public enum MediaResourceUserContentType: UInt8, Equatable { - case other = 0 - case image = 1 - case video = 2 - case audio = 3 - case file = 4 - case sticker = 6 - case avatar = 7 - case audioVideoMessage = 8 -} - public struct MediaResourceFetchParameters { public let tag: MediaResourceFetchTag? public let info: MediaResourceFetchInfo? public let location: MediaResourceStorageLocation? - public let contentType: MediaResourceUserContentType + public let contentType: UInt8 public let isRandomAccessAllowed: Bool - public init(tag: MediaResourceFetchTag?, info: MediaResourceFetchInfo?, location: MediaResourceStorageLocation?, contentType: MediaResourceUserContentType, isRandomAccessAllowed: Bool) { + public init(tag: MediaResourceFetchTag?, info: MediaResourceFetchInfo?, location: MediaResourceStorageLocation?, contentType: UInt8, isRandomAccessAllowed: Bool) { self.tag = tag self.info = info self.location = location diff --git a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift index fdbfa8d939..51be389a87 100644 --- a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift +++ b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift @@ -4,7 +4,6 @@ import SceneKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import ComponentFlow import AccountContext @@ -221,7 +220,7 @@ private final class PhoneView: UIView { self.contentContainerView.backgroundColor = .clear let videoContent = NativeVideoContent( - id: .message(1, MediaId(namespace: 0, id: Int64.random(in: 0.. Void - let removePeer: (PeerId) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let removePeer: (EnginePeer.Id) -> Void let addPeer: () -> Void let openPeer: (EnginePeer) -> Void let deleteAll: () -> Void - init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, deleteAll: @escaping () -> Void) { + init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, deleteAll: @escaping () -> Void) { self.context = context self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removePeer = removePeer @@ -39,7 +38,7 @@ private enum SelectivePrivacyPeersSection: Int32 { private enum SelectivePrivacyPeersEntryStableId: Hashable { case header case add - case peer(PeerId) + case peer(EnginePeer.Id) case delete } @@ -193,14 +192,14 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { private struct SelectivePrivacyPeersControllerState: Equatable { let editing: Bool - let peerIdWithRevealedOptions: PeerId? + let peerIdWithRevealedOptions: EnginePeer.Id? init() { self.editing = false self.peerIdWithRevealedOptions = nil } - init(editing: Bool, peerIdWithRevealedOptions: PeerId?) { + init(editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?) { self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions } @@ -219,7 +218,7 @@ private struct SelectivePrivacyPeersControllerState: Equatable { return SelectivePrivacyPeersControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions) } - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> SelectivePrivacyPeersControllerState { + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: EnginePeer.Id?) -> SelectivePrivacyPeersControllerState { return SelectivePrivacyPeersControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions) } } @@ -249,7 +248,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati return entries } -public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [PeerId: SelectivePrivacyPeer], updated: @escaping ([PeerId: SelectivePrivacyPeer]) -> Void) -> ViewController { +public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer]) -> Void) -> ViewController { let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: SelectivePrivacyPeersControllerState()) let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in @@ -293,7 +292,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri } peersPromise.set(.single(updatedPeers)) - var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:] + var updatedPeerDict: [EnginePeer.Id: SelectivePrivacyPeer] = [:] for peer in updatedPeers { updatedPeerDict[peer.peer.id] = peer } @@ -357,7 +356,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri |> mapToSignal { updatedPeers -> Signal in peersPromise.set(.single(updatedPeers)) - var updatedPeerDict: [PeerId: SelectivePrivacyPeer] = [:] + var updatedPeerDict: [EnginePeer.Id: SelectivePrivacyPeer] = [:] for peer in updatedPeers { updatedPeerDict[peer.peer.id] = peer } diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift index 4e4db063d1..333125c412 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceController.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit diff --git a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift index cb991de4aa..8c9e6c1639 100644 --- a/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift +++ b/submodules/SettingsUI/Sources/Terms of Service/TermsOfServiceControllerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import TelegramCore -import Postbox import SwiftSignalKit import Display import AsyncDisplayKit diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index 7bbfd06c43..c353ba6286 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -1,10 +1,10 @@ import Foundation import UIKit import Display -import Postbox import SwiftSignalKit import AsyncDisplayKit import TelegramCore +import Postbox import TelegramPresentationData import TelegramUIPreferences import AccountContext diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift index 486d63080e..81ca956786 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramUIPreferences import TelegramPresentationData import TelegramCore diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index e35dbd8b14..acce3f33a9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import LegacyComponents diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift index 7b3b91c209..aae0ab87c4 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import AccountContext import GridMessageSelectionNode diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift index 1981669ea4..9566b7797c 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index c3eff1a4ba..3f493e9dd5 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -12,6 +11,7 @@ import ShareController import UndoUI import InviteLinksUI import TextFormat +import Postbox private final class UsernameSetupControllerArguments { let account: Account @@ -404,7 +404,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData, public enum UsernameSetupMode: Equatable { case account - case bot(PeerId) + case bot(EnginePeer.Id) } public func usernameSetupController(context: AccountContext, mode: UsernameSetupMode = .account) -> ViewController { @@ -426,7 +426,7 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup let updateAddressNameDisposable = MetaDisposable() actionsDisposable.add(updateAddressNameDisposable) - let peerId: PeerId + let peerId: EnginePeer.Id let domain: AddressNameDomain switch mode { case .account: diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 29c2df3ac3..07ca59222c 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -1042,7 +1042,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate |> take(1) |> deliverOnMainQueue).start(next: { peers in if let strongSelf = self { - let searchContentNode = ShareSearchContainerNode(sharedContext: strongSelf.sharedContext, context: context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction!, recentPeers: peers.filter({ $0.peer.peerId.namespace != Namespaces.Peer.SecretChat }).map({ $0.peer })) + let searchContentNode = ShareSearchContainerNode(sharedContext: strongSelf.sharedContext, context: context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction!, recentPeers: peers.filter({ $0.peer.peerId.namespace != Namespaces.Peer.SecretChat }).map({ EngineRenderedPeer($0.peer) })) searchContentNode.cancel = { if let strongSelf = self, let peersContentNode = strongSelf.peersContentNode { strongSelf.transitionToContentNode(peersContentNode) diff --git a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift index 0b93022e03..5195c536fe 100644 --- a/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerPeerGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramPresentationData import TelegramStringFormatting import SelectablePeerNode @@ -14,7 +13,7 @@ import ShimmerEffect final class ShareControllerInteraction { var foundPeers: [EngineRenderedPeer] = [] - var selectedPeerIds = Set() + var selectedPeerIds = Set() var selectedPeers: [EngineRenderedPeer] = [] var selectedTopics: [EnginePeer.Id: (Int64, MessageHistoryThreadData)] = [:] diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index f3145f0398..900046237b 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit import Display -import Postbox import TelegramPresentationData import ActivityIndicator import RadialStatusNode @@ -14,6 +13,26 @@ import TelegramUniversalVideoContent import TelegramCore import AccountContext +private func fileSize(_ path: String, useTotalFileAllocatedSize: Bool = false) -> Int64? { + if useTotalFileAllocatedSize { + let url = URL(fileURLWithPath: path) + if let values = (try? url.resourceValues(forKeys: Set([.isRegularFileKey, .totalFileAllocatedSizeKey]))) { + if values.isRegularFile ?? false { + if let fileSize = values.totalFileAllocatedSize { + return Int64(fileSize) + } + } + } + } + + var value = stat() + if stat(path, &value) == 0 { + return value.st_size + } else { + return nil + } +} + public enum ShareLoadingState { case preparing case progress(Float) @@ -71,7 +90,7 @@ public final class ShareLoadingContainerNode: ASDisplayNode, ShareContentContain public func deactivate() { } - public func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + public func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { } public func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) { @@ -256,9 +275,9 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte if let account = account, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: MediaId(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [])]) - let videoContent = NativeVideoContent(id: .message(1, MediaId(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) + let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: account.postbox, audioSession: sharedContext.mediaManager.audioSession, manager: sharedContext.mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded) videoNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: 2.0)) @@ -285,7 +304,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte public func deactivate() { } - public func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + public func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { } public func setContentOffsetUpdated(_ f: ((CGFloat, ContainedViewLayoutTransition) -> Void)?) { diff --git a/submodules/ShareController/Sources/SharePeersContainerNode.swift b/submodules/ShareController/Sources/SharePeersContainerNode.swift index a9f094be3a..b04eb7317e 100644 --- a/submodules/ShareController/Sources/SharePeersContainerNode.swift +++ b/submodules/ShareController/Sources/SharePeersContainerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -140,7 +139,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { var openShare: ((ASDisplayNode, ContextGesture?) -> Void)? var segmentedSelectedIndexUpdated: ((Int) -> Void)? - private var ensurePeerVisibleOnLayout: PeerId? + private var ensurePeerVisibleOnLayout: EnginePeer.Id? private var validLayout: (CGSize, CGFloat)? private var overrideGridOffsetTransition: ContainedViewLayoutTransition? @@ -175,7 +174,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { var entries: [SharePeerEntry] = [] var index: Int32 = 0 - var existingPeerIds: Set = Set() + var existingPeerIds: Set = Set() entries.append(SharePeerEntry(index: index, peer: EngineRenderedPeer(peer: accountPeer), presence: nil, threadId: nil, threadData: nil, theme: theme, strings: strings)) existingPeerIds.insert(accountPeer.id) index += 1 @@ -338,7 +337,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } } - func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { self.ensurePeerVisibleOnLayout = peerId } diff --git a/submodules/ShareController/Sources/ShareSearchContainerNode.swift b/submodules/ShareController/Sources/ShareSearchContainerNode.swift index f229d67ce0..3b166ed261 100644 --- a/submodules/ShareController/Sources/ShareSearchContainerNode.swift +++ b/submodules/ShareController/Sources/ShareSearchContainerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import Display @@ -14,7 +13,7 @@ private let subtitleFont = Font.regular(12.0) private enum ShareSearchRecentEntryStableId: Hashable { case topPeers - case peerId(PeerId) + case peerId(EnginePeer.Id) static func ==(lhs: ShareSearchRecentEntryStableId, rhs: ShareSearchRecentEntryStableId) -> Bool { switch lhs { @@ -36,7 +35,7 @@ private enum ShareSearchRecentEntryStableId: Hashable { private enum ShareSearchRecentEntry: Comparable, Identifiable { case topPeers(PresentationTheme, PresentationStrings) - case peer(index: Int, theme: PresentationTheme, peer: Peer, associatedPeer: Peer?, presence: EnginePeer.Presence?, PresentationStrings) + case peer(index: Int, theme: PresentationTheme, peer: EnginePeer, associatedPeer: EnginePeer?, presence: EnginePeer.Presence?, PresentationStrings) var stableId: ShareSearchRecentEntryStableId { switch self { @@ -62,7 +61,7 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable { return false } case let .peer(lhsIndex, lhsTheme, lhsPeer, lhsAssociatedPeer, lhsPresence, lhsStrings): - if case let .peer(rhsIndex, rhsTheme, rhsPeer, rhsAssociatedPeer, rhsPresence, rhsStrings) = rhs, lhsPeer.isEqual(rhsPeer) && arePeersEqual(lhsAssociatedPeer, rhsAssociatedPeer) && lhsIndex == rhsIndex && lhsStrings === rhsStrings && lhsTheme === rhsTheme && lhsPresence == rhsPresence { + if case let .peer(rhsIndex, rhsTheme, rhsPeer, rhsAssociatedPeer, rhsPresence, rhsStrings) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsStrings === rhsStrings && lhsTheme === rhsTheme && lhsPresence == rhsPresence { return true } else { return false @@ -89,11 +88,11 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable { case let .topPeers(theme, strings): return ShareControllerRecentPeersGridItem(context: context, theme: theme, strings: strings, controllerInteraction: interfaceInteraction) case let .peer(_, theme, peer, associatedPeer, presence, strings): - var peers: [PeerId: Peer] = [peer.id: peer] + var peers: [EnginePeer.Id: EnginePeer] = [peer.id: peer] if let associatedPeer = associatedPeer { peers[associatedPeer.id] = associatedPeer } - let peer = EngineRenderedPeer(RenderedPeer(peerId: peer.id, peers: SimpleDictionary(peers), associatedMedia: [:])) + let peer = EngineRenderedPeer(peerId: peer.id, peers: peers, associatedMedia: [:]) return ShareControllerPeerGridItem(context: context, theme: theme, strings: strings, peer: peer, presence: presence, topicId: nil, threadData: nil, controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent, search: true) } } @@ -189,7 +188,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { var cancel: (() -> Void)? - private var ensurePeerVisibleOnLayout: PeerId? + private var ensurePeerVisibleOnLayout: EnginePeer.Id? private var validLayout: (CGSize, CGFloat)? private var overrideGridOffsetTransition: ContainedViewLayoutTransition? @@ -198,7 +197,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { private let searchQuery = ValuePromise("", ignoreRepeated: true) private let searchDisposable = MetaDisposable() - init(sharedContext: SharedAccountContext, context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ShareControllerInteraction, recentPeers recentPeerList: [RenderedPeer]) { + init(sharedContext: SharedAccountContext, context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ShareControllerInteraction, recentPeers recentPeerList: [EngineRenderedPeer]) { self.sharedContext = sharedContext self.context = context self.theme = theme @@ -264,13 +263,13 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { var entries: [ShareSearchPeerEntry] = [] var index: Int32 = 0 - var existingPeerIds = Set() + var existingPeerIds = Set() let lowercasedQuery = query.lowercased() if strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) { if !existingPeerIds.contains(accountPeer.id) { existingPeerIds.insert(accountPeer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(RenderedPeer(peer: accountPeer)), presence: nil, theme: theme, strings: strings)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(accountPeer)), presence: nil, theme: theme, strings: strings)) index += 1 } } @@ -297,7 +296,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let peer = foundPeer.peer if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(RenderedPeer(peer: foundPeer.peer)), presence: nil, theme: theme, strings: strings)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(foundPeer.peer)), presence: nil, theme: theme, strings: strings)) index += 1 } } @@ -306,7 +305,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { let peer = foundPeer.peer if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) { existingPeerIds.insert(peer.id) - entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(RenderedPeer(peer: peer)), presence: nil, theme: theme, strings: strings)) + entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, theme: theme, strings: strings)) index += 1 } } @@ -370,8 +369,8 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } var index = 0 for peer in recentPeerList { - if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer) { - recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer.associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, strings)) + if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer._asPeer()) { + recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer._asPeer().associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, strings)) index += 1 } } @@ -396,7 +395,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { self.recentDisposable.dispose() } - func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { + func setEnsurePeerVisibleOnLayout(_ peerId: EnginePeer.Id?) { self.ensurePeerVisibleOnLayout = peerId } diff --git a/submodules/ShareController/Sources/ShareTopicGridItem.swift b/submodules/ShareController/Sources/ShareTopicGridItem.swift index bac41acd22..b3a4af9703 100644 --- a/submodules/ShareController/Sources/ShareTopicGridItem.swift +++ b/submodules/ShareController/Sources/ShareTopicGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramPresentationData import TelegramStringFormatting import SelectablePeerNode diff --git a/submodules/ShareController/Sources/ShareTopicsContainerNode.swift b/submodules/ShareController/Sources/ShareTopicsContainerNode.swift index f4f6f076fc..bf68b640af 100644 --- a/submodules/ShareController/Sources/ShareTopicsContainerNode.swift +++ b/submodules/ShareController/Sources/ShareTopicsContainerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import Display diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index aea2de6d6f..600df725fb 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -23,17 +22,17 @@ private final class GroupStatsControllerArguments { let context: AccountContext let loadDetailedGraph: (StatsGraph, Int64) -> Signal let openPeer: (EnginePeer) -> Void - let openPeerHistory: (PeerId) -> Void - let openPeerAdminActions: (PeerId) -> Void - let promotePeer: (PeerId) -> Void + let openPeerHistory: (EnginePeer.Id) -> Void + let openPeerAdminActions: (EnginePeer.Id) -> Void + let promotePeer: (EnginePeer.Id) -> Void let expandTopPosters: () -> Void let expandTopAdmins: () -> Void let expandTopInviters: () -> Void - let setPostersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - let setAdminsPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - let setInvitersPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let setPostersPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let setAdminsPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let setInvitersPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPeer: @escaping (EnginePeer) -> Void, openPeerHistory: @escaping (PeerId) -> Void, openPeerAdminActions: @escaping (PeerId) -> Void, promotePeer: @escaping (PeerId) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void) { + init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPeer: @escaping (EnginePeer) -> Void, openPeerHistory: @escaping (EnginePeer.Id) -> Void, openPeerAdminActions: @escaping (EnginePeer.Id) -> Void, promotePeer: @escaping (EnginePeer.Id) -> Void, expandTopPosters: @escaping () -> Void, expandTopAdmins: @escaping () -> Void, expandTopInviters: @escaping () -> Void, setPostersPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setAdminsPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, setInvitersPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void) { self.context = context self.loadDetailedGraph = loadDetailedGraph self.openPeer = openPeer @@ -93,15 +92,15 @@ private enum StatsEntry: ItemListNodeEntry { case topWeekdaysGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case topPostersTitle(PresentationTheme, String, String) - case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster, Bool, Bool) + case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, GroupStatsTopPoster, Bool, Bool) case topPostersExpand(PresentationTheme, String) case topAdminsTitle(PresentationTheme, String, String) - case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin, Bool, Bool) + case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, GroupStatsTopAdmin, Bool, Bool) case topAdminsExpand(PresentationTheme, String) case topInvitersTitle(PresentationTheme, String, String) - case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter, Bool, Bool) + case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EnginePeer, GroupStatsTopInviter, Bool, Bool) case topInvitersExpand(PresentationTheme, String) var section: ItemListSectionId { @@ -309,7 +308,7 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster, lhsRevealed, lhsCanPromote): - if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { + if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsTopPoster == rhsTopPoster, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { return true } else { return false @@ -327,7 +326,7 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin, lhsRevealed, lhsCanPromote): - if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { + if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsTopAdmin == rhsTopAdmin, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { return true } else { return false @@ -345,7 +344,7 @@ private enum StatsEntry: ItemListNodeEntry { return false } case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter, lhsRevealed, lhsCanPromote): - if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { + if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter, rhsRevealed, rhsCanPromote) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsTopInviter == rhsTopInviter, lhsRevealed == rhsRevealed, lhsCanPromote == rhsCanPromote { return true } else { return false @@ -411,8 +410,8 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(EnginePeer(peer)) + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -442,8 +441,8 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(EnginePeer(peer)) + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -465,8 +464,8 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer), height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { - arguments.openPeer(EnginePeer(peer)) + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId) }, removePeer: { _ in }) @@ -478,7 +477,7 @@ private enum StatsEntry: ItemListNodeEntry { } } -private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStatsState, data: GroupStats?, channelPeer: Peer, peers: [EnginePeer.Id: EnginePeer]?, presentationData: PresentationData) -> [StatsEntry] { +private func groupStatsControllerEntries(accountPeerId: EnginePeer.Id, state: GroupStatsState, data: GroupStats?, channelPeer: EnginePeer, peers: [EnginePeer.Id: EnginePeer]?, presentationData: PresentationData) -> [StatsEntry] { var entries: [StatsEntry] = [] if let data = data { @@ -545,7 +544,7 @@ private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStat for topPoster in topPosters { if let peer = peers[topPoster.peerId], topPoster.messageCount > 0 { - entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions, canPromote)) + entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster, topPoster.peerId == state.posterPeerIdWithRevealedOptions, canPromote)) index += 1 } } @@ -568,7 +567,7 @@ private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStat for topAdmin in data.topAdmins { if let peer = peers[topAdmin.peerId], (topAdmin.deletedCount + topAdmin.kickedCount + topAdmin.bannedCount) > 0 { - entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions, canPromote)) + entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin, topAdmin.peerId == state.adminPeerIdWithRevealedOptions, canPromote)) index += 1 } } @@ -591,7 +590,7 @@ private func groupStatsControllerEntries(accountPeerId: PeerId, state: GroupStat for topInviter in data.topInviters { if let peer = peers[topInviter.peerId], topInviter.inviteCount > 0 { - entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer._asPeer(), topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions, canPromote)) + entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter, topInviter.peerId == state.inviterPeerIdWithRevealedOptions, canPromote)) index += 1 } } @@ -610,9 +609,9 @@ private struct GroupStatsState: Equatable { let topPostersExpanded: Bool let topAdminsExpanded: Bool let topInvitersExpanded: Bool - let posterPeerIdWithRevealedOptions: PeerId? - let adminPeerIdWithRevealedOptions: PeerId? - let inviterPeerIdWithRevealedOptions: PeerId? + let posterPeerIdWithRevealedOptions: EnginePeer.Id? + let adminPeerIdWithRevealedOptions: EnginePeer.Id? + let inviterPeerIdWithRevealedOptions: EnginePeer.Id? init() { self.topPostersExpanded = false @@ -623,7 +622,7 @@ private struct GroupStatsState: Equatable { self.inviterPeerIdWithRevealedOptions = nil } - init(topPostersExpanded: Bool, topAdminsExpanded: Bool, topInvitersExpanded: Bool, posterPeerIdWithRevealedOptions: PeerId?, adminPeerIdWithRevealedOptions: PeerId?, inviterPeerIdWithRevealedOptions: PeerId?) { + init(topPostersExpanded: Bool, topAdminsExpanded: Bool, topInvitersExpanded: Bool, posterPeerIdWithRevealedOptions: EnginePeer.Id?, adminPeerIdWithRevealedOptions: EnginePeer.Id?, inviterPeerIdWithRevealedOptions: EnginePeer.Id?) { self.topPostersExpanded = topPostersExpanded self.topAdminsExpanded = topAdminsExpanded self.topInvitersExpanded = topInvitersExpanded @@ -666,21 +665,21 @@ private struct GroupStatsState: Equatable { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: topInvitersExpanded, posterPeerIdWithRevealedOptions: self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: self.inviterPeerIdWithRevealedOptions) } - func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { + func withUpdatedPosterPeerIdWithRevealedOptions(_ posterPeerIdWithRevealedOptions: EnginePeer.Id?) -> GroupStatsState { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: posterPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions) } - func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { + func withUpdatedAdminPeerIdWithRevealedOptions(_ adminPeerIdWithRevealedOptions: EnginePeer.Id?) -> GroupStatsState { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: adminPeerIdWithRevealedOptions != nil ? nil : self.inviterPeerIdWithRevealedOptions) } - func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: PeerId?) -> GroupStatsState { + func withUpdatedInviterPeerIdWithRevealedOptions(_ inviterPeerIdWithRevealedOptions: EnginePeer.Id?) -> GroupStatsState { return GroupStatsState(topPostersExpanded: self.topPostersExpanded, topAdminsExpanded: self.topAdminsExpanded, topInvitersExpanded: self.topInvitersExpanded, posterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.posterPeerIdWithRevealedOptions, adminPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions != nil ? nil : self.adminPeerIdWithRevealedOptions, inviterPeerIdWithRevealedOptions: inviterPeerIdWithRevealedOptions) } } -private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initialParticipant: ChannelParticipant?) -> Bool { - if let channel = channelPeer as? TelegramChannel { +private func canEditAdminRights(accountPeerId: EnginePeer.Id, channelPeer: EnginePeer, initialParticipant: ChannelParticipant?) -> Bool { + if case let .channel(channel) = channelPeer { if channel.flags.contains(.isCreator) { return true } else if let initialParticipant = initialParticipant { @@ -697,7 +696,7 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } else { return channel.hasPermission(.addAdmins) } - } else if let group = channelPeer as? TelegramGroup { + } else if case let .legacyGroup(group) = channelPeer { if case .creator = group.role { return true } else { @@ -708,7 +707,7 @@ private func canEditAdminRights(accountPeerId: PeerId, channelPeer: Peer, initia } } -public func groupStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, statsDatacenterId: Int32?) -> ViewController { +public func groupStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, statsDatacenterId: Int32?) -> ViewController { let statePromise = ValuePromise(GroupStatsState()) let stateValue = Atomic(value: GroupStatsState()) let updateState: ((GroupStatsState) -> GroupStatsState) -> Void = { f in @@ -722,12 +721,11 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat let datacenterId: Int32 = statsDatacenterId ?? 0 var openPeerImpl: ((EnginePeer) -> Void)? - var openPeerHistoryImpl: ((PeerId) -> Void)? - var openPeerAdminActionsImpl: ((PeerId) -> Void)? - var promotePeerImpl: ((PeerId) -> Void)? + var openPeerHistoryImpl: ((EnginePeer.Id) -> Void)? + var openPeerAdminActionsImpl: ((EnginePeer.Id) -> Void)? + var promotePeerImpl: ((EnginePeer.Id) -> Void)? - let peerView = Promise() - peerView.set(context.account.viewTracker.peerView(peerId, updateData: true)) + actionsDisposable.add(context.account.viewTracker.peerView(peerId, updateData: true).start()) let statsContext = GroupStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId) let dataSignal: Signal = statsContext.state @@ -844,7 +842,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: channelPeer, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(accountPeerId: context.account.peerId, state: state, data: data, channelPeer: EnginePeer(channelPeer), peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index ce419b28e0..2a84ae83e6 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -18,9 +17,9 @@ import GraphUI private final class MessageStatsControllerArguments { let context: AccountContext let loadDetailedGraph: (StatsGraph, Int64) -> Signal - let openMessage: (MessageId) -> Void + let openMessage: (EngineMessage.Id) -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openMessage: @escaping (MessageId) -> Void) { + init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openMessage: @escaping (EngineMessage.Id) -> Void) { self.context = context self.loadDetailedGraph = loadDetailedGraph self.openMessage = openMessage @@ -41,7 +40,7 @@ private enum StatsEntry: ItemListNodeEntry { case interactionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType) case publicForwardsTitle(PresentationTheme, String) - case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message) + case publicForward(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, EngineMessage) var section: ItemListSectionId { switch self { @@ -176,7 +175,7 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search entries.append(.publicForwardsTitle(presentationData.theme, presentationData.strings.Stats_MessagePublicForwardsTitle.uppercased())) var index: Int32 = 0 for message in messages.messages { - entries.append(.publicForward(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, message)) + entries.append(.publicForward(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, EngineMessage(message))) index += 1 } } @@ -185,8 +184,8 @@ private func messageStatsControllerEntries(data: MessageStats?, messages: Search return entries } -public func messageStatsController(context: AccountContext, messageId: MessageId, statsDatacenterId: Int32?) -> ViewController { - var navigateToMessageImpl: ((MessageId) -> Void)? +public func messageStatsController(context: AccountContext, messageId: EngineMessage.Id, statsDatacenterId: Int32?) -> ViewController { + var navigateToMessageImpl: ((EngineMessage.Id) -> Void)? let actionsDisposable = DisposableSet() let dataPromise = Promise(nil) diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index c085b6ccec..ed3eeee0b1 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -965,7 +965,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { } } - items.append(VoiceChatPeerActionSheetItem(context: context, peer: peer.peer, title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { + items.append(VoiceChatPeerActionSheetItem(context: context, peer: EnginePeer(peer.peer), title: EnginePeer(peer.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), subtitle: subtitle ?? "", action: { dismissAction() completion(peer.peer.id) })) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 61283c8744..b2293dd85e 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -496,7 +496,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController text = .text(about, textIcon, .generic) } - return VoiceChatTileItem(account: context.account, peer: peerEntry.peer, videoEndpointId: videoEndpointId, videoReady: videoReady, videoTimeouted: videoTimeouted, isVideoLimit: false, videoLimit: 0, isPaused: videoIsPaused, isOwnScreencast: peerEntry.presentationEndpointId == videoEndpointId && peerEntry.isMyPeer, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, secondary: secondary, isTablet: isTablet, icon: showAsPresentation ? .presentation : icon, text: text, additionalText: additionalText, action: { + return VoiceChatTileItem(account: context.account, peer: EnginePeer(peerEntry.peer), videoEndpointId: videoEndpointId, videoReady: videoReady, videoTimeouted: videoTimeouted, isVideoLimit: false, videoLimit: 0, isPaused: videoIsPaused, isOwnScreencast: peerEntry.presentationEndpointId == videoEndpointId && peerEntry.isMyPeer, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, speaking: speaking, secondary: secondary, isTablet: isTablet, icon: showAsPresentation ? .presentation : icon, text: text, additionalText: additionalText, action: { interaction.switchToPeer(peer.id, videoEndpointId, !secondary) }, contextAction: { node, gesture in interaction.peerContextAction(peerEntry, node, gesture, false) @@ -2709,7 +2709,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController return } - let controller = VoiceChatRecordingSetupController(context: strongSelf.context, peer: peer, completion: { [weak self] videoOrientation in + let controller = VoiceChatRecordingSetupController(context: strongSelf.context, peer: EnginePeer(peer), completion: { [weak self] videoOrientation in if let strongSelf = self { let title: String let text: String @@ -5282,7 +5282,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController tileItems.removeAll() gridTileItems.removeAll() - tileItems.append(VoiceChatTileItem(account: self.context.account, peer: peer, videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, videoLimit: configuration.videoParticipantsMaxCount, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil)) + tileItems.append(VoiceChatTileItem(account: self.context.account, peer: EnginePeer(peer), videoEndpointId: "", videoReady: false, videoTimeouted: true, isVideoLimit: true, videoLimit: configuration.videoParticipantsMaxCount, isPaused: false, isOwnScreencast: false, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, speaking: false, secondary: false, isTablet: false, icon: .none, text: .none, additionalText: nil, action: {}, contextAction: nil, getVideo: { _ in return nil }, getAudioLevel: nil)) } else if let callState = self.callState, !tileItems.isEmpty && callState.isVideoWatchersLimitReached && self.connectedOnce && (callState.canManageCall || callState.adminIds.contains(self.context.account.peerId)) { reachedLimit = true } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 7310677131..af8d621318 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -383,7 +383,7 @@ class VoiceChatFullscreenParticipantItemNode: ItemListRevealOptionsItemNode { if let videoNode = self.videoNode, videoNode.supernode == self.videoContainerNode, !videoNode.alpha.isZero { hasVideo = true } - let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: item.peer, text: item.text, customNode: hasVideo ? self.videoContainerNode : nil, additionalEntry: .single(nil), requestDismiss: { [weak self] in + let profileNode = VoiceChatPeerProfileNode(context: item.context, size: extractedRect.size, sourceSize: nonExtractedRect.size, peer: EnginePeer(item.peer), text: item.text, customNode: hasVideo ? self.videoContainerNode : nil, additionalEntry: .single(nil), requestDismiss: { [weak self] in self?.contextSourceNode.requestDismiss?() }) profileNode.frame = CGRect(origin: CGPoint(), size: extractedRect.size) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift index 4b0ea7ee4c..7e724bd582 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatOverlayController.swift @@ -8,7 +8,6 @@ import TelegramUIPreferences import TelegramVoip import TelegramAudio import AccountContext -import Postbox import TelegramCore import AppBundle import ContextUI diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index b989d5d491..8af9954d51 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -512,7 +512,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context) avatarListWrapperNode.contentNode.clipsToBounds = true avatarListNode.backgroundColor = .clear - avatarListNode.peer = item.peer + avatarListNode.peer = EnginePeer(item.peer) avatarListNode.firstFullSizeOnly = true avatarListNode.offsetLocation = true avatarListNode.customCenterTapAction = { [weak self] in @@ -528,7 +528,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode) avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode) - avatarListNode.update(size: targetRect.size, peer: item.peer, customNode: nil, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate) + avatarListNode.update(size: targetRect.size, peer: EnginePeer(item.peer), customNode: nil, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate) strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode) strongSelf.audioLevelView?.alpha = 0.0 diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift index bd1eb8bb1f..16e876cb8d 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatPeerActionSheetItem.swift @@ -3,19 +3,18 @@ import UIKit import AsyncDisplayKit import Display import TelegramCore -import Postbox import TelegramPresentationData import AvatarNode import AccountContext public class VoiceChatPeerActionSheetItem: ActionSheetItem { public let context: AccountContext - public let peer: Peer + public let peer: EnginePeer public let title: String public let subtitle: String public let action: () -> Void - public init(context: AccountContext, peer: Peer, title: String, subtitle: String, action: @escaping () -> Void) { + public init(context: AccountContext, peer: EnginePeer, title: String, subtitle: String, action: @escaping () -> Void) { self.context = context self.peer = peer self.title = title @@ -120,7 +119,7 @@ public class VoiceChatPeerActionSheetItemNode: ActionSheetItemNode { self.subtitleNode.attributedText = NSAttributedString(string: item.subtitle, font: subtitleFont, textColor: self.theme.secondaryTextColor) let theme = item.context.sharedContext.currentPresentationData.with { $0 }.theme - self.avatarNode.setPeer(context: item.context, theme: theme, peer: EnginePeer(item.peer)) + self.avatarNode.setPeer(context: item.context, theme: theme, peer: item.peer) self.accessibilityArea.accessibilityTraits = [.button] self.accessibilityArea.accessibilityLabel = item.title diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift index 859b8302cc..9742a7f585 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatPeerProfileNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -20,7 +19,7 @@ private let backgroundCornerRadius: CGFloat = 14.0 final class VoiceChatPeerProfileNode: ASDisplayNode { private let context: AccountContext private let size: CGSize - private var peer: Peer + private var peer: EnginePeer private var text: VoiceChatParticipantItem.ParticipantText private let customNode: ASDisplayNode? private let additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError> @@ -36,7 +35,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { private var appeared = false - init(context: AccountContext, size: CGSize, sourceSize: CGSize, peer: Peer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) { + init(context: AccountContext, size: CGSize, sourceSize: CGSize, peer: EnginePeer, text: VoiceChatParticipantItem.ParticipantText, customNode: ASDisplayNode? = nil, additionalEntry: Signal<(TelegramMediaImageRepresentation, Float)?, NoError>, requestDismiss: (() -> Void)?) { self.context = context self.size = size self.peer = peer @@ -129,7 +128,7 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { let titleFont = Font.regular(17.0) let titleColor = UIColor.white var titleAttributedString: NSAttributedString? - if let user = self.peer as? TelegramUser { + if case let .user(user) = self.peer { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch presentationData.nameDisplayOrder { @@ -150,9 +149,9 @@ final class VoiceChatPeerProfileNode: ASDisplayNode { } else { titleAttributedString = NSAttributedString(string: presentationData.strings.User_DeletedAccount, font: titleFont, textColor: titleColor) } - } else if let group = peer as? TelegramGroup { + } else if case let .legacyGroup(group) = peer { titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor) - } else if let channel = peer as? TelegramChannel { + } else if case let .channel(channel) = peer { titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor) } self.titleNode.attributedText = titleAttributedString diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift index b53b66f7a8..f03d96ef09 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -18,14 +17,14 @@ final class VoiceChatRecordingSetupController: ViewController { } private let context: AccountContext - private let peer: Peer + private let peer: EnginePeer private let completion: (Bool?) -> Void private var animatedIn = false private var presentationDataDisposable: Disposable? - init(context: AccountContext, peer: Peer, completion: @escaping (Bool?) -> Void) { + init(context: AccountContext, peer: EnginePeer, completion: @escaping (Bool?) -> Void) { self.context = context self.peer = peer self.completion = completion @@ -149,7 +148,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, var dismiss: (() -> Void)? var cancel: (() -> Void)? - init(controller: VoiceChatRecordingSetupController, context: AccountContext, peer: Peer) { + init(controller: VoiceChatRecordingSetupController, context: AccountContext, peer: EnginePeer) { self.controller = controller self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -183,7 +182,7 @@ private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, self.contentBackgroundNode.backgroundColor = backgroundColor let isLivestream: Bool - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isLivestream = true } else { isLivestream = false diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift index df22c4536f..41f6d0c6ef 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTileItemNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramUIPreferences @@ -23,7 +22,7 @@ final class VoiceChatTileItem: Equatable { } let account: Account - let peer: Peer + let peer: EnginePeer let videoEndpointId: String let videoReady: Bool let videoTimeouted: Bool @@ -48,7 +47,7 @@ final class VoiceChatTileItem: Equatable { return self.videoEndpointId } - init(account: Account, peer: Peer, videoEndpointId: String, videoReady: Bool, videoTimeouted: Bool, isVideoLimit: Bool, videoLimit: Int32, isPaused: Bool, isOwnScreencast: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, secondary: Bool, isTablet: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping (GroupVideoNode.Position) -> GroupVideoNode?, getAudioLevel: (() -> Signal)?) { + init(account: Account, peer: EnginePeer, videoEndpointId: String, videoReady: Bool, videoTimeouted: Bool, isVideoLimit: Bool, videoLimit: Int32, isPaused: Bool, isOwnScreencast: Bool, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, speaking: Bool, secondary: Bool, isTablet: Bool, icon: Icon, text: VoiceChatParticipantItem.ParticipantText, additionalText: VoiceChatParticipantItem.ParticipantText?, action: @escaping () -> Void, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?, getVideo: @escaping (GroupVideoNode.Position) -> GroupVideoNode?, getAudioLevel: (() -> Signal)?) { self.account = account self.peer = peer self.videoEndpointId = videoEndpointId @@ -73,7 +72,7 @@ final class VoiceChatTileItem: Equatable { } static func == (lhs: VoiceChatTileItem, rhs: VoiceChatTileItem) -> Bool { - if !arePeersEqual(lhs.peer, rhs.peer) { + if lhs.peer != rhs.peer { return false } if lhs.videoEndpointId != rhs.videoEndpointId { @@ -451,7 +450,7 @@ final class VoiceChatTileItemNode: ASDisplayNode { var titleAttributedString: NSAttributedString? if item.isVideoLimit { titleAttributedString = nil - } else if let user = item.peer as? TelegramUser { + } else if case let .user(user) = item.peer { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch item.nameDisplayOrder { @@ -472,9 +471,9 @@ final class VoiceChatTileItemNode: ASDisplayNode { } else { titleAttributedString = NSAttributedString(string: item.strings.User_DeletedAccount, font: titleFont, textColor: titleColor) } - } else if let group = item.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = item.peer { titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor) - } else if let channel = item.peer as? TelegramChannel { + } else if case let .channel(channel) = item.peer { titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor) } @@ -910,7 +909,7 @@ private class VoiceChatTileShimmeringNode: ASDisplayNode { private var currentShimmering: Bool? private var currentSize: CGSize? - public init(account: Account, peer: Peer) { + public init(account: Account, peer: EnginePeer) { self.backgroundNode = ImageNode(enableHasImage: false, enableEmpty: false, enableAnimatedTransition: true) self.backgroundNode.displaysAsynchronously = false self.backgroundNode.contentMode = .scaleAspectFill @@ -930,7 +929,7 @@ private class VoiceChatTileShimmeringNode: ASDisplayNode { self.addSubnode(self.borderNode) self.borderNode.addSubnode(self.borderEffectNode) - self.backgroundNode.setSignal(peerAvatarCompleteImage(account: account, peer: EnginePeer(peer), size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true)) + self.backgroundNode.setSignal(peerAvatarCompleteImage(account: account, peer: peer, size: CGSize(width: 250.0, height: 250.0), round: false, font: Font.regular(16.0), drawLetters: false, fullSize: false, blurred: true)) } public override func didLoad() { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift index 4aee77ae3c..1ba276b3db 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTitleEditController.swift @@ -3,7 +3,6 @@ import UIKit import SwiftSignalKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import AccountContext diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index a478bb3e8a..ef4ca53c0f 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1282,7 +1282,7 @@ public class Account { return _internal_reindexCacheInBackground(account: self, lowImpact: lowImpact) |> then( Signal { subscriber in - return postbox.mediaBox.updateResourceIndex(lowImpact: lowImpact, completion: { + return postbox.mediaBox.updateResourceIndex(otherResourceContentType: MediaResourceUserContentType.other.rawValue, lowImpact: lowImpact, completion: { subscriber.putCompletion() }) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index d8c8d6d550..252e52681e 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -389,7 +389,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } } -public final class TelegramMediaAction: Media { +public final class TelegramMediaAction: Media, Equatable { public let id: MediaId? = nil public var peerIds: [PeerId] { return self.action.peerIds @@ -416,6 +416,10 @@ public final class TelegramMediaAction: Media { self.action.encode(encoder) } + public static func ==(lhs: TelegramMediaAction, rhs: TelegramMediaAction) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaAction { return self.action == other.action diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift index 184a367271..2a1fd8aeb0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaContact.swift @@ -1,6 +1,6 @@ import Postbox -public final class TelegramMediaContact: Media { +public final class TelegramMediaContact: Media, Equatable { public let id: MediaId? = nil public let firstName: String public let lastName: String @@ -51,6 +51,10 @@ public final class TelegramMediaContact: Media { } } + public static func ==(lhs: TelegramMediaContact, rhs: TelegramMediaContact) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaContact { if self.id == other.id && self.firstName == other.firstName && self.lastName == other.lastName && self.phoneNumber == other.phoneNumber && self.peerId == other.peerId && self.vCardData == other.vCardData && self.peerIds == other.peerIds { @@ -63,4 +67,4 @@ public final class TelegramMediaContact: Media { public func isSemanticallyEqual(to other: Media) -> Bool { return self.isEqual(to: other) } -} \ No newline at end of file +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift index 5a569fa0dd..d142f4d89d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaDice.swift @@ -1,6 +1,6 @@ import Postbox -public final class TelegramMediaDice: Media { +public final class TelegramMediaDice: Media, Equatable { public let emoji: String public let value: Int32? @@ -26,6 +26,10 @@ public final class TelegramMediaDice: Media { } } + public static func ==(lhs: TelegramMediaDice, rhs: TelegramMediaDice) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaDice { if self.emoji != other.emoji { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift index 17f650d664..cfa0f73ca6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaExpiredContent.swift @@ -6,7 +6,7 @@ public enum TelegramMediaExpiredContentData: Int32 { case file } -public final class TelegramMediaExpiredContent: Media { +public final class TelegramMediaExpiredContent: Media, Equatable { public let data: TelegramMediaExpiredContentData public let id: MediaId? = nil @@ -24,6 +24,10 @@ public final class TelegramMediaExpiredContent: Media { encoder.encodeInt32(self.data.rawValue, forKey: "d") } + public static func ==(lhs: TelegramMediaExpiredContent, rhs: TelegramMediaExpiredContent) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaExpiredContent { return self.data == other.data diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift index 981a067c14..93fb7d8e03 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaGame.swift @@ -1,6 +1,6 @@ import Postbox -public final class TelegramMediaGame: Media { +public final class TelegramMediaGame: Media, Equatable { public let gameId: Int64 public let accessHash: Int64 public let name: String @@ -52,6 +52,10 @@ public final class TelegramMediaGame: Media { } } + public static func ==(lhs: TelegramMediaGame, rhs: TelegramMediaGame) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { guard let other = other as? TelegramMediaGame else { return false diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift index b919f16065..3cb14d3ea0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaInvoice.swift @@ -87,7 +87,7 @@ public enum TelegramExtendedMedia: PostboxCoding, Equatable { } } -public final class TelegramMediaInvoice: Media { +public final class TelegramMediaInvoice: Media, Equatable { public static let lastVersion: Int32 = 1 public var peerIds: [PeerId] = [] @@ -171,6 +171,10 @@ public final class TelegramMediaInvoice: Media { encoder.encodeInt32(self.version, forKey: "vrs") } + public static func ==(lhs: TelegramMediaInvoice, rhs: TelegramMediaInvoice) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { guard let other = other as? TelegramMediaInvoice else { return false diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift index 3e9d71321f..e4fa801154 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaMap.swift @@ -130,7 +130,7 @@ public final class MapVenue: PostboxCoding, Equatable { } } -public final class TelegramMediaMap: Media { +public final class TelegramMediaMap: Media, Equatable { public let latitude: Double public let longitude: Double public let heading: Int32? @@ -200,6 +200,10 @@ public final class TelegramMediaMap: Media { } } + public static func ==(lhs: TelegramMediaMap, rhs: TelegramMediaMap) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if let other = other as? TelegramMediaMap { if self.latitude != other.latitude || self.longitude != other.longitude { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift index 9995166213..63cbf795b8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaUnsupported.swift @@ -2,7 +2,7 @@ import Foundation import Postbox -public final class TelegramMediaUnsupported: Media { +public final class TelegramMediaUnsupported: Media, Equatable { public let id: MediaId? = nil public let peerIds: [PeerId] = [] @@ -15,6 +15,10 @@ public final class TelegramMediaUnsupported: Media { public func encode(_ encoder: PostboxEncoder) { } + public static func ==(lhs: TelegramMediaUnsupported, rhs: TelegramMediaUnsupported) -> Bool { + return lhs.isEqual(to: rhs) + } + public func isEqual(to other: Media) -> Bool { if other is TelegramMediaUnsupported { return true diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift index 54a7f7ecf7..a590cc269c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Media.swift @@ -1,6 +1,6 @@ import Postbox -public enum EngineMedia { +public enum EngineMedia: Equatable { public typealias Id = MediaId case image(TelegramMediaImage) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index 82a3ab83af..853acdacf7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -3,6 +3,17 @@ import SwiftSignalKit import Postbox import TelegramApi +public enum MediaResourceUserContentType: UInt8, Equatable { + case other = 0 + case image = 1 + case video = 2 + case audio = 3 + case file = 4 + case sticker = 6 + case avatar = 7 + case audioVideoMessage = 8 +} + public extension MediaResourceUserContentType { init(file: TelegramMediaFile) { if file.isInstantVideo || file.isVoice { @@ -25,6 +36,12 @@ public extension MediaResourceUserContentType { } } +public extension MediaResourceFetchParameters { + init(tag: MediaResourceFetchTag?, info: MediaResourceFetchInfo?, location: MediaResourceStorageLocation?, contentType: MediaResourceUserContentType, isRandomAccessAllowed: Bool) { + self.init(tag: tag, info: info, location: location, contentType: contentType.rawValue, isRandomAccessAllowed: isRandomAccessAllowed) + } +} + func bufferedFetch(_ signal: Signal) -> Signal { return Signal { subscriber in final class State { @@ -251,7 +268,7 @@ public extension TelegramEngine { return _internal_reindexCacheInBackground(account: self.account, lowImpact: lowImpact) |> then(Signal { subscriber in - return mediaBox.updateResourceIndex(lowImpact: lowImpact, completion: { + return mediaBox.updateResourceIndex(otherResourceContentType: MediaResourceUserContentType.other.rawValue, lowImpact: lowImpact, completion: { subscriber.putCompletion() }) }) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index 9e139f7c9a..bb0fbf6763 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -92,6 +92,28 @@ public struct ImportSticker { } } +public extension ImportSticker { + var stickerPackItem: StickerPackItem? { + guard let resource = self.resource as? TelegramMediaResource else { + return nil + } + var fileAttributes: [TelegramMediaFileAttribute] = [] + if self.mimeType == "video/webm" { + fileAttributes.append(.FileName(fileName: "sticker.webm")) + fileAttributes.append(.Animated) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + } else if self.mimeType == "application/x-tgsticker" { + fileAttributes.append(.FileName(fileName: "sticker.tgs")) + fileAttributes.append(.Animated) + fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) + } else { + fileAttributes.append(.FileName(fileName: "sticker.webp")) + } + fileAttributes.append(.ImageSize(size: self.dimensions)) + return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: self.mimeType, size: nil, attributes: fileAttributes), indexKeys: []) + } +} + public enum CreateStickerSetStatus { case progress(Float, Int32, Int32) case complete(StickerPackCollectionInfo, [StickerPackItem]) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 52087d1c77..dbfea1dce2 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import AppBundle diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index aedf979642..7b5ead0a17 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -294,8 +294,6 @@ swift_library( "//submodules/AdUI:AdUI", "//submodules/SparseItemGrid:SparseItemGrid", "//submodules/CalendarMessageScreen:CalendarMessageScreen", - "//submodules/LottieMeshSwift:LottieMeshSwift", - "//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/DirectMediaImageCache:DirectMediaImageCache", "//submodules/CodeInputView:CodeInputView", "//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift index 9465e1d7c5..369dfc812d 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift @@ -5,7 +5,6 @@ import ComponentFlow import PagerComponent import TelegramPresentationData import TelegramCore -import Postbox import ComponentDisplayAdapters import BundleIconComponent diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 9db9edbbb1..8bda5b9955 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -16,7 +16,6 @@ import TelegramCallsUI import TelegramBaseController import AsyncDisplayKit import PresentationDataUtils -import MeshAnimationCache import FetchManagerImpl import InAppPurchaseManager import AnimationCache @@ -168,7 +167,6 @@ public final class AccountContextImpl: AccountContext { private var experimentalUISettingsDisposable: Disposable? public let cachedGroupCallContexts: AccountGroupCallContextCache - public let meshAnimationCache: MeshAnimationCache public let animationCache: AnimationCache public let animationRenderer: MultiAnimationRenderer @@ -281,7 +279,6 @@ public final class AccountContextImpl: AccountContext { } self.cachedGroupCallContexts = AccountGroupCallContextCacheImpl() - self.meshAnimationCache = MeshAnimationCache(mediaBox: account.postbox.mediaBox) let cacheStorageBox = self.account.postbox.mediaBox.cacheStorageBox self.animationCache = AnimationCacheImpl(basePath: self.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d1e479f861..1c434dca0a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -66,7 +66,6 @@ import WallpaperBackgroundNode import ChatListUI import CalendarMessageScreen import ReactionSelectionNode -import LottieMeshSwift import ReactionListContextMenuContent import AttachmentUI import AttachmentTextInputPanelNode @@ -3859,7 +3858,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let galleryController = AvatarGalleryController(context: context, peer: peer._asPeer(), remoteEntries: nil, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in }, synchronousLoad: true) galleryController.setHintWillBePresentedInPreviewingContext(true) @@ -4521,7 +4520,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer, peer.smallProfileImage != nil else { return } - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in }, synchronousLoad: true) galleryController.setHintWillBePresentedInPreviewingContext(true) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index 5cf74c2ebc..c420091dd2 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -25,7 +25,6 @@ import ShimmerEffect import WallpaperBackgroundNode import LocalMediaResources import AppBundle -import LottieMeshSwift import ChatPresentationInterfaceState import TextNodeWithEntities import ChatControllerInteraction @@ -2009,38 +2008,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let incomingMessage = item.message.effectivelyIncoming(item.context.account.peerId) - if #available(iOS 13.0, *), !"".isEmpty, item.context.sharedContext.immediateExperimentalUISettings.acceleratedStickers, let meshAnimation = item.context.meshAnimationCache.get(resource: resource) { - var overlayMeshAnimationNode: ChatMessageTransitionNode.DecorationItemNode? - if let current = self.overlayMeshAnimationNode { - overlayMeshAnimationNode = current - } else { - if let animationView = MeshRenderer() { - let animationFrame = animationNodeFrame.insetBy(dx: -animationNodeFrame.width, dy: -animationNodeFrame.height) - .offsetBy(dx: incomingMessage ? animationNodeFrame.width - 10.0 : -animationNodeFrame.width + 10.0, dy: 0.0) - animationView.frame = animationFrame - - animationView.allAnimationsCompleted = { [weak transitionNode, weak animationView, weak self] in - guard let strongSelf = self, let animationView = animationView else { - return - } - guard let overlayMeshAnimationNode = strongSelf.overlayMeshAnimationNode else { - return - } - if overlayMeshAnimationNode.contentView !== animationView { - return - } - strongSelf.overlayMeshAnimationNode = nil - transitionNode?.remove(decorationNode: overlayMeshAnimationNode) - } - - overlayMeshAnimationNode = transitionNode.add(decorationView: animationView, itemNode: self) - self.overlayMeshAnimationNode = overlayMeshAnimationNode - } - } - if let meshRenderer = overlayMeshAnimationNode?.contentView as? MeshRenderer { - meshRenderer.add(mesh: meshAnimation, offset: CGPoint(x: CGFloat.random(in: -30.0 ... 30.0), y: CGFloat.random(in: -30.0 ... 30.0))) - } - } else { + do { let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) let additionalAnimationNode = DefaultAnimatedStickerNodeImpl() additionalAnimationNode.setup(source: source, width: Int(animationSize.width * 1.6), height: Int(animationSize.height * 1.6), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index c0a3cd4c3d..cb75a7ff71 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -335,7 +335,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } case .playbackStatus: - if let context = self.context, let message = self.message, let type = peerMessageMediaPlayerType(message) { + if let context = self.context, let message = self.message, let type = peerMessageMediaPlayerType(EngineMessage(message)) { context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type) } } @@ -543,19 +543,19 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { if statusUpdated { if arguments.message.flags.isSending { - updatedStatusSignal = combineLatest(messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions), messageMediaFileStatus(context: arguments.context, messageId: arguments.message.id, file: arguments.file)) + updatedStatusSignal = combineLatest(messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions), messageMediaFileStatus(context: arguments.context, messageId: arguments.message.id, file: arguments.file)) |> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, actualFetchStatus) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) } else { - updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions) + updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions) |> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in return (resourceStatus, nil) } - updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) + updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) } - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: EngineMessage(arguments.message), isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false) } var isAudio = false @@ -1196,7 +1196,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { peak: audioWaveform?.peak ?? 0, status: strongSelf.playbackStatus.get(), seek: { timestamp in - if let strongSelf = self, let context = strongSelf.context, let message = strongSelf.message, let type = peerMessageMediaPlayerType(message) { + if let strongSelf = self, let context = strongSelf.context, let message = strongSelf.message, let type = peerMessageMediaPlayerType(EngineMessage(message)) { context.sharedContext.mediaManager.playlistControl(.seek(timestamp), type: type) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift index 2beb1ff859..f7492a9ec5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveInstantVideoNode.swift @@ -416,7 +416,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { var updatedPlaybackStatus: Signal? if let updatedFile = updatedFile, updatedMedia || updatedMessageId { - updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: item.message, isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id) |> map { $0.0 }) + updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: EngineMessage(item.message), isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in if let pendingStatus = pendingStatus { var progress = pendingStatus.progress @@ -1200,7 +1200,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { } playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5) - let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false, isDownloadList: false) + let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: EngineMessage(item.message), isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false, isDownloadList: false) playbackStatusNode.status = status self.durationNode?.status = status |> map(Optional.init) diff --git a/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift b/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift index 4aa1bf13af..6b0550d34b 100644 --- a/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift +++ b/submodules/TelegramUI/Sources/ChatOverlayNavigationBar.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 87f9351a43..b4555bbca1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2586,7 +2586,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.state = state self.peer = peer self.threadData = threadData - self.avatarListNode.listContainerNode.peer = peer + self.avatarListNode.listContainerNode.peer = peer.flatMap(EnginePeer.init) let isFirstTime = self.validLayout == nil self.validLayout = (width, deviceMetrics) @@ -3382,7 +3382,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.avatarListNode.containerNode.view.mask = nil } - self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition) + self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer.flatMap(EnginePeer.init), isExpanded: self.isAvatarExpanded, transition: transition) if self.avatarListNode.listContainerNode.isCollapsing && !self.ignoreCollapse { self.avatarListNode.avatarContainerNode.canAttachVideo = false } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index d7cda1b9b4..734ece2992 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -3027,7 +3027,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } let entriesPromise = Promise<[AvatarGalleryEntry]>(entries) - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceCorners: .round, remoteEntries: entriesPromise, skipInitial: true, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), sourceCorners: .round, remoteEntries: entriesPromise, skipInitial: true, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in }) galleryController.openAvatarSetup = { [weak self] completion in self?.openAvatarForEditing(fromGallery: true, completion: { _ in @@ -3728,7 +3728,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate })) ] - let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, remoteEntries: nil, replaceRootController: { controller, ready in + let galleryController = AvatarGalleryController(context: strongSelf.context, peer: EnginePeer(peer), remoteEntries: nil, replaceRootController: { controller, ready in }, synchronousLoad: true) galleryController.setHintWillBePresentedInPreviewingContext(true) diff --git a/submodules/TelegramUI/Sources/PrefetchManager.swift b/submodules/TelegramUI/Sources/PrefetchManager.swift index 60b69c7f6e..4bc128c4f0 100644 --- a/submodules/TelegramUI/Sources/PrefetchManager.swift +++ b/submodules/TelegramUI/Sources/PrefetchManager.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import TelegramUIPreferences import AccountContext @@ -29,7 +28,7 @@ private final class PrefetchManagerInnerImpl { private var listDisposable: Disposable? - private var contexts: [MediaId: PrefetchMediaContext] = [:] + private var contexts: [EngineMedia.Id: PrefetchMediaContext] = [:] private let preloadGreetingStickerDisposable = MetaDisposable() fileprivate let preloadedGreetingStickerPromise = Promise(nil) @@ -109,7 +108,7 @@ private final class PrefetchManagerInnerImpl { return } #endif - var validIds = Set() + var validIds = Set() var order: Int32 = 0 for mediaItem in items { switch mediaItem { @@ -136,15 +135,15 @@ private final class PrefetchManagerInnerImpl { } else { peerType = .otherPrivate } - var mediaResource: MediaResource? + var mediaResource: EngineMediaResource? if let telegramImage = mediaItem.media.media as? TelegramMediaImage { - mediaResource = largestRepresentationForPhoto(telegramImage)?.resource + mediaResource = (largestRepresentationForPhoto(telegramImage)?.resource).flatMap(EngineMediaResource.init) if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) { automaticDownload = .full } } else if let telegramFile = mediaItem.media.media as? TelegramMediaFile { - mediaResource = telegramFile.resource + mediaResource = EngineMediaResource(telegramFile.resource) if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) { automaticDownload = .full } else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) { @@ -173,7 +172,7 @@ private final class PrefetchManagerInnerImpl { if case .full = automaticDownload { if let image = media as? TelegramMediaImage { - context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerId: nil).start()) + context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource._asResource(), userInitiated: false, priority: priority, storeToDownloadsPeerId: nil).start()) } else if let _ = media as? TelegramMediaWebFile { //strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start()) } else if let file = media as? TelegramMediaFile { @@ -213,7 +212,7 @@ private final class PrefetchManagerInnerImpl { context = PrefetchMediaContext() self.contexts[id] = context - let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, threadId: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: 0, id: order), timestamp: 0)) + let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, threadId: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: EngineMessage.Index(id: EngineMessage.Id(peerId: EnginePeer.Id(0), namespace: 0, id: order), timestamp: 0)) if case .full = automaticDownload { let fetchSignal = freeMediaFileInteractiveFetched(fetchManager: self.fetchManager, fileReference: .standalone(media: media), priority: priority) @@ -224,7 +223,7 @@ private final class PrefetchManagerInnerImpl { } } } - var removeIds: [MediaId] = [] + var removeIds: [EngineMedia.Id] = [] for key in self.contexts.keys { if !validIds.contains(key) { removeIds.append(key) diff --git a/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift b/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift index 0df5c16081..58b471333f 100644 --- a/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift +++ b/submodules/TelegramUI/Sources/PrepareSecretThumbnailData.swift @@ -1,10 +1,10 @@ import Foundation import UIKit -import Postbox import Display +import TelegramCore -func prepareSecretThumbnailData(_ data: MediaResourceData) -> (CGSize, Data)? { - if data.complete, let image = UIImage(contentsOfFile: data.path) { +func prepareSecretThumbnailData(_ data: EngineMediaResource.ResourceData) -> (CGSize, Data)? { + if data.isComplete, let image = UIImage(contentsOfFile: data.path) { if image.size.width < 100 && image.size.height < 100 { if let resultData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { return (image.size, resultData) diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index 459906cc6a..6c8e75a175 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -106,7 +106,7 @@ func makeTelegramAccountAuxiliaryMethods(appDelegate: AppDelegate?) -> AccountAu } return .single(nil) }, prepareSecretThumbnailData: { data in - return prepareSecretThumbnailData(data).flatMap { size, data in + return prepareSecretThumbnailData(EngineMediaResource.ResourceData(data)).flatMap { size, data in return (PixelDimensions(size), data) } }, backgroundUpload: { postbox, _, resource in From 95947fdb997be7785df5f825746e6037fd7d223e Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 20 Apr 2023 18:43:29 +0400 Subject: [PATCH 5/7] Refactoring [skip ci] --- .../ChatListUI/Sources/ChatContextMenus.swift | 2 +- .../Sources/ChannelAdminController.swift | 2 +- .../Sources/ChannelAdminsController.swift | 167 ++++++++++++------ ...elDiscussionGroupSearchContainerNode.swift | 29 ++- ...hannelDiscussionGroupSetupController.swift | 118 ++++++++----- ...hannelDiscussionGroupSetupSearchItem.swift | 9 +- .../Sources/ChannelMembersController.swift | 36 ++-- .../ChannelMembersSearchContainerNode.swift | 65 ++++--- .../ChannelMembersSearchController.swift | 13 +- .../ChannelMembersSearchControllerNode.swift | 2 +- .../ChannelOwnershipTransferController.swift | 25 ++- .../ChannelPermissionsController.swift | 34 ++-- .../Sources/ChannelVisibilityController.swift | 52 +++--- .../ConvertToSupergroupController.swift | 3 +- .../Sources/DeviceContactInfoController.swift | 73 ++++---- .../Sources/GroupInfoSearchItem.swift | 9 +- .../Sources/OldChannelsController.swift | 11 +- .../Sources/OldChannelsSearch.swift | 21 ++- .../Sources/PeerReportController.swift | 7 +- .../Sources/SecretChatKeyController.swift | 5 +- .../Sources/SecretChatKeyControllerNode.swift | 7 +- .../Sources/UserInfoController.swift | 1 - .../Sources/CachedFaqInstantPage.swift | 1 - ...hareProxyServerActionSheetController.swift | 1 - .../Sources/DeleteAccountDataController.swift | 6 +- .../DeleteAccountOptionsController.swift | 3 +- .../Sources/LogoutOptionsController.swift | 3 +- .../NotificationExceptionControllerNode.swift | 91 +++++----- .../NotificationsAndSoundsController.swift | 12 +- .../NotificationsPeerCategoryController.swift | 63 ++++--- .../ConfirmPhoneNumberController.swift | 1 - .../CreatePasswordController.swift | 1 - .../GlobalAutoremoveScreen.swift | 1 - .../PrivacyIntroControllerNode.swift | 1 - .../ItemListRecentSessionItem.swift | 1 - .../RecentSessionsController.swift | 2 +- .../RecentSessionScreen.swift | 7 +- .../SelectivePrivacySettingsController.swift | 35 ++-- .../Search/SettingsSearchableItems.swift | 12 +- .../Themes/WallpaperOptionButtonNode.swift | 1 - .../Watch/WatchSettingsController.swift | 1 - .../Sources/ShareContentContainerNode.swift | 1 - .../ShareControllerRecentPeersGridItem.swift | 1 - .../Sources/CallKitIntegration.swift | 7 +- .../Sources/VoiceChatController.swift | 2 - .../Peers/TelegramEnginePeers.swift | 21 ++- .../Sources/ChatFolderLinkPreviewScreen.swift | 1 - .../Sources/PeerListItemComponent.swift | 1 - .../Sources/ChatTimerScreen.swift | 1 - .../Sources/ForumCreateTopicScreen.swift | 4 +- .../NotificationExceptionsScreen.swift | 2 +- .../NotificationPeerExceptionController.swift | 31 ++-- .../Sources/SendInviteLinkScreen.swift | 1 - .../Sources/DataCategoriesComponent.swift | 1 - .../Sources/DataCategoryItemCompoment.swift | 1 - .../Sources/DataUsageScreen.swift | 1 - .../Sources/PieChartComponent.swift | 1 - .../Sources/SegmentControlComponent.swift | 1 - .../Sources/StorageCategoriesComponent.swift | 1 - .../StorageCategoryItemCompoment.swift | 1 - .../Sources/StorageKeepSizeComponent.swift | 1 - .../StorageMediaGridPanelComponent.swift | 11 +- .../StoragePeerListPanelComponent.swift | 1 - .../StoragePeerTypeItemComponent.swift | 1 - .../Sources/StorageUsageScreen.swift | 14 +- ...geUsageScreenSelectionPanelComponent.swift | 1 - .../ChatMessageAttachedContentNode.swift | 1 - .../Sources/ChatMessageGiftItemNode.swift | 7 +- .../Sources/OwnershipTransferController.swift | 1 - .../Sources/PeerInfo/PeerInfoScreen.swift | 4 +- .../Sources/PeersNearbyManager.swift | 1 - .../Sources/PollResultsController.swift | 19 +- 72 files changed, 562 insertions(+), 514 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 5af6a04022..6459d60996 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -733,7 +733,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: let canRemove = false - let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: channel, threadId: threadId, canRemove: canRemove, defaultSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in + let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: .channel(channel), threadId: threadId, canRemove: canRemove, defaultSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in let _ = (updatePeerSound(peerId, sound) |> deliverOnMainQueue).start(next: { _ in }) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index 05fe16fdbb..4a9ea3b17d 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -909,7 +909,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD } transferOwnershipDisposable.set((context.engine.peers.checkOwnershipTranfserAvailability(memberId: adminId) |> deliverOnMainQueue).start(error: { error in - let controller = channelOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, peer: peer._asPeer(), member: member, initialError: error, present: { c, a in + let controller = channelOwnershipTransferController(context: context, updatedPresentationData: updatedPresentationData, peer: peer, member: member, initialError: error, present: { c, a in presentControllerImpl?(c, a) }, completion: { upgradedPeerId in if let upgradedPeerId = upgradedPeerId { diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 066a127e4c..50dc14e4e8 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -20,13 +19,13 @@ private final class ChannelAdminsControllerArguments { let context: AccountContext let openRecentActions: () -> Void - let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - let removeAdmin: (PeerId) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let removeAdmin: (EnginePeer.Id) -> Void let addAdmin: () -> Void let openAdmin: (ChannelParticipant) -> Void let updateAntiSpamEnabled: (Bool) -> Void - init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removeAdmin: @escaping (PeerId) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void) { + init(context: AccountContext, openRecentActions: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removeAdmin: @escaping (EnginePeer.Id) -> Void, addAdmin: @escaping () -> Void, openAdmin: @escaping (ChannelParticipant) -> Void, updateAntiSpamEnabled: @escaping (Bool) -> Void) { self.context = context self.openRecentActions = openRecentActions self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions @@ -44,7 +43,7 @@ private enum ChannelAdminsSection: Int32 { private enum ChannelAdminsEntryStableId: Hashable { case index(Int32) - case peer(PeerId) + case peer(EnginePeer.Id) } private enum ChannelAdminsEntry: ItemListNodeEntry { @@ -266,9 +265,9 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { private struct ChannelAdminsControllerState: Equatable { let editing: Bool - let peerIdWithRevealedOptions: PeerId? - let removingPeerId: PeerId? - let removedPeerIds: Set + let peerIdWithRevealedOptions: EnginePeer.Id? + let removingPeerId: EnginePeer.Id? + let removedPeerIds: Set let temporaryAdmins: [RenderedChannelParticipant] let searchingMembers: Bool @@ -281,7 +280,7 @@ private struct ChannelAdminsControllerState: Equatable { self.searchingMembers = false } - init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, removedPeerIds: Set, temporaryAdmins: [RenderedChannelParticipant], searchingMembers: Bool) { + init(editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?, removingPeerId: EnginePeer.Id?, removedPeerIds: Set, temporaryAdmins: [RenderedChannelParticipant], searchingMembers: Bool) { self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.removingPeerId = removingPeerId @@ -321,15 +320,15 @@ private struct ChannelAdminsControllerState: Equatable { return ChannelAdminsControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelAdminsControllerState { + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: EnginePeer.Id?) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } - func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelAdminsControllerState { + func withUpdatedRemovingPeerId(_ removingPeerId: EnginePeer.Id?) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, removedPeerIds: self.removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } - func withUpdatedRemovedPeerIds(_ removedPeerIds: Set) -> ChannelAdminsControllerState { + func withUpdatedRemovedPeerIds(_ removedPeerIds: Set) -> ChannelAdminsControllerState { return ChannelAdminsControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, removedPeerIds: removedPeerIds, temporaryAdmins: self.temporaryAdmins, searchingMembers: self.searchingMembers) } @@ -338,13 +337,13 @@ private struct ChannelAdminsControllerState: Equatable { } } -private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: PeerId, view: PeerView, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamAvailable: Bool, antiSpamEnabled: Bool) -> [ChannelAdminsEntry] { +private func channelAdminsControllerEntries(presentationData: PresentationData, accountPeerId: EnginePeer.Id, peer: EnginePeer?, state: ChannelAdminsControllerState, participants: [RenderedChannelParticipant]?, antiSpamAvailable: Bool, antiSpamEnabled: Bool) -> [ChannelAdminsEntry] { if participants == nil || participants?.count == nil { return [] } var entries: [ChannelAdminsEntry] = [] - if let peer = view.peers[view.peerId] as? TelegramChannel { + if case let .channel(peer) = peer { var isGroup = false if case .group = peer.info { isGroup = true @@ -364,7 +363,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData, } var combinedParticipants: [RenderedChannelParticipant] = participants - var existingParticipantIds = Set() + var existingParticipantIds = Set() for participant in participants { existingParticipantIds.insert(participant.peer.id) } @@ -431,7 +430,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData, entries.append(.adminsInfo(presentationData.theme, info)) } } - } else if let peer = view.peers[view.peerId] as? TelegramGroup { + } else if case let .legacyGroup(peer) = peer { let isGroup = true //entries.append(.recentActions(presentationData.theme, presentationData.strings.Group_Info_AdminLog)) @@ -443,7 +442,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData, } var combinedParticipants: [RenderedChannelParticipant] = participants - var existingParticipantIds = Set() + var existingParticipantIds = Set() for participant in participants { existingParticipantIds.insert(participant.peer.id) } @@ -512,7 +511,7 @@ private func channelAdminsControllerEntries(presentationData: PresentationData, return entries } -public func channelAdminsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId initialPeerId: PeerId, loadCompleted: @escaping () -> Void = {}) -> ViewController { +public func channelAdminsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId initialPeerId: EnginePeer.Id, loadCompleted: @escaping () -> Void = {}) -> ViewController { let statePromise = ValuePromise(ChannelAdminsControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelAdminsControllerState()) let updateState: ((ChannelAdminsControllerState) -> ChannelAdminsControllerState) -> Void = { f in @@ -559,16 +558,16 @@ public func channelAdminsController(context: AccountContext, updatedPresentation ) } - var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)? + var upgradedToSupergroupImpl: ((EnginePeer.Id, @escaping () -> Void) -> Void)? - let currentPeerId = ValuePromise(initialPeerId) + let currentPeerId = ValuePromise(initialPeerId) - let upgradedToSupergroup: (PeerId, @escaping () -> Void) -> Void = { upgradedPeerId, f in + let upgradedToSupergroup: (EnginePeer.Id, @escaping () -> Void) -> Void = { upgradedPeerId, f in currentPeerId.set(upgradedPeerId) upgradedToSupergroupImpl?(upgradedPeerId, f) } - let transferedOwnership: (PeerId) -> Void = { memberId in + let transferedOwnership: (EnginePeer.Id) -> Void = { memberId in let presentationData = context.sharedContext.currentPresentationData.with { $0 } let _ = (currentPeerId.get() |> take(1) @@ -586,21 +585,57 @@ public func channelAdminsController(context: AccountContext, updatedPresentation }) } - let peerView = Promise() + actionsDisposable.add((currentPeerId.get() + |> mapToSignal { peerId in + return context.engine.peers.keepPeerUpdated(id: peerId, forceUpdate: false) + }).start()) + + struct PeerData: Equatable { + var peerId: EnginePeer.Id + var peer: EnginePeer? + var participantCount: Int? + + init( + peerId: EnginePeer.Id, + peer: EnginePeer?, + participantCount: Int? + ) { + self.peerId = peerId + self.peer = peer + self.participantCount = participantCount + } + } + + let peerView = Promise() peerView.set(currentPeerId.get() |> mapToSignal { peerId in - return context.account.viewTracker.peerView(peerId) + return context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), + TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId) + ) + |> map { peer, participantCount -> PeerData in + return PeerData( + peerId: peerId, + peer: peer, + participantCount: participantCount + ) + } }) let arguments = ChannelAdminsControllerArguments(context: context, openRecentActions: { let _ = (currentPeerId.get() |> take(1) |> deliverOnMainQueue).start(next: { peerId in - let _ = (context.account.postbox.loadedPeerWithId(peerId) + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) |> deliverOnMainQueue).start(next: { peer in - if peer is TelegramGroup { + guard let peer else { + return + } + if case .legacyGroup = peer { } else { - pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: nil)) + pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: nil)) } }) }) @@ -660,7 +695,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation if banInfo.restrictedBy != context.account.peerId { canUnban = true } - if let channel = peerView.peers[peerId] as? TelegramChannel { + if case let .channel(channel) = peerView.peer { if channel.hasPermission(.banMembers) { canUnban = true } @@ -723,42 +758,68 @@ public func channelAdminsController(context: AccountContext, updatedPresentation membersDisposableValue.set(membersAndLoadMoreControl.0) } else { loadCompleted() - let membersDisposable = (peerView.get() - |> map { peerView -> [RenderedChannelParticipant]? in - guard let cachedData = peerView.cachedData as? CachedGroupData, let participants = cachedData.participants else { - return nil + let membersDisposable = (currentPeerId.get() + |> mapToSignal { peerId -> Signal<[RenderedChannelParticipant]?, NoError> in + return context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.LegacyGroupParticipants(id: peerId) + ) + |> mapToSignal { participants -> Signal<[(EngineLegacyGroupParticipant, EnginePeer?)]?, NoError> in + guard case let .known(participants) = participants else { + return .single(nil) + } + + return context.engine.data.subscribe( + EngineDataMap(participants.map { TelegramEngine.EngineData.Item.Peer.Peer(id: $0.peerId) }) + ) + |> map { peers -> [(EngineLegacyGroupParticipant, EnginePeer?)]? in + var result: [(EngineLegacyGroupParticipant, EnginePeer?)] = [] + for participant in participants { + var peer: EnginePeer? + if let peerValue = peers[participant.peerId] { + peer = peerValue + } + result.append((participant, peer)) + } + return result + } } - var result: [RenderedChannelParticipant] = [] - var creatorPeer: Peer? - for participant in participants.participants { - if let peer = peerView.peers[participant.peerId] { - switch participant { + |> map { participants -> [RenderedChannelParticipant]? in + guard let participants else { + return nil + } + + var result: [RenderedChannelParticipant] = [] + var creatorPeer: EnginePeer? + for (participant, peer) in participants { + if let peer { + switch participant { case .creator: creatorPeer = peer default: break + } } } - } - guard let creator = creatorPeer else { - return nil - } - for participant in participants.participants { - if let peer = peerView.peers[participant.peerId] { - switch participant { + guard let creator = creatorPeer else { + return nil + } + for (participant, peer) in participants { + if let peer { + switch participant { case .creator: - result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer)) + result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer._asPeer())) case .admin: - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] peers[creator.id] = creator peers[peer.id] = peer - result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers)) + result.append(RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: .internal_groupSpecific), promotedBy: creator.id, canBeEditedByAccountPeer: creator.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer._asPeer(), peers: peers.mapValues({ $0._asPeer() }))) case .member: break + } } } + return result } - return result }).start(next: { members in adminsPromise.set(.single(members)) }) @@ -787,7 +848,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation let peerId = view.peerId var antiSpamAvailable = false - if let cachedData = view.cachedData as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount, memberCount >= antiSpamConfiguration.minimumGroupParticipants { + if case .channel = view.peer, let participantCount = view.participantCount, participantCount >= antiSpamConfiguration.minimumGroupParticipants { antiSpamAvailable = true } @@ -800,7 +861,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation return state.withUpdatedEditing(false) } }) - } else if let peer = view.peers[peerId] as? TelegramChannel, peer.flags.contains(.isCreator) { + } else if case let .channel(peer) = view.peer, peer.flags.contains(.isCreator) { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { updateState { state in return state.withUpdatedEditing(true) @@ -829,9 +890,9 @@ public func channelAdminsController(context: AccountContext, updatedPresentation previousPeers = admins var isGroup = true - if let peer = view.peers[peerId] as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(peer) = view.peer, case .broadcast = peer.info { isGroup = false - } else if let _ = view.peers[peerId] as? TelegramGroup { + } else if case .legacyGroup = view.peer { isGroup = true } @@ -862,7 +923,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(isGroup ? presentationData.strings.ChatAdmins_Title : presentationData.strings.Channel_Management_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, view: view, state: state, participants: admins, antiSpamAvailable: antiSpamAvailable, antiSpamEnabled: antiSpamEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelAdminsControllerEntries(presentationData: presentationData, accountPeerId: context.account.peerId, peer: view.peer, state: state, participants: admins, antiSpamAvailable: antiSpamAvailable, antiSpamEnabled: antiSpamEnabled), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && admins != nil && previous!.count >= admins!.count) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift index ff3bb6b05f..52ff8fb04f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSearchContainerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -14,20 +13,20 @@ import ContactsPeerItem import ItemListUI private enum ChannelDiscussionGroupSearchContent: Equatable { - case peer(Peer) + case peer(EnginePeer) static func ==(lhs: ChannelDiscussionGroupSearchContent, rhs: ChannelDiscussionGroupSearchContent) -> Bool { switch lhs { case let .peer(lhsPeer): if case let .peer(rhsPeer) = rhs { - return lhsPeer.isEqual(rhsPeer) + return lhsPeer == rhsPeer } else { return false } } } - var peerId: PeerId { + var peerId: EnginePeer.Id { switch self { case let .peer(peer): return peer.id @@ -36,15 +35,15 @@ private enum ChannelDiscussionGroupSearchContent: Equatable { } private final class ChannelDiscussionGroupSearchInteraction { - let peerSelected: (Peer) -> Void + let peerSelected: (EnginePeer) -> Void - init(peerSelected: @escaping (Peer) -> Void) { + init(peerSelected: @escaping (EnginePeer) -> Void) { self.peerSelected = peerSelected } } private struct ChannelDiscussionGroupSearchEntryId: Hashable { - let peerId: PeerId + let peerId: EnginePeer.Id } private final class ChannelDiscussionGroupSearchEntry: Comparable, Identifiable { @@ -71,7 +70,7 @@ private final class ChannelDiscussionGroupSearchEntry: Comparable, Identifiable func item(context: AccountContext, presentationData: PresentationData, interaction: ChannelDiscussionGroupSearchInteraction) -> ListViewItem { switch self.content { case let .peer(peer): - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: nil, action: { _ in interaction.peerSelected(peer) }) } @@ -100,7 +99,7 @@ private struct ChannelDiscussionGroupSearchContainerState: Equatable { final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerContentNode { private let context: AccountContext - private let openPeer: (Peer) -> Void + private let openPeer: (EnginePeer) -> Void private let dimNode: ASDisplayNode private let listNode: ListView @@ -120,7 +119,7 @@ final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerCo return true } - init(context: AccountContext, peers: [Peer], openPeer: @escaping (Peer) -> Void) { + init(context: AccountContext, peers: [EnginePeer], openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.openPeer = openPeer @@ -153,9 +152,9 @@ final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerCo openPeer(peer) }) - var searchIndex: [ValueBoxKey: [Peer]] = [:] + var searchIndex: [EngineDataBuffer: [EnginePeer]] = [:] for peer in peers { - for token in peer.indexName.indexTokens { + for token in peer._asPeer().indexName.indexTokens { if searchIndex[token] == nil { searchIndex[token] = [] } @@ -170,9 +169,9 @@ final class ChannelDiscussionGroupSearchContainerNode: SearchDisplayControllerCo } var entries: [ChannelDiscussionGroupSearchEntry] = [] - let searchQueryTokens = stringIndexTokens(query.lowercased(), transliteration: .none) - var filteredPeers: [Peer] = [] - var existingPeers = Set() + let searchQueryTokens = context.engine.peers.tokenizeSearchString(string: query.lowercased(), transliteration: .none) + var filteredPeers: [EnginePeer] = [] + var existingPeers = Set() for (key, values) in searchIndex { inner: for token in searchQueryTokens { if token.isPrefix(to: key) { diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift index 53aca6894c..0efb5f7960 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -20,10 +19,10 @@ import UndoUI private final class ChannelDiscussionGroupSetupControllerArguments { let context: AccountContext let createGroup: () -> Void - let selectGroup: (PeerId) -> Void + let selectGroup: (EnginePeer.Id) -> Void let unlinkGroup: () -> Void - init(context: AccountContext, createGroup: @escaping () -> Void, selectGroup: @escaping (PeerId) -> Void, unlinkGroup: @escaping () -> Void) { + init(context: AccountContext, createGroup: @escaping () -> Void, selectGroup: @escaping (EnginePeer.Id) -> Void, unlinkGroup: @escaping () -> Void) { self.context = context self.createGroup = createGroup self.selectGroup = selectGroup @@ -39,13 +38,13 @@ private enum ChannelDiscussionGroupSetupControllerSection: Int32 { private enum ChannelDiscussionGroupSetupControllerEntryStableId: Hashable { case id(Int) - case peer(PeerId) + case peer(EnginePeer.Id) } private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { case header(PresentationTheme, PresentationStrings, String?, Bool, String) case create(PresentationTheme, String) - case group(Int, PresentationTheme, PresentationStrings, Peer, PresentationPersonNameOrder) + case group(Int, PresentationTheme, PresentationStrings, EnginePeer, PresentationPersonNameOrder) case groupsInfo(PresentationTheme, String) case unlink(PresentationTheme, String) @@ -90,7 +89,7 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { return false } case let .group(lhsIndex, lhsTheme, lhsStrings, lhsPeer, lhsNameOrder): - if case let .group(rhsIndex, rhsTheme, rhsStrings, rhsPeer, rhsNameOrder) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings == rhsStrings, lhsPeer.isEqual(rhsPeer), lhsNameOrder == rhsNameOrder { + if case let .group(rhsIndex, rhsTheme, rhsStrings, rhsPeer, rhsNameOrder) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings == rhsStrings, lhsPeer == rhsPeer, lhsNameOrder == rhsNameOrder { return true } else { return false @@ -150,14 +149,14 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { }) case let .group(_, _, strings, peer, nameOrder): let text: String - if let peer = peer as? TelegramChannel, let addressName = peer.addressName, !addressName.isEmpty { + if case let .channel(peer) = peer, let addressName = peer.addressName, !addressName.isEmpty { text = "@\(addressName)" - } else if let peer = peer as? TelegramChannel, case .broadcast = peer.info { + } else if case let .channel(peer) = peer, case .broadcast = peer.info { text = strings.Channel_DiscussionGroup_PrivateChannel } else { text = strings.Channel_DiscussionGroup_PrivateGroup } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: nameOrder, context: arguments.context, peer: EnginePeer(peer), aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.selectGroup(peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) case let .groupsInfo(_, title): @@ -170,8 +169,8 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { } } -private func channelDiscussionGroupSetupControllerEntries(presentationData: PresentationData, view: PeerView, groups: [Peer]?) -> [ChannelDiscussionGroupSetupControllerEntry] { - guard let peer = view.peers[view.peerId] as? TelegramChannel, let cachedData = view.cachedData as? CachedChannelData else { +private func channelDiscussionGroupSetupControllerEntries(presentationData: PresentationData, peer: EnginePeer?, linkedDiscussionPeer: EnginePeer?, groups: [EnginePeer]?) -> [ChannelDiscussionGroupSetupControllerEntry] { + guard case let .channel(peer) = peer else { return [] } @@ -179,25 +178,23 @@ private func channelDiscussionGroupSetupControllerEntries(presentationData: Pres var entries: [ChannelDiscussionGroupSetupControllerEntry] = [] - if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { - if let group = view.peers[linkedDiscussionPeerId] { + if let group = linkedDiscussionPeer { + if case .group = peer.info { + entries.append(.header(presentationData.theme, presentationData.strings, group.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), true, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) + } else { + entries.append(.header(presentationData.theme, presentationData.strings, group.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), false, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) + } + + entries.append(.group(0, presentationData.theme, presentationData.strings, group, presentationData.nameDisplayOrder)) + entries.append(.groupsInfo(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_Info)) + if canEditChannel { + let unlinkText: String if case .group = peer.info { - entries.append(.header(presentationData.theme, presentationData.strings, EnginePeer(group).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), true, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) + unlinkText = presentationData.strings.Channel_DiscussionGroup_UnlinkChannel } else { - entries.append(.header(presentationData.theme, presentationData.strings, EnginePeer(group).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), false, presentationData.strings.Channel_DiscussionGroup_HeaderLabel)) - } - - entries.append(.group(0, presentationData.theme, presentationData.strings, group, presentationData.nameDisplayOrder)) - entries.append(.groupsInfo(presentationData.theme, presentationData.strings.Channel_DiscussionGroup_Info)) - if canEditChannel { - let unlinkText: String - if case .group = peer.info { - unlinkText = presentationData.strings.Channel_DiscussionGroup_UnlinkChannel - } else { - unlinkText = presentationData.strings.Channel_DiscussionGroup_UnlinkGroup - } - entries.append(.unlink(presentationData.theme, unlinkText)) + unlinkText = presentationData.strings.Channel_DiscussionGroup_UnlinkGroup } + entries.append(.unlink(presentationData.theme, unlinkText)) } } else if case .broadcast = peer.info, canEditChannel { if let groups = groups { @@ -220,30 +217,63 @@ private struct ChannelDiscussionGroupSetupControllerState: Equatable { var searching: Bool = false } -public func channelDiscussionGroupSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId) -> ViewController { +public func channelDiscussionGroupSetupController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id) -> ViewController { let statePromise = ValuePromise(ChannelDiscussionGroupSetupControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelDiscussionGroupSetupControllerState()) let updateState: ((ChannelDiscussionGroupSetupControllerState) -> ChannelDiscussionGroupSetupControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - let groupPeers = Promise<[Peer]?>() + let groupPeers = Promise<[EnginePeer]?>() groupPeers.set(.single(nil) |> then( context.engine.peers.availableGroupsForChannelDiscussion() |> map(Optional.init) - |> `catch` { _ -> Signal<[Peer]?, NoError> in + |> `catch` { _ -> Signal<[EnginePeer]?, NoError> in return .single(nil) } )) - let peerView = context.account.viewTracker.peerView(peerId) + struct PeerData { + var peer: EnginePeer? + var linkedDiscussionPeer: EnginePeer? + var hasLinkedDiscussionPeerValue: Bool + } + let peerView: Signal = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), + TelegramEngine.EngineData.Item.Peer.LinkedDiscussionPeerId(id: peerId) + ) + |> mapToSignal { peer, linkedDiscussionPeerId -> Signal in + var linkedDiscussionPeer: Signal + var hasLinkedDiscussionPeerValue: Bool = false + if case let .known(linkedDiscussionPeerId) = linkedDiscussionPeerId { + hasLinkedDiscussionPeerValue = true + if let linkedDiscussionPeerIdValue = linkedDiscussionPeerId { + linkedDiscussionPeer = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: linkedDiscussionPeerIdValue) + ) + } else { + linkedDiscussionPeer = .single(nil) + } + } else { + linkedDiscussionPeer = .single(nil) + } + + return linkedDiscussionPeer + |> map { linkedDiscussionPeer -> PeerData in + return PeerData( + peer: peer, + linkedDiscussionPeer: linkedDiscussionPeer, + hasLinkedDiscussionPeerValue: hasLinkedDiscussionPeerValue + ) + } + } var dismissImpl: (() -> Void)? var dismissInputImpl: (() -> Void)? var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? - var navigateToGroupImpl: ((PeerId) -> Void)? + var navigateToGroupImpl: ((EnginePeer.Id) -> Void)? let actionsDisposable = DisposableSet() @@ -329,7 +359,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, updat actionSheet?.dismissAnimated() var applySignal: Signal - var updatedPeerId: PeerId? = nil + var updatedPeerId: EnginePeer.Id? = nil if case let .legacyGroup(legacyGroup) = groupPeer { applySignal = context.engine.peers.convertGroupToSupergroup(peerId: legacyGroup.id) |> mapError { error -> ChannelDiscussionGroupError in @@ -356,7 +386,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, updat } for i in 0 ..< groups.count { if groups[i].id == groupId { - groups[i] = groupPeer._asPeer() + groups[i] = groupPeer break } } @@ -495,7 +525,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, updat return } - let applyPeerId: PeerId + let applyPeerId: EnginePeer.Id if case .broadcast = peer.info { applyPeerId = peerId } else if case let .known(maybeLinkedDiscussionPeerId) = linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { @@ -555,7 +585,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, updat |> deliverOnMainQueue |> map { presentationData, state, view, groups -> (ItemListControllerState, (ItemListNodeState, Any)) in let title: String - if let peer = view.peers[view.peerId] as? TelegramChannel, case .broadcast = peer.info { + if case let .channel(peer) = view.peer, case .broadcast = peer.info { title = presentationData.strings.Channel_DiscussionGroup } else { title = presentationData.strings.Group_LinkedChannel @@ -564,15 +594,11 @@ public func channelDiscussionGroupSetupController(context: AccountContext, updat var crossfade = false var isEmptyState = false var displayGroupList = false - if let cachedData = view.cachedData as? CachedChannelData { - var isEmpty = true - switch cachedData.linkedDiscussionPeerId { - case .unknown: - isEmpty = true - case let .known(value): - isEmpty = value == nil - } - if let peer = view.peers[view.peerId] as? TelegramChannel, case .broadcast = peer.info { + if view.hasLinkedDiscussionPeerValue { + let isEmpty: Bool + isEmpty = view.linkedDiscussionPeer == nil + + if case let .channel(peer) = view.peer, case .broadcast = peer.info { if isEmpty { if groups == nil { isEmptyState = true @@ -621,7 +647,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, updat } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelDiscussionGroupSetupControllerEntries(presentationData: presentationData, view: view, groups: groups), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelDiscussionGroupSetupControllerEntries(presentationData: presentationData, peer: view.peer, linkedDiscussionPeer: view.linkedDiscussionPeer, groups: groups), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, crossfadeState: crossfade, animateChanges: false) return (controllerState, (listState, arguments)) } diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift index 637245089b..e631a36ed9 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupSearchItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -13,12 +12,12 @@ import SearchBarNode final class ChannelDiscussionGroupSetupSearchItem: ItemListControllerSearch { let context: AccountContext - let peers: [Peer] + let peers: [EnginePeer] let cancel: () -> Void let dismissInput: () -> Void - let openPeer: (Peer) -> Void + let openPeer: (EnginePeer) -> Void - init(context: AccountContext, peers: [Peer], cancel: @escaping () -> Void, dismissInput: @escaping () -> Void, openPeer: @escaping (Peer) -> Void) { + init(context: AccountContext, peers: [EnginePeer], cancel: @escaping () -> Void, dismissInput: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void) { self.context = context self.peers = peers self.cancel = cancel @@ -60,7 +59,7 @@ final class ChannelDiscussionGroupSetupSearchItem: ItemListControllerSearch { private final class ChannelDiscussionGroupSetupSearchItemNode: ItemListControllerSearchNode { private let containerNode: ChannelDiscussionGroupSearchContainerNode - init(context: AccountContext, peers: [Peer], openPeer: @escaping (Peer) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping (Bool) -> Void, dismissInput: @escaping () -> Void) { + init(context: AccountContext, peers: [EnginePeer], openPeer: @escaping (EnginePeer) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping (Bool) -> Void, dismissInput: @escaping () -> Void) { self.containerNode = ChannelDiscussionGroupSearchContainerNode(context: context, peers: peers, openPeer: { peer in openPeer(peer) }) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index 0b947e0716..993025828a 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -16,19 +15,20 @@ import ItemListPeerActionItem import InviteLinksUI import UndoUI import SendInviteLinkScreen +import Postbox private final class ChannelMembersControllerArguments { let context: AccountContext let addMember: () -> Void - let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - let removePeer: (PeerId) -> Void - let openPeer: (Peer) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let removePeer: (EnginePeer.Id) -> Void + let openPeer: (EnginePeer) -> Void let inviteViaLink: () -> Void let updateHideMembers: (Bool) -> Void let displayHideMembersTip: (HideMembersDisabledReason) -> Void - init(context: AccountContext, addMember: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (Peer) -> Void, inviteViaLink: @escaping () -> Void, updateHideMembers: @escaping (Bool) -> Void, displayHideMembersTip: @escaping (HideMembersDisabledReason) -> Void) { + init(context: AccountContext, addMember: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (EnginePeer) -> Void, inviteViaLink: @escaping () -> Void, updateHideMembers: @escaping (Bool) -> Void, displayHideMembersTip: @escaping (HideMembersDisabledReason) -> Void) { self.context = context self.addMember = addMember self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions @@ -49,7 +49,7 @@ private enum ChannelMembersSection: Int32 { private enum ChannelMembersEntryStableId: Hashable { case index(Int32) - case peer(PeerId) + case peer(EnginePeer.Id) } private enum HideMembersDisabledReason: Equatable { @@ -287,7 +287,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { text = .presence } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: participant.presences[participant.peer.id].flatMap(EnginePeer.Presence.init), text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: participant.peer.id != arguments.context.account.peerId, sectionId: self.section, action: { - arguments.openPeer(participant.peer) + arguments.openPeer(EnginePeer(participant.peer)) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in @@ -299,8 +299,8 @@ private enum ChannelMembersEntry: ItemListNodeEntry { private struct ChannelMembersControllerState: Equatable { let editing: Bool - let peerIdWithRevealedOptions: PeerId? - let removingPeerId: PeerId? + let peerIdWithRevealedOptions: EnginePeer.Id? + let removingPeerId: EnginePeer.Id? let searchingMembers: Bool init() { @@ -310,7 +310,7 @@ private struct ChannelMembersControllerState: Equatable { self.searchingMembers = false } - init(editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, searchingMembers: Bool) { + init(editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?, removingPeerId: EnginePeer.Id?, searchingMembers: Bool) { self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions self.removingPeerId = removingPeerId @@ -341,11 +341,11 @@ private struct ChannelMembersControllerState: Equatable { return ChannelMembersControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: self.searchingMembers) } - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelMembersControllerState { + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: EnginePeer.Id?) -> ChannelMembersControllerState { return ChannelMembersControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: self.searchingMembers) } - func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelMembersControllerState { + func withUpdatedRemovingPeerId(_ removingPeerId: EnginePeer.Id?) -> ChannelMembersControllerState { return ChannelMembersControllerState(editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, searchingMembers: self.searchingMembers) } } @@ -428,7 +428,7 @@ private func channelMembersControllerEntries(context: AccountContext, presentati var index: Int32 = 0 - var existingPeerIds = Set() + var existingPeerIds = Set() var addedContactsHeader = false if !contacts.isEmpty { @@ -484,7 +484,7 @@ private func channelMembersControllerEntries(context: AccountContext, presentati return entries } -public func channelMembersController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId) -> ViewController { +public func channelMembersController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id) -> ViewController { let statePromise = ValuePromise(ChannelMembersControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelMembersControllerState()) let updateState: ((ChannelMembersControllerState) -> ChannelMembersControllerState) -> Void = { f in @@ -523,7 +523,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio addMembersDisposable.set(( contactsController.result |> deliverOnMainQueue - |> mapToSignal { [weak contactsController] result -> Signal<[(PeerId, AddChannelMemberError)], NoError> in + |> mapToSignal { [weak contactsController] result -> Signal<[(EnginePeer.Id, AddChannelMemberError)], NoError> in contactsController?.displayProgress = true var contacts: [ContactListPeerId] = [] @@ -531,7 +531,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio contacts = peerIdsValue } - let signal = context.peerChannelMemberCategoriesContextsManager.addMembersAllowPartial(engine: context.engine, peerId: peerId, memberIds: contacts.compactMap({ contact -> PeerId? in + let signal = context.peerChannelMemberCategoriesContextsManager.addMembersAllowPartial(engine: context.engine, peerId: peerId, memberIds: contacts.compactMap({ contact -> EnginePeer.Id? in switch contact { case let .peer(contactId): return contactId @@ -649,7 +649,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio } })) }, openPeer: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(controller) } }, inviteViaLink: { @@ -726,7 +726,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio return state.withUpdatedSearchingMembers(false) } }, openPeer: { peer, _ in - if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let infoController = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(infoController) } }, pushController: { c in diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift index 0850513f01..aa97ed2b2f 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchContainerNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -67,14 +66,14 @@ private enum ChannelMembersSearchSection { } private enum ChannelMembersSearchContent: Equatable { - case peer(Peer) + case peer(EnginePeer) case participant(participant: RenderedChannelParticipant, label: String?, revealActions: [ParticipantRevealAction], revealed: Bool, enabled: Bool) static func ==(lhs: ChannelMembersSearchContent, rhs: ChannelMembersSearchContent) -> Bool { switch lhs { case let .peer(lhsPeer): if case let .peer(rhsPeer) = rhs { - return lhsPeer.isEqual(rhsPeer) + return lhsPeer == rhsPeer } else { return false } @@ -87,7 +86,7 @@ private enum ChannelMembersSearchContent: Equatable { } } - var peerId: PeerId { + var peerId: EnginePeer.Id { switch self { case let .peer(peer): return peer.id @@ -98,18 +97,18 @@ private enum ChannelMembersSearchContent: Equatable { } private struct RevealedPeerId: Equatable { - let peerId: PeerId + let peerId: EnginePeer.Id let section: ChannelMembersSearchSection } private final class ChannelMembersSearchContainerInteraction { - let peerSelected: (Peer, RenderedChannelParticipant?) -> Void + let peerSelected: (EnginePeer, RenderedChannelParticipant?) -> Void let setPeerIdWithRevealedOptions: (RevealedPeerId?, RevealedPeerId?) -> Void let promotePeer: (RenderedChannelParticipant) -> Void let restrictPeer: (RenderedChannelParticipant) -> Void - let removePeer: (PeerId) -> Void + let removePeer: (EnginePeer.Id) -> Void - init(peerSelected: @escaping (Peer, RenderedChannelParticipant?) -> Void, setPeerIdWithRevealedOptions: @escaping (RevealedPeerId?, RevealedPeerId?) -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (PeerId) -> Void) { + init(peerSelected: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void, setPeerIdWithRevealedOptions: @escaping (RevealedPeerId?, RevealedPeerId?) -> Void, promotePeer: @escaping (RenderedChannelParticipant) -> Void, restrictPeer: @escaping (RenderedChannelParticipant) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void) { self.peerSelected = peerSelected self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.promotePeer = promotePeer @@ -119,7 +118,7 @@ private final class ChannelMembersSearchContainerInteraction { } private struct ChannelMembersSearchEntryId: Hashable { - let peerId: PeerId + let peerId: EnginePeer.Id let section: ChannelMembersSearchSection } @@ -153,7 +152,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: ChannelMembersSearchContainerInteraction) -> ListViewItem { switch self.content { case let .peer(peer): - return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) }), action: { _ in + return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) }), action: { _ in interaction.peerSelected(peer, nil) }) case let .participant(participant, label, revealActions, revealed, enabled): @@ -188,7 +187,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable { } return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(participant.peer), chatPeer: EnginePeer(participant.peer)), status: status, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: revealed), options: options, actionIcon: actionIcon, index: nil, header: self.section.chatListHeaderType.flatMap({ ChatListSearchItemHeader(type: $0, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) }), action: { _ in - interaction.peerSelected(participant.peer, participant) + interaction.peerSelected(EnginePeer(participant.peer), participant) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in interaction.setPeerIdWithRevealedOptions(RevealedPeerId(peerId: participant.peer.id, section: self.section), fromPeerId.flatMap({ RevealedPeerId(peerId: $0, section: self.section) })) }) @@ -212,7 +211,7 @@ private enum GroupMemberCategory { case members } -private func categorySignal(context: AccountContext, peerId: PeerId, category: GroupMemberCategory) -> Signal<[RenderedChannelParticipant], NoError> { +private func categorySignal(context: AccountContext, peerId: EnginePeer.Id, category: GroupMemberCategory) -> Signal<[RenderedChannelParticipant], NoError> { return Signal<[RenderedChannelParticipant], NoError> { subscriber in let disposableAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?) func processListState(_ listState: ChannelMemberListState) { @@ -255,7 +254,7 @@ private struct GroupMembersSearchContextState { public final class GroupMembersSearchContext { fileprivate let state = Promise() - public init(context: AccountContext, peerId: PeerId) { + public init(context: AccountContext, peerId: EnginePeer.Id) { assert(Queue.mainQueue().isCurrent()) let combinedSignal = combineLatest(queue: .mainQueue(), categorySignal(context: context, peerId: peerId, category: .contacts), categorySignal(context: context, peerId: peerId, category: .bots), categorySignal(context: context, peerId: peerId, category: .admins), categorySignal(context: context, peerId: peerId, category: .members)) @@ -285,12 +284,12 @@ private func channelMembersSearchContainerPreparedRecentTransition(from fromEntr private struct ChannelMembersSearchContainerState: Equatable { var revealedPeerId: RevealedPeerId? - var removingParticipantIds = Set() + var removingParticipantIds = Set() } public final class ChannelMembersSearchContainerNode: SearchDisplayControllerContentNode { private let context: AccountContext - private let openPeer: (Peer, RenderedChannelParticipant?) -> Void + private let openPeer: (EnginePeer, RenderedChannelParticipant?) -> Void private let mode: ChannelMembersSearchMode private let emptyQueryListNode: ListView @@ -320,7 +319,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon return _hasDim } - public init(context: AccountContext, forceTheme: PresentationTheme?, peerId: PeerId, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) { + public init(context: AccountContext, forceTheme: PresentationTheme?, peerId: EnginePeer.Id, mode: ChannelMembersSearchMode, filters: [ChannelMembersSearchFilter], searchContext: GroupMembersSearchContext?, openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void, updateActivity: @escaping (Bool) -> Void, pushController: @escaping (ViewController) -> Void) { self.context = context self.openPeer = openPeer self.mode = mode @@ -666,7 +665,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon foundMembers = .single([]) } - let foundContacts: Signal<([Peer], [PeerId: PeerPresence]), NoError> + let foundContacts: Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.Presence]), NoError> let foundRemotePeers: Signal<([FoundPeer], [FoundPeer]), NoError> switch mode { case .inviteActions, .banAndPromoteActions: @@ -680,7 +679,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon foundContacts = .single(([], [:])) foundRemotePeers = .single(([], [])) } else { - foundContacts = context.account.postbox.searchContacts(query: query.lowercased()) + foundContacts = context.engine.contacts.searchContacts(query: query.lowercased()) foundRemotePeers = .single(([], [])) |> then(context.engine.contacts.searchRemotePeers(query: query) |> delay(0.2, queue: Queue.concurrentDefaultQueue())) } @@ -693,7 +692,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon |> map { foundGroupMembers, foundMembers, foundContacts, foundRemotePeers, presentationData, state -> [ChannelMembersSearchEntry]? in var entries: [ChannelMembersSearchEntry] = [] - var existingPeerIds = Set() + var existingPeerIds = Set() var excludeBots = false for filter in filters { switch filter { @@ -894,7 +893,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon } for peer in foundContacts.0 { - if excludeBots, let user = peer as? TelegramUser, user.botInfo != nil { + if excludeBots, case let .user(user) = peer, user.botInfo != nil { continue } @@ -914,7 +913,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if !existingPeerIds.contains(peer.id) && peer is TelegramUser { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } @@ -927,7 +926,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if !existingPeerIds.contains(peer.id) && peer is TelegramUser { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } @@ -951,12 +950,12 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if !peer.indexName.matchesByTokens(query.lowercased()) { continue } - var creatorPeer: Peer? + var creatorPeer: EnginePeer? for participant in participants.participants { if let peer = peerView.peers[participant.peerId] { switch participant { case .creator: - creatorPeer = peer + creatorPeer = EnginePeer(peer) default: break } @@ -968,16 +967,16 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon case .creator: renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer) case .admin: - var peers: [PeerId: Peer] = [:] + var peers: [EnginePeer.Id: EnginePeer] = [:] if let creator = creatorPeer { peers[creator.id] = creator } - peers[peer.id] = peer - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers) + peers[peer.id] = EnginePeer(peer) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: TelegramChatAdminRightsFlags.peerSpecific(peer: .legacyGroup(group))), promotedBy: creatorPeer?.id ?? context.account.peerId, canBeEditedByAccountPeer: creatorPeer?.id == context.account.peerId), banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() })) case .member: - var peers: [PeerId: Peer] = [:] - peers[peer.id] = peer - renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers) + var peers: [EnginePeer.Id: EnginePeer] = [:] + peers[peer.id] = EnginePeer(peer) + renderedParticipant = RenderedChannelParticipant(participant: .member(id: peer.id, invitedAt: 0, adminInfo: nil, banInfo: nil, rank: nil), peer: peer, peers: peers.mapValues({ $0._asPeer() })) } matchingMembers.append(renderedParticipant) } @@ -1009,7 +1008,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon |> map { foundGroupMembers, foundMembers, foundRemotePeers, presentationData, state -> [ChannelMembersSearchEntry]? in var entries: [ChannelMembersSearchEntry] = [] - var existingPeerIds = Set() + var existingPeerIds = Set() var excludeBots = false for filter in filters { switch filter { @@ -1166,7 +1165,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if !existingPeerIds.contains(peer.id) && peer is TelegramUser { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } @@ -1180,7 +1179,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon if !existingPeerIds.contains(peer.id) && peer is TelegramUser { existingPeerIds.insert(peer.id) - entries.append(ChannelMembersSearchEntry(index: index, content: .peer(peer), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) + entries.append(ChannelMembersSearchEntry(index: index, content: .peer(EnginePeer(peer)), section: .global, dateTimeFormat: presentationData.dateTimeFormat)) index += 1 } } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index e00c4d8015..46e0fc4489 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import TelegramCore -import Postbox import SwiftSignalKit import TelegramPresentationData import AccountContext @@ -15,8 +14,8 @@ public enum ChannelMembersSearchControllerMode { } public enum ChannelMembersSearchFilter { - case exclude([PeerId]) - case disable([PeerId]) + case exclude([EnginePeer.Id]) + case disable([EnginePeer.Id]) case excludeNonMembers case excludeBots } @@ -25,10 +24,10 @@ public final class ChannelMembersSearchController: ViewController { private let queue = Queue() private let context: AccountContext - private let peerId: PeerId + private let peerId: EnginePeer.Id private let mode: ChannelMembersSearchControllerMode private let filters: [ChannelMembersSearchFilter] - private let openPeer: (Peer, RenderedChannelParticipant?) -> Void + private let openPeer: (EnginePeer, RenderedChannelParticipant?) -> Void public var copyInviteLink: (() -> Void)? @@ -44,7 +43,7 @@ public final class ChannelMembersSearchController: ViewController { private var searchContentNode: NavigationBarSearchContentNode? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, forceTheme: PresentationTheme? = nil, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, forceTheme: PresentationTheme? = nil, mode: ChannelMembersSearchControllerMode, filters: [ChannelMembersSearchFilter] = [], openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void) { self.context = context self.peerId = peerId self.mode = mode @@ -120,7 +119,7 @@ public final class ChannelMembersSearchController: ViewController { self?.deactivateSearch(animated: true) } self.controllerNode.requestOpenPeerFromSearch = { [weak self] peer, participant in - self?.openPeer(peer._asPeer(), participant) + self?.openPeer(peer, participant) } self.controllerNode.requestCopyInviteLink = { [weak self] in self?.copyInviteLink?() diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift index b48c6fe0c2..5b399fe10a 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchControllerNode.swift @@ -676,7 +676,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode { } self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChannelMembersSearchContainerNode(context: self.context, forceTheme: self.forceTheme, peerId: self.peerId, mode: .banAndPromoteActions, filters: self.filters, searchContext: nil, openPeer: { [weak self] peer, participant in - self?.requestOpenPeerFromSearch?(EnginePeer(peer), participant) + self?.requestOpenPeerFromSearch?(peer, participant) }, updateActivity: { value in }, pushController: { [weak self] c in diff --git a/submodules/PeerInfoUI/Sources/ChannelOwnershipTransferController.swift b/submodules/PeerInfoUI/Sources/ChannelOwnershipTransferController.swift index db0e459c4d..f227c32cda 100644 --- a/submodules/PeerInfoUI/Sources/ChannelOwnershipTransferController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelOwnershipTransferController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ActivityIndicator @@ -425,7 +424,7 @@ public final class ChannelOwnershipTransferAlertContentNode: AlertContentNode { } } -private func commitChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: Peer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (PeerId?) -> Void) -> ViewController { +private func commitChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } var dismissImpl: (() -> Void)? @@ -464,13 +463,13 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u } contentNode.updateIsChecking(true) - let signal: Signal - if let peer = peer as? TelegramChannel { + let signal: Signal + if case let .channel(peer) = peer { signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: peer.id, memberId: member.id, password: contentNode.password) |> mapToSignal { _ in return .complete() } |> then(.single(nil)) - } else if let peer = peer as? TelegramGroup { + } else if case let .legacyGroup(peer) = peer { signal = context.engine.peers.convertGroupToSupergroup(peerId: peer.id) |> map(Optional.init) |> mapError { error -> ChannelOwnershipTransferError in @@ -482,7 +481,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u } } |> deliverOnMainQueue - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in guard let upgradedPeerId = upgradedPeerId else { return .fail(.generic) } @@ -500,7 +499,7 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u completion(upgradedPeerId) }, error: { [weak contentNode] error in var isGroup = true - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } @@ -540,12 +539,12 @@ private func commitChannelOwnershipTransferController(context: AccountContext, u return controller } -private func confirmChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: Peer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (PeerId?) -> Void) -> ViewController { +private func confirmChannelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, member: TelegramUser, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let theme = AlertControllerTheme(presentationData: presentationData) var isGroup = true - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } @@ -553,10 +552,10 @@ private func confirmChannelOwnershipTransferController(context: AccountContext, var text: String if isGroup { title = presentationData.strings.Group_OwnershipTransfer_Title - text = presentationData.strings.Group_OwnershipTransfer_DescriptionInfo(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), EnginePeer(member).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + text = presentationData.strings.Group_OwnershipTransfer_DescriptionInfo(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), EnginePeer.user(member).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string } else { title = presentationData.strings.Channel_OwnershipTransfer_Title - text = presentationData.strings.Channel_OwnershipTransfer_DescriptionInfo(EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), EnginePeer(member).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string + text = presentationData.strings.Channel_OwnershipTransfer_DescriptionInfo(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), EnginePeer.user(member).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string } let attributedTitle = NSAttributedString(string: title, font: Font.semibold(presentationData.listsFontSize.baseDisplaySize), textColor: theme.primaryColor, paragraphAlignment: .center) @@ -571,7 +570,7 @@ private func confirmChannelOwnershipTransferController(context: AccountContext, return controller } -func channelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: Peer, member: TelegramUser, initialError: ChannelOwnershipTransferError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (PeerId?) -> Void) -> ViewController { +func channelOwnershipTransferController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, member: TelegramUser, initialError: ChannelOwnershipTransferError, present: @escaping (ViewController, Any?) -> Void, completion: @escaping (EnginePeer.Id?) -> Void) -> ViewController { let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let theme = AlertControllerTheme(presentationData: presentationData) @@ -581,7 +580,7 @@ func channelOwnershipTransferController(context: AccountContext, updatedPresenta let textFontSize = presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0 var isGroup = true - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { isGroup = false } diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index b79d5c3d47..3845e8f792 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -18,16 +17,17 @@ import TelegramPermissionsUI import ItemListPeerActionItem import Markdown import UndoUI +import Postbox private final class ChannelPermissionsControllerArguments { let context: AccountContext let updatePermission: (TelegramChatBannedRightsFlags, Bool) -> Void - let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void let addPeer: () -> Void - let removePeer: (PeerId) -> Void + let removePeer: (EnginePeer.Id) -> Void let openPeer: (ChannelParticipant) -> Void - let openPeerInfo: (Peer) -> Void + let openPeerInfo: (EnginePeer) -> Void let openKicked: () -> Void let presentRestrictedPermissionAlert: (TelegramChatBannedRightsFlags) -> Void let presentConversionToBroadcastGroup: () -> Void @@ -35,7 +35,7 @@ private final class ChannelPermissionsControllerArguments { let updateSlowmode: (Int32) -> Void let toggleIsOptionExpanded: (TelegramChatBannedRightsFlags) -> Void - init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (Peer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { + init(context: AccountContext, updatePermission: @escaping (TelegramChatBannedRightsFlags, Bool) -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (ChannelParticipant) -> Void, openPeerInfo: @escaping (EnginePeer) -> Void, openKicked: @escaping () -> Void, presentRestrictedPermissionAlert: @escaping (TelegramChatBannedRightsFlags) -> Void, presentConversionToBroadcastGroup: @escaping () -> Void, openChannelExample: @escaping () -> Void, updateSlowmode: @escaping (Int32) -> Void, toggleIsOptionExpanded: @escaping (TelegramChatBannedRightsFlags) -> Void) { self.context = context self.updatePermission = updatePermission self.addPeer = addPeer @@ -62,7 +62,7 @@ private enum ChannelPermissionsSection: Int32 { private enum ChannelPermissionsEntryStableId: Hashable { case index(Int) - case peer(PeerId) + case peer(EnginePeer.Id) } struct SubPermission: Equatable { @@ -363,7 +363,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(participant.peer), presence: nil, text: text, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: canOpen ? { arguments.openPeer(participant.participant) } : { - arguments.openPeerInfo(participant.peer) + arguments.openPeerInfo(EnginePeer(participant.peer)) }, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in @@ -374,8 +374,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { } private struct ChannelPermissionsControllerState: Equatable { - var peerIdWithRevealedOptions: PeerId? - var removingPeerId: PeerId? + var peerIdWithRevealedOptions: EnginePeer.Id? + var removingPeerId: EnginePeer.Id? var searchingMembers: Bool = false var modifiedRightsFlags: TelegramChatBannedRightsFlags? var modifiedSlowmodeTimeout: Int32? @@ -656,7 +656,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen return entries } -public func channelPermissionsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId originalPeerId: PeerId, loadCompleted: @escaping () -> Void = {}) -> ViewController { +public func channelPermissionsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId originalPeerId: EnginePeer.Id, loadCompleted: @escaping () -> Void = {}) -> ViewController { let statePromise = ValuePromise(ChannelPermissionsControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelPermissionsControllerState()) let updateState: ((ChannelPermissionsControllerState) -> ChannelPermissionsControllerState) -> Void = { f in @@ -665,7 +665,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent var presentControllerImpl: ((ViewController, Any?) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? - var navigateToChatControllerImpl: ((PeerId) -> Void)? + var navigateToChatControllerImpl: ((EnginePeer.Id) -> Void)? var dismissInputImpl: (() -> Void)? var dismissToChatController: (() -> Void)? var resetSlowmodeVisualValueImpl: (() -> Void)? @@ -681,12 +681,12 @@ public func channelPermissionsController(context: AccountContext, updatedPresent let removePeerDisposable = MetaDisposable() actionsDisposable.add(removePeerDisposable) - let sourcePeerId = Promise<(PeerId, Bool)>((originalPeerId, false)) + let sourcePeerId = Promise<(EnginePeer.Id, Bool)>((originalPeerId, false)) let peersDisposable = MetaDisposable() let loadMoreControl = Atomic(value: nil) - let peersPromise = Promise<(PeerId, [RenderedChannelParticipant]?)>() + let peersPromise = Promise<(EnginePeer.Id, [RenderedChannelParticipant]?)>() actionsDisposable.add((sourcePeerId.get() |> deliverOnMainQueue).start(next: { peerId, updated in @@ -722,7 +722,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent peerView.set(sourcePeerId.get() |> mapToSignal(context.account.viewTracker.peerView)) - var upgradedToSupergroupImpl: ((PeerId, @escaping () -> Void) -> Void)? + var upgradedToSupergroupImpl: ((EnginePeer.Id, @escaping () -> Void) -> Void)? let arguments = ChannelPermissionsControllerArguments(context: context, updatePermission: { rights, value in let _ = (peerView.get() @@ -922,7 +922,7 @@ public func channelPermissionsController(context: AccountContext, updatedPresent }), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) }, openPeerInfo: { peer in - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(controller) } }, openKicked: { @@ -1042,9 +1042,9 @@ public func channelPermissionsController(context: AccountContext, updatedPresent return .generic } } - |> mapToSignal { upgradedPeerId -> Signal in + |> mapToSignal { upgradedPeerId -> Signal in return context.engine.peers.updateChannelSlowModeInteractively(peerId: upgradedPeerId, timeout: modifiedSlowmodeTimeout == 0 ? nil : value) - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(upgradedPeerId)) diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index b0d86bc067..731e974567 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -24,14 +23,15 @@ import UndoUI import QrCodeUI import PremiumUI import TextFormat +import Postbox private final class ChannelVisibilityControllerArguments { let context: AccountContext let updateCurrentType: (CurrentChannelType) -> Void let updatePublicLinkText: (String?, String) -> Void let scrollToPublicLinkText: () -> Void - let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void - let revokePeerId: (PeerId) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void + let revokePeerId: (EnginePeer.Id) -> Void let copyLink: (ExportedInvitation) -> Void let shareLink: (ExportedInvitation) -> Void let linkContextAction: (ASDisplayNode, ContextGesture?) -> Void @@ -44,7 +44,7 @@ private final class ChannelVisibilityControllerArguments { let deactivateLink: (String) -> Void let openAuction: (String) -> Void - init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) { + init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, revokePeerId: @escaping (EnginePeer.Id) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) { self.context = context self.updateCurrentType = updateCurrentType self.updatePublicLinkText = updatePublicLinkText @@ -114,7 +114,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String) case existingLinksInfo(PresentationTheme, String) - case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) + case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, EnginePeer, ItemListPeerItemEditing, Bool) case additionalLinkHeader(PresentationTheme, String) case additionalLink(PresentationTheme, TelegramPeerUsername, Int32) @@ -348,7 +348,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { if lhsNameOrder != rhsNameOrder { return false } - if !lhsPeer.isEqual(rhsPeer) { + if lhsPeer != rhsPeer { return false } if lhsEditing != rhsEditing { @@ -704,7 +704,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { if let addressName = peer.addressName { label = "t.me/" + addressName } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .text(label, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(label, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.revokePeerId(peerId) @@ -772,8 +772,8 @@ private struct ChannelVisibilityControllerState: Equatable { let editingPublicLinkText: String? let addressNameValidationStatus: AddressNameValidationStatus? let updatingAddressName: Bool - let revealedRevokePeerId: PeerId? - let revokingPeerId: PeerId? + let revealedRevokePeerId: EnginePeer.Id? + let revokingPeerId: EnginePeer.Id? let revokingPrivateLink: Bool let forwardingEnabled: Bool? let joinToSend: CurrentChannelJoinToSend? @@ -855,11 +855,11 @@ private struct ChannelVisibilityControllerState: Equatable { return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) } - func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: PeerId?) -> ChannelVisibilityControllerState { + func withUpdatedRevealedRevokePeerId(_ revealedRevokePeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState { return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: revealedRevokePeerId, revokingPeerId: self.revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) } - func withUpdatedRevokingPeerId(_ revokingPeerId: PeerId?) -> ChannelVisibilityControllerState { + func withUpdatedRevokingPeerId(_ revokingPeerId: EnginePeer.Id?) -> ChannelVisibilityControllerState { return ChannelVisibilityControllerState(selectedType: self.selectedType, editingPublicLinkText: self.editingPublicLinkText, addressNameValidationStatus: self.addressNameValidationStatus, updatingAddressName: updatingAddressName, revealedRevokePeerId: self.revealedRevokePeerId, revokingPeerId: revokingPeerId, revokingPrivateLink: self.revokingPrivateLink, forwardingEnabled: self.forwardingEnabled, joinToSend: self.joinToSend, approveMembers: self.approveMembers) } @@ -880,7 +880,7 @@ private struct ChannelVisibilityControllerState: Equatable { } } -private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [Peer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool, temporaryOrder: [String]?) -> [ChannelVisibilityEntry] { +private func channelVisibilityControllerEntries(presentationData: PresentationData, mode: ChannelVisibilityControllerMode, view: PeerView, publicChannelsToRevoke: [EnginePeer]?, importers: PeerInvitationImportersState?, state: ChannelVisibilityControllerState, limits: EngineConfiguration.UserLimits, premiumLimits: EngineConfiguration.UserLimits, isPremium: Bool, isPremiumDisabled: Bool, temporaryOrder: [String]?) -> [ChannelVisibilityEntry] { var entries: [ChannelVisibilityEntry] = [] let isInitialSetup: Bool @@ -1009,10 +1009,10 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in var lhsDate: Int32 = 0 var rhsDate: Int32 = 0 - if let lhs = lhs as? TelegramChannel { + if case let .channel(lhs) = lhs { lhsDate = lhs.creationDate } - if let rhs = rhs as? TelegramChannel { + if case let .channel(rhs) = rhs { rhsDate = rhs.creationDate } return lhsDate > rhsDate @@ -1185,10 +1185,10 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in var lhsDate: Int32 = 0 var rhsDate: Int32 = 0 - if let lhs = lhs as? TelegramChannel { + if case let .channel(lhs) = lhs { lhsDate = lhs.creationDate } - if let rhs = rhs as? TelegramChannel { + if case let .channel(rhs) = rhs { rhsDate = rhs.creationDate } return lhsDate > rhsDate @@ -1336,8 +1336,8 @@ private func effectiveChannelType(mode: ChannelVisibilityControllerMode, state: return selectedType } -private func updatedAddressName(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: Peer, cachedData: CachedPeerData?) -> String? { - if let peer = peer as? TelegramChannel { +private func updatedAddressName(mode: ChannelVisibilityControllerMode, state: ChannelVisibilityControllerState, peer: EnginePeer, cachedData: CachedPeerData?) -> String? { + if case let .channel(peer) = peer { let selectedType = effectiveChannelType(mode: mode, state: state, peer: peer, cachedData: cachedData) let currentUsername: String @@ -1368,7 +1368,7 @@ private func updatedAddressName(mode: ChannelVisibilityControllerMode, state: Ch } else { return nil } - } else if let _ = peer as? TelegramGroup { + } else if case .legacyGroup = peer { let currentUsername = state.editingPublicLinkText ?? "" if !currentUsername.isEmpty { return currentUsername @@ -1384,17 +1384,17 @@ public enum ChannelVisibilityControllerMode { case initialSetup case generic case privateLink - case revokeNames([Peer]) + case revokeNames([EnginePeer]) } -public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (PeerId, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil, revokedPeerAddressName: ((PeerId) -> Void)? = nil) -> ViewController { +public func channelVisibilityController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id, mode: ChannelVisibilityControllerMode, upgradedToSupergroup: @escaping (EnginePeer.Id, @escaping () -> Void) -> Void, onDismissRemoveController: ViewController? = nil, revokedPeerAddressName: ((EnginePeer.Id) -> Void)? = nil) -> ViewController { let statePromise = ValuePromise(ChannelVisibilityControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ChannelVisibilityControllerState()) let updateState: ((ChannelVisibilityControllerState) -> ChannelVisibilityControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - let adminedPublicChannels = Promise<[Peer]?>() + let adminedPublicChannels = Promise<[EnginePeer]?>() if case let .revokeNames(peers) = mode { adminedPublicChannels.set(.single(peers)) } else { @@ -1402,8 +1402,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta |> map(Optional.init)) } - let peersDisablingAddressNameAssignment = Promise<[Peer]?>() - peersDisablingAddressNameAssignment.set(.single(nil) |> then(context.engine.peers.channelAddressNameAssignmentAvailability(peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[Peer]?, NoError> in + let peersDisablingAddressNameAssignment = Promise<[EnginePeer]?>() + peersDisablingAddressNameAssignment.set(.single(nil) |> then(context.engine.peers.channelAddressNameAssignmentAvailability(peerId: peerId.namespace == Namespaces.Peer.CloudChannel ? peerId : nil) |> mapToSignal { result -> Signal<[EnginePeer]?, NoError> in if case .addressNameLimitReached = result { return context.engine.peers.adminedPublicChannels(scope: .all) |> map(Optional.init) @@ -1857,7 +1857,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta rightNavigationButton = ItemListNavigationButton(content: .text(isInitialSetup ? presentationData.strings.Common_Next : presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { var updatedAddressNameValue: String? updateState { state in - updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: view.cachedData) + updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .channel(peer), cachedData: view.cachedData) return state } @@ -1953,7 +1953,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: state.updatingAddressName ? .activity : .bold, enabled: doneEnabled, action: { var updatedAddressNameValue: String? updateState { state in - updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: peer, cachedData: nil) + updatedAddressNameValue = updatedAddressName(mode: mode, state: state, peer: .legacyGroup(peer), cachedData: nil) return state } diff --git a/submodules/PeerInfoUI/Sources/ConvertToSupergroupController.swift b/submodules/PeerInfoUI/Sources/ConvertToSupergroupController.swift index fa2162d8ff..e5c9a37338 100644 --- a/submodules/PeerInfoUI/Sources/ConvertToSupergroupController.swift +++ b/submodules/PeerInfoUI/Sources/ConvertToSupergroupController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -120,7 +119,7 @@ private func convertToSupergroupEntries(presentationData: PresentationData) -> [ return entries } -public func convertToSupergroupController(context: AccountContext, peerId: PeerId) -> ViewController { +public func convertToSupergroupController(context: AccountContext, peerId: EnginePeer.Id) -> ViewController { var replaceControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 69618abfd2..e310908f96 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import MessageUI import TelegramPresentationData @@ -24,6 +23,7 @@ import PhoneNumberFormat import UndoUI import GalleryUI import PeerAvatarGalleryUI +import Postbox private enum DeviceContactInfoAction { case sendMessage @@ -48,9 +48,9 @@ private final class DeviceContactInfoControllerArguments { let openAddress: (DeviceContactAddressData) -> Void let displayCopyContextMenu: (DeviceContactInfoEntryTag, String) -> Void let updateShareViaException: (Bool) -> Void - let openAvatar: (Peer) -> Void + let openAvatar: (EnginePeer) -> Void - init(context: AccountContext, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void, openAvatar: @escaping (Peer) -> Void) { + init(context: AccountContext, isPlain: Bool, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void, updateShareViaException: @escaping (Bool) -> Void, openAvatar: @escaping (EnginePeer) -> Void) { self.context = context self.isPlain = isPlain self.updateEditingName = updateEditingName @@ -127,7 +127,7 @@ private enum DeviceContactInfoEntryId: Hashable { } private enum DeviceContactInfoEntry: ItemListNodeEntry { - case info(Int, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer, state: ItemListAvatarAndNameInfoItemState, job: String?, isPlain: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) + case info(Int, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: EnginePeer, state: ItemListAvatarAndNameInfoItemState, job: String?, isPlain: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) case invite(Int, PresentationTheme, String) case sendMessage(Int, PresentationTheme, String) @@ -223,7 +223,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { if lhsDateTimeFormat != rhsDateTimeFormat { return false } - if !arePeersEqual(lhsPeer, rhsPeer) { + if lhsPeer != rhsPeer { return false } if lhsState != rhsState { @@ -404,7 +404,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { let arguments = arguments as! DeviceContactInfoControllerArguments switch self { case let .info(_, _, _, dateTimeFormat, peer, state, jobSummary, _, hiddenAvatar): - return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: EnginePeer(peer), presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in + return ItemListAvatarAndNameInfoItem(accountContext: arguments.context, presentationData: presentationData, dateTimeFormat: dateTimeFormat, mode: .contact, peer: peer, presence: nil, label: jobSummary, memberCount: nil, state: state, sectionId: self.section, style: arguments.isPlain ? .plain : .blocks(withTopInset: false, withExtendedBottomInset: true), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, avatarTapped: { if peer.smallProfileImage != nil { @@ -625,7 +625,7 @@ private func filteredContactData(contactData: DeviceContactExtendedData, exclude return DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumbers: phoneNumbers), middleName: contactData.middleName, prefix: contactData.prefix, suffix: contactData.suffix, organization: includeJob ? contactData.organization : "", jobTitle: includeJob ? contactData.jobTitle : "", department: includeJob ? contactData.department : "", emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: includeBirthday ? contactData.birthdayDate : nil, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles, note: includeNote ? contactData.note : "") } -private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: Peer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) -> [DeviceContactInfoEntry] { +private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: EnginePeer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool, hiddenAvatar: TelegramMediaImageRepresentation?) -> [DeviceContactInfoEntry] { var entries: [DeviceContactInfoEntry] = [] var editingName: ItemListAvatarAndNameInfoItemName? @@ -663,7 +663,7 @@ private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, firstName = presentationData.strings.Message_Contact } - entries.append(.info(entries.count, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer ?? TelegramUser(id: PeerId(namespace: .max, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: firstName, lastName: isOrganization ? nil : personName.1, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: isOrganization ? nil : jobSummary, isPlain: !isShare, hiddenAvatar: hiddenAvatar)) + entries.append(.info(entries.count, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer: peer ?? EnginePeer.user(TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: firstName, lastName: isOrganization ? nil : personName.1, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [])), state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), job: isOrganization ? nil : jobSummary, isPlain: !isShare, hiddenAvatar: hiddenAvatar)) if !selecting { if let _ = peer { @@ -696,7 +696,7 @@ private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, } else if !personName.1.isEmpty { personCompactName = personName.1 } else { - personCompactName = EnginePeer(peer).compactDisplayTitle + personCompactName = peer.compactDisplayTitle } if contactData.basicData.phoneNumbers.isEmpty { @@ -859,7 +859,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } var addToExistingImpl: (() -> Void)? - var openChatImpl: ((PeerId) -> Void)? + var openChatImpl: ((EnginePeer.Id) -> Void)? var replaceControllerImpl: ((ViewController) -> Void)? var pushControllerImpl: ((ViewController) -> Void)? var presentControllerImpl: ((ViewController, Any?) -> Void)? @@ -867,7 +867,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta var openAddressImpl: ((DeviceContactAddressData) -> Void)? var inviteImpl: (([String]) -> Void)? var dismissImpl: ((Bool) -> Void)? - var openAvatarImpl: ((Peer) -> Void)? + var openAvatarImpl: ((EnginePeer) -> Void)? let actionsDisposable = DisposableSet() @@ -918,16 +918,16 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta }) } - let contactData: Signal<(Peer?, DeviceContactStableId?, DeviceContactExtendedData), NoError> + let contactData: Signal<(EnginePeer?, DeviceContactStableId?, DeviceContactExtendedData), NoError> var isShare = false var shareViaException = false switch subject { case let .vcard(peer, id, data): - contactData = .single((peer, id, data)) + contactData = .single((peer.flatMap(EnginePeer.init), id, data)) case let .filter(peer, id, data, _): - contactData = .single((peer, id, data)) + contactData = .single((peer.flatMap(EnginePeer.init), id, data)) case let .create(peer, data, share, shareViaExceptionValue, _): - contactData = .single((peer, nil, data)) + contactData = .single((peer.flatMap(EnginePeer.init), nil, data)) isShare = share shareViaException = shareViaExceptionValue } @@ -1084,7 +1084,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } else if case let .filter(_, _, _, completion) = subject { let filteredData = filteredContactData(contactData: peerAndContactData.2, excludedComponents: state.excludedComponents) rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.ShareMenu_Send), style: .bold, enabled: !filteredData.basicData.phoneNumbers.isEmpty, action: { - completion(peerAndContactData.0, filteredData) + completion(peerAndContactData.0?._asPeer(), filteredData) dismissImpl?(true) }) } else if case let .create(createForPeer, _, _, _, completion) = subject { @@ -1113,6 +1113,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } composedContactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: filteredPhoneNumbers), middleName: filteredData.middleName, prefix: filteredData.prefix, suffix: filteredData.suffix, organization: filteredData.organization, jobTitle: filteredData.jobTitle, department: filteredData.department, emailAddresses: filteredData.emailAddresses, urls: urls, addresses: filteredData.addresses, birthdayDate: filteredData.birthdayDate, socialProfiles: filteredData.socialProfiles, instantMessagingProfiles: filteredData.instantMessagingProfiles, note: filteredData.note) } + rightNavigationButton = ItemListNavigationButton(content: .text(isShare ? presentationData.strings.Common_Done : presentationData.strings.Compose_Create), style: .bold, enabled: (isShare || !filteredPhoneNumbers.isEmpty) && composedContactData != nil, action: { if let composedContactData = composedContactData { var addToPrivacyExceptions = false @@ -1152,7 +1153,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta let _ = (contactDataManager.createContactWithData(composedContactData) |> castError(AddContactError.self) - |> mapToSignal { contactIdAndData -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in + |> mapToSignal { contactIdAndData -> Signal<(DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)?, AddContactError> in guard let (id, data) = contactIdAndData else { return .single(nil) } @@ -1161,13 +1162,13 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta case let .create(peer, _, share, shareViaException, _): if share, let peer = peer { return context.engine.contacts.addContactInteractively(peerId: peer.id, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers.first?.value ?? "", addToPrivacyExceptions: shareViaException && addToPrivacyExceptions) - |> mapToSignal { _ -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in + |> mapToSignal { _ -> Signal<(DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)?, AddContactError> in } |> then( context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) |> castError(AddContactError.self) - |> map { result -> (DeviceContactStableId, DeviceContactExtendedData, Peer?)? in - return (id, data, result?._asPeer()) + |> map { result -> (DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)? in + return (id, data, result) } ) } @@ -1177,12 +1178,12 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta return context.engine.contacts.importContact(firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers[0].value) |> castError(AddContactError.self) - |> mapToSignal { peerId -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, AddContactError> in + |> mapToSignal { peerId -> Signal<(DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)?, AddContactError> in if let peerId = peerId { return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> castError(AddContactError.self) - |> map { result -> (DeviceContactStableId, DeviceContactExtendedData, Peer?)? in - return (id, data, result?._asPeer()) + |> map { result -> (DeviceContactStableId, DeviceContactExtendedData, EnginePeer?)? in + return (id, data, result) } } else { return .single((id, data, nil)) @@ -1199,7 +1200,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta return state } if let contactIdAndData = contactIdAndData { - completion(contactIdAndData.2, contactIdAndData.0, contactIdAndData.1) + completion(contactIdAndData.2?._asPeer(), contactIdAndData.0, contactIdAndData.1) } completed?() dismissImpl?(true) @@ -1231,7 +1232,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta let editingPhoneIds = Set(state.phoneNumbers.map({ $0.id })) let previousPhoneIds = previousEditingPhoneIds.swap(editingPhoneIds) - let insertedPhoneIds = editingPhoneIds.subtracting(previousPhoneIds ?? Set()) + let insertedPhoneIds = editingPhoneIds.subtracting(previousPhoneIds ?? Set()) var insertedPhoneId: Int64? if insertedPhoneIds.count == 1, let id = insertedPhoneIds.first { for phoneNumber in state.phoneNumbers { @@ -1264,7 +1265,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta return } addContactToExisting(context: context, parentController: controller, contactData: subject.contactData, completion: { peer, contactId, contactData in - replaceControllerImpl?(deviceContactInfoController(context: context, subject: .vcard(peer, contactId, contactData), completed: nil, cancelled: nil)) + replaceControllerImpl?(deviceContactInfoController(context: context, subject: .vcard(peer?._asPeer(), contactId, contactData), completed: nil, cancelled: nil)) }) } openChatImpl = { [weak controller] peerId in @@ -1349,7 +1350,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta } } openAvatarImpl = { [weak controller] peer in - let avatarController = AvatarGalleryController(context: context, peer: EnginePeer(peer), replaceRootController: { _, _ in + let avatarController = AvatarGalleryController(context: context, peer: peer, replaceRootController: { _, _ in }) hiddenAvatarPromise.set( avatarController.hiddenMedia @@ -1378,14 +1379,14 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta return controller } -private func addContactToExisting(context: AccountContext, parentController: ViewController, contactData: DeviceContactExtendedData, completion: @escaping (Peer?, DeviceContactStableId, DeviceContactExtendedData) -> Void) { +private func addContactToExisting(context: AccountContext, parentController: ViewController, contactData: DeviceContactExtendedData, completion: @escaping (EnginePeer?, DeviceContactStableId, DeviceContactExtendedData) -> Void) { let contactsController = context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: context, title: { $0.Contacts_Title }, displayDeviceContacts: true)) contactsController.navigationPresentation = .modal (parentController.navigationController as? NavigationController)?.pushViewController(contactsController) let _ = (contactsController.result |> deliverOnMainQueue).start(next: { result in if let (peers, _, _, _, _) = result, let peer = peers.first { - let dataSignal: Signal<(Peer?, DeviceContactStableId?), NoError> + let dataSignal: Signal<(EnginePeer?, DeviceContactStableId?), NoError> switch peer { case let .peer(contact, _, _): guard let contact = contact as? TelegramUser, let phoneNumber = contact.phone else { @@ -1393,7 +1394,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie } dataSignal = (context.sharedContext.contactDataManager?.basicData() ?? .single([:])) |> take(1) - |> mapToSignal { basicData -> Signal<(Peer?, DeviceContactStableId?), NoError> in + |> mapToSignal { basicData -> Signal<(EnginePeer?, DeviceContactStableId?), NoError> in var stableId: String? let queryPhoneNumber = formatPhoneNumber(phoneNumber) outer: for (id, data) in basicData { @@ -1404,7 +1405,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie } } } - return .single((contact, stableId)) + return .single((EnginePeer.user(contact), stableId)) } case let .deviceContact(id, _): dataSignal = .single((nil, id)) @@ -1412,8 +1413,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie let _ = (dataSignal |> deliverOnMainQueue).start(next: { peer, stableId in guard let stableId = stableId else { - parentController.present(deviceContactInfoController(context: context, subject: .create(peer: peer, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in - + parentController.present(deviceContactInfoController(context: context, subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in }), completed: nil, cancelled: nil), in: .window(.root)) return } @@ -1440,7 +1440,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie } } } - completion(foundPeer?._asPeer(), stableId, contactData) + completion(foundPeer, stableId, contactData) }) }) } @@ -1449,7 +1449,7 @@ private func addContactToExisting(context: AccountContext, parentController: Vie }) } -func addContactOptionsController(context: AccountContext, peer: Peer?, contactData: DeviceContactExtendedData) -> ActionSheetController { +func addContactOptionsController(context: AccountContext, peer: EnginePeer?, contactData: DeviceContactExtendedData) -> ActionSheetController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationData: presentationData) let dismissAction: () -> Void = { [weak controller] in @@ -1459,7 +1459,7 @@ func addContactOptionsController(context: AccountContext, peer: Peer?, contactDa controller.setItemGroups([ ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: presentationData.strings.Profile_CreateNewContact, action: { [weak controller] in - controller?.present(context.sharedContext.makeDeviceContactInfoController(context: context, subject: .create(peer: peer, contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in + controller?.present(context.sharedContext.makeDeviceContactInfoController(context: context, subject: .create(peer: peer?._asPeer(), contactData: contactData, isSharing: peer != nil, shareViaException: false, completion: { _, _, _ in }), completed: nil, cancelled: nil), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) dismissAction() }), @@ -1468,7 +1468,6 @@ func addContactOptionsController(context: AccountContext, peer: Peer?, contactDa return } addContactToExisting(context: context, parentController: controller, contactData: contactData, completion: { peer, contactId, contactData in - }) dismissAction() }) diff --git a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift index 66fcbf2133..020bb7bda2 100644 --- a/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift +++ b/submodules/PeerInfoUI/Sources/GroupInfoSearchItem.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import ItemListUI @@ -11,10 +10,10 @@ import AccountContext final class ChannelMembersSearchItem: ItemListControllerSearch { let context: AccountContext - let peerId: PeerId + let peerId: EnginePeer.Id let searchContext: GroupMembersSearchContext? let cancel: () -> Void - let openPeer: (Peer, RenderedChannelParticipant?) -> Void + let openPeer: (EnginePeer, RenderedChannelParticipant?) -> Void let pushController: (ViewController) -> Void let dismissInput: () -> Void let searchMode: ChannelMembersSearchMode @@ -23,7 +22,7 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, peerId: PeerId, searchContext: GroupMembersSearchContext?, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) { + init(context: AccountContext, peerId: EnginePeer.Id, searchContext: GroupMembersSearchContext?, searchMode: ChannelMembersSearchMode = .searchMembers, cancel: @escaping () -> Void, openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) { self.context = context self.peerId = peerId self.searchContext = searchContext @@ -85,7 +84,7 @@ final class ChannelMembersSearchItem: ItemListControllerSearch { private final class ChannelMembersSearchItemNode: ItemListControllerSearchNode { private let containerNode: ChannelMembersSearchContainerNode - init(context: AccountContext, peerId: PeerId, searchMode: ChannelMembersSearchMode, searchContext: GroupMembersSearchContext?, openPeer: @escaping (Peer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) { + init(context: AccountContext, peerId: EnginePeer.Id, searchMode: ChannelMembersSearchMode, searchContext: GroupMembersSearchContext?, openPeer: @escaping (EnginePeer, RenderedChannelParticipant?) -> Void, cancel: @escaping () -> Void, updateActivity: @escaping(Bool) -> Void, pushController: @escaping (ViewController) -> Void, dismissInput: @escaping () -> Void) { self.containerNode = ChannelMembersSearchContainerNode(context: context, forceTheme: nil, peerId: peerId, mode: searchMode, filters: [], searchContext: searchContext, openPeer: { peer, participant in openPeer(peer, participant) }, updateActivity: updateActivity, pushController: pushController) diff --git a/submodules/PeerInfoUI/Sources/OldChannelsController.swift b/submodules/PeerInfoUI/Sources/OldChannelsController.swift index 1ed188000e..1dc627c391 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsController.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -61,11 +60,11 @@ func localizedOldChannelDate(peer: InactiveChannel, strings: PresentationStrings private final class OldChannelsItemArguments { let context: AccountContext - let togglePeer: (PeerId, Bool) -> Void + let togglePeer: (EnginePeer.Id, Bool) -> Void init( context: AccountContext, - togglePeer: @escaping (PeerId, Bool) -> Void + togglePeer: @escaping (EnginePeer.Id, Bool) -> Void ) { self.context = context self.togglePeer = togglePeer @@ -80,7 +79,7 @@ private enum OldChannelsSection: Int32 { private enum OldChannelsEntryId: Hashable { case info case peersHeader - case peer(PeerId) + case peer(EnginePeer.Id) } private enum OldChannelsEntry: ItemListNodeEntry { @@ -181,7 +180,7 @@ private enum OldChannelsEntry: ItemListNodeEntry { } private struct OldChannelsState: Equatable { - var selectedPeers: Set = Set() + var selectedPeers: Set = Set() var isSearching: Bool = false } @@ -255,7 +254,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa var pushImpl: ((ViewController) -> Void)? var setDisplayNavigationBarImpl: ((Bool) -> Void)? - var ensurePeerVisibleImpl: ((PeerId) -> Void)? + var ensurePeerVisibleImpl: ((EnginePeer.Id) -> Void)? var leaveActionImpl: (() -> Void)? diff --git a/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift b/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift index 903b05bc1a..cb679ae79e 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsSearch.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -33,14 +32,14 @@ final class OldChannelsSearchItem: ItemListControllerSearch { let activated: Bool let updateActivated: (Bool) -> Void let peers: Signal<[InactiveChannel], NoError> - let selectedPeerIds: Signal, NoError> - let togglePeer: (PeerId) -> Void + let selectedPeerIds: Signal, NoError> + let togglePeer: (EnginePeer.Id) -> Void private var updateActivity: ((Bool) -> Void)? private var activity: ValuePromise = ValuePromise(ignoreRepeated: false) private let activityDisposable = MetaDisposable() - init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal, NoError>, togglePeer: @escaping (PeerId) -> Void) { + init(context: AccountContext, theme: PresentationTheme, placeholder: String, activated: Bool, updateActivated: @escaping (Bool) -> Void, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal, NoError>, togglePeer: @escaping (EnginePeer.Id) -> Void) { self.context = context self.theme = theme self.placeholder = placeholder @@ -101,9 +100,9 @@ final class OldChannelsSearchItem: ItemListControllerSearch { } private final class OldChannelsSearchInteraction { - let togglePeer: (PeerId) -> Void + let togglePeer: (EnginePeer.Id) -> Void - init(togglePeer: @escaping (PeerId) -> Void) { + init(togglePeer: @escaping (EnginePeer.Id) -> Void) { self.togglePeer = togglePeer } } @@ -111,7 +110,7 @@ private final class OldChannelsSearchInteraction { private enum OldChannelsSearchEntry: Comparable, Identifiable { case peer(Int, InactiveChannel, Bool) - var stableId: PeerId { + var stableId: EnginePeer.Id { switch self { case let .peer(_, peer, _): return peer.peer.id @@ -180,7 +179,7 @@ private final class OldChannelsSearchContainerNode: SearchDisplayControllerConte private var presentationDataDisposable: Disposable? private let presentationDataPromise: Promise - init(context: AccountContext, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal, NoError>, togglePeer: @escaping (PeerId) -> Void) { + init(context: AccountContext, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal, NoError>, togglePeer: @escaping (EnginePeer.Id) -> Void) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = presentationData self.presentationDataPromise = Promise(self.presentationData) @@ -352,10 +351,10 @@ private final class OldChannelsSearchItemNode: ItemListControllerSearchNode { var cancel: () -> Void private let peers: Signal<[InactiveChannel], NoError> - private let selectedPeerIds: Signal, NoError> - private let togglePeer: (PeerId) -> Void + private let selectedPeerIds: Signal, NoError> + private let togglePeer: (EnginePeer.Id) -> Void - init(context: AccountContext, cancel: @escaping () -> Void, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal, NoError>, togglePeer: @escaping (PeerId) -> Void) { + init(context: AccountContext, cancel: @escaping () -> Void, peers: Signal<[InactiveChannel], NoError>, selectedPeerIds: Signal, NoError>, togglePeer: @escaping (EnginePeer.Id) -> Void) { self.context = context self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.cancel = cancel diff --git a/submodules/PeerInfoUI/Sources/PeerReportController.swift b/submodules/PeerInfoUI/Sources/PeerReportController.swift index d26b4c00d8..3e77918bc5 100644 --- a/submodules/PeerInfoUI/Sources/PeerReportController.swift +++ b/submodules/PeerInfoUI/Sources/PeerReportController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI @@ -17,9 +16,9 @@ import TelegramPermissionsUI import Markdown public enum PeerReportSubject { - case peer(PeerId) - case messages([MessageId]) - case profilePhoto(PeerId, Int64) + case peer(EnginePeer.Id) + case messages([EngineMessage.Id]) + case profilePhoto(EnginePeer.Id, Int64) } public enum PeerReportOption { diff --git a/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift b/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift index 120e211973..ebafd19083 100644 --- a/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift +++ b/submodules/PeerInfoUI/Sources/SecretChatKeyController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import TelegramPresentationData import AccountContext @@ -14,11 +13,11 @@ public final class SecretChatKeyController: ViewController { private let context: AccountContext private let fingerprint: SecretChatKeyFingerprint - private let peer: Peer + private let peer: EnginePeer private var presentationData: PresentationData - public init(context: AccountContext, fingerprint: SecretChatKeyFingerprint, peer: Peer) { + public init(context: AccountContext, fingerprint: SecretChatKeyFingerprint, peer: EnginePeer) { self.context = context self.fingerprint = fingerprint self.peer = peer diff --git a/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift b/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift index ac7cb5d653..985a4bf850 100644 --- a/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift +++ b/submodules/PeerInfoUI/Sources/SecretChatKeyControllerNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import TelegramCore -import Postbox import TelegramPresentationData import TextFormat import AccountContext @@ -30,7 +29,7 @@ final class SecretChatKeyControllerNode: ViewControllerTracingNode { private let context: AccountContext private var presentationData: PresentationData private let fingerprint: SecretChatKeyFingerprint - private let peer: Peer + private let peer: EnginePeer private let getNavigationController: () -> NavigationController? private let scrollNode: ASScrollNode @@ -40,7 +39,7 @@ final class SecretChatKeyControllerNode: ViewControllerTracingNode { private var validImageSize: CGSize? - init(context: AccountContext, presentationData: PresentationData, fingerprint: SecretChatKeyFingerprint, peer: Peer, getNavigationController: @escaping () -> NavigationController?) { + init(context: AccountContext, presentationData: PresentationData, fingerprint: SecretChatKeyFingerprint, peer: EnginePeer, getNavigationController: @escaping () -> NavigationController?) { self.context = context self.presentationData = presentationData self.fingerprint = fingerprint @@ -118,7 +117,7 @@ final class SecretChatKeyControllerNode: ViewControllerTracingNode { let (keyTextLayout, keyTextApply) = makeKeyTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: text, font: Font.semiboldMonospace(15.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: layout.size.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) - let infoString = self.presentationData.strings.EncryptionKey_Description(EnginePeer(self.peer).compactDisplayTitle, EnginePeer(self.peer).compactDisplayTitle) + let infoString = self.presentationData.strings.EncryptionKey_Description(self.peer.compactDisplayTitle, self.peer.compactDisplayTitle) let infoText = NSMutableAttributedString(string: infoString.string, attributes: [.font: Font.regular(14.0), .foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor]) for range in infoString.ranges { diff --git a/submodules/PeerInfoUI/Sources/UserInfoController.swift b/submodules/PeerInfoUI/Sources/UserInfoController.swift index 3ecf99828c..5204d23782 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoController.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramPresentationData diff --git a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift index 58f145fd42..6c80171197 100644 --- a/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift +++ b/submodules/SettingsUI/Sources/CachedFaqInstantPage.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import InstantPageUI diff --git a/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift b/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift index 322a22c1f6..56251e3eb4 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ShareProxyServerActionSheetController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import TelegramCore -import Postbox import AsyncDisplayKit import UIKit import SwiftSignalKit diff --git a/submodules/SettingsUI/Sources/DeleteAccountDataController.swift b/submodules/SettingsUI/Sources/DeleteAccountDataController.swift index 7b2934724c..efa45cab1c 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountDataController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountDataController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramPresentationData @@ -283,10 +282,7 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat return peers } - preloadedGroupPeers.set(context.engine.peers.adminedPublicChannels(scope: .all) - |> map { peers -> [EnginePeer] in - return peers.map { EnginePeer($0) } - }) + preloadedGroupPeers.set(context.engine.peers.adminedPublicChannels(scope: .all)) case let .groups(preloadedPeers): peers = .single(preloadedPeers.shuffled()) default: diff --git a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift index 5422f7abe9..67f73ad248 100644 --- a/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift +++ b/submodules/SettingsUI/Sources/DeleteAccountOptionsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramPresentationData @@ -322,7 +321,7 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let supportPeer = Promise() + let supportPeer = Promise() supportPeer.set(context.engine.peers.supportPeerId()) var faqUrl = presentationData.strings.Settings_FAQ_URL diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index d6750a91a0..b217e08261 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import LegacyComponents import TelegramPresentationData @@ -193,7 +192,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle pushControllerImpl?(introController) dismissImpl?() }, contactSupport: { [weak navigationController] in - let supportPeer = Promise() + let supportPeer = Promise() supportPeer.set(context.engine.peers.supportPeerId()) let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index 6d109338d2..c474bb262f 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -20,14 +19,15 @@ import ChatListUI import ItemListPeerActionItem import TelegramStringFormatting import NotificationPeerExceptionController +import Postbox private final class NotificationExceptionState : Equatable { let mode: NotificationExceptionMode let isSearchMode: Bool - let revealedPeerId: PeerId? + let revealedPeerId: EnginePeer.Id? let editing: Bool - init(mode: NotificationExceptionMode, isSearchMode: Bool = false, revealedPeerId: PeerId? = nil, editing: Bool = false) { + init(mode: NotificationExceptionMode, isSearchMode: Bool = false, revealedPeerId: EnginePeer.Id? = nil, editing: Bool = false) { self.mode = mode self.isSearchMode = isSearchMode self.revealedPeerId = revealedPeerId @@ -46,19 +46,19 @@ private final class NotificationExceptionState : Equatable { return NotificationExceptionState(mode: self.mode, isSearchMode: self.isSearchMode, revealedPeerId: self.revealedPeerId, editing: editing) } - func withUpdatedRevealedPeerId(_ revealedPeerId: PeerId?) -> NotificationExceptionState { + func withUpdatedRevealedPeerId(_ revealedPeerId: EnginePeer.Id?) -> NotificationExceptionState { return NotificationExceptionState(mode: self.mode, isSearchMode: self.isSearchMode, revealedPeerId: revealedPeerId, editing: self.editing) } - func withUpdatedPeerSound(_ peer: Peer, _ sound: PeerMessageSound) -> NotificationExceptionState { + func withUpdatedPeerSound(_ peer: EnginePeer, _ sound: PeerMessageSound) -> NotificationExceptionState { return NotificationExceptionState(mode: mode.withUpdatedPeerSound(peer, sound), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing) } - func withUpdatedPeerMuteInterval(_ peer: Peer, _ muteInterval: Int32?) -> NotificationExceptionState { + func withUpdatedPeerMuteInterval(_ peer: EnginePeer, _ muteInterval: Int32?) -> NotificationExceptionState { return NotificationExceptionState(mode: mode.withUpdatedPeerMuteInterval(peer, muteInterval), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing) } - func withUpdatedPeerDisplayPreviews(_ peer: Peer, _ displayPreviews: PeerNotificationDisplayPreviews) -> NotificationExceptionState { + func withUpdatedPeerDisplayPreviews(_ peer: EnginePeer, _ displayPreviews: PeerNotificationDisplayPreviews) -> NotificationExceptionState { return NotificationExceptionState(mode: mode.withUpdatedPeerDisplayPreviews(peer, displayPreviews), isSearchMode: isSearchMode, revealedPeerId: self.revealedPeerId, editing: self.editing) } @@ -67,25 +67,25 @@ private final class NotificationExceptionState : Equatable { } } -private func notificationsExceptionEntries(presentationData: PresentationData, notificationSoundList: NotificationSoundList?, state: NotificationExceptionState, query: String? = nil, foundPeers: [RenderedPeer] = []) -> [NotificationExceptionEntry] { +private func notificationsExceptionEntries(presentationData: PresentationData, notificationSoundList: NotificationSoundList?, state: NotificationExceptionState, query: String? = nil, foundPeers: [EngineRenderedPeer] = []) -> [NotificationExceptionEntry] { var entries: [NotificationExceptionEntry] = [] if !state.isSearchMode { entries.append(.addException(presentationData.theme, presentationData.strings, state.mode.mode, state.editing)) } - var existingPeerIds = Set() + var existingPeerIds = Set() var index: Int = 0 for (_, value) in state.mode.settings.filter({ (_, value) in if let query = query, !query.isEmpty { - return !EnginePeer(value.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder).lowercased().components(separatedBy: " ").filter { $0.hasPrefix(query.lowercased())}.isEmpty + return !value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder).lowercased().components(separatedBy: " ").filter { $0.hasPrefix(query.lowercased())}.isEmpty } else { return true } }).sorted(by: { lhs, rhs in - let lhsName = EnginePeer(lhs.value.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - let rhsName = EnginePeer(rhs.value.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let lhsName = lhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let rhsName = rhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) if let lhsDate = lhs.value.date, let rhsDate = rhs.value.date { return lhsDate > rhsDate @@ -95,7 +95,7 @@ private func notificationsExceptionEntries(presentationData: PresentationData, n return false } - if let lhsPeer = lhs.value.peer as? TelegramUser, let rhsPeer = rhs.value.peer as? TelegramUser { + if case let .user(lhsPeer) = lhs.value.peer, case let .user(rhsPeer) = rhs.value.peer { if lhsPeer.botInfo != nil && rhsPeer.botInfo == nil { return false } else if lhsPeer.botInfo == nil && rhsPeer.botInfo != nil { @@ -173,18 +173,18 @@ private func notificationsExceptionEntries(presentationData: PresentationData, n } switch state.mode { case .channels: - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { + if case let .channel(channel) = peer, case .broadcast = channel.info { } else { continue } case .groups: - if let channel = peer as? TelegramChannel, case .broadcast = channel.info { - } else if peer is TelegramGroup { + if case let .channel(channel) = peer, case .broadcast = channel.info { + } else if case .legacyGroup = peer { } else { continue } case .users: - if peer is TelegramUser { + if case .user = peer { } else { continue } @@ -208,13 +208,13 @@ private func notificationsExceptionEntries(presentationData: PresentationData, n private final class NotificationExceptionArguments { let context: AccountContext let activateSearch:()->Void - let openPeer: (Peer) -> Void + let openPeer: (EnginePeer) -> Void let selectPeer: ()->Void - let updateRevealedPeerId:(PeerId?)->Void - let deletePeer:(Peer) -> Void + let updateRevealedPeerId: (EnginePeer.Id?)->Void + let deletePeer:(EnginePeer) -> Void let removeAll:() -> Void - init(context: AccountContext, activateSearch:@escaping() -> Void, openPeer: @escaping(Peer) -> Void, selectPeer: @escaping()->Void, updateRevealedPeerId:@escaping(PeerId?)->Void, deletePeer: @escaping(Peer) -> Void, removeAll:@escaping() -> Void) { + init(context: AccountContext, activateSearch:@escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, selectPeer: @escaping()->Void, updateRevealedPeerId:@escaping (EnginePeer.Id?)->Void, deletePeer: @escaping (EnginePeer) -> Void, removeAll:@escaping() -> Void) { self.context = context self.activateSearch = activateSearch self.openPeer = openPeer @@ -286,8 +286,8 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { typealias ItemGenerationArguments = NotificationExceptionArguments case search(PresentationTheme, PresentationStrings) - case peer(index: Int, peer: Peer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, description: String, notificationSettings: TelegramPeerNotificationSettings, revealed: Bool, editing: Bool, isSearching: Bool) - case addPeer(index: Int, peer: Peer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) + case peer(index: Int, peer: EnginePeer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, description: String, notificationSettings: TelegramPeerNotificationSettings, revealed: Bool, editing: Bool, isSearching: Bool) + case addPeer(index: Int, peer: EnginePeer, theme: PresentationTheme, strings: PresentationStrings, dateFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder) case addException(PresentationTheme, PresentationStrings, NotificationExceptionMode.Mode, Bool) case removeAll(PresentationTheme, PresentationStrings) @@ -312,7 +312,7 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { arguments.selectPeer() }) case let .peer(_, peer, _, _, dateTimeFormat, nameDisplayOrder, value, _, revealed, editing, isSearching): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .text(value, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(value, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.updateRevealedPeerId(peerId) @@ -320,7 +320,7 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { arguments.deletePeer(peer) }, hasTopStripe: false, hasTopGroupInset: false, noInsets: isSearching) case let .addPeer(_, peer, theme, strings, _, nameDisplayOrder): - return ContactsPeerItem(presentationData: presentationData, sortOrder: nameDisplayOrder, displayOrder: nameDisplayOrder, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer), chatPeer: EnginePeer(peer)), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .add, index: nil, header: ChatListSearchItemHeader(type: .addToExceptions, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in + return ContactsPeerItem(presentationData: presentationData, sortOrder: nameDisplayOrder, displayOrder: nameDisplayOrder, context: arguments.context, peerMode: .peer, peer: .peer(peer: peer, chatPeer: peer), status: .none, enabled: true, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .add, index: nil, header: ChatListSearchItemHeader(type: .addToExceptions, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { _ in arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { _, _ in }) @@ -365,14 +365,14 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsValue, lhsSettings, lhsRevealed, lhsEditing, lhsIsSearching): switch rhs { case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsValue, rhsSettings, rhsRevealed, rhsEditing, rhsIsSearching): - return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsDateTimeFormat == rhsDateTimeFormat && lhsNameOrder == rhsNameOrder && lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) && lhsValue == rhsValue && lhsSettings == rhsSettings && lhsRevealed == rhsRevealed && lhsEditing == rhsEditing && lhsIsSearching == rhsIsSearching + return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsDateTimeFormat == rhsDateTimeFormat && lhsNameOrder == rhsNameOrder && lhsIndex == rhsIndex && lhsPeer == rhsPeer && lhsValue == rhsValue && lhsSettings == rhsSettings && lhsRevealed == rhsRevealed && lhsEditing == rhsEditing && lhsIsSearching == rhsIsSearching default: return false } case let .addPeer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder): switch rhs { case let .addPeer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder): - return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsDateTimeFormat == rhsDateTimeFormat && lhsNameOrder == rhsNameOrder && lhsIndex == rhsIndex && lhsPeer.isEqual(rhsPeer) + return lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsDateTimeFormat == rhsDateTimeFormat && lhsNameOrder == rhsNameOrder && lhsIndex == rhsIndex && lhsPeer == rhsPeer default: return false } @@ -515,7 +515,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { } let updateNotificationsDisposable = self.updateNotificationsDisposable - var peerIds: Set = Set(mode.peerIds) + var peerIds: Set = Set(mode.peerIds) let updateNotificationsView: (@escaping () -> Void) -> Void = { completion in updateState { current in @@ -534,12 +534,12 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { for (key, value) in notificationSettingsMap { if let local = current.mode.settings[key] { if !value._asNotificationSettings().isEqual(to: local.settings), let maybePeer = peerMap[key], let peer = maybePeer, let settings = notificationSettingsMap[key], !settings._asNotificationSettings().isEqual(to: local.settings) { - current = current.withUpdatedPeerSound(peer._asPeer(), settings.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer._asPeer(), settings.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer._asPeer(), settings.displayPreviews._asDisplayPreviews()) + current = current.withUpdatedPeerSound(peer, settings.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer, settings.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer, settings.displayPreviews._asDisplayPreviews()) } } else if let maybePeer = peerMap[key], let peer = maybePeer { if case .default = value.messageSound, case .unmuted = value.muteState, case .default = value.displayPreviews { } else { - current = current.withUpdatedPeerSound(peer._asPeer(), value.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer._asPeer(), value.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer._asPeer(), value.displayPreviews._asDisplayPreviews()) + current = current.withUpdatedPeerSound(peer, value.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer, value.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer, value.displayPreviews._asDisplayPreviews()) } } } @@ -560,15 +560,15 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { let presentationData = context.sharedContext.currentPresentationData.modify {$0} - let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + let updatePeerSound: (EnginePeer.Id, PeerMessageSound) -> Signal = { peerId, sound in return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: nil, sound: sound) |> deliverOnMainQueue } - let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in + let updatePeerNotificationInterval: (EnginePeer.Id, Int32?) -> Signal = { peerId, muteInterval in return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: nil, muteInterval: muteInterval) |> deliverOnMainQueue } - let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal = { + let updatePeerDisplayPreviews:(EnginePeer.Id, PeerNotificationDisplayPreviews) -> Signal = { peerId, displayPreviews in return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue } @@ -580,7 +580,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { requestActivateSearch() } - let presentPeerSettings: (PeerId, @escaping () -> Void) -> Void = { [weak self] peerId, completion in + let presentPeerSettings: (EnginePeer.Id, @escaping () -> Void) -> Void = { [weak self] peerId, completion in (self?.searchDisplayController?.contentNode as? NotificationExceptionsSearchContainerNode)?.listNode.clearHighlightAnimated(true) let _ = (context.engine.data.get( @@ -610,13 +610,13 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { defaultSound = globalSettings.privateChats.sound._asMessageSound() } - presentControllerImpl?(notificationPeerExceptionController(context: context, peer: peer._asPeer(), threadId: nil, canRemove: canRemove, defaultSound: defaultSound, updatePeerSound: { peerId, sound in + presentControllerImpl?(notificationPeerExceptionController(context: context, peer: peer, threadId: nil, canRemove: canRemove, defaultSound: defaultSound, updatePeerSound: { peerId, sound in _ = updatePeerSound(peer.id, sound).start(next: { _ in updateNotificationsDisposable.set(nil) _ = combineLatest(updatePeerSound(peer.id, sound), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in if let peer = peer { updateState { value in - return value.withUpdatedPeerSound(peer._asPeer(), sound) + return value.withUpdatedPeerSound(peer, sound) } } updateNotificationsView({}) @@ -627,7 +627,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { _ = combineLatest(updatePeerNotificationInterval(peerId, muteInterval), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in if let peer = peer { updateState { value in - return value.withUpdatedPeerMuteInterval(peer._asPeer(), muteInterval) + return value.withUpdatedPeerMuteInterval(peer, muteInterval) } } updateNotificationsView({}) @@ -637,7 +637,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { _ = combineLatest(updatePeerDisplayPreviews(peerId, displayPreviews), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in if let peer = peer { updateState { value in - return value.withUpdatedPeerDisplayPreviews(peer._asPeer(), displayPreviews) + return value.withUpdatedPeerDisplayPreviews(peer, displayPreviews) } } updateNotificationsView({}) @@ -652,7 +652,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { return } updateState { value in - return value.withUpdatedPeerDisplayPreviews(peer._asPeer(), .default).withUpdatedPeerSound(peer._asPeer(), .default).withUpdatedPeerMuteInterval(peer._asPeer(), nil) + return value.withUpdatedPeerDisplayPreviews(peer, .default).withUpdatedPeerSound(peer, .default).withUpdatedPeerMuteInterval(peer, nil) } updateNotificationsView({}) }) @@ -693,7 +693,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { return current.withUpdatedRevealedPeerId(peerId) } }, deletePeer: { peer in - let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [EnginePeer(peer)]) + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [peer]) |> deliverOnMainQueue).start(completed: { updateNotificationsDisposable.set(nil) updateState { value in @@ -713,7 +713,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode { actionSheet?.dismissAnimated() let values = stateValue.with { $0.mode.settings.values } - let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { EnginePeer($0.peer) }) + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { $0.peer }) |> deliverOnMainQueue).start(completed: { updateNotificationsDisposable.set(nil) updateState { state in @@ -979,7 +979,7 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont for (key, value) in notificationSettingsMap { if let local = current.mode.settings[key] { if !value._asNotificationSettings().isEqual(to: local.settings), let maybePeer = peerMap[key], let peer = maybePeer, let settings = notificationSettingsMap[key], !settings._asNotificationSettings().isEqual(to: local.settings) { - current = current.withUpdatedPeerSound(peer._asPeer(), settings.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer._asPeer(), settings.muteState.timeInterval) + current = current.withUpdatedPeerSound(peer, settings.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer, settings.muteState.timeInterval) } } } @@ -1013,10 +1013,13 @@ private final class NotificationExceptionsSearchContainerNode: SearchDisplayCont |> distinctUntilChanged let searchSignal = stateQuery - |> mapToSignal { query -> Signal<(PresentationData, NotificationSoundList?, (NotificationExceptionState, String?), PreferencesView, [RenderedPeer]), NoError> in - var contactsSignal: Signal<[RenderedPeer], NoError> = .single([]) + |> mapToSignal { query -> Signal<(PresentationData, NotificationSoundList?, (NotificationExceptionState, String?), PreferencesView, [EngineRenderedPeer]), NoError> in + var contactsSignal: Signal<[EngineRenderedPeer], NoError> = .single([]) if let query = query { contactsSignal = context.account.postbox.searchPeers(query: query) + |> map { items -> [EngineRenderedPeer] in + return items.map(EngineRenderedPeer.init) + } } return combineLatest(context.sharedContext.presentationData, context.engine.peers.notificationSoundList(), stateAndPeers, preferences, contactsSignal) } diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift index 934bd43a6d..2ad55a81a8 100644 --- a/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift +++ b/submodules/SettingsUI/Sources/Notifications/NotificationsAndSoundsController.swift @@ -726,24 +726,24 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: peer) + users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) default: if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - channels[key] = NotificationExceptionWrapper(settings: value, peer: peer) + channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) + groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) } } } default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: peer) + users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) default: if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - channels[key] = NotificationExceptionWrapper(settings: value, peer: peer) + channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) + groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) } } } diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index bc5cf90044..ba2dc6aefc 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -43,14 +42,14 @@ private final class NotificationsPeerCategoryControllerArguments { let openSound: (PeerMessageSound) -> Void let addException: () -> Void - let openException: (Peer) -> Void + let openException: (EnginePeer) -> Void let removeAllExceptions: () -> Void - let updateRevealedPeerId: (PeerId?) -> Void - let removePeer: (Peer) -> Void + let updateRevealedPeerId: (EnginePeer.Id?) -> Void + let removePeer: (EnginePeer) -> Void let updatedExceptionMode: (NotificationExceptionMode) -> Void - init(context: AccountContext, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, openSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (Peer) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedPeerId: @escaping (PeerId?) -> Void, removePeer: @escaping (Peer) -> Void, updatedExceptionMode: @escaping (NotificationExceptionMode) -> Void) { + init(context: AccountContext, soundSelectionDisposable: MetaDisposable, updateEnabled: @escaping (Bool) -> Void, updatePreviews: @escaping (Bool) -> Void, openSound: @escaping (PeerMessageSound) -> Void, addException: @escaping () -> Void, openException: @escaping (EnginePeer) -> Void, removeAllExceptions: @escaping () -> Void, updateRevealedPeerId: @escaping (EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer) -> Void, updatedExceptionMode: @escaping (NotificationExceptionMode) -> Void) { self.context = context self.soundSelectionDisposable = soundSelectionDisposable @@ -97,7 +96,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry { case exceptionsHeader(PresentationTheme, String) case addException(PresentationTheme, String) - case exception(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, String, TelegramPeerNotificationSettings, Bool, Bool) + case exception(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, EnginePeer, String, TelegramPeerNotificationSettings, Bool, Bool) case removeAllExceptions(PresentationTheme, String) var section: ItemListSectionId { @@ -184,7 +183,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry { return false } case let .exception(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsDisplayNameOrder, lhsPeer, lhsDescription, lhsSettings, lhsEditing, lhsRevealed): - if case let .exception(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayNameOrder, rhsPeer, rhsDescription, rhsSettings, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayNameOrder == rhsDisplayNameOrder, arePeersEqual(lhsPeer, rhsPeer), lhsDescription == rhsDescription, lhsSettings == rhsSettings, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed { + if case let .exception(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsDisplayNameOrder, rhsPeer, rhsDescription, rhsSettings, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsDisplayNameOrder == rhsDisplayNameOrder, lhsPeer == rhsPeer, lhsDescription == rhsDescription, lhsSettings == rhsSettings, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed { return true } else { return false @@ -227,7 +226,7 @@ private enum NotificationsPeerCategoryEntry: ItemListNodeEntry { arguments.addException() }) case let .exception(_, _, _, dateTimeFormat, nameDisplayOrder, peer, description, _, editing, revealed): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: nil, text: .text(description, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(description, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.openException(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.updateRevealedPeerId(peerId) @@ -277,8 +276,8 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor let sortedExceptions = notificationExceptions.settings.sorted(by: { lhs, rhs in - let lhsName = EnginePeer(lhs.value.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - let rhsName = EnginePeer(rhs.value.peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let lhsName = lhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let rhsName = rhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) if let lhsDate = lhs.value.date, let rhsDate = rhs.value.date { return lhsDate > rhsDate @@ -288,7 +287,7 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor return false } - if let lhsPeer = lhs.value.peer as? TelegramUser, let rhsPeer = rhs.value.peer as? TelegramUser { + if case let .user(lhsPeer) = lhs.value.peer, case let .user(rhsPeer) = rhs.value.peer { if lhsPeer.botInfo != nil && rhsPeer.botInfo == nil { return false } else if lhsPeer.botInfo == nil && rhsPeer.botInfo != nil { @@ -299,7 +298,7 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor return lhsName < rhsName }) - var existingPeerIds = Set() + var existingPeerIds = Set() var index: Int = 0 for (_, value) in sortedExceptions { @@ -379,10 +378,10 @@ public enum NotificationsPeerCategory { private final class NotificationExceptionState : Equatable { let mode: NotificationExceptionMode - let revealedPeerId: PeerId? + let revealedPeerId: EnginePeer.Id? let editing: Bool - init(mode: NotificationExceptionMode, revealedPeerId: PeerId? = nil, editing: Bool = false) { + init(mode: NotificationExceptionMode, revealedPeerId: EnginePeer.Id? = nil, editing: Bool = false) { self.mode = mode self.revealedPeerId = revealedPeerId self.editing = editing @@ -396,19 +395,19 @@ private final class NotificationExceptionState : Equatable { return NotificationExceptionState(mode: self.mode, revealedPeerId: self.revealedPeerId, editing: editing) } - func withUpdatedRevealedPeerId(_ revealedPeerId: PeerId?) -> NotificationExceptionState { + func withUpdatedRevealedPeerId(_ revealedPeerId: EnginePeer.Id?) -> NotificationExceptionState { return NotificationExceptionState(mode: self.mode, revealedPeerId: revealedPeerId, editing: self.editing) } - func withUpdatedPeerSound(_ peer: Peer, _ sound: PeerMessageSound) -> NotificationExceptionState { + func withUpdatedPeerSound(_ peer: EnginePeer, _ sound: PeerMessageSound) -> NotificationExceptionState { return NotificationExceptionState(mode: mode.withUpdatedPeerSound(peer, sound), revealedPeerId: self.revealedPeerId, editing: self.editing) } - func withUpdatedPeerMuteInterval(_ peer: Peer, _ muteInterval: Int32?) -> NotificationExceptionState { + func withUpdatedPeerMuteInterval(_ peer: EnginePeer, _ muteInterval: Int32?) -> NotificationExceptionState { return NotificationExceptionState(mode: mode.withUpdatedPeerMuteInterval(peer, muteInterval), revealedPeerId: self.revealedPeerId, editing: self.editing) } - func withUpdatedPeerDisplayPreviews(_ peer: Peer, _ displayPreviews: PeerNotificationDisplayPreviews) -> NotificationExceptionState { + func withUpdatedPeerDisplayPreviews(_ peer: EnginePeer, _ displayPreviews: PeerNotificationDisplayPreviews) -> NotificationExceptionState { return NotificationExceptionState(mode: mode.withUpdatedPeerDisplayPreviews(peer, displayPreviews), revealedPeerId: self.revealedPeerId, editing: self.editing) } @@ -438,20 +437,20 @@ public func notificationsPeerCategoryController(context: AccountContext, categor updatedMode(result.mode) } - let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + let updatePeerSound: (EnginePeer.Id, PeerMessageSound) -> Signal = { peerId, sound in return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: nil, sound: sound) |> deliverOnMainQueue } - let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in + let updatePeerNotificationInterval: (EnginePeer.Id, Int32?) -> Signal = { peerId, muteInterval in return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: nil, muteInterval: muteInterval) |> deliverOnMainQueue } - let updatePeerDisplayPreviews:(PeerId, PeerNotificationDisplayPreviews) -> Signal = { + let updatePeerDisplayPreviews:(EnginePeer.Id, PeerNotificationDisplayPreviews) -> Signal = { peerId, displayPreviews in return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: nil, displayPreviews: displayPreviews) |> deliverOnMainQueue } - var peerIds: Set = Set(mode.peerIds) + var peerIds: Set = Set(mode.peerIds) let updateNotificationsDisposable = MetaDisposable() let updateNotificationsView: (@escaping () -> Void) -> Void = { completion in updateState { current in @@ -472,12 +471,12 @@ public func notificationsPeerCategoryController(context: AccountContext, categor for (key, value) in combinedPeerNotificationSettings { if let local = current.mode.settings[key] { if !value._asNotificationSettings().isEqual(to: local.settings), let maybePeer = peerMap[key], let peer = maybePeer, let settings = notificationSettingsMap[key], !settings._asNotificationSettings().isEqual(to: local.settings) { - current = current.withUpdatedPeerSound(peer._asPeer(), settings.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer._asPeer(), settings.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer._asPeer(), settings.displayPreviews._asDisplayPreviews()) + current = current.withUpdatedPeerSound(peer, settings.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer, settings.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer, settings.displayPreviews._asDisplayPreviews()) } } else if let maybePeer = peerMap[key], let peer = maybePeer { if case .default = value.messageSound, case .unmuted = value.muteState, case .default = value.displayPreviews { } else { - current = current.withUpdatedPeerSound(peer._asPeer(), value.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer._asPeer(), value.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer._asPeer(), value.displayPreviews._asDisplayPreviews()) + current = current.withUpdatedPeerSound(peer, value.messageSound._asMessageSound()).withUpdatedPeerMuteInterval(peer, value.muteState.timeInterval).withUpdatedPeerDisplayPreviews(peer, value.displayPreviews._asDisplayPreviews()) } } } @@ -493,7 +492,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor updateNotificationsView({}) - let presentPeerSettings: (PeerId, @escaping () -> Void) -> Void = { peerId, completion in + let presentPeerSettings: (EnginePeer.Id, @escaping () -> Void) -> Void = { peerId, completion in let _ = (context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.NotificationSettings.Global() @@ -519,13 +518,13 @@ public func notificationsPeerCategoryController(context: AccountContext, categor defaultSound = globalSettings.privateChats.sound._asMessageSound() } - pushControllerImpl?(notificationPeerExceptionController(context: context, peer: peer._asPeer(), threadId: nil, canRemove: canRemove, defaultSound: defaultSound, updatePeerSound: { peerId, sound in + pushControllerImpl?(notificationPeerExceptionController(context: context, peer: peer, threadId: nil, canRemove: canRemove, defaultSound: defaultSound, updatePeerSound: { peerId, sound in _ = updatePeerSound(peer.id, sound).start(next: { _ in updateNotificationsDisposable.set(nil) _ = combineLatest(updatePeerSound(peer.id, sound), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in if let peer = peer { updateState { value in - return value.withUpdatedPeerSound(peer._asPeer(), sound) + return value.withUpdatedPeerSound(peer, sound) } } updateNotificationsView({}) @@ -536,7 +535,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor _ = combineLatest(updatePeerNotificationInterval(peerId, muteInterval), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in if let peer = peer { updateState { value in - return value.withUpdatedPeerMuteInterval(peer._asPeer(), muteInterval) + return value.withUpdatedPeerMuteInterval(peer, muteInterval) } } updateNotificationsView({}) @@ -546,7 +545,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor _ = combineLatest(updatePeerDisplayPreviews(peerId, displayPreviews), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { _, peer in if let peer = peer { updateState { value in - return value.withUpdatedPeerDisplayPreviews(peer._asPeer(), displayPreviews) + return value.withUpdatedPeerDisplayPreviews(peer, displayPreviews) } } updateNotificationsView({}) @@ -561,7 +560,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor return } updateState { value in - return value.withUpdatedPeerDisplayPreviews(peer._asPeer(), .default).withUpdatedPeerSound(peer._asPeer(), .default).withUpdatedPeerMuteInterval(peer._asPeer(), nil) + return value.withUpdatedPeerDisplayPreviews(peer, .default).withUpdatedPeerSound(peer, .default).withUpdatedPeerMuteInterval(peer, nil) } updateNotificationsView({}) }) @@ -647,7 +646,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor let values = stateValue.with { $0.mode.settings.values } - let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { EnginePeer($0.peer) }) + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: values.map { $0.peer }) |> deliverOnMainQueue).start(completed: { updateNotificationsDisposable.set(nil) updateState { state in @@ -674,7 +673,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor return current.withUpdatedRevealedPeerId(peerId) } }, removePeer: { peer in - let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [EnginePeer(peer)]) + let _ = (context.engine.peers.ensurePeersAreLocallyAvailable(peers: [peer]) |> deliverOnMainQueue).start(completed: { updateNotificationsDisposable.set(nil) updateState { value in diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift index d7e2205182..2d45a6f58b 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift b/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift index 1bee2600cf..67bb0ba0e3 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/CreatePasswordController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift index 0492707e9b..50fa263b07 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/GlobalAutoremoveScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift index 66a1e65b1a..4265629d2c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift index 1f7a766846..209f328de8 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/ItemListRecentSessionItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift index 293df2023a..1bc481e1dd 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/Recent Sessions/RecentSessionsController.swift @@ -712,7 +712,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont }) presentControllerImpl?(controller, nil) }, openWebSession: { session, peer in - let controller = RecentSessionScreen(context: context, subject: .website(session, peer), updateAcceptSecretChats: { _ in }, updateAcceptIncomingCalls: { _ in }, remove: { completion in + let controller = RecentSessionScreen(context: context, subject: .website(session, peer.flatMap(EnginePeer.init)), updateAcceptSecretChats: { _ in }, updateAcceptIncomingCalls: { _ in }, remove: { completion in removeWebSessionImpl(session.hash) completion() }) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift index 7847792e6e..a4640d3867 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/RecentSessionScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext @@ -44,7 +43,7 @@ private func closeButtonImage(theme: PresentationTheme) -> UIImage? { final class RecentSessionScreen: ViewController { enum Subject { case session(RecentAccountSession) - case website(WebAuthorization, Peer?) + case website(WebAuthorization, EnginePeer?) } private var controllerNode: RecentSessionScreenNode { return self.displayNode as! RecentSessionScreenNode @@ -361,7 +360,7 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe self.terminateButton.title = self.presentationData.strings.AuthSessions_View_Logout if let peer = peer { - title = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + title = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } else { title = "" } @@ -388,7 +387,7 @@ private class RecentSessionScreenNode: ViewControllerTracingNode, UIScrollViewDe let avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 12.0)) avatarNode.clipsToBounds = true avatarNode.cornerRadius = 17.0 - if let peer = peer.flatMap({ EnginePeer($0) }) { + if let peer { avatarNode.setPeer(context: context, theme: presentationData.theme, peer: peer, authorOfMessage: nil, overrideImage: nil, emptyColor: nil, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: 72.0, height: 72.0), storeUnrounded: false) } self.avatarNode = avatarNode diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index b5a667f6c0..f1287de315 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -85,7 +84,7 @@ private enum SelectivePrivacySettingsSection: Int32 { case photo } -private func stringForUserCount(_ peers: [PeerId: SelectivePrivacyPeer], strings: PresentationStrings) -> String { +private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String { if peers.isEmpty { return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder } else { @@ -486,22 +485,22 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { private struct SelectivePrivacySettingsControllerState: Equatable { let setting: SelectivePrivacySettingType - let enableFor: [PeerId: SelectivePrivacyPeer] - let disableFor: [PeerId: SelectivePrivacyPeer] + let enableFor: [EnginePeer.Id: SelectivePrivacyPeer] + let disableFor: [EnginePeer.Id: SelectivePrivacyPeer] let saving: Bool let callDataSaving: VoiceCallDataSaving? let callP2PMode: SelectivePrivacySettingType? - let callP2PEnableFor: [PeerId: SelectivePrivacyPeer]? - let callP2PDisableFor: [PeerId: SelectivePrivacyPeer]? + let callP2PEnableFor: [EnginePeer.Id: SelectivePrivacyPeer]? + let callP2PDisableFor: [EnginePeer.Id: SelectivePrivacyPeer]? let callIntegrationAvailable: Bool? let callIntegrationEnabled: Bool? let phoneDiscoveryEnabled: Bool? let uploadedPhoto: UIImage? - init(setting: SelectivePrivacySettingType, enableFor: [PeerId: SelectivePrivacyPeer], disableFor: [PeerId: SelectivePrivacyPeer], saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [PeerId: SelectivePrivacyPeer]?, callP2PDisableFor: [PeerId: SelectivePrivacyPeer]?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?, phoneDiscoveryEnabled: Bool?, uploadedPhoto: UIImage?) { + init(setting: SelectivePrivacySettingType, enableFor: [EnginePeer.Id: SelectivePrivacyPeer], disableFor: [EnginePeer.Id: SelectivePrivacyPeer], saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PDisableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?, phoneDiscoveryEnabled: Bool?, uploadedPhoto: UIImage?) { self.setting = setting self.enableFor = enableFor self.disableFor = disableFor @@ -561,11 +560,11 @@ private struct SelectivePrivacySettingsControllerState: Equatable { return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) } - func withUpdatedEnableFor(_ enableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + func withUpdatedEnableFor(_ enableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) } - func withUpdatedDisableFor(_ disableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + func withUpdatedDisableFor(_ disableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) } @@ -577,11 +576,11 @@ private struct SelectivePrivacySettingsControllerState: Equatable { return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) } - func withUpdatedCallP2PEnableFor(_ enableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + func withUpdatedCallP2PEnableFor(_ enableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) } - func withUpdatedCallP2PDisableFor(_ disableFor: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { + func withUpdatedCallP2PDisableFor(_ disableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, uploadedPhoto: self.uploadedPhoto) } @@ -763,8 +762,8 @@ func selectivePrivacySettingsController( ) -> ViewController { let strings = context.sharedContext.currentPresentationData.with { $0 }.strings - var initialEnableFor: [PeerId: SelectivePrivacyPeer] = [:] - var initialDisableFor: [PeerId: SelectivePrivacyPeer] = [:] + var initialEnableFor: [EnginePeer.Id: SelectivePrivacyPeer] = [:] + var initialDisableFor: [EnginePeer.Id: SelectivePrivacyPeer] = [:] switch current { case let .disableEveryone(enableFor): initialEnableFor = enableFor @@ -774,8 +773,8 @@ func selectivePrivacySettingsController( case let .enableEveryone(disableFor): initialDisableFor = disableFor } - var initialCallP2PEnableFor: [PeerId: SelectivePrivacyPeer]? - var initialCallP2PDisableFor: [PeerId: SelectivePrivacyPeer]? + var initialCallP2PEnableFor: [EnginePeer.Id: SelectivePrivacyPeer]? + var initialCallP2PDisableFor: [EnginePeer.Id: SelectivePrivacyPeer]? if let callCurrent = callSettings?.0 { switch callCurrent { case let .disableEveryone(enableFor): @@ -847,7 +846,7 @@ func selectivePrivacySettingsController( title = strings.Privacy_VoiceMessages_NeverAllow_Title } } - var peerIds: [PeerId: SelectivePrivacyPeer] = [:] + var peerIds: [EnginePeer.Id: SelectivePrivacyPeer] = [:] updateState { state in if enable { switch target { @@ -896,8 +895,8 @@ func selectivePrivacySettingsController( EngineDataMap(filteredIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init)), EngineDataMap(filteredIds.map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init)) ) - |> map { peerMap, participantCountMap -> [PeerId: SelectivePrivacyPeer] in - var updatedPeers: [PeerId: SelectivePrivacyPeer] = [:] + |> map { peerMap, participantCountMap -> [EnginePeer.Id: SelectivePrivacyPeer] in + var updatedPeers: [EnginePeer.Id: SelectivePrivacyPeer] = [:] var existingIds = Set(updatedPeers.values.map { $0.peer.id }) for peerId in peerIds { guard case let .peer(peerId) = peerId else { diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index f4f70ba9f8..bd3b7a722c 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -414,24 +414,24 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: peer) + users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) default: if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - channels[key] = NotificationExceptionWrapper(settings: value, peer: peer) + channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) + groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) } } } default: switch key.namespace { case Namespaces.Peer.CloudUser: - users[key] = NotificationExceptionWrapper(settings: value, peer: peer) + users[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) default: if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - channels[key] = NotificationExceptionWrapper(settings: value, peer: peer) + channels[key] = NotificationExceptionWrapper(settings: value, peer: .channel(peer)) } else { - groups[key] = NotificationExceptionWrapper(settings: value, peer: peer) + groups[key] = NotificationExceptionWrapper(settings: value, peer: EnginePeer(peer)) } } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 65455e1c73..281dc08339 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import CheckNode import AnimationUI diff --git a/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift b/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift index e863dd1dae..481e5d5320 100644 --- a/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift +++ b/submodules/SettingsUI/Sources/Watch/WatchSettingsController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences diff --git a/submodules/ShareController/Sources/ShareContentContainerNode.swift b/submodules/ShareController/Sources/ShareContentContainerNode.swift index 507514063c..57179bcf47 100644 --- a/submodules/ShareController/Sources/ShareContentContainerNode.swift +++ b/submodules/ShareController/Sources/ShareContentContainerNode.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import TelegramPresentationData diff --git a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift index c1ee78c826..0efd076ebe 100644 --- a/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift +++ b/submodules/ShareController/Sources/ShareControllerRecentPeersGridItem.swift @@ -4,7 +4,6 @@ import Display import TelegramCore import SwiftSignalKit import AsyncDisplayKit -import Postbox import TelegramPresentationData import ChatListSearchRecentPeersNode import AccountContext diff --git a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift index e33ed339f7..45a2f734ea 100644 --- a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift +++ b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift @@ -3,7 +3,6 @@ import UIKit import CallKit import Intents import AVFoundation -import Postbox import TelegramCore import SwiftSignalKit import AppBundle @@ -70,7 +69,7 @@ public final class CallKitIntegration { } } - func startCall(context: AccountContext, peerId: PeerId, phoneNumber: String?, localContactId: String?, isVideo: Bool, displayTitle: String) { + func startCall(context: AccountContext, peerId: EnginePeer.Id, phoneNumber: String?, localContactId: String?, isVideo: Bool, displayTitle: String) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { (sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle) self.donateIntent(peerId: peerId, displayTitle: displayTitle, localContactId: localContactId) @@ -101,7 +100,7 @@ public final class CallKitIntegration { } } - private func donateIntent(peerId: PeerId, displayTitle: String, localContactId: String?) { + private func donateIntent(peerId: EnginePeer.Id, displayTitle: String, localContactId: String?) { if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { let handle = INPersonHandle(value: "tg\(peerId.id._internalGetInt64Value())", type: .unknown) let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: localContactId, customIdentifier: "tg\(peerId.id._internalGetInt64Value())") @@ -218,7 +217,7 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { self.requestTransaction(transaction) } - func startCall(context: AccountContext, peerId: PeerId, phoneNumber: String?, isVideo: Bool, displayTitle: String) { + func startCall(context: AccountContext, peerId: EnginePeer.Id, phoneNumber: String?, isVideo: Bool, displayTitle: String) { let uuid = UUID() self.currentStartCallAccount = (uuid, context) let handle: CXHandle diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index b2293dd85e..c4e26a38ce 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1239,8 +1239,6 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController return } - let peer = EnginePeer(peer) - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } if peer.id == strongSelf.callState?.myPeerId { return diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index ba0442c7c5..b92a84d4cb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -9,6 +9,8 @@ public enum AddressNameValidationStatus: Equatable { case availability(AddressNameAvailability) } +public typealias EngineStringIndexTokenTransliteration = StringIndexTokenTransliteration + public final class OpaqueChatInterfaceState { public let opaqueData: Data? public let historyScrollMessageIndex: MessageIndex? @@ -64,8 +66,11 @@ public extension TelegramEngine { return _internal_checkPublicChannelCreationAvailability(account: self.account, location: location) } - public func adminedPublicChannels(scope: AdminedPublicChannelsScope = .all) -> Signal<[Peer], NoError> { + public func adminedPublicChannels(scope: AdminedPublicChannelsScope = .all) -> Signal<[EnginePeer], NoError> { return _internal_adminedPublicChannels(account: self.account, scope: scope) + |> map { peers -> [EnginePeer] in + return peers.map(EnginePeer.init) + } } public func channelAddressNameAssignmentAvailability(peerId: PeerId?) -> Signal { @@ -373,8 +378,11 @@ public extension TelegramEngine { return _internal_removePeerMember(account: self.account, peerId: peerId, memberId: memberId) } - public func availableGroupsForChannelDiscussion() -> Signal<[Peer], AvailableChannelDiscussionGroupError> { + public func availableGroupsForChannelDiscussion() -> Signal<[EnginePeer], AvailableChannelDiscussionGroupError> { return _internal_availableGroupsForChannelDiscussion(postbox: self.account.postbox, network: self.account.network) + |> map { peers -> [EnginePeer] in + return peers.map(EnginePeer.init) + } } public func updateGroupDiscussionForChannel(channelId: PeerId?, groupId: PeerId?) -> Signal { @@ -1115,6 +1123,15 @@ public extension TelegramEngine { public func requestLeaveChatFolderSuggestions(folderId: Int32) -> Signal<[EnginePeer.Id], NoError> { return _internal_requestLeaveChatFolderSuggestions(account: self.account, folderId: folderId) } + + public func keepPeerUpdated(id: EnginePeer.Id, forceUpdate: Bool) -> Signal { + return self.account.viewTracker.peerView(id, updateData: forceUpdate) + |> ignoreValues + } + + public func tokenizeSearchString(string: String, transliteration: EngineStringIndexTokenTransliteration) -> [EngineDataBuffer] { + return stringIndexTokens(string, transliteration: transliteration) + } } } diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift index b91f2cbd83..5a44d2a9b2 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift @@ -10,7 +10,6 @@ import TelegramPresentationData import AccountContext import TelegramCore import MultilineTextComponent -import Postbox import SolidRoundedButtonComponent import PresentationDataUtils import Markdown diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift index d5acbc67b8..10f12a5634 100644 --- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift @@ -7,7 +7,6 @@ import SwiftSignalKit import AccountContext import TelegramCore import MultilineTextComponent -import Postbox import AvatarNode import TelegramPresentationData import CheckNode diff --git a/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift b/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift index 586b4780c9..3866a794c9 100644 --- a/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift +++ b/submodules/TelegramUI/Components/ChatTimerScreen/Sources/ChatTimerScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import AccountContext diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index a36e244c2f..508ae2bda0 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -13,9 +13,9 @@ import EntityKeyboard import PagerComponent import MultilineTextComponent import EmojiStatusComponent -import Postbox import PremiumUI import ProgressNavigationButtonNode +import Postbox private final class SwitchComponent: Component { typealias EnvironmentType = Empty @@ -654,7 +654,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { areUnicodeEmojiEnabled: false, areCustomEmojiEnabled: true, chatPeerId: self.context.account.peerId, - selectedItems: Set([MediaId(namespace: Namespaces.Media.CloudFile, id: self.fileId)]), + selectedItems: Set([EngineMedia.Id(namespace: Namespaces.Media.CloudFile, id: self.fileId)]), topicTitle: self.title, topicColor: self.iconColor ) diff --git a/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift b/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift index 7c2aff0683..418228f4d0 100644 --- a/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift +++ b/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift @@ -466,7 +466,7 @@ public func threadNotificationExceptionsScreen(context: AccountContext, peerId: let canRemove = true let defaultSound: PeerMessageSound = globalSettings.groupChats.sound._asMessageSound() - pushControllerImpl?(notificationPeerExceptionController(context: context, peer: peer._asPeer(), customTitle: item.info.title, threadId: item.threadId, canRemove: canRemove, defaultSound: defaultSound, updatePeerSound: { _, sound in + pushControllerImpl?(notificationPeerExceptionController(context: context, peer: peer, customTitle: item.info.title, threadId: item.threadId, canRemove: canRemove, defaultSound: defaultSound, updatePeerSound: { _, sound in let _ = (updateThreadSound(item.threadId, sound) |> deliverOnMainQueue).start(next: { _ in updateState { value in diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift index ab90c03438..de14533077 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import AsyncDisplayKit -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -16,9 +15,9 @@ import NotificationSoundSelectionUI public struct NotificationExceptionWrapper : Equatable { public let settings: TelegramPeerNotificationSettings public let date: TimeInterval? - public let peer: Peer + public let peer: EnginePeer - public init(settings: TelegramPeerNotificationSettings, peer: Peer, date: TimeInterval? = nil) { + public init(settings: TelegramPeerNotificationSettings, peer: EnginePeer, date: TimeInterval? = nil) { self.settings = settings self.date = date self.peer = peer @@ -90,12 +89,12 @@ public enum NotificationExceptionMode : Equatable { } } - case users([PeerId : NotificationExceptionWrapper]) - case groups([PeerId : NotificationExceptionWrapper]) - case channels([PeerId : NotificationExceptionWrapper]) + case users([EnginePeer.Id : NotificationExceptionWrapper]) + case groups([EnginePeer.Id : NotificationExceptionWrapper]) + case channels([EnginePeer.Id : NotificationExceptionWrapper]) - public func withUpdatedPeerSound(_ peer: Peer, _ sound: PeerMessageSound) -> NotificationExceptionMode { - let apply:([PeerId : NotificationExceptionWrapper], PeerId, PeerMessageSound) -> [PeerId : NotificationExceptionWrapper] = { values, peerId, sound in + public func withUpdatedPeerSound(_ peer: EnginePeer, _ sound: PeerMessageSound) -> NotificationExceptionMode { + let apply:([EnginePeer.Id : NotificationExceptionWrapper], EnginePeer.Id, PeerMessageSound) -> [EnginePeer.Id : NotificationExceptionWrapper] = { values, peerId, sound in var values = values if let value = values[peerId] { switch sound { @@ -130,8 +129,8 @@ public enum NotificationExceptionMode : Equatable { } } - public func withUpdatedPeerMuteInterval(_ peer: Peer, _ muteInterval: Int32?) -> NotificationExceptionMode { - let apply:([PeerId : NotificationExceptionWrapper], PeerId, PeerMuteState) -> [PeerId : NotificationExceptionWrapper] = { values, peerId, muteState in + public func withUpdatedPeerMuteInterval(_ peer: EnginePeer, _ muteInterval: Int32?) -> NotificationExceptionMode { + let apply:([EnginePeer.Id : NotificationExceptionWrapper], EnginePeer.Id, PeerMuteState) -> [EnginePeer.Id : NotificationExceptionWrapper] = { values, peerId, muteState in var values = values if let value = values[peerId] { switch muteState { @@ -182,8 +181,8 @@ public enum NotificationExceptionMode : Equatable { } } - public func withUpdatedPeerDisplayPreviews(_ peer: Peer, _ displayPreviews: PeerNotificationDisplayPreviews) -> NotificationExceptionMode { - let apply:([PeerId : NotificationExceptionWrapper], PeerId, PeerNotificationDisplayPreviews) -> [PeerId : NotificationExceptionWrapper] = { values, peerId, displayPreviews in + public func withUpdatedPeerDisplayPreviews(_ peer: EnginePeer, _ displayPreviews: PeerNotificationDisplayPreviews) -> NotificationExceptionMode { + let apply:([EnginePeer.Id : NotificationExceptionWrapper], EnginePeer.Id, PeerNotificationDisplayPreviews) -> [EnginePeer.Id : NotificationExceptionWrapper] = { values, peerId, displayPreviews in var values = values if let value = values[peerId] { switch displayPreviews { @@ -218,14 +217,14 @@ public enum NotificationExceptionMode : Equatable { } } - public var peerIds: [PeerId] { + public var peerIds: [EnginePeer.Id] { switch self { case let .users(settings), let .groups(settings), let .channels(settings): return settings.map {$0.key} } } - public var settings: [PeerId : NotificationExceptionWrapper] { + public var settings: [EnginePeer.Id : NotificationExceptionWrapper] { switch self { case let .users(settings), let .groups(settings), let .channels(settings): return settings @@ -585,7 +584,7 @@ private struct NotificationExceptionPeerState : Equatable { } } -public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: Peer, customTitle: String? = nil, threadId: Int64?, canRemove: Bool, defaultSound: PeerMessageSound, edit: Bool = false, updatePeerSound: @escaping(PeerId, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(PeerId, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(PeerId, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController { +public func notificationPeerExceptionController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer, customTitle: String? = nil, threadId: Int64?, canRemove: Bool, defaultSound: PeerMessageSound, edit: Bool = false, updatePeerSound: @escaping(EnginePeer.Id, PeerMessageSound) -> Void, updatePeerNotificationInterval: @escaping(EnginePeer.Id, Int32?) -> Void, updatePeerDisplayPreviews: @escaping(EnginePeer.Id, PeerNotificationDisplayPreviews) -> Void, removePeerFromExceptions: @escaping () -> Void, modifiedPeer: @escaping () -> Void) -> ViewController { let initialState = NotificationExceptionPeerState(canRemove: false) let statePromise = Promise(initialState) let stateValue = Atomic(value: initialState) @@ -683,7 +682,7 @@ public func notificationPeerExceptionController(context: AccountContext, updated if let customTitle = customTitle { titleString = customTitle } else { - titleString = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + titleString = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(titleString), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) diff --git a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift index 1c7609aeb1..ea93f3a000 100644 --- a/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift +++ b/submodules/TelegramUI/Components/SendInviteLinkScreen/Sources/SendInviteLinkScreen.swift @@ -10,7 +10,6 @@ import TelegramPresentationData import AccountContext import TelegramCore import MultilineTextComponent -import Postbox import SolidRoundedButtonComponent import PresentationDataUtils import Markdown diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift index eb6a9587ff..5faf537638 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoriesComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import CheckNode import SolidRoundedButtonComponent diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift index d9d6280419..0e7f6e926d 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataCategoryItemCompoment.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index d4463a89a4..d26fa0d642 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import Markdown import ContextUI import AnimatedAvatarSetNode diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift index cd901ca5b8..0ebd20b6f7 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/PieChartComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox private func processChartData(data: PieChartComponent.ChartData) -> PieChartComponent.ChartData { var data = data diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift index 1b71a910ec..e6ff6db679 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/SegmentControlComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode import SegmentedControlNode diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift index 9588072219..44056145f0 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoriesComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import CheckNode import SolidRoundedButtonComponent diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift index 41605decc5..10e3360b82 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageCategoryItemCompoment.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift index 95c6c5623e..783f1b1901 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import CheckNode import SolidRoundedButtonComponent import LegacyComponents diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift index 8057e16c8b..7d61c393cd 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageMediaGridPanelComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode import AvatarNode @@ -27,7 +26,7 @@ private final class MediaGridLayer: SimpleLayer { case editing(isSelected: Bool) } - private(set) var message: Message? + private(set) var message: EngineMessage? private var disposable: Disposable? private var size: CGSize? @@ -77,7 +76,7 @@ private final class MediaGridLayer: SimpleLayer { } } - func setup(context: AccountContext, strings: PresentationStrings, message: Message, size: Int64) { + func setup(context: AccountContext, strings: PresentationStrings, message: EngineMessage, size: Int64) { self.message = message var isVideo = false @@ -221,11 +220,11 @@ final class StorageMediaGridPanelComponent: Component { typealias EnvironmentType = StorageUsagePanelEnvironment final class Item: Equatable { - let message: Message + let message: EngineMessage let size: Int64 init( - message: Message, + message: EngineMessage, size: Int64 ) { self.message = message @@ -478,7 +477,7 @@ final class StorageMediaGridPanelComponent: Component { fatalError("init(coder:) has not been implemented") } - func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { var foundItemLayer: MediaGridLayer? for (_, itemLayer) in self.visibleLayers { if let message = itemLayer.message, message.id == messageId { diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift index b1f3e311bb..6e9f125649 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerListPanelComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode import AvatarNode diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift index 9a75fced24..8e54180182 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StoragePeerTypeItemComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index e379cd1fec..43115b1632 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -9,9 +9,9 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import TelegramCore +import Postbox import MultilineTextComponent import EmojiStatusComponent -import Postbox import Markdown import ContextUI import AnimatedAvatarSetNode @@ -571,7 +571,7 @@ final class StorageUsageScreenComponent: Component { for item in imageItems.items { if deselectedPhotos.contains(item.message.id) { selectedSize -= item.size - clearExcludeMessages.append(item.message) + clearExcludeMessages.append(item.message._asMessage()) } } } @@ -581,7 +581,7 @@ final class StorageUsageScreenComponent: Component { for item in imageItems.items { if selectedPhotos.contains(item.message.id) { selectedSize += item.size - clearIncludeMessages.append(item.message) + clearIncludeMessages.append(item.message._asMessage()) } } } @@ -593,7 +593,7 @@ final class StorageUsageScreenComponent: Component { for item in imageItems.items { if deselectedVideos.contains(item.message.id) { selectedSize -= item.size - clearExcludeMessages.append(item.message) + clearExcludeMessages.append(item.message._asMessage()) } } } @@ -603,7 +603,7 @@ final class StorageUsageScreenComponent: Component { for item in imageItems.items { if selectedVideos.contains(item.message.id) { selectedSize += item.size - clearIncludeMessages.append(item.message) + clearIncludeMessages.append(item.message._asMessage()) } } } @@ -2356,7 +2356,7 @@ final class StorageUsageScreenComponent: Component { if matches { result.imageItems.append(StorageMediaGridPanelComponent.Item( - message: message, + message: EngineMessage(message), size: messageSize )) } @@ -2728,7 +2728,7 @@ final class StorageUsageScreenComponent: Component { if let panelContainerView = self.panelContainer.view as? StorageUsagePanelContainerComponent.View { if let currentPanelView = panelContainerView.currentPanelView as? StorageMediaGridPanelComponent.View { - return currentPanelView.transitionNodeForGallery(messageId: messageId, media: media) + return currentPanelView.transitionNodeForGallery(messageId: messageId, media: EngineMedia(media)) } } diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreenSelectionPanelComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreenSelectionPanelComponent.swift index 09fd43a2fc..96a324bb12 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreenSelectionPanelComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreenSelectionPanelComponent.swift @@ -11,7 +11,6 @@ import AccountContext import TelegramCore import MultilineTextComponent import EmojiStatusComponent -import Postbox import TelegramStringFormatting import CheckNode import SolidRoundedButtonComponent diff --git a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift index 9f240ce8a7..5d5f0f75de 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAttachedContentNode.swift @@ -5,7 +5,6 @@ import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore -import Postbox import TelegramPresentationData import TelegramUIPreferences import TextFormat diff --git a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift index 0bf736e88e..9eb69af77c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageGiftItemNode.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import AccountContext import TelegramPresentationData @@ -19,8 +18,8 @@ import TelegramAnimatedStickerNode import ChatControllerInteraction import ShimmerEffect -private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? { - return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false, forForumOverview: false) +private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? { + return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false) } class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { @@ -176,7 +175,7 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in let giftSize = CGSize(width: 220.0, height: 240.0) - let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: item.message, accountPeerId: item.context.account.peerId) + let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId) let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText diff --git a/submodules/TelegramUI/Sources/OwnershipTransferController.swift b/submodules/TelegramUI/Sources/OwnershipTransferController.swift index 10ff585ed3..82cf7b3d44 100644 --- a/submodules/TelegramUI/Sources/OwnershipTransferController.swift +++ b/submodules/TelegramUI/Sources/OwnershipTransferController.swift @@ -3,7 +3,6 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ActivityIndicator diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 734ece2992..450d31e755 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4573,7 +4573,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let canRemove = false - let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, threadId: threadId, canRemove: canRemove, defaultSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in + let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: EnginePeer(peer), threadId: threadId, canRemove: canRemove, defaultSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in let _ = (updatePeerSound(peer.id, sound) |> deliverOnMainQueue).start(next: { _ in }) @@ -6522,7 +6522,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate guard let data = self.data, let peer = data.peer, let encryptionKeyFingerprint = data.encryptionKeyFingerprint else { return } - self.controller?.push(SecretChatKeyController(context: self.context, fingerprint: encryptionKeyFingerprint, peer: peer)) + self.controller?.push(SecretChatKeyController(context: self.context, fingerprint: encryptionKeyFingerprint, peer: EnginePeer(peer))) } private func openShareBot() { diff --git a/submodules/TelegramUI/Sources/PeersNearbyManager.swift b/submodules/TelegramUI/Sources/PeersNearbyManager.swift index 8faddf9d12..dc44ce6199 100644 --- a/submodules/TelegramUI/Sources/PeersNearbyManager.swift +++ b/submodules/TelegramUI/Sources/PeersNearbyManager.swift @@ -1,6 +1,5 @@ import Foundation import SwiftSignalKit -import Postbox import TelegramCore import TelegramApi import DeviceLocationManager diff --git a/submodules/TelegramUI/Sources/PollResultsController.swift b/submodules/TelegramUI/Sources/PollResultsController.swift index 8e486a83c7..7642dab1cc 100644 --- a/submodules/TelegramUI/Sources/PollResultsController.swift +++ b/submodules/TelegramUI/Sources/PollResultsController.swift @@ -1,5 +1,4 @@ import Foundation -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData @@ -16,10 +15,10 @@ private final class PollResultsControllerArguments { let context: AccountContext let collapseOption: (Data) -> Void let expandOption: (Data) -> Void - let openPeer: (RenderedPeer) -> Void + let openPeer: (EngineRenderedPeer) -> Void let expandSolution: () -> Void - init(context: AccountContext, collapseOption: @escaping (Data) -> Void, expandOption: @escaping (Data) -> Void, openPeer: @escaping (RenderedPeer) -> Void, expandSolution: @escaping () -> Void) { + init(context: AccountContext, collapseOption: @escaping (Data) -> Void, expandOption: @escaping (Data) -> Void, openPeer: @escaping (EngineRenderedPeer) -> Void, expandSolution: @escaping () -> Void) { self.context = context self.collapseOption = collapseOption self.expandOption = expandOption @@ -67,7 +66,7 @@ private enum PollResultsItemTag: ItemListItemTag, Equatable { private enum PollResultsEntry: ItemListNodeEntry { case text(String) - case optionPeer(optionId: Int, index: Int, peer: RenderedPeer, optionText: String, optionAdditionalText: String, optionCount: Int32, optionExpanded: Bool, opaqueIdentifier: Data, shimmeringAlternation: Int?, isFirstInOption: Bool) + case optionPeer(optionId: Int, index: Int, peer: EngineRenderedPeer, optionText: String, optionAdditionalText: String, optionCount: Int32, optionExpanded: Bool, opaqueIdentifier: Data, shimmeringAlternation: Int?, isFirstInOption: Bool) case optionExpand(optionId: Int, opaqueIdentifier: Data, text: String, enabled: Bool) case solutionHeader(String) case solutionText(String) @@ -187,7 +186,7 @@ private enum PollResultsEntry: ItemListNodeEntry { let header = ItemListPeerItemHeader(theme: presentationData.theme, strings: presentationData.strings, text: optionText, additionalText: optionAdditionalText, actionTitle: optionExpanded ? presentationData.strings.PollResults_Collapse : presentationData.strings.MessagePoll_VotedCount(optionCount), id: Int64(optionId), action: optionExpanded ? { arguments.collapseOption(opaqueIdentifier) } : nil) - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: EnginePeer(peer.peers[peer.peerId]!), presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: shimmeringAlternation == nil, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer.peers[peer.peerId]!, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: shimmeringAlternation == nil, sectionId: self.section, action: { arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in @@ -251,8 +250,8 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po displayCount = Int(voterCount) } for peerIndex in 0 ..< displayCount { - let fakeUser = TelegramUser(id: PeerId(namespace: .max, id: PeerId.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []) - let peer = RenderedPeer(peer: fakeUser) + let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: []) + let peer = EngineRenderedPeer(peer: EnginePeer(fakeUser)) entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: optionTextHeader, optionAdditionalText: optionAdditionalTextHeader, optionCount: voterCount, optionExpanded: false, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: peerIndex % 2, isFirstInOption: peerIndex == 0)) } if displayCount < Int(voterCount) { @@ -295,7 +294,7 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po if peerIndex >= displayCount { break inner } - entries.append(.optionPeer(optionId: i, index: peerIndex, peer: peer, optionText: optionTextHeader, optionAdditionalText: optionAdditionalTextHeader, optionCount: Int32(count), optionExpanded: optionExpandedAtCount != nil, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: nil, isFirstInOption: peerIndex == 0)) + entries.append(.optionPeer(optionId: i, index: peerIndex, peer: EngineRenderedPeer(peer), optionText: optionTextHeader, optionAdditionalText: optionAdditionalTextHeader, optionCount: Int32(count), optionExpanded: optionExpandedAtCount != nil, opaqueIdentifier: option.opaqueIdentifier, shimmeringAlternation: nil, isFirstInOption: peerIndex == 0)) peerIndex += 1 } @@ -310,7 +309,7 @@ private func pollResultsControllerEntries(presentationData: PresentationData, po return entries } -public func pollResultsController(context: AccountContext, messageId: MessageId, poll: TelegramMediaPoll, focusOnOptionWithOpaqueIdentifier: Data? = nil) -> ViewController { +public func pollResultsController(context: AccountContext, messageId: EngineMessage.Id, poll: TelegramMediaPoll, focusOnOptionWithOpaqueIdentifier: Data? = nil) -> ViewController { let statePromise = ValuePromise(PollResultsControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: PollResultsControllerState()) let updateState: ((PollResultsControllerState) -> PollResultsControllerState) -> Void = { f in @@ -349,7 +348,7 @@ public func pollResultsController(context: AccountContext, messageId: MessageId, }) }, openPeer: { peer in if let peer = peer.peers[peer.peerId] { - if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { pushControllerImpl?(controller) } } From 1640a8c76f745ab30ae5994ff1fcdf70746410ab Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 20 Apr 2023 19:22:25 +0400 Subject: [PATCH 6/7] Refactoring [skip ci] --- .../Sources/LegacyLiveUploadInterface.swift | 16 +++---- .../Sources/SecureIdAuthControllerNode.swift | 2 +- .../Sources/SecureIdAuthFormContentNode.swift | 7 ++- .../Sources/SecureIdAuthHeaderNode.swift | 1 - .../Sources/ChannelBlacklistController.swift | 48 ++++++++++--------- .../ProxyListSettingsController.swift | 4 +- .../ProxyServerSettingsController.swift | 5 +- .../ProxySettingsServerItem.swift | 1 - .../Sources/VoiceChatController.swift | 2 +- .../Sources/VoiceChatParticipantItem.swift | 19 ++++---- .../State/MessageMediaPreuploadManager.swift | 13 +++-- .../Data/OrderedListsData.swift | 42 ++++++++++++++++ .../Resources/TelegramEngineResources.swift | 2 +- .../PresentationThemeEssentialGraphics.swift | 1 - .../Sources/EmojiStatusPreviewScreen.swift | 1 - .../EmojiStatusSelectionComponent.swift | 28 +++++------ .../Sources/EmojiSuggestionsComponent.swift | 9 ++-- 17 files changed, 121 insertions(+), 80 deletions(-) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift index 3c9dae033a..ce818b9685 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyLiveUploadInterface.swift @@ -1,6 +1,5 @@ import Foundation import UIKit -import Postbox import TelegramCore import LegacyComponents import SwiftSignalKit @@ -49,8 +48,8 @@ public final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUplo private var path: String? private var size: Int? - private let data = Promise() - private let dataValue = Atomic(value: nil) + private let data = Promise() + private let dataValue = Atomic(value: nil) public init(context: AccountContext) { self.context = context @@ -70,14 +69,13 @@ public final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUplo strongSelf.size = size let result = strongSelf.dataValue.modify { dataValue in - if let dataValue = dataValue, dataValue.complete { - return MediaResourceData(path: path, offset: 0, size: Int64(size), complete: true) + if let dataValue = dataValue, dataValue.isComplete { + return EngineMediaResource.ResourceData(path: path, availableSize: Int64(size), isComplete: true) } else { - return MediaResourceData(path: path, offset: 0, size: Int64(size), complete: false) + return EngineMediaResource.ResourceData(path: path, availableSize: Int64(size), isComplete: false) } } if let result = result { - print("**set1 \(result) \(result.complete)") strongSelf.data.set(.single(result)) } } @@ -89,17 +87,15 @@ public final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUplo override public func fileUpdated(_ completed: Bool) -> Any! { let _ = super.fileUpdated(completed) - print("**fileUpdated \(completed)") if completed { let result = self.dataValue.modify { dataValue in if let dataValue = dataValue { - return MediaResourceData(path: dataValue.path, offset: dataValue.offset, size: dataValue.size, complete: true) + return EngineMediaResource.ResourceData(path: dataValue.path, availableSize: dataValue.availableSize, isComplete: true) } else { return nil } } if let result = result { - print("**set2 \(result) \(completed)") self.data.set(.single(result)) return LegacyLiveUploadInterfaceResult(id: self.id) } else { diff --git a/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift b/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift index 3f14922c1f..a4e0ff2753 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthControllerNode.swift @@ -303,7 +303,7 @@ final class SecureIdAuthControllerNode: ViewControllerTracingNode { current.updateValues(formData.values) contentNode = current } else { - let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, peer: encryptedFormData.servicePeer, privacyPolicyUrl: encryptedFormData.form.termsUrl, form: formData, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, openField: { [weak self] field in + let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, peer: EnginePeer(encryptedFormData.servicePeer), privacyPolicyUrl: encryptedFormData.form.termsUrl, form: formData, primaryLanguageByCountry: encryptedFormData.primaryLanguageByCountry, openField: { [weak self] field in if let strongSelf = self { switch field { case .identity, .address: diff --git a/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift b/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift index 3b1f79e9fd..55ccbd1e97 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthFormContentNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -24,7 +23,7 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, private let requestLayout: () -> Void private var validLayout: CGFloat? - init(theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peer: Peer, privacyPolicyUrl: String?, form: SecureIdForm, primaryLanguageByCountry: [String: String], openField: @escaping (SecureIdParsedRequestedFormField) -> Void, openURL: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void, requestLayout: @escaping () -> Void) { + init(theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peer: EnginePeer, privacyPolicyUrl: String?, form: SecureIdForm, primaryLanguageByCountry: [String: String], openField: @escaping (SecureIdParsedRequestedFormField) -> Void, openURL: @escaping (String) -> Void, openMention: @escaping (TelegramPeerMention) -> Void, requestLayout: @escaping () -> Void) { self.requestLayout = requestLayout self.primaryLanguageByCountry = primaryLanguageByCountry @@ -57,13 +56,13 @@ final class SecureIdAuthFormContentNode: ASDisplayNode, SecureIdAuthContentNode, let privacyPolicyAttributes = MarkdownAttributeSet(font: infoFont, textColor: theme.list.freeTextColor) let privacyPolicyLinkAttributes = MarkdownAttributeSet(font: infoFont, textColor: theme.list.itemAccentColor, additionalAttributes: [NSAttributedString.Key.underlineStyle.rawValue: NSUnderlineStyle.single.rawValue as NSNumber, TelegramTextAttributes.URL: privacyPolicyUrl]) - text = parseMarkdownIntoAttributedString(strings.Passport_PrivacyPolicy(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder), (EnginePeer(peer).addressName ?? "")).string.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: privacyPolicyAttributes, bold: privacyPolicyAttributes, link: privacyPolicyLinkAttributes, linkAttribute: { _ in + text = parseMarkdownIntoAttributedString(strings.Passport_PrivacyPolicy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder), (peer.addressName ?? "")).string.replacingOccurrences(of: "]", with: "]()"), attributes: MarkdownAttributes(body: privacyPolicyAttributes, bold: privacyPolicyAttributes, link: privacyPolicyLinkAttributes, linkAttribute: { _ in return nil }), textAlignment: .center) } else { - text = NSAttributedString(string: strings.Passport_AcceptHelp(EnginePeer(peer).displayTitle(strings: strings, displayOrder: nameDisplayOrder), (peer.addressName ?? "")).string, font: infoFont, textColor: theme.list.freeTextColor, paragraphAlignment: .left) + text = NSAttributedString(string: strings.Passport_AcceptHelp(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder), (peer.addressName ?? "")).string, font: infoFont, textColor: theme.list.freeTextColor, paragraphAlignment: .left) } self.textNode.attributedText = text diff --git a/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift b/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift index 0312848ef9..732cfa73af 100644 --- a/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift +++ b/submodules/PassportUI/Sources/SecureIdAuthHeaderNode.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import AsyncDisplayKit import Display -import Postbox import TelegramCore import SwiftSignalKit import TelegramPresentationData diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index 3412d5a1d4..b028a7f806 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -17,12 +16,12 @@ import ItemListPeerItem private final class ChannelBlacklistControllerArguments { let context: AccountContext - let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void + let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void let addPeer: () -> Void - let removePeer: (PeerId) -> Void + let removePeer: (EnginePeer.Id) -> Void let openPeer: (RenderedChannelParticipant) -> Void - init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (RenderedChannelParticipant) -> Void) { + init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, addPeer: @escaping () -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, openPeer: @escaping (RenderedChannelParticipant) -> Void) { self.context = context self.addPeer = addPeer self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions @@ -38,7 +37,7 @@ private enum ChannelBlacklistSection: Int32 { private enum ChannelBlacklistEntryStableId: Hashable { case index(Int) - case peer(PeerId) + case peer(EnginePeer.Id) } private enum ChannelBlacklistEntry: ItemListNodeEntry { @@ -182,8 +181,8 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { private struct ChannelBlacklistControllerState: Equatable { let referenceTimestamp: Int32 let editing: Bool - let peerIdWithRevealedOptions: PeerId? - let removingPeerId: PeerId? + let peerIdWithRevealedOptions: EnginePeer.Id? + let removingPeerId: EnginePeer.Id? let searchingMembers: Bool init(referenceTimestamp: Int32) { @@ -194,7 +193,7 @@ private struct ChannelBlacklistControllerState: Equatable { self.searchingMembers = false } - init(referenceTimestamp: Int32, editing: Bool, peerIdWithRevealedOptions: PeerId?, removingPeerId: PeerId?, searchingMembers: Bool) { + init(referenceTimestamp: Int32, editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?, removingPeerId: EnginePeer.Id?, searchingMembers: Bool) { self.referenceTimestamp = referenceTimestamp self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions @@ -231,19 +230,19 @@ private struct ChannelBlacklistControllerState: Equatable { } - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: PeerId?) -> ChannelBlacklistControllerState { + func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: EnginePeer.Id?) -> ChannelBlacklistControllerState { return ChannelBlacklistControllerState(referenceTimestamp: self.referenceTimestamp, editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions, removingPeerId: self.removingPeerId, searchingMembers: self.searchingMembers) } - func withUpdatedRemovingPeerId(_ removingPeerId: PeerId?) -> ChannelBlacklistControllerState { + func withUpdatedRemovingPeerId(_ removingPeerId: EnginePeer.Id?) -> ChannelBlacklistControllerState { return ChannelBlacklistControllerState(referenceTimestamp: self.referenceTimestamp, editing: self.editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions, removingPeerId: removingPeerId, searchingMembers: self.searchingMembers) } } -private func channelBlacklistControllerEntries(presentationData: PresentationData, view: PeerView, state: ChannelBlacklistControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelBlacklistEntry] { +private func channelBlacklistControllerEntries(presentationData: PresentationData, peer: EnginePeer?, state: ChannelBlacklistControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelBlacklistEntry] { var entries: [ChannelBlacklistEntry] = [] - if let channel = view.peers[view.peerId] as? TelegramChannel, let participants = participants { + if case let .channel(channel) = peer, let participants = participants { entries.append(.add(presentationData.theme, presentationData.strings.GroupRemoved_Remove)) let isGroup: Bool @@ -267,7 +266,7 @@ private func channelBlacklistControllerEntries(presentationData: PresentationDat return entries } -public func channelBlacklistController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: PeerId) -> ViewController { +public func channelBlacklistController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peerId: EnginePeer.Id) -> ViewController { let statePromise = ValuePromise(ChannelBlacklistControllerState(referenceTimestamp: Int32(Date().timeIntervalSince1970)), ignoreRepeated: true) let stateValue = Atomic(value: ChannelBlacklistControllerState(referenceTimestamp: Int32(Date().timeIntervalSince1970))) let updateState: ((ChannelBlacklistControllerState) -> ChannelBlacklistControllerState) -> Void = { f in @@ -287,8 +286,8 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat let removePeerDisposable = MetaDisposable() actionsDisposable.add(removePeerDisposable) - let peerView = Promise() - peerView.set(context.account.viewTracker.peerView(peerId)) + actionsDisposable.add(context.engine.peers.keepPeerUpdated(id: peerId, forceUpdate: false).start()) + let blacklistPromise = Promise<[RenderedChannelParticipant]?>(nil) let arguments = ChannelBlacklistControllerArguments(context: context, setPeerIdWithRevealedOptions: { peerId, fromPeerId in @@ -347,10 +346,11 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat } })) }, openPeer: { participant in - let _ = (peerView.get() - |> take(1) - |> deliverOnMainQueue).start(next: { peerView in - guard let channel = peerView.peers[peerId] as? TelegramChannel else { + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).start(next: { peer in + guard case let .channel(channel) = peer else { return } let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -435,10 +435,14 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat let previousParticipantsValue = Atomic<[RenderedChannelParticipant]?>(value: nil) + let peer = context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData - let signal = combineLatest(queue: .mainQueue(), presentationData, statePromise.get(), peerView.get(), blacklistPromise.get()) + let signal = combineLatest(queue: .mainQueue(), presentationData, statePromise.get(), peer, blacklistPromise.get()) |> deliverOnMainQueue - |> map { presentationData, state, view, participants -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, peer, participants -> (ItemListControllerState, (ItemListNodeState, Any)) in var rightNavigationButton: ItemListNavigationButton? var secondaryRightNavigationButton: ItemListNavigationButton? if let participants = participants, !participants.isEmpty { @@ -498,7 +502,7 @@ public func channelBlacklistController(context: AccountContext, updatedPresentat } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.GroupRemoved_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, secondaryRightNavigationButton: secondaryRightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelBlacklistControllerEntries(presentationData: presentationData, view: view, state: state, participants: participants), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && participants != nil && previous!.count >= participants!.count) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelBlacklistControllerEntries(presentationData: presentationData, peer: peer, state: state, participants: participants), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, animateChanges: previous != nil && participants != nil && previous!.count >= participants!.count) return (controllerState, (listState, arguments)) } diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift index 6dac2550dd..56fa4ceb78 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxyListSettingsController.swift @@ -341,7 +341,7 @@ public func proxySettingsController(accountManager: AccountManager ViewController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } - return proxyServerSettingsController(context: context, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, accountManager: context.sharedContext.accountManager, postbox: context.account.postbox, network: context.account.network, currentSettings: currentSettings) + return proxyServerSettingsController(context: context, presentationData: presentationData, updatedPresentationData: context.sharedContext.presentationData, accountManager: context.sharedContext.accountManager, network: context.account.network, currentSettings: currentSettings) } -func proxyServerSettingsController(context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal, accountManager: AccountManager, postbox: Postbox, network: Network, currentSettings: ProxyServerSettings?) -> ViewController { +func proxyServerSettingsController(context: AccountContext? = nil, presentationData: PresentationData, updatedPresentationData: Signal, accountManager: AccountManager, network: Network, currentSettings: ProxyServerSettings?) -> ViewController { var currentMode: ProxyServerSettingsControllerMode = .socks5 var currentUsername: String? var currentPassword: String? diff --git a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift index b39dd1856d..c062830a95 100644 --- a/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift +++ b/submodules/SettingsUI/Sources/Data and Storage/ProxySettingsServerItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import ItemListUI diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index c4e26a38ce..6b72bdd7c3 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -717,7 +717,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController expandedText = .text(about, textIcon, .generic) } - return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, text: text, expandedText: expandedText, icon: icon, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, action: { node in + return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: EnginePeer(peer), text: text, expandedText: expandedText, icon: icon, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, action: { node in if let node = node { interaction.peerContextAction(peerEntry, node, nil, false) } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index 8af9954d51..1ed491fde8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -3,7 +3,6 @@ import UIKit import Display import AsyncDisplayKit import SwiftSignalKit -import Postbox import TelegramCore import TelegramPresentationData import TelegramUIPreferences @@ -59,7 +58,7 @@ final class VoiceChatParticipantItem: ListViewItem { let dateTimeFormat: PresentationDateTimeFormat let nameDisplayOrder: PresentationPersonNameOrder let context: AccountContext - let peer: Peer + let peer: EnginePeer let text: ParticipantText let expandedText: ParticipantText? let icon: Icon @@ -71,7 +70,7 @@ final class VoiceChatParticipantItem: ListViewItem { public let selectable: Bool = true - public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, getAudioLevel: (() -> Signal)?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) { + public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, getAudioLevel: (() -> Signal)?, action: ((ASDisplayNode?) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) { self.presentationData = presentationData self.dateTimeFormat = dateTimeFormat self.nameDisplayOrder = nameDisplayOrder @@ -512,7 +511,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context) avatarListWrapperNode.contentNode.clipsToBounds = true avatarListNode.backgroundColor = .clear - avatarListNode.peer = EnginePeer(item.peer) + avatarListNode.peer = item.peer avatarListNode.firstFullSizeOnly = true avatarListNode.offsetLocation = true avatarListNode.customCenterTapAction = { [weak self] in @@ -528,7 +527,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode) avatarListWrapperNode.contentNode.addSubnode(avatarListContainerNode) - avatarListNode.update(size: targetRect.size, peer: EnginePeer(item.peer), customNode: nil, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate) + avatarListNode.update(size: targetRect.size, peer: item.peer, customNode: nil, additionalEntry: item.getUpdatingAvatar(), isExpanded: true, transition: .immediate) strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode) strongSelf.audioLevelView?.alpha = 0.0 @@ -785,7 +784,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { let rightInset: CGFloat = params.rightInset var updatedTitle = false - if let user = item.peer as? TelegramUser { + if case let .user(user) = item.peer { if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty { let string = NSMutableAttributedString() switch item.nameDisplayOrder { @@ -806,9 +805,9 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { } else { titleAttributedString = NSAttributedString(string: item.presentationData.strings.User_DeletedAccount, font: titleFont, textColor: titleColor) } - } else if let group = item.peer as? TelegramGroup { + } else if case let .legacyGroup(group) = item.peer { titleAttributedString = NSAttributedString(string: group.title, font: titleFont, textColor: titleColor) - } else if let channel = item.peer as? TelegramChannel { + } else if case let .channel(channel) = item.peer { titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor) } if let currentTitle = currentTitle, currentTitle != titleAttributedString?.string { @@ -840,7 +839,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) } else if item.peer.isFake { credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) - } else if let user = item.peer as? TelegramUser, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled { + } else if case let .user(user) = item.peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled { credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) } else if item.peer.isVerified { credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) @@ -1133,7 +1132,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { if item.peer.isDeleted { overrideImage = .deletedIcon } - strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(item.peer), overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad, storeUnrounded: true) + strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad, storeUnrounded: true) strongSelf.highlightContainerNode.frame = CGRect(origin: CGPoint(x: params.leftInset, y: -UIScreenPixel), size: CGSize(width: params.width - params.leftInset - params.rightInset, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel + 11.0)) diff --git a/submodules/TelegramCore/Sources/State/MessageMediaPreuploadManager.swift b/submodules/TelegramCore/Sources/State/MessageMediaPreuploadManager.swift index f5226403b6..21b60c4c44 100644 --- a/submodules/TelegramCore/Sources/State/MessageMediaPreuploadManager.swift +++ b/submodules/TelegramCore/Sources/State/MessageMediaPreuploadManager.swift @@ -32,11 +32,18 @@ private final class MessageMediaPreuploadManagerContext { assert(self.queue.isCurrent()) } - func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete: (()->Void)? = nil) { + func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete: (()->Void)? = nil) { let context = MessageMediaPreuploadManagerUploadContext() self.uploadContexts[id] = context let queue = self.queue - context.disposable.set(multipartUpload(network: network, postbox: postbox, source: .custom(source), encrypt: encrypt, tag: tag, hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false).start(next: { [weak self] next in + context.disposable.set(multipartUpload(network: network, postbox: postbox, source: .custom(source |> map { data in + return MediaResourceData( + path: data.path, + offset: 0, + size: data.availableSize, + complete: data.isComplete + ) + }), encrypt: encrypt, tag: tag, hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false).start(next: { [weak self] next in queue.async { if let strongSelf = self, let context = strongSelf.uploadContexts[id] { switch next { @@ -112,7 +119,7 @@ final class MessageMediaPreuploadManager { }) } - func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete:(()->Void)? = nil) { + func add(network: Network, postbox: Postbox, id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete:(()->Void)? = nil) { self.impl.with { context in context.add(network: network, postbox: postbox, id: id, encrypt: encrypt, tag: tag, source: source, onComplete: onComplete) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift index 6701b555dd..84ee94709c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/OrderedListsData.swift @@ -2,6 +2,48 @@ import SwiftSignalKit import Postbox public extension TelegramEngine.EngineData.Item { + enum Collections { + public struct FeaturedStickerPacks: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = [FeaturedStickerPackItem] + + public init() { + } + + var key: PostboxViewKey { + return .orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? OrderedItemListView else { + preconditionFailure() + } + return view.items.compactMap { item in + return item.contents.get(FeaturedStickerPackItem.self) + } + } + } + + public struct FeaturedEmojiPacks: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = [FeaturedStickerPackItem] + + public init() { + } + + var key: PostboxViewKey { + return .orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? OrderedItemListView else { + preconditionFailure() + } + return view.items.compactMap { item in + return item.contents.get(FeaturedStickerPackItem.self) + } + } + } + } + enum OrderedLists { public struct ListItems: TelegramEngineDataItem, PostboxViewDataItem { public typealias Result = [OrderedItemListEntry] diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index 853acdacf7..b47f405aa9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -231,7 +231,7 @@ public extension TelegramEngine { self.account = account } - public func preUpload(id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete: (()->Void)? = nil) { + public func preUpload(id: Int64, encrypt: Bool, tag: MediaResourceFetchTag?, source: Signal, onComplete: (()->Void)? = nil) { return self.account.messageMediaPreuploadManager.add(network: self.account.network, postbox: self.account.postbox, id: id, encrypt: encrypt, tag: tag, source: source, onComplete: onComplete) } diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index d9178200be..048308dd71 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import TelegramUIPreferences import AppBundle diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift index b8505beb86..c9d0fcd28f 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusPreviewScreen.swift @@ -2,7 +2,6 @@ import Foundation import UIKit import Display import SwiftSignalKit -import Postbox import TelegramCore import ComponentFlow import TelegramPresentationData diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 68f6e7504c..94f96cd074 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -11,7 +11,6 @@ import ComponentDisplayAdapters import TelegramPresentationData import AccountContext import PagerComponent -import Postbox import TelegramCore import Lottie import EmojiTextAttachmentView @@ -19,6 +18,7 @@ import TextFormat import AppBundle import GZip import EmojiStatusComponent +import Postbox private func randomGenericReactionEffect(context: AccountContext) -> Signal { return context.engine.stickers.loadedStickerPack(reference: .emojiGenericAnimations, forceActualized: false) @@ -344,16 +344,16 @@ public final class EmojiStatusSelectionController: ViewController { self.layer.addSublayer(self.cloudLayer0) self.layer.addSublayer(self.cloudLayer1) - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - self.stableEmptyResultEmojiDisposable.set((self.context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] views in - guard let strongSelf = self, let view = views.views[viewKey] as? OrderedItemListView else { + self.stableEmptyResultEmojiDisposable.set((self.context.engine.data.get( + TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks() + ) + |> deliverOnMainQueue).start(next: { [weak self] featuredEmojiPacks in + guard let strongSelf = self else { return } var filteredFiles: [TelegramMediaFile] = [] let filterList: [String] = ["😖", "😫", "🫠", "😨", "❓"] - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredEmojiPack in featuredEmojiPacks { for item in featuredEmojiPack.topItems { for attribute in item.file.attributes { switch attribute { @@ -423,14 +423,14 @@ public final class EmojiStatusSelectionController: ViewController { return } - let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks) - let _ = (strongSelf.context.account.postbox.combinedView(keys: [viewKey]) - |> take(1) - |> deliverOnMainQueue).start(next: { views in - guard let strongSelf = self, let view = views.views[viewKey] as? OrderedItemListView else { + let _ = (strongSelf.context.engine.data.get( + TelegramEngine.EngineData.Item.Collections.FeaturedEmojiPacks() + ) + |> deliverOnMainQueue).start(next: { featuredEmojiPacks in + guard let strongSelf = self else { return } - for featuredEmojiPack in view.items.lazy.map({ $0.contents.get(FeaturedStickerPackItem.self)! }) { + for featuredEmojiPack in featuredEmojiPacks { if featuredEmojiPack.info.id == collectionId { if let strongSelf = self { strongSelf.scheduledEmojiContentAnimationHint = EmojiPagerContentComponent.ContentAnimation(type: .groupInstalled(id: collectionId, scrollToGroup: true)) @@ -592,7 +592,7 @@ public final class EmojiStatusSelectionController: ViewController { |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] - var existingIds = Set() + var existingIds = Set() for itemFile in files { if existingIds.contains(itemFile.fileId) { continue diff --git a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift index 4d00e0b9d7..535b79b95d 100644 --- a/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift +++ b/submodules/TelegramUI/Components/EmojiSuggestionsComponent/Sources/EmojiSuggestionsComponent.swift @@ -7,7 +7,6 @@ import MultiAnimationRenderer import ComponentFlow import AccountContext import TelegramCore -import Postbox import TelegramPresentationData import EmojiTextAttachmentView import TextFormat @@ -52,7 +51,7 @@ public final class EmojiSuggestionsComponent: Component { let normalizedQuery = query.basicEmoji.0 - var existingIds = Set() + var existingIds = Set() for entry in view.entries { guard let item = entry.item as? StickerPackItem else { continue @@ -179,7 +178,7 @@ public final class EmojiSuggestionsComponent: Component { private var itemLayout: ItemLayout? private var ignoreScrolling: Bool = false - private var visibleLayers: [MediaId: InlineStickerItemLayer] = [:] + private var visibleLayers: [EngineMedia.Id: InlineStickerItemLayer] = [:] override init(frame: CGRect) { self.blurView = BlurredBackgroundView(color: .clear, enableBlur: true) @@ -296,7 +295,7 @@ public final class EmojiSuggestionsComponent: Component { let visibleBounds = self.scrollView.bounds - var visibleIds = Set() + var visibleIds = Set() for i in 0 ..< component.files.count { let itemFrame = itemLayout.frame(at: i) if visibleBounds.intersects(itemFrame) { @@ -330,7 +329,7 @@ public final class EmojiSuggestionsComponent: Component { } } - var removedIds: [MediaId] = [] + var removedIds: [EngineMedia.Id] = [] for (id, itemLayer) in self.visibleLayers { if !visibleIds.contains(id) { itemLayer.removeFromSuperlayer() From 06434cc5ef267d341d27907a2307bae5373c7f20 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 21 Apr 2023 19:52:09 +0400 Subject: [PATCH 7/7] [WIP] Stories UI --- .../Sources/AccountContext.swift | 3 + .../Sources/ChatListControllerNode.swift | 6 +- .../Messages/TelegramEngineMessages.swift | 42 +++ .../Sources/Utils/MessageUtils.swift | 8 +- .../MessageInputPanelComponent/BUILD | 1 + .../MessageInputActionButtonComponent.swift | 167 +++++++++ .../Sources/MessageInputPanelComponent.swift | 136 +++++-- .../Stories/StoryContainerScreen/BUILD | 4 + .../Sources/StoryActionsComponent.swift | 181 ++++++++++ .../Sources/StoryContainerScreen.swift | 341 ++++++++++++++++-- .../Sources/StoryContent.swift | 12 +- .../Sources/StoryChatContent.swift | 21 +- .../StoryMessageContentComponent.swift | 71 +++- .../Components/TextFieldComponent/BUILD | 19 + .../Sources/TextFieldComponent.swift | 170 +++++++++ .../InlineLike.imageset/Contents.json | 12 + .../InlineLike.imageset/GalleryShare.svg | 3 + .../InlineShare.imageset/Contents.json | 12 + .../InlineShare.imageset/GalleryForward.svg | 3 + .../Sources/SharedAccountContext.swift | 17 + 20 files changed, 1151 insertions(+), 78 deletions(-) create mode 100644 submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift create mode 100644 submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryActionsComponent.swift create mode 100644 submodules/TelegramUI/Components/TextFieldComponent/BUILD create mode 100644 submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/GalleryShare.svg create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/GalleryForward.svg diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 49f3d885c0..abd301d1c9 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -842,6 +842,9 @@ public protocol SharedAccountContext: AnyObject { var hasOngoingCall: ValuePromise { get } var immediateHasOngoingCall: Bool { get } + var enablePreloads: Promise { get } + var hasPreloadBlockingContent: Promise { get } + var hasGroupCallOnScreen: Signal { get } var currentGroupCallController: ViewController? { get } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 7169a163e8..875f0a0b05 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -909,12 +909,12 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele if self.controlsHistoryPreload, case .chatList(groupId: .root) = self.location { self.context.account.viewTracker.chatListPreloadItems.set(combineLatest(queue: .mainQueue(), - context.sharedContext.hasOngoingCall.get(), + context.sharedContext.enablePreloads.get(), itemNode.listNode.preloadItems.get(), enablePreload ) - |> map { hasOngoingCall, preloadItems, enablePreload -> Set in - if hasOngoingCall || !enablePreload { + |> map { enablePreloads, preloadItems, enablePreload -> Set in + if !enablePreloads || !enablePreload { return Set() } else { return Set(preloadItems) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 357c52e266..61a89f8c32 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -3,6 +3,10 @@ import SwiftSignalKit import Postbox import TelegramApi +public enum EngineOutgoingMessageContent { + case text(String) +} + public extension TelegramEngine { final class Messages { private let account: Account @@ -189,6 +193,31 @@ public extension TelegramEngine { public func exportMessageLink(peerId: PeerId, messageId: MessageId, isThread: Bool = false) -> Signal { return _internal_exportMessageLink(account: self.account, peerId: peerId, messageId: messageId, isThread: isThread) } + + public func enqueueOutgoingMessage( + to peerId: EnginePeer.Id, + replyTo replyToMessageId: EngineMessage.Id?, + content: EngineOutgoingMessageContent + ) { + switch content { + case let .text(text): + let message: EnqueueMessage = .message( + text: text, + attributes: [], + inlineStickers: [:], + mediaReference: nil, + replyToMessageId: replyToMessageId, + localGroupingKey: nil, + correlationId: nil, + bubbleUpEmojiOrStickersets: [] + ) + let _ = enqueueMessages( + account: self.account, + peerId: peerId, + messages: [message] + ).start() + } + } public func enqueueOutgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId? = nil, hideVia: Bool = false, silentPosting: Bool = false, scheduleTime: Int32? = nil, correlationId: Int64? = nil) -> Bool { return _internal_enqueueOutgoingMessageWithChatContextResult(account: self.account, to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) @@ -197,6 +226,19 @@ public extension TelegramEngine { public func outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: Int64?, botId: PeerId, result: ChatContextResult, replyToMessageId: MessageId?, hideVia: Bool, silentPosting: Bool, scheduleTime: Int32?, correlationId: Int64?) -> EnqueueMessage? { return _internal_outgoingMessageWithChatContextResult(to: peerId, threadId: threadId, botId: botId, result: result, replyToMessageId: replyToMessageId, hideVia: hideVia, silentPosting: silentPosting, scheduleTime: scheduleTime, correlationId: correlationId) } + + public func setMessageReactions( + id: EngineMessage.Id, + reactions: [UpdateMessageReaction] + ) { + let _ = updateMessageReactionsInteractively( + account: self.account, + messageId: id, + reactions: reactions, + isLarge: false, + storeAsRecentlyUsed: false + ).start() + } public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal { return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index 8f6a87d262..e9921659df 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -347,12 +347,16 @@ public extension Message { var hasReactions: Bool { for attribute in self.attributes { if let attribute = attribute as? ReactionsMessageAttribute { - return !attribute.reactions.isEmpty + if !attribute.reactions.isEmpty { + return true + } } } for attribute in self.attributes { if let attribute = attribute as? PendingReactionsMessageAttribute { - return !attribute.reactions.isEmpty + if !attribute.reactions.isEmpty { + return true + } } } return false diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index 1f614ccdb6..c7f54f14a7 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Display", "//submodules/ComponentFlow", "//submodules/AppBundle", + "//submodules/TelegramUI/Components/TextFieldComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift new file mode 100644 index 0000000000..933a84d00e --- /dev/null +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift @@ -0,0 +1,167 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AppBundle + +public final class MessageInputActionButtonComponent: Component { + public enum Mode { + case send + case voiceInput + case videoInput + } + + public let mode: Mode + public let action: () -> Void + + public init( + mode: Mode, + action: @escaping () -> Void + ) { + self.mode = mode + self.action = action + } + + public static func ==(lhs: MessageInputActionButtonComponent, rhs: MessageInputActionButtonComponent) -> Bool { + if lhs.mode != rhs.mode { + return false + } + return true + } + + public final class View: HighlightTrackingButton { + private let microphoneIconView: UIImageView + private let cameraIconView: UIImageView + private let sendIconView: UIImageView + + private var component: MessageInputActionButtonComponent? + private weak var componentState: EmptyComponentState? + + override init(frame: CGRect) { + self.microphoneIconView = UIImageView() + + self.cameraIconView = UIImageView() + self.sendIconView = UIImageView() + + super.init(frame: frame) + + self.isMultipleTouchEnabled = false + + self.addSubview(self.microphoneIconView) + self.addSubview(self.cameraIconView) + self.addSubview(self.sendIconView) + + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + + let scale: CGFloat = highlighted ? 0.6 : 1.0 + + let transition = Transition(animation: .curve(duration: highlighted ? 0.5 : 0.3, curve: .spring)) + transition.setSublayerTransform(view: self, transform: CATransform3DMakeScale(scale, scale, 1.0)) + } + + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func pressed() { + self.component?.action() + } + + override public func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + return super.continueTracking(touch, with: event) + } + + func update(component: MessageInputActionButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.componentState = state + + if self.microphoneIconView.image == nil { + self.microphoneIconView.image = UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone")?.withRenderingMode(.alwaysTemplate) + self.microphoneIconView.tintColor = .white + } + if self.cameraIconView.image == nil { + self.cameraIconView.image = UIImage(bundleImageName: "Chat/Input/Text/IconVideo")?.withRenderingMode(.alwaysTemplate) + self.cameraIconView.tintColor = .white + } + + if self.sendIconView.image == nil { + self.sendIconView.image = generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.white.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + context.setStrokeColor(UIColor.clear.cgColor) + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setLineJoin(.round) + + context.translateBy(x: 5.45, y: 4.0) + + context.saveGState() + context.translateBy(x: 4.0, y: 4.0) + let _ = try? drawSvgPath(context, path: "M1,7 L7,1 L13,7 S ") + context.restoreGState() + + context.saveGState() + context.translateBy(x: 10.0, y: 4.0) + let _ = try? drawSvgPath(context, path: "M1,16 V1 S ") + context.restoreGState() + }) + } + + var sendAlpha: CGFloat = 0.0 + var microphoneAlpha: CGFloat = 0.0 + var cameraAlpha: CGFloat = 0.0 + + switch component.mode { + case .send: + sendAlpha = 1.0 + case .videoInput: + cameraAlpha = 1.0 + case .voiceInput: + microphoneAlpha = 1.0 + } + + transition.setAlpha(view: self.sendIconView, alpha: sendAlpha) + transition.setScale(view: self.sendIconView, scale: sendAlpha == 0.0 ? 0.01 : 1.0) + + transition.setAlpha(view: self.cameraIconView, alpha: cameraAlpha) + transition.setScale(view: self.cameraIconView, scale: cameraAlpha == 0.0 ? 0.01 : 1.0) + + transition.setAlpha(view: self.microphoneIconView, alpha: microphoneAlpha) + transition.setScale(view: self.microphoneIconView, scale: microphoneAlpha == 0.0 ? 0.01 : 1.0) + + if let image = self.sendIconView.image { + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - image.size.width) * 0.5), y: floorToScreenPixels((availableSize.height - image.size.height) * 0.5)), size: image.size) + transition.setPosition(view: self.sendIconView, position: iconFrame.center) + transition.setBounds(view: self.sendIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + if let image = self.cameraIconView.image { + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - image.size.width) * 0.5), y: floorToScreenPixels((availableSize.height - image.size.height) * 0.5)), size: image.size) + transition.setPosition(view: self.cameraIconView, position: iconFrame.center) + transition.setBounds(view: self.cameraIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + if let image = self.microphoneIconView.image { + let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - image.size.width) * 0.5), y: floorToScreenPixels((availableSize.height - image.size.height) * 0.5)), size: image.size) + transition.setPosition(view: self.microphoneIconView, position: iconFrame.center) + transition.setBounds(view: self.microphoneIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) + } + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 9a24a16301..93d9542efb 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -3,31 +3,56 @@ import UIKit import Display import ComponentFlow import AppBundle +import TextFieldComponent public final class MessageInputPanelComponent: Component { - public init() { + public final class ExternalState { + public fileprivate(set) var hasText: Bool = false + public init() { + } + } + + public let externalState: ExternalState + public let sendMessageAction: () -> Void + + public init( + externalState: ExternalState, + sendMessageAction: @escaping () -> Void + ) { + self.externalState = externalState + self.sendMessageAction = sendMessageAction } public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool { + if lhs.externalState !== rhs.externalState { + return false + } return true } + public enum SendMessageInput { + case text(String) + } + public final class View: UIView { private let fieldBackgroundView: UIImageView - private let fieldPlaceholder = ComponentView() + + private let textField = ComponentView() + private let textFieldExternalState = TextFieldComponent.ExternalState() private let attachmentIconView: UIImageView - private let recordingIconView: UIImageView + private let inputActionButton = ComponentView() private let stickerIconView: UIImageView + private var currentMediaInputIsVoice: Bool = true + private var component: MessageInputPanelComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { self.fieldBackgroundView = UIImageView() self.attachmentIconView = UIImageView() - self.recordingIconView = UIImageView() self.stickerIconView = UIImageView() super.init(frame: frame) @@ -36,7 +61,6 @@ public final class MessageInputPanelComponent: Component { self.addSubview(self.fieldBackgroundView) self.addSubview(self.attachmentIconView) - self.addSubview(self.recordingIconView) self.addSubview(self.stickerIconView) } @@ -44,7 +68,22 @@ public final class MessageInputPanelComponent: Component { fatalError("init(coder:) has not been implemented") } + public func getSendMessageInput() -> SendMessageInput { + guard let textFieldView = self.textField.view as? TextFieldComponent.View else { + return .text("") + } + + return .text(textFieldView.getText()) + } + + public func clearSendMessageInput() { + if let textFieldView = self.textField.view as? TextFieldComponent.View { + textFieldView.setText(string: "") + } + } + func update(component: MessageInputPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let baseHeight: CGFloat = 44.0 let insets = UIEdgeInsets(top: 5.0, left: 41.0, bottom: 5.0, right: 41.0) let fieldCornerRadius: CGFloat = 16.0 @@ -58,47 +97,82 @@ public final class MessageInputPanelComponent: Component { self.attachmentIconView.image = UIImage(bundleImageName: "Chat/Input/Text/IconAttachment")?.withRenderingMode(.alwaysTemplate) self.attachmentIconView.tintColor = .white } - if self.recordingIconView.image == nil { - self.recordingIconView.image = UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone")?.withRenderingMode(.alwaysTemplate) - self.recordingIconView.tintColor = .white - } if self.stickerIconView.image == nil { self.stickerIconView.image = UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconStickers")?.withRenderingMode(.alwaysTemplate) self.stickerIconView.tintColor = .white } - let fieldFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: availableSize.width - insets.left - insets.right, height: availableSize.height - insets.top - insets.bottom)) + let availableTextFieldSize = CGSize(width: availableSize.width - insets.left - insets.right, height: availableSize.height - insets.top - insets.bottom) + + self.textField.parentState = state + let textFieldSize = self.textField.update( + transition: .immediate, + component: AnyComponent(TextFieldComponent( + externalState: self.textFieldExternalState, + placeholder: "Reply Privately..." + )), + environment: {}, + containerSize: availableTextFieldSize + ) + + let fieldFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: availableSize.width - insets.left - insets.right, height: textFieldSize.height)) transition.setFrame(view: self.fieldBackgroundView, frame: fieldFrame) let rightFieldInset: CGFloat = 34.0 - let placeholderSize = self.fieldPlaceholder.update( - transition: .immediate, - component: AnyComponent(Text(text: "Reply Privately...", font: Font.regular(17.0), color: UIColor(white: 1.0, alpha: 0.16))), - environment: {}, - containerSize: fieldFrame.size - ) - if let fieldPlaceholderView = self.fieldPlaceholder.view { - if fieldPlaceholderView.superview == nil { - fieldPlaceholderView.layer.anchorPoint = CGPoint() - fieldPlaceholderView.isUserInteractionEnabled = false - self.addSubview(fieldPlaceholderView) + let size = CGSize(width: availableSize.width, height: textFieldSize.height + insets.top + insets.bottom) + + if let textFieldView = self.textField.view { + if textFieldView.superview == nil { + self.addSubview(textFieldView) } - fieldPlaceholderView.bounds = CGRect(origin: CGPoint(), size: placeholderSize) - transition.setPosition(view: fieldPlaceholderView, position: CGPoint(x: fieldFrame.minX + 12.0, y: fieldFrame.minY + floor((fieldFrame.height - placeholderSize.height) * 0.5))) + transition.setFrame(view: textFieldView, frame: CGRect(origin: CGPoint(x: fieldFrame.minX, y: fieldFrame.maxY - textFieldSize.height), size: textFieldSize)) } if let image = self.attachmentIconView.image { - transition.setFrame(view: self.attachmentIconView, frame: CGRect(origin: CGPoint(x: floor((insets.left - image.size.width) * 0.5), y: floor((availableSize.height - image.size.height) * 0.5)), size: image.size)) - } - if let image = self.recordingIconView.image { - transition.setFrame(view: self.recordingIconView, frame: CGRect(origin: CGPoint(x: availableSize.width - insets.right + floor((insets.right - image.size.width) * 0.5), y: floor((availableSize.height - image.size.height) * 0.5)), size: image.size)) - } - if let image = self.stickerIconView.image { - transition.setFrame(view: self.stickerIconView, frame: CGRect(origin: CGPoint(x: fieldFrame.maxX - rightFieldInset + floor((rightFieldInset - image.size.width) * 0.5), y: fieldFrame.minY + floor((fieldFrame.height - image.size.height) * 0.5)), size: image.size)) + transition.setFrame(view: self.attachmentIconView, frame: CGRect(origin: CGPoint(x: floor((insets.left - image.size.width) * 0.5), y: size.height - baseHeight + floor((baseHeight - image.size.height) * 0.5)), size: image.size)) } - return availableSize + let inputActionButtonSize = self.inputActionButton.update( + transition: transition, + component: AnyComponent(MessageInputActionButtonComponent( + mode: self.textFieldExternalState.hasText ? .send : (self.currentMediaInputIsVoice ? .voiceInput : .videoInput), + action: { [weak self] in + guard let self else { + return + } + + if case .text("") = self.getSendMessageInput() { + self.currentMediaInputIsVoice = !self.currentMediaInputIsVoice + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + + HapticFeedback().impact() + } else { + self.component?.sendMessageAction() + } + } + )), + environment: {}, + containerSize: CGSize(width: 33.0, height: 33.0) + ) + if let inputActionButtonView = self.inputActionButton.view { + if inputActionButtonView.superview == nil { + self.addSubview(inputActionButtonView) + } + transition.setFrame(view: inputActionButtonView, frame: CGRect(origin: CGPoint(x: size.width - insets.right + floorToScreenPixels((insets.right - inputActionButtonSize.width) * 0.5), y: size.height - baseHeight + floorToScreenPixels((baseHeight - inputActionButtonSize.height) * 0.5)), size: inputActionButtonSize)) + } + if let image = self.stickerIconView.image { + let stickerIconFrame = CGRect(origin: CGPoint(x: fieldFrame.maxX - rightFieldInset + floor((rightFieldInset - image.size.width) * 0.5), y: fieldFrame.minY + floor((fieldFrame.height - image.size.height) * 0.5)), size: image.size) + transition.setPosition(view: self.stickerIconView, position: stickerIconFrame.center) + transition.setBounds(view: self.stickerIconView, bounds: CGRect(origin: CGPoint(), size: stickerIconFrame.size)) + + transition.setAlpha(view: self.stickerIconView, alpha: self.textFieldExternalState.hasText ? 0.0 : 1.0) + transition.setScale(view: self.stickerIconView, scale: self.textFieldExternalState.hasText ? 0.1 : 1.0) + } + + component.externalState.hasText = self.textFieldExternalState.hasText + + return size } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index 7c3d66be69..039f18159f 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -13,10 +13,14 @@ swift_library( "//submodules/Display", "//submodules/ComponentFlow", "//submodules/Components/ViewControllerComponent", + "//submodules/Components/ComponentDisplayAdapters", "//submodules/TelegramUI/Components/MessageInputPanelComponent", "//submodules/AccountContext", "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AppBundle", + "//submodules/TelegramCore", + "//submodules/ShareController", + "//submodules/UndoUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryActionsComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryActionsComponent.swift new file mode 100644 index 0000000000..af11956e7c --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryActionsComponent.swift @@ -0,0 +1,181 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AppBundle +import ComponentDisplayAdapters + +public final class StoryActionsComponent: Component { + public struct Item: Equatable { + public enum Kind { + case like + case share + } + + public let kind: Kind + public let isActivated: Bool + + public init(kind: Kind, isActivated: Bool) { + self.kind = kind + self.isActivated = isActivated + } + } + + public let items: [Item] + public let action: (Item) -> Void + + public init( + items: [Item], + action: @escaping (Item) -> Void + ) { + self.items = items + self.action = action + } + + public static func ==(lhs: StoryActionsComponent, rhs: StoryActionsComponent) -> Bool { + if lhs.items != rhs.items { + return false + } + return true + } + + private final class ItemView: HighlightTrackingButton { + let action: (Item) -> Void + + let maskBackgroundView = UIImageView() + let iconView: UIImageView + + private var item: Item? + + init(action: @escaping (Item) -> Void) { + self.action = action + + self.iconView = UIImageView() + + super.init(frame: CGRect()) + + self.addSubview(self.iconView) + + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + + let scale: CGFloat = highlighted ? 0.6 : 1.0 + + let transition = Transition(animation: .curve(duration: highlighted ? 0.5 : 0.3, curve: .spring)) + transition.setSublayerTransform(view: self, transform: CATransform3DMakeScale(scale, scale, 1.0)) + transition.setScale(view: self.maskBackgroundView, scale: scale) + } + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func pressed() { + guard let item = self.item else { + return + } + self.action(item) + } + + func update(item: Item, size: CGSize, transition: Transition) { + if self.item == item { + return + } + self.item = item + + switch item.kind { + case .like: + self.iconView.image = UIImage(bundleImageName: "Media Gallery/InlineLike")?.withRenderingMode(.alwaysTemplate) + case .share: + self.iconView.image = UIImage(bundleImageName: "Media Gallery/InlineShare")?.withRenderingMode(.alwaysTemplate) + } + + self.iconView.tintColor = item.isActivated ? UIColor(rgb: 0xFF6F66) : UIColor.white + + if let image = self.iconView.image { + transition.setFrame(view: self.iconView, frame: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)) + } + } + } + + public final class View: UIView { + private let backgroundView: BlurredBackgroundView + private let backgroundMaskView: UIView + + private var itemViews: [Item.Kind: ItemView] = [:] + + private var component: StoryActionsComponent? + private weak var componentState: EmptyComponentState? + + override init(frame: CGRect) { + self.backgroundView = BlurredBackgroundView(color: nil, enableBlur: true) + self.backgroundMaskView = UIView() + self.backgroundView.mask = self.backgroundMaskView + + super.init(frame: frame) + + self.addSubview(self.backgroundView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: StoryActionsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.componentState = state + + var contentHeight: CGFloat = 0.0 + var validIds: [Item.Kind] = [] + for item in component.items { + validIds.append(item.kind) + + let itemView: ItemView + var itemTransition = transition + if let current = self.itemViews[item.kind] { + itemView = current + } else { + itemTransition = .immediate + itemView = ItemView(action: { [weak self] item in + self?.component?.action(item) + }) + self.itemViews[item.kind] = itemView + self.addSubview(itemView) + + itemView.maskBackgroundView.image = generateFilledCircleImage(diameter: 44.0, color: .white) + self.backgroundMaskView.addSubview(itemView.maskBackgroundView) + } + + if !contentHeight.isZero { + contentHeight += 10.0 + } + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: 44.0, height: 44.0)) + itemView.update(item: item, size: itemFrame.size, transition: itemTransition) + itemTransition.setFrame(view: itemView, frame: itemFrame) + itemTransition.setPosition(view: itemView.maskBackgroundView, position: itemFrame.center) + itemTransition.setBounds(view: itemView.maskBackgroundView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size)) + + contentHeight += itemFrame.height + } + + let contentSize = CGSize(width: 44.0, height: contentHeight) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: contentSize)) + self.backgroundView.updateColor(color: UIColor(white: 0.0, alpha: 0.3), transition: .immediate) + self.backgroundView.update(size: contentSize, transition: transition.containedViewLayoutTransition) + + return contentSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index eb08a95dc0..e5e3f1d83a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -7,19 +7,40 @@ import AccountContext import SwiftSignalKit import AppBundle import MessageInputPanelComponent +import ShareController +import TelegramCore +import UndoUI + +private func hasFirstResponder(_ view: UIView) -> Bool { + if view.isFirstResponder { + return true + } + for subview in view.subviews { + if hasFirstResponder(subview) { + return true + } + } + return false +} private final class StoryContainerScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment + let context: AccountContext let initialContent: StoryContentItemSlice init( + context: AccountContext, initialContent: StoryContentItemSlice ) { + self.context = context self.initialContent = initialContent } static func ==(lhs: StoryContainerScreenComponent, rhs: StoryContainerScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } if lhs.initialContent !== rhs.initialContent { return false } @@ -65,16 +86,20 @@ private final class StoryContainerScreenComponent: Component { private let contentContainerView: UIView private let topContentGradientLayer: SimpleGradientLayer + private let bottomContentGradientLayer: SimpleGradientLayer + private let contentDimLayer: SimpleLayer private let closeButton: HighlightableButton private let closeButtonIconView: UIImageView private let navigationStrip = ComponentView() + private let inlineActions = ComponentView() private var centerInfoItem: InfoItem? private var rightInfoItem: InfoItem? private let inputPanel = ComponentView() + private let inputPanelExternalState = MessageInputPanelComponent.ExternalState() private var component: StoryContainerScreenComponent? private weak var state: EmptyComponentState? @@ -89,6 +114,8 @@ private final class StoryContainerScreenComponent: Component { private var visibleItems: [AnyHashable: VisibleItem] = [:] + private var preloadContexts: [AnyHashable: Disposable] = [:] + override init(frame: CGRect) { self.scrollView = ScrollView() @@ -97,6 +124,8 @@ private final class StoryContainerScreenComponent: Component { self.contentContainerView.isUserInteractionEnabled = false self.topContentGradientLayer = SimpleGradientLayer() + self.bottomContentGradientLayer = SimpleGradientLayer() + self.contentDimLayer = SimpleLayer() self.closeButton = HighlightableButton() self.closeButtonIconView = UIImageView() @@ -123,7 +152,9 @@ private final class StoryContainerScreenComponent: Component { self.scrollView.clipsToBounds = true self.addSubview(self.contentContainerView) + self.layer.addSublayer(self.contentDimLayer) self.layer.addSublayer(self.topContentGradientLayer) + self.layer.addSublayer(self.bottomContentGradientLayer) self.closeButton.addSubview(self.closeButtonIconView) self.addSubview(self.closeButton) @@ -142,32 +173,36 @@ private final class StoryContainerScreenComponent: Component { @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state, let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }), let itemLayout = self.itemLayout { - let point = recognizer.location(in: self) - - var nextIndex: Int - if point.x < itemLayout.size.width * 0.5 { - nextIndex = currentIndex + 1 + if hasFirstResponder(self) { + self.endEditing(true) } else { - nextIndex = currentIndex - 1 - } - nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1)) - if nextIndex != currentIndex { - let focusedItemId = currentSlice.items[nextIndex].id - self.focusedItemId = focusedItemId - self.state?.updated(transition: .immediate) + let point = recognizer.location(in: self) - self.currentSliceDisposable?.dispose() - self.currentSliceDisposable = (currentSlice.update( - currentSlice, - focusedItemId - ) - |> deliverOnMainQueue).start(next: { [weak self] contentSlice in - guard let self else { - return - } - self.currentSlice = contentSlice + var nextIndex: Int + if point.x < itemLayout.size.width * 0.5 { + nextIndex = currentIndex + 1 + } else { + nextIndex = currentIndex - 1 + } + nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1)) + if nextIndex != currentIndex { + let focusedItemId = currentSlice.items[nextIndex].id + self.focusedItemId = focusedItemId self.state?.updated(transition: .immediate) - }) + + self.currentSliceDisposable?.dispose() + self.currentSliceDisposable = (currentSlice.update( + currentSlice, + focusedItemId + ) + |> deliverOnMainQueue).start(next: { [weak self] contentSlice in + guard let self else { + return + } + self.currentSlice = contentSlice + self.state?.updated(transition: .immediate) + }) + } } } } @@ -256,6 +291,123 @@ private final class StoryContainerScreenComponent: Component { }) } + private func performSendMessageAction() { + guard let component = self.component else { + return + } + guard let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) else { + return + } + guard let targetMessageId = focusedItem.targetMessageId else { + return + } + guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else { + return + } + + switch inputPanelView.getSendMessageInput() { + case let .text(text): + if !text.isEmpty { + component.context.engine.messages.enqueueOutgoingMessage( + to: targetMessageId.peerId, + replyTo: targetMessageId, + content: .text(text) + ) + inputPanelView.clearSendMessageInput() + + if let controller = self.environment?.controller() { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + controller.present(UndoOverlayController( + presentationData: presentationData, + content: .succeed(text: "Message Sent"), + elevatedLayout: false, + animateInAsReplacement: false, + action: { _ in return false } + ), in: .current) + } + } + } + } + + private func performInlineAction(item: StoryActionsComponent.Item) { + guard let component = self.component else { + return + } + guard let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) else { + return + } + guard let targetMessageId = focusedItem.targetMessageId else { + return + } + + switch item.kind { + case .like: + if item.isActivated { + component.context.engine.messages.setMessageReactions( + id: targetMessageId, + reactions: [ + ] + ) + } else { + component.context.engine.messages.setMessageReactions( + id: targetMessageId, + reactions: [ + .builtin("❤") + ] + ) + } + case .share: + let _ = (component.context.engine.data.get( + TelegramEngine.EngineData.Item.Messages.Message(id: targetMessageId) + ) + |> deliverOnMainQueue).start(next: { [weak self] message in + guard let self, let message, let component = self.component, let controller = self.environment?.controller() else { + return + } + let shareController = ShareController( + context: component.context, + subject: .messages([message._asMessage()]), + externalShare: false, + immediateExternalShare: false, + updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }), + component.context.sharedContext.presentationData) + ) + controller.present(shareController, in: .window(.root)) + }) + } + } + + private func updatePreloads() { + var validIds: [AnyHashable] = [] + if let currentSlice = self.currentSlice, let focusedItemId = self.focusedItemId, let currentIndex = currentSlice.items.firstIndex(where: { $0.id == focusedItemId }) { + for i in 0 ..< 2 { + var nextIndex: Int = currentIndex - 1 - i + nextIndex = max(0, min(nextIndex, currentSlice.items.count - 1)) + if nextIndex != currentIndex { + let nextItem = currentSlice.items[nextIndex] + + validIds.append(nextItem.id) + if self.preloadContexts[nextItem.id] == nil { + if let signal = nextItem.preload { + self.preloadContexts[nextItem.id] = signal.start() + } + } + } + } + } + + var removeIds: [AnyHashable] = [] + for (id, disposable) in self.preloadContexts { + if !validIds.contains(id) { + removeIds.append(id) + disposable.dispose() + } + } + for id in removeIds { + self.preloadContexts.removeValue(forKey: id) + } + } + func update(component: StoryContainerScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { let isFirstTime = self.component == nil @@ -298,6 +450,27 @@ private final class StoryContainerScreenComponent: Component { self.topContentGradientLayer.colors = colors self.topContentGradientLayer.type = .axial } + if self.bottomContentGradientLayer.colors == nil { + var locations: [NSNumber] = [] + var colors: [CGColor] = [] + let numStops = 10 + let baseAlpha: CGFloat = 0.7 + for i in 0 ..< numStops { + let step = 1.0 - CGFloat(i) / CGFloat(numStops - 1) + locations.append((1.0 - step) as NSNumber) + let alphaStep: CGFloat = pow(step, 1.5) + colors.append(UIColor.black.withAlphaComponent(alphaStep * baseAlpha).cgColor) + } + + self.bottomContentGradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0) + self.bottomContentGradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0) + + self.bottomContentGradientLayer.locations = locations + self.bottomContentGradientLayer.colors = colors + self.bottomContentGradientLayer.type = .axial + + self.contentDimLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.3).cgColor + } if let focusedItemId = self.focusedItemId { if let currentSlice = self.currentSlice { @@ -309,15 +482,47 @@ private final class StoryContainerScreenComponent: Component { } } + self.updatePreloads() + self.component = component self.state = state self.environment = environment - let bottomContentInset: CGFloat + var bottomContentInset: CGFloat if !environment.safeInsets.bottom.isZero { - bottomContentInset = environment.safeInsets.bottom + 5.0 + 44.0 + bottomContentInset = environment.safeInsets.bottom + 5.0 } else { - bottomContentInset = 44.0 + bottomContentInset = 0.0 + } + + self.inputPanel.parentState = state + let inputPanelSize = self.inputPanel.update( + transition: transition, + component: AnyComponent(MessageInputPanelComponent( + externalState: self.inputPanelExternalState, + sendMessageAction: { [weak self] in + guard let self else { + return + } + self.performSendMessageAction() + } + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 200.0) + ) + + let bottomContentInsetWithoutInput = bottomContentInset + + let inputPanelBottomInset: CGFloat + let inputPanelIsOverlay: Bool + if environment.inputHeight < bottomContentInset + inputPanelSize.height { + inputPanelBottomInset = bottomContentInset + bottomContentInset += inputPanelSize.height + inputPanelIsOverlay = false + } else { + bottomContentInset += 44.0 + inputPanelBottomInset = environment.inputHeight + inputPanelIsOverlay = true } let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: environment.statusBarHeight), size: CGSize(width: availableSize.width, height: availableSize.height - environment.statusBarHeight - bottomContentInset)) @@ -348,7 +553,7 @@ private final class StoryContainerScreenComponent: Component { if let rightInfoItem = self.rightInfoItem, currentRightInfoItem?.component != rightInfoItem.component { self.rightInfoItem = nil if let view = rightInfoItem.view.view { - view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.5, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak view] _ in view?.removeFromSuperview() }) @@ -395,7 +600,7 @@ private final class StoryContainerScreenComponent: Component { if animateIn, !isFirstTime { view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + view.layer.animateScale(from: 0.5, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) } } } @@ -445,27 +650,76 @@ private final class StoryContainerScreenComponent: Component { } transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: contentFrame.minX + navigationStripSideInset, y: contentFrame.minY + navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0))) } + + if let focusedItemId = self.focusedItemId, let focusedItem = self.currentSlice?.items.first(where: { $0.id == focusedItemId }) { + let inlineActionsSize = self.inlineActions.update( + transition: transition, + component: AnyComponent(StoryActionsComponent( + items: [ + StoryActionsComponent.Item( + kind: .like, + isActivated: focusedItem.hasLike + ), + StoryActionsComponent.Item( + kind: .share, + isActivated: false + ) + ], + action: { [weak self] item in + guard let self else { + return + } + self.performInlineAction(item: item) + } + )), + environment: {}, + containerSize: contentFrame.size + ) + if let inlineActionsView = self.inlineActions.view { + if inlineActionsView.superview == nil { + self.addSubview(inlineActionsView) + } + transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.maxX - 10.0 - inlineActionsSize.width, y: contentFrame.maxY - 20.0 - inlineActionsSize.height), size: inlineActionsSize)) + transition.setAlpha(view: inlineActionsView, alpha: inputPanelIsOverlay ? 0.0 : 1.0) + } + } } let gradientHeight: CGFloat = 74.0 transition.setFrame(layer: self.topContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: gradientHeight))) - let itemLayout = ItemLayout(size: contentFrame.size) + let itemLayout = ItemLayout(size: CGSize(width: contentFrame.width, height: availableSize.height - environment.statusBarHeight - 44.0 - bottomContentInsetWithoutInput)) self.itemLayout = itemLayout - let inputPanelSize = self.inputPanel.update( - transition: transition, - component: AnyComponent(MessageInputPanelComponent( - )), - environment: {}, - containerSize: CGSize(width: availableSize.width, height: 44.0) - ) + let inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize) if let inputPanelView = self.inputPanel.view { if inputPanelView.superview == nil { self.addSubview(inputPanelView) } - transition.setFrame(view: inputPanelView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentFrame.maxY), size: inputPanelSize)) + transition.setFrame(view: inputPanelView, frame: inputPanelFrame) } + let bottomGradientHeight = inputPanelSize.height + 32.0 + transition.setFrame(layer: self.bottomContentGradientLayer, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: availableSize.height - environment.inputHeight - bottomGradientHeight), size: CGSize(width: contentFrame.width, height: bottomGradientHeight))) + transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0) + + if let controller = environment.controller() { + let subLayout = ContainerViewLayout( + size: availableSize, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: availableSize.height - min(inputPanelFrame.minY, contentFrame.maxY), right: 0.0), + safeInsets: UIEdgeInsets(), + additionalInsets: UIEdgeInsets(), + statusBarHeight: nil, + inputHeight: nil, + inputHeightIsInteractivellyChanging: false, + inVoiceOver: false + ) + controller.presentationContext.containerLayoutUpdated(subLayout, transition: transition.containedViewLayoutTransition) + } + + transition.setFrame(layer: self.contentDimLayer, frame: contentFrame) + transition.setAlpha(layer: self.contentDimLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0) self.ignoreScrolling = true transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height))) @@ -490,20 +744,26 @@ private final class StoryContainerScreenComponent: Component { } public class StoryContainerScreen: ViewControllerComponentContainer { + private let context: AccountContext private var isDismissed: Bool = false public init( context: AccountContext, initialContent: StoryContentItemSlice ) { + self.context = context + super.init(context: context, component: StoryContainerScreenComponent( + context: context, initialContent: initialContent ), navigationBarAppearance: .none) self.statusBar.statusBarStyle = .White self.navigationPresentation = .flatModal self.blocksBackgroundWhenInOverlay = true - //self.automaticallyControlPresentationContextLayout = false + self.automaticallyControlPresentationContextLayout = false + + self.context.sharedContext.hasPreloadBlockingContent.set(.single(true)) } required public init(coder aDecoder: NSCoder) { @@ -511,6 +771,7 @@ public class StoryContainerScreen: ViewControllerComponentContainer { } deinit { + self.context.sharedContext.hasPreloadBlockingContent.set(.single(false)) } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -531,7 +792,11 @@ public class StoryContainerScreen: ViewControllerComponentContainer { if !self.isDismissed { self.isDismissed = true + self.statusBar.updateStatusBarStyle(.Ignore, animated: true) + if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View { + componentView.endEditing(true) + componentView.animateOut(completion: { [weak self] in completion?() self?.dismiss(animated: false) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift index c5756dc148..194a52d30b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContent.swift @@ -3,6 +3,7 @@ import UIKit import Display import ComponentFlow import SwiftSignalKit +import TelegramCore public final class StoryContentItem { public let id: AnyHashable @@ -10,19 +11,28 @@ public final class StoryContentItem { public let component: AnyComponent public let centerInfoComponent: AnyComponent? public let rightInfoComponent: AnyComponent? + public let targetMessageId: EngineMessage.Id? + public let preload: Signal? + public let hasLike: Bool public init( id: AnyHashable, position: Int, component: AnyComponent, centerInfoComponent: AnyComponent?, - rightInfoComponent: AnyComponent? + rightInfoComponent: AnyComponent?, + targetMessageId: EngineMessage.Id?, + preload: Signal?, + hasLike: Bool ) { self.id = id self.position = position self.component = component self.centerInfoComponent = centerInfoComponent self.rightInfoComponent = rightInfoComponent + self.targetMessageId = targetMessageId + self.preload = preload + self.hasLike = hasLike } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift index 598f9926cd..59f6050388 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryChatContent.swift @@ -30,6 +30,22 @@ public enum StoryChatContent { if let location = entry.location { totalCount = location.count } + + var hasLike = false + if let reactions = entry.message.effectiveReactions { + for reaction in reactions { + if !reaction.isSelected { + continue + } + if reaction.value == .builtin("❤") { + hasLike = true + } + } + } + + var preload: Signal? + preload = StoryMessageContentComponent.preload(context: context, message: EngineMessage(entry.message)) + items.append(StoryContentItem( id: AnyHashable(entry.message.id), position: entry.location?.index ?? 0, @@ -46,7 +62,10 @@ public enum StoryChatContent { context: context, peer: EnginePeer(author) )) - } + }, + targetMessageId: entry.message.id, + preload: preload, + hasLike: hasLike )) } return StoryContentItemSlice( diff --git a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift index 173773ca5a..6c7ba42183 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContentComponent/Sources/StoryMessageContentComponent.swift @@ -28,6 +28,56 @@ final class StoryMessageContentComponent: Component { } return true } + + static func preload(context: AccountContext, message: EngineMessage) -> Signal { + var messageMedia: EngineMedia? + for media in message.media { + switch media { + case let image as TelegramMediaImage: + messageMedia = .image(image) + case let file as TelegramMediaFile: + messageMedia = .file(file) + default: + break + } + } + + guard let messageMedia else { + return .complete() + } + + var fetchSignal: Signal? + switch messageMedia { + case let .image(image): + if let representation = image.representations.last { + fetchSignal = fetchedMediaResource( + mediaBox: context.account.postbox.mediaBox, + userLocation: .peer(message.id.peerId), + userContentType: .image, + reference: ImageMediaReference.message(message: MessageReference(message._asMessage()), media: image).resourceReference(representation.resource) + ) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + } + case let .file(file): + fetchSignal = fetchedMediaResource( + mediaBox: context.account.postbox.mediaBox, + userLocation: .peer(message.id.peerId), + userContentType: .image, + reference: FileMediaReference.message(message: MessageReference(message._asMessage()), media: file).resourceReference(file.resource) + ) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + default: + break + } + + return fetchSignal ?? .complete() + } final class View: UIView { private let imageNode: TransformImageNode @@ -85,6 +135,7 @@ final class StoryMessageContentComponent: Component { return } if value { + self.videoNode?.seek(0.0) self.videoNode?.play() } } @@ -133,8 +184,16 @@ final class StoryMessageContentComponent: Component { highQuality: true ) if let representation = image.representations.last { - fetchSignal = messageMediaImageInteractiveFetched(context: component.context, message: component.message._asMessage(), image: image, resource: representation.resource, userInitiated: true, storeToDownloadsPeerId: component.message.id.peerId) + fetchSignal = fetchedMediaResource( + mediaBox: component.context.account.postbox.mediaBox, + userLocation: .peer(component.message.id.peerId), + userContentType: .image, + reference: ImageMediaReference.message(message: MessageReference(component.message._asMessage()), media: image).resourceReference(representation.resource) + ) |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } } case let .file(file): signal = chatMessageVideo( @@ -143,8 +202,16 @@ final class StoryMessageContentComponent: Component { videoReference: .message(message: MessageReference(component.message._asMessage()), media: file), synchronousLoad: true ) - fetchSignal = messageMediaFileInteractiveFetched(context: component.context, message: component.message._asMessage(), file: file, userInitiated: true, storeToDownloadsPeerId: component.message.id.peerId) + fetchSignal = fetchedMediaResource( + mediaBox: component.context.account.postbox.mediaBox, + userLocation: .peer(component.message.id.peerId), + userContentType: .image, + reference: FileMediaReference.message(message: MessageReference(component.message._asMessage()), media: file).resourceReference(file.resource) + ) |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } default: break } diff --git a/submodules/TelegramUI/Components/TextFieldComponent/BUILD b/submodules/TelegramUI/Components/TextFieldComponent/BUILD new file mode 100644 index 0000000000..b63a8be5ba --- /dev/null +++ b/submodules/TelegramUI/Components/TextFieldComponent/BUILD @@ -0,0 +1,19 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "TextFieldComponent", + module_name = "TextFieldComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift new file mode 100644 index 0000000000..8887912ac0 --- /dev/null +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -0,0 +1,170 @@ +import Foundation +import UIKit +import Display +import ComponentFlow + +public final class TextFieldComponent: Component { + public final class ExternalState { + public fileprivate(set) var hasText: Bool = false + + public init() { + } + } + + public final class AnimationHint { + public enum Kind { + case textChanged + } + + public let kind: Kind + + fileprivate init(kind: Kind) { + self.kind = kind + } + } + + public let externalState: ExternalState + public let placeholder: String + + public init( + externalState: ExternalState, + placeholder: String + ) { + self.externalState = externalState + self.placeholder = placeholder + } + + public static func ==(lhs: TextFieldComponent, rhs: TextFieldComponent) -> Bool { + if lhs.externalState !== rhs.externalState { + return false + } + if lhs.placeholder != rhs.placeholder { + return false + } + return true + } + + public final class View: UIView, UITextViewDelegate, UIScrollViewDelegate { + private let placeholder = ComponentView() + + private let textContainer: NSTextContainer + private let textStorage: NSTextStorage + private let layoutManager: NSLayoutManager + private let textView: UITextView + + private var component: TextFieldComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.textContainer = NSTextContainer(size: CGSize()) + self.textContainer.widthTracksTextView = false + self.textContainer.heightTracksTextView = false + self.textContainer.lineBreakMode = .byWordWrapping + self.textContainer.lineFragmentPadding = 8.0 + + self.textStorage = NSTextStorage() + + self.layoutManager = NSLayoutManager() + self.layoutManager.allowsNonContiguousLayout = false + self.layoutManager.addTextContainer(self.textContainer) + self.textStorage.addLayoutManager(self.layoutManager) + + self.textView = UITextView(frame: CGRect(), textContainer: self.textContainer) + self.textView.translatesAutoresizingMaskIntoConstraints = false + self.textView.textContainerInset = UIEdgeInsets(top: 6.0, left: 8.0, bottom: 7.0, right: 8.0) + self.textView.backgroundColor = nil + self.textView.layer.isOpaque = false + self.textView.keyboardAppearance = .dark + self.textView.indicatorStyle = .white + self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0) + + super.init(frame: frame) + + self.clipsToBounds = true + + self.textView.delegate = self + self.addSubview(self.textView) + + self.textContainer.widthTracksTextView = false + self.textContainer.heightTracksTextView = false + + self.textView.typingAttributes = [ + NSAttributedString.Key.font: Font.regular(17.0), + NSAttributedString.Key.foregroundColor: UIColor.white + ] + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func textViewDidChange(_ textView: UITextView) { + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + //print("didScroll \(scrollView.bounds)") + } + + public func getText() -> String { + Keyboard.applyAutocorrection(textView: self.textView) + return self.textView.text ?? "" + } + + public func setText(string: String) { + self.textView.text = string + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + } + + func update(component: TextFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.state = state + + self.textContainer.size = CGSize(width: availableSize.width - self.textView.textContainerInset.left - self.textView.textContainerInset.right, height: 10000000.0) + self.layoutManager.ensureLayout(for: self.textContainer) + + let boundingRect = self.layoutManager.boundingRect(forGlyphRange: NSRange(location: 0, length: self.textStorage.length), in: self.textContainer) + let size = CGSize(width: availableSize.width, height: min(100.0, ceil(boundingRect.height) + self.textView.textContainerInset.top + self.textView.textContainerInset.bottom)) + + let refreshScrolling = self.textView.bounds.size != size + self.textView.frame = CGRect(origin: CGPoint(), size: size) + //transition.setFrame(view: self.textView, frame: ) + + if refreshScrolling { + self.textView.setContentOffset(CGPoint(x: 0.0, y: max(0.0, self.textView.contentSize.height - self.textView.bounds.height)), animated: false) + } + + let placeholderSize = self.placeholder.update( + transition: .immediate, + component: AnyComponent(Text(text: component.placeholder, font: Font.regular(17.0), color: UIColor(white: 1.0, alpha: 0.25))), + environment: {}, + containerSize: availableSize + ) + if let placeholderView = self.placeholder.view { + if placeholderView.superview == nil { + placeholderView.layer.anchorPoint = CGPoint() + placeholderView.isUserInteractionEnabled = false + self.insertSubview(placeholderView, belowSubview: self.textView) + } + + let placeholderFrame = CGRect(origin: CGPoint(x: self.textView.textContainerInset.left + 5.0, y: self.textView.textContainerInset.top), size: placeholderSize) + placeholderView.bounds = CGRect(origin: CGPoint(), size: placeholderFrame.size) + transition.setPosition(view: placeholderView, position: placeholderFrame.origin) + + placeholderView.isHidden = self.textStorage.length != 0 + } + + component.externalState.hasText = self.textStorage.length != 0 + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/Contents.json new file mode 100644 index 0000000000..02d32abd47 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "GalleryShare.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/GalleryShare.svg b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/GalleryShare.svg new file mode 100644 index 0000000000..6912911acf --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineLike.imageset/GalleryShare.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/Contents.json new file mode 100644 index 0000000000..1eb5fb05ef --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "GalleryForward.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/GalleryForward.svg b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/GalleryForward.svg new file mode 100644 index 0000000000..d2a5c9f43a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Gallery/InlineShare.imageset/GalleryForward.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3ea5d9910b..aebb08f135 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -130,6 +130,9 @@ public final class SharedAccountContextImpl: SharedAccountContext { } private var hasOngoingCallDisposable: Disposable? + public let enablePreloads = Promise() + public let hasPreloadBlockingContent = Promise(false) + private var accountUserInterfaceInUseContexts: [AccountRecordId: AccountUserInterfaceInUseContext] = [:] var switchingData: (settingsController: (SettingsController & ViewController)?, chatListController: ChatListController?, chatListBadge: String?) = (nil, nil, nil) @@ -869,6 +872,20 @@ public final class SharedAccountContextImpl: SharedAccountContext { let _ = immediateHasOngoingCallValue.swap(value) }) + self.enablePreloads.set(combineLatest( + self.hasOngoingCall.get(), + self.hasPreloadBlockingContent.get() + ) + |> map { hasOngoingCall, hasPreloadBlockingContent -> Bool in + if hasOngoingCall { + return false + } + if hasPreloadBlockingContent { + return false + } + return true + }) + let _ = managedCleanupAccounts(networkArguments: networkArguments, accountManager: self.accountManager, rootPath: rootPath, auxiliaryMethods: makeTelegramAccountAuxiliaryMethods(appDelegate: appDelegate), encryptionParameters: encryptionParameters).start() self.updateNotificationTokensRegistration()