mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
b006c36c59
commit
8120dde68c
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +187,8 @@ protocol BrowserContent: UIView {
|
||||
func addToRecentlyVisited()
|
||||
|
||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, fullInsets: UIEdgeInsets, transition: ComponentTransition)
|
||||
|
||||
func makeContentSnapshotView() -> UIView?
|
||||
}
|
||||
|
||||
struct ContentScrollingUpdate {
|
||||
|
@ -468,4 +468,8 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate
|
||||
|
||||
func addToRecentlyVisited() {
|
||||
}
|
||||
|
||||
func makeContentSnapshotView() -> UIView? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -1418,4 +1418,8 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
||||
let _ = addRecentlyVisitedLink(engine: self.context.engine, webPage: webPage).startStandalone()
|
||||
}
|
||||
}
|
||||
|
||||
func makeContentSnapshotView() -> UIView? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -460,4 +460,8 @@ final class BrowserPdfContent: UIView, BrowserContent, WKNavigationDelegate, WKU
|
||||
|
||||
func addToRecentlyVisited() {
|
||||
}
|
||||
|
||||
func makeContentSnapshotView() -> UIView? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user