Various improvements

This commit is contained in:
Ilya Laktyushin 2024-07-25 00:54:36 +02:00
parent b006c36c59
commit 8120dde68c
21 changed files with 400 additions and 168 deletions

View File

@ -981,7 +981,7 @@ public protocol SharedAccountContext: AnyObject {
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: String?, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController
func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: (url: String, name: String?)?, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController
func makeBotPreviewEditorScreen(context: AccountContext, source: Any?, target: Stories.PendingTarget, transitionArguments: (UIView, CGRect, UIImage?)?, transitionOut: @escaping () -> BotPreviewEditorTransitionOut?, externalState: MediaEditorTransitionOutExternalState, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void, cancelled: @escaping () -> Void) -> ViewController

View File

@ -1365,4 +1365,13 @@ public class AttachmentController: ViewController, MinimizableController {
})
return disposableSet
}
public func makeContentSnapshotView() -> UIView? {
let snapshotView = self.view.snapshotView(afterScreenUpdates: false)
if let contentSnapshotView = self.mainController.makeContentSnapshotView() {
contentSnapshotView.frame = contentSnapshotView.frame.offsetBy(dx: 0.0, dy: 64.0 + 56.0)
snapshotView?.addSubview(contentSnapshotView)
}
return snapshotView
}
}

View File

@ -187,6 +187,8 @@ protocol BrowserContent: UIView {
func addToRecentlyVisited()
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition)
func makeContentSnapshotView() -> UIView?
}
struct ContentScrollingUpdate {

View File

@ -468,4 +468,8 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
func addToRecentlyVisited() {
}
func makeContentSnapshotView() -> UIView? {
return nil
}
}

View File

@ -1418,4 +1418,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
let _ = addRecentlyVisitedLink(engine: self.context.engine, webPage: webPage).startStandalone()
}
}
func makeContentSnapshotView() -> UIView? {
return nil
}
}

View File

@ -460,4 +460,8 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU
func addToRecentlyVisited() {
}
func makeContentSnapshotView() -> UIView? {
return nil
}
}

View File

@ -1170,16 +1170,18 @@ public class BrowserScreen: ViewController, MinimizableController {
var openPreviousOnClose = false
private var validLayout: ContainerViewLayout?
public static let supportedDocumentMimeTypes: [String] = [
"text/plain",
"text/rtf",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
// "text/plain",
// "text/rtf",
// "application/pdf",
// "application/msword",
// "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
// "application/vnd.ms-excel",
// "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
// "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
// "application/vnd.openxmlformats-officedocument.presentationml.presentation"
]
public init(context: AccountContext, subject: Subject) {
@ -1212,6 +1214,8 @@ public class BrowserScreen: ViewController, MinimizableController {
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
super.containerLayoutUpdated(layout, transition: transition)
self.node.containerLayoutUpdated(layout: layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.height, transition: ComponentTransition(transition))
@ -1221,7 +1225,15 @@ public class BrowserScreen: ViewController, MinimizableController {
self.node.minimize(topEdgeOffset: topEdgeOffset, damping: 180.0, initialVelocity: initialVelocity)
}
public var isMinimized = false
public var isMinimized = false {
didSet {
if let webContent = self.node.content.last as? BrowserWebContent {
if !self.isMinimized {
webContent.webView.setNeedsLayout()
}
}
}
}
public var isMinimizable = true
public var minimizedIcon: UIImage? {
@ -1244,6 +1256,20 @@ public class BrowserScreen: ViewController, MinimizableController {
}
return nil
}
public func makeContentSnapshotView() -> UIView? {
if let contentSnapshot = self.node.content.last?.makeContentSnapshotView(), let layout = self.validLayout {
if let wrapperView = self.view.snapshotView(afterScreenUpdates: false) {
contentSnapshot.frame = contentSnapshot.frame.offsetBy(dx: 0.0, dy: self.navigationLayout(layout: layout).navigationFrame.height)
wrapperView.addSubview(contentSnapshot)
return wrapperView
} else {
return contentSnapshot
}
} else {
return self.view.snapshotView(afterScreenUpdates: false)
}
}
}
private final class BrowserReferenceContentSource: ContextReferenceContentSource {

View File

@ -119,7 +119,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
private let context: AccountContext
private var presentationData: PresentationData
private let webView: WKWebView
let webView: WKWebView
private let errorView: ComponentHostView<Empty>
private var currentError: Error?
@ -486,7 +486,9 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
if keyPath == "title" {
self.updateState { $0.withUpdatedTitle(self.webView.title ?? "") }
} else if keyPath == "URL" {
self.updateState { $0.withUpdatedUrl(self.webView.url?.absoluteString ?? "") }
if let url = self.webView.url {
self.updateState { $0.withUpdatedUrl(url.absoluteString) }
}
self.didSetupSearch = false
} else if keyPath == "estimatedProgress" {
self.updateState { $0.withUpdatedEstimatedProgress(self.webView.estimatedProgress) }
@ -571,7 +573,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
if (error as NSError).code != -999 {
if [-1003, -1100].contains((error as NSError).code) {
self.currentError = error
} else {
self.currentError = nil
@ -580,18 +582,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate)
}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
if (error as NSError).code != -999 {
self.currentError = error
} else {
self.currentError = nil
}
if let (size, insets, fullInsets) = self.validLayout {
self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, transition: .immediate)
}
}
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
if let url = navigationAction.request.url?.absoluteString {
@ -752,7 +743,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
var nodeList = document.getElementsByTagName('link');
for (var i = 0; i < nodeList.length; i++)
{
if((nodeList[i].getAttribute('rel') == 'icon')||(nodeList[i].getAttribute('rel') == 'shortcut icon'))
if((nodeList[i].getAttribute('rel') == 'icon')||(nodeList[i].getAttribute('rel') == 'shortcut icon')||(nodeList[i].getAttribute('rel').startsWith('apple-touch-icon')))
{
const node = nodeList[i];
favicons.push({
@ -872,6 +863,18 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
func addToRecentlyVisited() {
self.addToRecentsWhenReady = true
}
func makeContentSnapshotView() -> UIView? {
let configuration = WKSnapshotConfiguration()
configuration.rect = CGRect(origin: .zero, size: self.webView.frame.size)
let imageView = UIImageView()
imageView.frame = CGRect(origin: .zero, size: self.webView.frame.size)
self.webView.takeSnapshot(with: configuration, completionHandler: { image, _ in
imageView.image = image
})
return imageView
}
}
private final class ErrorComponent: CombinedComponent {

View File

@ -16,6 +16,7 @@ public extension MediaEditorScreen {
peer: EnginePeer,
storyItem: EngineStoryItem,
videoPlaybackPosition: Double?,
cover: Bool,
repost: Bool,
transitionIn: MediaEditorScreen.TransitionIn,
transitionOut: MediaEditorScreen.TransitionOut?,
@ -88,6 +89,7 @@ public extension MediaEditorScreen {
mode: .storyEditor,
subject: subject,
isEditing: !repost,
isEditingCover: cover,
forwardSource: repost ? (peer, storyItem) : nil,
initialCaption: initialCaption,
initialPrivacy: initialPrivacy,

View File

@ -18,11 +18,11 @@ private final class MediaCoverScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let mediaEditor: MediaEditor
let mediaEditor: Signal<MediaEditor?, NoError>
init(
context: AccountContext,
mediaEditor: MediaEditor
mediaEditor: Signal<MediaEditor?, NoError>
) {
self.context = context
self.mediaEditor = mediaEditor
@ -57,16 +57,26 @@ private final class MediaCoverScreenComponent: Component {
var playerStateDisposable: Disposable?
var playerState: MediaEditorPlayerState?
init(mediaEditor: MediaEditor) {
private(set) var mediaEditor: MediaEditor?
init(mediaEditor: Signal<MediaEditor?, NoError>) {
super.init()
self.playerStateDisposable = (mediaEditor.playerState(framesCount: 16)
|> deliverOnMainQueue).start(next: { [weak self] playerState in
if let self {
if self.playerState != playerState {
self.playerState = playerState
self.updated()
}
let _ = (mediaEditor
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] mediaEditor in
if let self, let mediaEditor {
self.mediaEditor = mediaEditor
self.playerStateDisposable = (mediaEditor.playerState(framesCount: 16)
|> deliverOnMainQueue).start(next: { [weak self] playerState in
if let self {
if self.playerState != playerState {
self.playerState = playerState
self.updated()
}
}
})
}
})
}
@ -258,7 +268,7 @@ private final class MediaCoverScreenComponent: Component {
isEnabled: true,
displaysProgress: false,
action: { [weak controller, weak self] in
if let playerState = self?.state?.playerState, let mediaEditor = self?.component?.mediaEditor, let image = mediaEditor.resultImage {
if let playerState = self?.state?.playerState, let mediaEditor = self?.state?.mediaEditor, let image = mediaEditor.resultImage {
mediaEditor.setCoverImageTimestamp(playerState.position)
controller?.completed(playerState.position, image)
}
@ -318,7 +328,6 @@ private final class MediaCoverScreenComponent: Component {
if let playerState = state.playerState {
let visibleTracks = playerState.tracks.filter { $0.id == 0 }.map { MediaScrubberComponent.Track($0) }
let mediaEditor = component.mediaEditor
let scrubberInset: CGFloat = buttonSideInset
let scrubberSize = self.scrubber.update(
transition: transition,
@ -333,13 +342,13 @@ private final class MediaCoverScreenComponent: Component {
isPlaying: playerState.isPlaying,
tracks: visibleTracks,
portalView: controller.portalView,
positionUpdated: { [weak mediaEditor] position, apply in
if let mediaEditor {
positionUpdated: { [weak state] position, apply in
if let mediaEditor = state?.mediaEditor {
mediaEditor.seek(position, andPlay: false)
}
},
coverPositionUpdated: { [weak mediaEditor] position, tap, commit in
if let mediaEditor {
coverPositionUpdated: { [weak state] position, tap, commit in
if let mediaEditor = state?.mediaEditor {
if tap {
mediaEditor.setOnNextDisplay {
commit()
@ -436,7 +445,7 @@ final class MediaCoverScreen: ViewController {
}
func animateOutToEditor(completion: @escaping () -> Void) {
if let mediaEditor = self.controller?.mediaEditor {
self.controller?.withMediaEditor { mediaEditor in
mediaEditor.play()
}
if let view = self.componentHost.view as? MediaCoverScreenComponent.View {
@ -534,18 +543,26 @@ final class MediaCoverScreen: ViewController {
}
fileprivate let context: AccountContext
fileprivate let mediaEditor: MediaEditor
fileprivate let mediaEditor: Signal<MediaEditor?, NoError>
fileprivate let previewView: MediaEditorPreviewView
fileprivate let portalView: PortalView
func withMediaEditor(_ f: @escaping (MediaEditor) -> Void) {
let _ = (self.mediaEditor
|> take(1)
|> deliverOnMainQueue).start(next: { mediaEditor in
if let mediaEditor {
f(mediaEditor)
}
})
}
var completed: (Double, UIImage) -> Void = { _, _ in }
var dismissed: () -> Void = {}
private var initialValues: MediaEditorValues
init(
context: AccountContext,
mediaEditor: MediaEditor,
mediaEditor: Signal<MediaEditor?, NoError>,
previewView: MediaEditorPreviewView,
portalView: PortalView
) {
@ -553,7 +570,6 @@ final class MediaCoverScreen: ViewController {
self.mediaEditor = mediaEditor
self.previewView = previewView
self.portalView = portalView
self.initialValues = mediaEditor.values.makeCopy()
super.init(navigationBarPresentationData: nil)
self.navigationPresentation = .flatModal
@ -562,10 +578,12 @@ final class MediaCoverScreen: ViewController {
self.statusBar.statusBarStyle = .White
if let coverImageTimestamp = mediaEditor.values.coverImageTimestamp {
mediaEditor.seek(coverImageTimestamp, andPlay: false)
} else {
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: false)
self.withMediaEditor { mediaEditor in
if let coverImageTimestamp = mediaEditor.values.coverImageTimestamp {
mediaEditor.seek(coverImageTimestamp, andPlay: false)
} else {
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: false)
}
}
}

View File

@ -732,7 +732,7 @@ final class MediaEditorScreenComponent: Component {
transition = transition.withUserData(nextTransitionUserData)
}
let isEditingStory = controller.isEditingStory
let isEditingStory = controller.isEditingStory || controller.isEditingStoryCover
if self.component == nil {
if let initialCaption = controller.initialCaption {
self.inputPanelExternalState.initialText = initialCaption
@ -2807,6 +2807,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.availableReactions = reactions
}
})
if controller.isEditingStoryCover {
self.openCoverSelection(immediate: true)
}
}
deinit {
@ -4563,43 +4567,30 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
controller.push(linkController)
}
func addInitialLink(_ link: String) {
func addInitialLink(_ link: (url: String, name: String?)) {
guard self.context.isPremium else {
Queue.mainQueue().after(0.3) {
let context = self.context
var replaceImpl: ((ViewController) -> Void)?
let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .stories, forceDark: true, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesLinks, forceDark: true, dismissed: {})
replaceImpl?(controller)
}, dismissed: {})
replaceImpl = { [weak self, weak demoController] c in
demoController?.dismiss(animated: true, completion: {
guard let self else {
return
}
self.controller?.push(c)
})
}
self.controller?.push(demoController)
}
return
}
let text = link
var attributes: [MessageAttribute] = []
attributes.append(TextEntitiesMessageAttribute(entities: [.init(range: 0 ..< (text as NSString).length, type: .Url)]))
// attributes.append(WebpagePreviewMessageAttribute(leadingPreview: !self.positionBelowText, forceLargeMedia: self.largeMedia, isManuallyAdded: false, isSafe: true))
let effectiveMedia: TelegramMediaWebpage? = nil
// if let webpage = self.webpage, case .Loaded = webpage.content {
// effectiveMedia = webpage
// }
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: text, attributes: attributes, media: effectiveMedia.flatMap { [$0] } ?? [], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
let renderer = DrawingMessageRenderer(context: self.context, messages: [message], parentView: self.view, isLink: true)
renderer.render(completion: { [weak self] renderResult in
guard let self else {
return
}
let result = CreateLinkScreen.Result(
url: link,
name: "",
webpage: effectiveMedia,
positionBelowText: false,
largeMedia: nil,
image: effectiveMedia != nil ? renderResult.dayImage : nil,
nightImage: effectiveMedia != nil ? renderResult.nightImage : nil
)
let entity = DrawingLinkEntity(url: result.url, name: result.name, webpage: result.webpage, positionBelowText: result.positionBelowText, largeMedia: result.largeMedia, style: .white)
self.interaction?.insertEntity(entity, position: CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.width / 3.0 * 4.0), select: false)
})
let entity = DrawingLinkEntity(url: link.url, name: link.name ?? "", webpage: nil, positionBelowText: false, largeMedia: nil, style: .white)
self.interaction?.insertEntity(entity, position: CGPoint(x: storyDimensions.width / 2.0, y: storyDimensions.width / 3.0 * 4.0), select: false)
}
func addReaction() {
@ -4643,7 +4634,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return
}
let weatherPromise = Promise<StickerPickerScreen.Weather>()
weatherPromise.set(getWeather(context: self.context))
weatherPromise.set(getWeather(context: self.context, load: true))
self.weatherPromise = weatherPromise
let _ = (weatherPromise.get()
@ -4696,7 +4687,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
guard controller.checkCaptionLimit() else {
return
}
if controller.isEditingStory {
if controller.isEditingStory || controller.isEditingStoryCover {
controller.requestStoryCompletion(animated: true)
} else {
if controller.checkIfCompletionIsAllowed() {
@ -4713,11 +4704,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
func openCoverSelection() {
guard let mediaEditor = self.mediaEditor else {
return
}
func openCoverSelection(immediate: Bool) {
guard let portalView = PortalView(matchPosition: false) else {
return
}
@ -4732,9 +4719,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
let coverController = MediaCoverScreen(
context: self.context,
mediaEditor: mediaEditor,
mediaEditor: self.mediaEditorPromise.get(),
previewView: self.previewView,
portalView: portalView
portalView: portalView
)
coverController.dismissed = { [weak self] in
if let self {
@ -4749,7 +4736,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
self.controller?.present(coverController, in: .current)
self.coverScreen = coverController
self.animateOutToTool(tool: .cover)
if immediate {
self.isDisplayingTool = .cover
self.requestUpdate(transition: .immediate)
} else {
self.animateOutToTool(tool: .cover)
}
}
func updateModalTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
@ -4933,6 +4926,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
let editorConfiguration = MediaEditorConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
var weatherSignal: Signal<StickerPickerScreen.Weather, NoError>
if hasInteractiveStickers {
let weatherPromise: Promise<StickerPickerScreen.Weather>
@ -4940,7 +4935,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
weatherPromise = current
} else {
weatherPromise = Promise()
weatherPromise.set(getWeather(context: self.context))
weatherPromise.set(getWeather(context: self.context, load: editorConfiguration.preloadWeather))
self.weatherPromise = weatherPromise
}
weatherSignal = weatherPromise.get()
@ -5028,6 +5023,14 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
switch result {
case let .loaded(weather):
self.addWeather(weather)
case .notPreloaded:
weatherPromise.set(getWeather(context: self.context, load: true))
let _ = (weatherPromise.get()
|> take(1)).start(next: { [weak self] result in
if let self, case let .loaded(weather) = result {
self.addWeather(weather)
}
})
case .notDetermined, .notAllowed:
self.presentLocationAccessAlert()
default:
@ -5223,7 +5226,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.controller?.present(controller, in: .window(.root))
self.animateOutToTool(tool: .tools)
case .cover:
self.openCoverSelection()
self.openCoverSelection(immediate: false)
}
}
},
@ -5571,6 +5574,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let mode: Mode
let subject: Signal<Subject?, NoError>
let isEditingStory: Bool
let isEditingStoryCover: Bool
fileprivate let customTarget: EnginePeer.Id?
let forwardSource: (EnginePeer, EngineStoryItem)?
@ -5578,7 +5582,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
fileprivate let initialPrivacy: EngineStoryPrivacy?
fileprivate let initialMediaAreas: [MediaArea]?
fileprivate let initialVideoPosition: Double?
fileprivate let initialLink: String?
fileprivate let initialLink: (url: String, name: String?)?
fileprivate let transitionIn: TransitionIn?
fileprivate let transitionOut: (Bool, Bool?) -> TransitionOut?
@ -5608,12 +5612,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
subject: Signal<Subject?, NoError>,
customTarget: EnginePeer.Id? = nil,
isEditing: Bool = false,
isEditingCover: Bool = false,
forwardSource: (EnginePeer, EngineStoryItem)? = nil,
initialCaption: NSAttributedString? = nil,
initialPrivacy: EngineStoryPrivacy? = nil,
initialMediaAreas: [MediaArea]? = nil,
initialVideoPosition: Double? = nil,
initialLink: String? = nil,
initialLink: (url: String, name: String?)? = nil,
transitionIn: TransitionIn?,
transitionOut: @escaping (Bool, Bool?) -> TransitionOut?,
completion: @escaping (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
@ -5623,6 +5628,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.subject = subject
self.customTarget = customTarget
self.isEditingStory = isEditing
self.isEditingStoryCover = isEditingCover
self.forwardSource = forwardSource
self.initialCaption = initialCaption
self.initialPrivacy = initialPrivacy
@ -5799,7 +5805,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
fileprivate var isEmbeddedEditor: Bool {
return self.isEditingStory || self.forwardSource != nil
return self.isEditingStory || self.isEditingStoryCover || self.forwardSource != nil
}
private var currentCoverImage: UIImage?
@ -5917,7 +5923,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
editCoverImpl = { [weak self, weak controller] in
if let self {
self.node.openCoverSelection()
self.node.openCoverSelection(immediate: false)
}
if let controller {
controller.dismiss()
@ -6478,7 +6484,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
return
}
if !self.isEditingStory {
if !(self.isEditingStory || self.isEditingStoryCover) {
let privacy = self.state.privacy
let _ = updateMediaEditorStoredStateInteractively(engine: self.context.engine, { current in
if let current {
@ -8283,3 +8289,27 @@ private func stickerFile(resource: TelegramMediaResource, thumbnailResource: Tel
return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: isVideo ? "video/webm" : "image/webp", size: size, attributes: fileAttributes)
}
private struct MediaEditorConfiguration {
static var defaultValue: MediaEditorConfiguration {
return MediaEditorConfiguration(preloadWeather: true)
}
let preloadWeather: Bool
fileprivate init(preloadWeather: Bool) {
self.preloadWeather = preloadWeather
}
static func with(appConfiguration: AppConfiguration) -> MediaEditorConfiguration {
if let data = appConfiguration.data {
var preloadWeather = false
if let value = data["story_weather_preload"] as? Bool {
preloadWeather = value
}
return MediaEditorConfiguration(preloadWeather: preloadWeather)
} else {
return .defaultValue
}
}
}

View File

@ -47,7 +47,7 @@ private func getWeatherData(context: AccountContext, location: CLLocationCoordin
}
}
func getWeather(context: AccountContext) -> Signal<StickerPickerScreen.Weather, NoError> {
func getWeather(context: AccountContext, load: Bool) -> Signal<StickerPickerScreen.Weather, NoError> {
guard let locationManager = context.sharedContext.locationManager else {
return .single(.none)
}
@ -60,33 +60,37 @@ func getWeather(context: AccountContext) -> Signal<StickerPickerScreen.Weather,
case .denied, .restricted, .unreachable:
return .single(.notAllowed)
case .allowed:
return .single(.fetching)
|> then(
currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0)
|> mapToSignal { location in
if let location {
return getWeatherData(context: context, location: location)
|> mapToSignal { weather in
if let weather {
let effectiveEmoji = emojiFor(for: weather.emoji.strippedEmoji, date: Date(), location: location)
if let match = context.animatedEmojiStickersValue[effectiveEmoji]?.first {
return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather(
emoji: effectiveEmoji,
emojiFile: match.file,
temperature: weather.temperature
)))
if load {
return .single(.fetching)
|> then(
currentLocationManagerCoordinate(manager: locationManager, timeout: 5.0)
|> mapToSignal { location in
if let location {
return getWeatherData(context: context, location: location)
|> mapToSignal { weather in
if let weather {
let effectiveEmoji = emojiFor(for: weather.emoji.strippedEmoji, date: Date(), location: location)
if let match = context.animatedEmojiStickersValue[effectiveEmoji]?.first {
return .single(.loaded(StickerPickerScreen.Weather.LoadedWeather(
emoji: effectiveEmoji,
emojiFile: match.file,
temperature: weather.temperature
)))
} else {
return .single(.none)
}
} else {
return .single(.none)
}
} else {
return .single(.none)
}
} else {
return .single(.none)
}
} else {
return .single(.none)
}
}
)
)
} else {
return .single(.notPreloaded)
}
}
}
}

View File

@ -616,8 +616,6 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
return
}
// self.scrollView.contentOffset = CGPoint(x: 0.0, y: max(0.0, self.scrollView.contentSize.height - self.scrollView.bounds.height))
if self.items.count == 1, let item = self.items.first {
if let navigationController = self.navigationController {
item.beforeMaximize(navigationController, { [weak self] in
@ -625,6 +623,12 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
})
}
} else {
let contentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.bounds.height)
self.scrollView.contentOffset = CGPoint(x: 0.0, y: contentOffset)
for itemNode in self.itemNodes.values {
itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: contentOffset)
}
self.isExpanded = true
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
}
@ -646,7 +650,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
if scrollView.contentOffset.y < -64.0, let lastItemId = self.items.last?.id, let itemNode = self.itemNodes[lastItemId] {
let velocity = scrollView.panGestureRecognizer.velocity(in: self.view).y
let distance = layout.size.height - self.collapsedHeight(layout: layout) - itemNode.frame.minY
let initialVelocity = distance != 0.0 ? abs(velocity / distance) : 0.0
let initialVelocity = min(8.0, distance != 0.0 ? abs(velocity / distance) : 0.0)
self.isExpanded = false
scrollView.isScrollEnabled = false
@ -747,6 +751,10 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
var index = 0
let contentHeight = frameForIndex(index: self.items.count - 1, size: layout.size, insets: itemInsets, itemCount: self.items.count, boundingSize: layout.size).midY - 70.0
var effectiveScrollBounds = self.scrollView.bounds
effectiveScrollBounds.origin.y = max(0.0, min(contentHeight - self.scrollView.bounds.height, effectiveScrollBounds.origin.y))
for item in self.items {
if let currentTransition = self.currentTransition {
if currentTransition.matches(item: item) {
@ -859,14 +867,14 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
if self.isExpanded {
let currentItemFrame = frameForIndex(index: index, size: layout.size, insets: itemInsets, itemCount: self.items.count, boundingSize: layout.size)
let currentItemTransform = final3dTransform(for: currentItemFrame.minY, size: currentItemFrame.size, contentHeight: contentHeight, itemCount: self.items.count, additionalAngle: self.highlightedItemId == item.id ? 0.04 : nil, scrollBounds: self.scrollView.bounds, insets: itemInsets)
let currentItemTransform = final3dTransform(for: currentItemFrame.minY, size: currentItemFrame.size, contentHeight: contentHeight, itemCount: self.items.count, additionalAngle: self.highlightedItemId == item.id ? 0.04 : nil, scrollBounds: effectiveScrollBounds, insets: itemInsets)
var effectiveItemFrame = currentItemFrame
var effectiveItemTransform = currentItemTransform
let effectiveItemTransform = currentItemTransform
if let dismissingItemId = self.dismissingItemId, let deletingIndex = self.items.firstIndex(where: { $0.id == dismissingItemId }), let offset = self.dismissingItemOffset {
var targetItemFrame: CGRect?
var targetItemTransform: CATransform3D?
// var targetItemFrame: CGRect?
// var targetItemTransform: CATransform3D?
if deletingIndex == index {
let effectiveOffset: CGFloat
if offset <= 0.0 {
@ -875,25 +883,26 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
effectiveOffset = scrollingRubberBandingOffset(offset: offset, bandingStart: 0.0, range: 20.0)
}
effectiveItemFrame = effectiveItemFrame.offsetBy(dx: effectiveOffset, dy: 0.0)
} else if index < deletingIndex {
let frame = frameForIndex(index: index, size: layout.size, insets: itemInsets, itemCount: self.items.count - 1, boundingSize: layout.size)
let spacing = interitemSpacing(itemCount: self.items.count - 1, boundingSize: layout.size, insets: itemInsets)
targetItemFrame = frame
targetItemTransform = final3dTransform(for: frame.minY, size: layout.size, contentHeight: contentHeight - layout.size.height - spacing, itemCount: self.items.count - 1, scrollBounds: self.scrollView.bounds, insets: itemInsets)
} else {
let frame = frameForIndex(index: index - 1, size: layout.size, insets: itemInsets, itemCount: self.items.count - 1, boundingSize: layout.size)
let spacing = interitemSpacing(itemCount: self.items.count - 1, boundingSize: layout.size, insets: itemInsets)
targetItemFrame = frame
targetItemTransform = final3dTransform(for: frame.minY, size: layout.size, contentHeight: contentHeight - layout.size.height - spacing, itemCount: self.items.count - 1, scrollBounds: self.scrollView.bounds, insets: itemInsets)
}
}
// else if index < deletingIndex {
// let frame = frameForIndex(index: index, size: layout.size, insets: itemInsets, itemCount: self.items.count - 1, boundingSize: layout.size)
// let spacing = interitemSpacing(itemCount: self.items.count - 1, boundingSize: layout.size, insets: itemInsets)
//
// targetItemFrame = frame
// targetItemTransform = final3dTransform(for: frame.minY, size: layout.size, contentHeight: contentHeight - layout.size.height - spacing, itemCount: self.items.count - 1, scrollBounds: self.scrollView.bounds, insets: itemInsets)
// } else {
// let frame = frameForIndex(index: index - 1, size: layout.size, insets: itemInsets, itemCount: self.items.count - 1, boundingSize: layout.size)
// let spacing = interitemSpacing(itemCount: self.items.count - 1, boundingSize: layout.size, insets: itemInsets)
//
// targetItemFrame = frame
// targetItemTransform = final3dTransform(for: frame.minY, size: layout.size, contentHeight: contentHeight - layout.size.height - spacing, itemCount: self.items.count - 1, scrollBounds: self.scrollView.bounds, insets: itemInsets)
// }
if let targetItemFrame, let targetItemTransform {
let fraction = max(0.0, min(1.0, -1.0 * offset / (layout.size.width * 1.5)))
effectiveItemFrame = effectiveItemFrame.interpolate(with: targetItemFrame, fraction: fraction)
effectiveItemTransform = effectiveItemTransform.interpolate(with: targetItemTransform, fraction: fraction)
}
// if let targetItemFrame, let targetItemTransform {
// let fraction = max(0.0, min(1.0, -1.0 * offset / (layout.size.width * 1.5)))
// effectiveItemFrame = effectiveItemFrame.interpolate(with: targetItemFrame, fraction: fraction)
// effectiveItemTransform = effectiveItemTransform.interpolate(with: targetItemTransform, fraction: fraction)
// }
}
itemFrame = effectiveItemFrame
itemTransform = effectiveItemTransform
@ -904,6 +913,8 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
var hideTransform = false
if let currentTransition = self.currentTransition {
if case let .maximize(itemId) = currentTransition {
itemOffset += self.scrollView.bounds.origin.y
itemOffset += layout.size.height * 0.25
if let lastItemNode = self.scrollView.subviews.last?.asyncdisplaykit_node as? ItemNode, lastItemNode.item.id == itemId {
hideTransform = true
@ -954,7 +965,16 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
let contentSize = CGSize(width: layout.size.width, height: contentHeight)
if self.scrollView.contentSize != contentSize {
var contentSizeDelta: CGFloat?
if contentSize.height < self.scrollView.contentSize.height, transition.isAnimated {
let currentContentOffset = self.scrollView.contentOffset.y
let updatedContentOffset = max(0.0, contentSize.height - self.scrollView.bounds.height)
contentSizeDelta = currentContentOffset - updatedContentOffset
}
self.scrollView.contentSize = contentSize
if let contentSizeDelta {
transition.animateBounds(layer: self.scrollView.layer, from: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.contentOffset.y + contentSizeDelta), size: self.scrollView.bounds.size))
}
}
if self.scrollView.frame != bounds {
self.scrollView.frame = bounds

View File

@ -77,8 +77,12 @@ final class MinimizedHeaderNode: ASDisplayNode {
if titles.count == 1, let title = titles.first {
self.title = title
} else if let title = titles.last {
var trimmedTitle = title
if trimmedTitle.count > 20 {
trimmedTitle = "\(trimmedTitle.prefix(20).trimmingCharacters(in: .whitespacesAndNewlines))\u{2026}"
}
let othersString = self.strings.WebApp_MinimizedTitle_Others(Int32(titles.count - 1))
self.title = self.strings.WebApp_MinimizedTitleFormat(title, othersString).string
self.title = self.strings.WebApp_MinimizedTitleFormat(trimmedTitle, othersString).string
} else {
self.title = nil
}

View File

@ -2541,6 +2541,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, ASScr
peer: peer,
storyItem: item,
videoPlaybackPosition: nil,
cover: false,
repost: false,
transitionIn: .gallery(MediaEditorScreen.TransitionIn.GalleryTransitionIn(sourceView: self.itemGrid.view, sourceRect: foundItemLayer?.frame ?? .zero, sourceImage: sourceImage)),
transitionOut: MediaEditorScreen.TransitionOut(destinationView: self.itemGrid.view, destinationRect: foundItemLayer?.frame ?? .zero, destinationCornerRadius: 0.0),

View File

@ -2067,6 +2067,7 @@ public class StickerPickerScreen: ViewController {
case none
case notDetermined
case notAllowed
case notPreloaded
case fetching
case loaded(StickerPickerScreen.Weather.LoadedWeather)
}
@ -2727,7 +2728,7 @@ final class StoryStickersContentView: UIView, EmojiCustomContentView {
let weatherButtonContent: AnyComponent<Empty>
switch self.weather {
case .notAllowed, .notDetermined:
case .notAllowed, .notDetermined, .notPreloaded:
weatherButtonContent = AnyComponent(
InteractiveStickerButtonContent(
context: self.context,

View File

@ -5340,7 +5340,7 @@ public final class StoryItemSetContainerComponent: Component {
}
private let updateDisposable = MetaDisposable()
func openStoryEditing(repost: Bool = false) {
func openStoryEditing(repost: Bool = false, cover: Bool = false) {
guard let component = self.component else {
return
}
@ -5351,7 +5351,17 @@ public final class StoryItemSetContainerComponent: Component {
var videoPlaybackPosition: Double?
if let visibleItem = self.visibleItems[component.slice.item.id], let view = visibleItem.view.view as? StoryItemContentComponent.View {
videoPlaybackPosition = view.videoPlaybackPosition
if cover {
if case let .file(file) = component.slice.item.storyItem.media {
for attribute in file.attributes {
if case let .Video(_, _, _, _, coverTime) = attribute {
videoPlaybackPosition = coverTime
}
}
}
} else {
videoPlaybackPosition = view.videoPlaybackPosition
}
}
guard let controller = MediaEditorScreen.makeEditStoryController(
@ -5359,6 +5369,7 @@ public final class StoryItemSetContainerComponent: Component {
peer: component.slice.effectivePeer,
storyItem: component.slice.item.storyItem,
videoPlaybackPosition: videoPlaybackPosition,
cover: cover,
repost: repost,
transitionIn: .noAnimation,
transitionOut: nil,
@ -6107,6 +6118,19 @@ public final class StoryItemSetContainerComponent: Component {
self.openStoryEditing()
})))
if case .file = component.slice.item.storyItem.media {
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_EditCover, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
self.openStoryEditing(cover: true)
})))
}
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: component.slice.item.storyItem.isPinned ? component.strings.Story_Context_RemoveFromProfile : component.strings.Story_Context_SaveToProfile, icon: { theme in
@ -6291,6 +6315,19 @@ public final class StoryItemSetContainerComponent: Component {
}
self.openStoryEditing()
})))
if case .file = component.slice.item.storyItem.media {
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_EditCover, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
guard let self else {
return
}
self.openStoryEditing(cover: true)
})))
}
}
if !items.isEmpty {

View File

@ -128,6 +128,7 @@ import MessageUI
import PhoneNumberFormat
import OwnershipTransferController
import OldChannelsController
import BrowserUI
public enum ChatControllerPeekActions {
case standard
@ -398,11 +399,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var historyNavigationStack = ChatHistoryNavigationStack()
public let canReadHistory = ValuePromise<Bool>(true, ignoreRepeated: true)
public let hasBrowserOrAppInFront = Promise<Bool>(false)
var reminderActivity: NSUserActivity?
var isReminderActivityEnabled: Bool = false
var canReadHistoryValue = false
var canReadHistoryValue = false {
didSet {
self.computedCanReadHistoryPromise.set(self.canReadHistoryValue)
}
}
var canReadHistoryDisposable: Disposable?
var computedCanReadHistoryPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
var themeEmoticonAndDarkAppearancePreviewPromise = Promise<(String?, Bool?)>((nil, nil))
var didSetPresentationData = false
@ -6502,8 +6509,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
self.canReadHistoryDisposable = (combineLatest(context.sharedContext.applicationBindings.applicationInForeground, self.canReadHistory.get()) |> map { a, b in
return a && b
self.canReadHistoryDisposable = (combineLatest(
context.sharedContext.applicationBindings.applicationInForeground,
self.canReadHistory.get(),
self.hasBrowserOrAppInFront.get()
) |> map { inForeground, globallyEnabled, hasBrowserOrWebAppInFront in
return inForeground && globallyEnabled && !hasBrowserOrWebAppInFront
} |> deliverOnMainQueue).startStrict(next: { [weak self] value in
if let strongSelf = self, strongSelf.canReadHistoryValue != value {
strongSelf.canReadHistoryValue = value
@ -7119,6 +7132,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
)
}
}
let hasBrowserOrWebAppInFront: Signal<Bool, NoError> = .single([])
|> then(
self.effectiveNavigationController?.viewControllersSignal ?? .single([])
)
|> map { controllers in
if controllers.last is BrowserScreen || controllers.last is AttachmentController {
return true
} else {
return false
}
}
self.hasBrowserOrAppInFront.set(hasBrowserOrWebAppInFront)
}
var returnInputViewFocus = false
@ -7129,9 +7155,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.didAppear = true
self.chatDisplayNode.historyNode.experimentalSnapScrollToItem = false
self.chatDisplayNode.historyNode.canReadHistory.set(combineLatest(self.context.sharedContext.applicationBindings.applicationInForeground, self.canReadHistory.get()) |> map { a, b in
return a && b
})
self.chatDisplayNode.historyNode.canReadHistory.set(self.computedCanReadHistoryPromise.get())
self.chatDisplayNode.loadInputPanels(theme: self.presentationInterfaceState.theme, strings: self.presentationInterfaceState.strings, fontSize: self.presentationInterfaceState.fontSize)

View File

@ -2654,7 +2654,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return editorController
}
public func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: String?, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController {
public func makeStoryMediaEditorScreen(context: AccountContext, source: Any?, text: String?, link: (url: String, name: String?)?, completion: @escaping (MediaEditorScreenResult, @escaping (@escaping () -> Void) -> Void) -> Void) -> ViewController {
let subject: Signal<MediaEditorScreen.Subject?, NoError>
if let image = source as? UIImage {
subject = .single(.image(image, PixelDimensions(image.size), nil, .bottomRight))

View File

@ -674,7 +674,17 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
return nil
}
}
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: values.coverImageTimestamp)
var coverTime: Double?
if let coverImageTimestamp = values.coverImageTimestamp {
if let trimRange = values.videoTrimRange {
coverTime = coverImageTimestamp - trimRange.lowerBound
} else {
coverTime = coverImageTimestamp
}
}
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: coverTime)
}
default:
break

View File

@ -1104,7 +1104,18 @@ public final class WebAppController: ViewController, AttachmentContainable {
case "web_app_share_to_story":
if let json = json, let mediaUrl = json["media_url"] as? String {
let text = json["text"] as? String
let link = json["widget_link"] as? String
let link = json["widget_link"] as? [String: Any]
var linkUrl: String?
var linkName: String?
if let link {
if let url = link["url"] as? String {
linkUrl = url
if let name = link["name"] as? String {
linkName = name
}
}
}
enum FetchResult {
case result(Data)
@ -1112,7 +1123,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
let controller = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: {
}))
self.controller?.present(controller, in: .window(.root))
@ -1154,7 +1164,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
isPeerArchived: false,
transitionOut: nil
)
let controller = self.context.sharedContext.makeStoryMediaEditorScreen(context: self.context, source: source, text: text, link: link, completion: { result, commit in
let controller = self.context.sharedContext.makeStoryMediaEditorScreen(context: self.context, source: source, text: text, link: linkUrl.flatMap { ($0, linkName) }, completion: { result, commit in
// let targetPeerId: EnginePeer.Id
let target: Stories.PendingTarget
// if let sendAsPeerId = result.options.sendAsPeerId {
@ -2114,8 +2124,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
public func isContainerPanningUpdated(_ isPanning: Bool) {
self.controllerNode.isContainerPanningUpdated(isPanning)
}
private var validLayout: ContainerViewLayout?
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
@ -2171,6 +2183,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.controllerNode.webView?.hideScrollIndicators()
} else {
self.requestLayout(transition: .immediate)
self.controllerNode.webView?.setNeedsLayout()
}
}
}
@ -2205,6 +2218,22 @@ public final class WebAppController: ViewController, AttachmentContainable {
public var minimizedIcon: UIImage? {
return self.controllerNode.icon
}
public func makeContentSnapshotView() -> UIView? {
guard let webView = self.controllerNode.webView, let _ = self.validLayout else {
return nil
}
let configuration = WKSnapshotConfiguration()
configuration.rect = CGRect(origin: .zero, size: webView.frame.size)
let imageView = UIImageView()
imageView.frame = CGRect(origin: .zero, size: webView.frame.size)
webView.takeSnapshot(with: configuration, completionHandler: { image, _ in
imageView.image = image
})
return imageView
}
}
final class WebAppPickerContext: AttachmentMediaPickerContext {