Files
Swiftgram/submodules/WebSearchUI/Sources/LegacyWebSearchGallery.swift
Kylmakalle fd86110711 Version 11.3.1
Fixes

fix localeWithStrings globally (#30)

Fix badge on zoomed devices. closes #9

Hide channel bottom panel closes #27

Another attempt to fix badge on some Zoomed devices

Force System Share sheet tg://sg/debug

fixes for device badge

New Crowdin updates (#34)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

Fix input panel hidden on selection (#31)

* added if check for selectionState != nil

* same order of subnodes

Revert "Fix input panel hidden on selection (#31)"

This reverts commit e8a8bb1496.

Fix input panel for channels Closes #37

Quickly share links with system's share menu

force tabbar when editing

increase height for correct animation

New translations sglocalizable.strings (Ukrainian) (#38)

Hide Post Story button

Fix 10.15.1

Fix archive option for long-tap

Enable in-app Safari

Disable some unsupported purchases

disableDeleteChatSwipeOption + refactor restart alert

Hide bot in suggestions list

Fix merge v11.0

Fix exceptions for safari webview controller

New Crowdin updates (#47)

* New translations sglocalizable.strings (Romanian)

* New translations sglocalizable.strings (French)

* New translations sglocalizable.strings (Spanish)

* New translations sglocalizable.strings (Afrikaans)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Catalan)

* New translations sglocalizable.strings (Czech)

* New translations sglocalizable.strings (Danish)

* New translations sglocalizable.strings (German)

* New translations sglocalizable.strings (Greek)

* New translations sglocalizable.strings (Finnish)

* New translations sglocalizable.strings (Hebrew)

* New translations sglocalizable.strings (Hungarian)

* New translations sglocalizable.strings (Italian)

* New translations sglocalizable.strings (Japanese)

* New translations sglocalizable.strings (Korean)

* New translations sglocalizable.strings (Dutch)

* New translations sglocalizable.strings (Norwegian)

* New translations sglocalizable.strings (Polish)

* New translations sglocalizable.strings (Portuguese)

* New translations sglocalizable.strings (Serbian (Cyrillic))

* New translations sglocalizable.strings (Swedish)

* New translations sglocalizable.strings (Turkish)

* New translations sglocalizable.strings (Vietnamese)

* New translations sglocalizable.strings (Indonesian)

* New translations sglocalizable.strings (Hindi)

* New translations sglocalizable.strings (Uzbek)

New Crowdin updates (#49)

* New translations sglocalizable.strings (Arabic)

* New translations sglocalizable.strings (Arabic)

New translations sglocalizable.strings (Russian) (#51)

Call confirmation

WIP Settings search

Settings Search

Localize placeholder

Update AccountUtils.swift

mark mutual contact

Align back context action to left

New Crowdin updates (#54)

* New translations sglocalizable.strings (Chinese Simplified)

* New translations sglocalizable.strings (Chinese Traditional)

* New translations sglocalizable.strings (Ukrainian)

Independent Playground app for simulator

New translations sglocalizable.strings (Ukrainian) (#55)

Playground UIKit base and controllers

Inject SwiftUI view with overflow to AsyncDisplayKit

Launch Playgound project on simulator

Create .swiftformat

Move Playground to example

Update .swiftformat

Init SwiftUIViewController

wip

New translations sglocalizable.strings (Chinese Traditional) (#57)

Xcode 16 fixes

Fix

New translations sglocalizable.strings (Italian) (#59)

New translations sglocalizable.strings (Chinese Simplified) (#63)

Force disable CallKit integration due to missing NSE Entitlement

Fix merge

Fix whole chat translator

Sweetpad config

Bump version

11.3.1 fixes

Mutual contact placement fix

Disable Video PIP swipe

Update versions.json

Fix PIP crash
2024-12-20 09:38:13 +02:00

477 lines
21 KiB
Swift

import Foundation
import UIKit
import LegacyComponents
import SwiftSignalKit
import TelegramCore
import SSignalKit
import UIKit
import Display
import TelegramPresentationData
import AccountContext
import PhotoResources
import LegacyUI
import LegacyMediaPickerUI
import Postbox
class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem {
var isVideo: Bool {
return false
}
var uniqueIdentifier: String! {
return self.result.id
}
let result: ChatContextResult
private(set) var thumbnailResource: TelegramMediaResource?
private(set) var imageResource: TelegramMediaResource?
let dimensions: CGSize
let thumbnailImage: Signal<UIImage, NoError>
let originalImage: Signal<UIImage, NoError>
let progress: Signal<Float, NoError>
init(result: ChatContextResult) {
self.result = result
self.dimensions = CGSize()
self.thumbnailImage = .complete()
self.originalImage = .complete()
self.progress = .complete()
}
init(result: ChatContextResult, thumbnailResource: TelegramMediaResource?, imageResource: TelegramMediaResource?, dimensions: CGSize, thumbnailImage: Signal<UIImage, NoError>, originalImage: Signal<UIImage, NoError>, progress: Signal<Float, NoError>) {
self.result = result
self.thumbnailResource = thumbnailResource
self.imageResource = imageResource
self.dimensions = dimensions
self.thumbnailImage = thumbnailImage
self.originalImage = originalImage
self.progress = progress
}
var originalSize: CGSize {
return self.dimensions
}
func thumbnailImageSignal() -> SSignal! {
return SSignal(generator: { subscriber -> SDisposable? in
let disposable = self.thumbnailImage.start(next: { image in
subscriber.putNext(image)
subscriber.putCompletion()
})
return SBlockDisposable(block: {
disposable.dispose()
})
})
}
func screenImageAndProgressSignal() -> SSignal {
return SSignal { subscriber in
let imageDisposable = self.originalImage.start(next: { image in
if !image.degraded() {
subscriber.putNext(1.0)
}
subscriber.putNext(image)
if !image.degraded() {
subscriber.putCompletion()
}
})
let progressDisposable = (self.progress
|> deliverOnMainQueue).start(next: { next in
subscriber.putNext(next)
})
return SBlockDisposable {
imageDisposable.dispose()
progressDisposable.dispose()
}
}
}
func screenImageSignal(_ position: TimeInterval) -> SSignal! {
return self.originalImageSignal(position)
}
func originalImageSignal(_ position: TimeInterval) -> SSignal! {
return SSignal(generator: { subscriber -> SDisposable? in
let disposable = self.originalImage.start(next: { image in
subscriber.putNext(image)
if !image.degraded() {
subscriber.putCompletion()
}
})
return SBlockDisposable(block: {
disposable.dispose()
})
})
}
}
private class LegacyWebSearchGalleryItem: TGModernGalleryImageItem, TGModernGalleryEditableItem, TGModernGallerySelectableItem {
var selectionContext: TGMediaSelectionContext!
var editingContext: TGMediaEditingContext!
var stickersContext: TGPhotoPaintStickersContext!
let item: LegacyWebSearchItem
init(item: LegacyWebSearchItem) {
self.item = item
super.init()
}
func editableMediaItem() -> TGMediaEditableItem! {
return self.item
}
func selectableMediaItem() -> TGMediaSelectableItem! {
return self.item
}
func toolbarTabs() -> TGPhotoEditorTab {
return [.cropTab, .paintTab, .toolsTab]
}
func uniqueId() -> String! {
return self.item.uniqueIdentifier
}
override func viewClass() -> AnyClass! {
return LegacyWebSearchGalleryItemView.self
}
override func isEqual(_ object: Any?) -> Bool {
if let item = object as? LegacyWebSearchGalleryItem {
return item.item.result.id == self.item.result.id
}
return false
}
}
private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGModernGalleryEditableItemView {
private let readyForTransition = SVariable()
@objc func setHiddenAsBeingEdited(_ hidden: Bool) {
self.imageView.isHidden = hidden
}
@objc func singleTap() {
if let item = item as? LegacyWebSearchGalleryItem, let selectionContext = item.selectionContext {
selectionContext.toggleItemSelection(item.selectableMediaItem(), success: nil)
}
}
override func readyForTransitionIn() -> SSignal! {
return self.readyForTransition.signal().take(1)
}
override func setItem(_ item: TGModernGalleryItem!, synchronously: Bool) {
if let item = item as? LegacyWebSearchGalleryItem {
self._setItem(item)
self.imageSize = TGFitSize(item.editableMediaItem().originalSize!, CGSize(width: 1600, height: 1600))
let signal = item.editingContext.imageSignal(for: item.editableMediaItem())?.map(toSignal: { result -> SSignal in
if let image = result as? UIImage {
return SSignal.single(image)
} else if result == nil, let mediaItem = item.editableMediaItem() as? LegacyWebSearchItem {
return mediaItem.screenImageAndProgressSignal()
} else {
return SSignal.complete()
}
})
self.imageView.setSignal(signal?.deliver(on: SQueue.main()).afterNext({ [weak self] next in
if let strongSelf = self, let image = next as? UIImage {
strongSelf.imageSize = image.size
strongSelf.reset()
strongSelf.readyForTransition.set(SSignal.single(true))
}
}))
self.reset()
} else {
self.imageView.setSignal(nil)
super.setItem(item, synchronously: synchronously)
}
}
override func contentView() -> UIView! {
return self.imageView
}
override func transitionContentView() -> UIView! {
return self.contentView()
}
override func transitionViewContentRect() -> CGRect {
let contentView = self.transitionContentView()!
return contentView.convert(contentView.bounds, to: self.transitionView())
}
}
func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyWebSearchItem? {
var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource?
var imageDimensions = CGSize()
var immediateThumbnailData: Data?
let thumbnailSignal: Signal<UIImage, NoError>
let originalSignal: Signal<UIImage, NoError>
switch result {
case let .externalReference(externalReference):
if let content = externalReference.content {
imageResource = content.resource
}
if let thumbnail = externalReference.thumbnail {
thumbnailResource = thumbnail.resource
thumbnailDimensions = thumbnail.dimensions?.cgSize
}
if let dimensions = externalReference.content?.dimensions {
imageDimensions = dimensions.cgSize
}
case let .internalReference(internalReference):
immediateThumbnailData = internalReference.image?.immediateThumbnailData
if let image = internalReference.image {
if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 1000, height: 800)) {
imageDimensions = imageRepresentation.dimensions.cgSize
imageResource = imageRepresentation.resource
}
if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 200, height: 100)) {
thumbnailDimensions = thumbnailRepresentation.dimensions.cgSize
thumbnailResource = thumbnailRepresentation.resource
}
}
}
if let imageResource = imageResource {
let progressSignal = account.postbox.mediaBox.resourceStatus(imageResource)
|> map { status -> Float in
switch status {
case .Local:
return 1.0
case .Remote, .Paused:
return 0.027
case let .Fetching(_, progress):
return max(progress, 0.1)
}
}
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
}
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(imageDimensions), resource: imageResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
let tmpImage = TelegramMediaImage(imageId: EngineMedia.Id(namespace: 0, id: 0), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), autoFetchFullSize: false)
|> mapToSignal { value -> Signal<UIImage, NoError> in
let thumbnailData = value._0
if let data = thumbnailData, let image = UIImage(data: data) {
return .single(image)
} else {
return .complete()
}
}
originalSignal = chatMessagePhotoDatas(postbox: account.postbox, userLocation: .other, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true)
|> mapToSignal { value -> Signal<UIImage, NoError> in
let thumbnailData = value._0
let fullSizeData = value._1
let fullSizeComplete = value._3
if fullSizeComplete, let data = fullSizeData, let image = UIImage(data: data) {
return .single(image)
} else if let data = thumbnailData, let image = UIImage(data: data) {
image.setDegraded(true)
return .single(image)
} else {
return .complete()
}
}
return LegacyWebSearchItem(result: result, thumbnailResource: thumbnailResource, imageResource: imageResource, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal, progress: progressSignal)
} else {
return nil
}
}
private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
var focusItem: TGModernGalleryItem?
var galleryItems: [TGModernGalleryItem] = []
for result in results {
if let item = legacyWebSearchItem(account: account, result: result) {
let galleryItem = LegacyWebSearchGalleryItem(item: item)
galleryItem.selectionContext = selectionContext
galleryItem.editingContext = editingContext
if result.id == current.id {
focusItem = galleryItem
}
galleryItems.append(galleryItem)
}
}
return (galleryItems, focusItem)
}
func presentLegacyWebSearchGallery(context: AccountContext, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, presentationData: PresentationData, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: (ViewController, Any?) -> Void) {
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
let recipientName: String?
if let threadTitle {
recipientName = threadTitle
} else {
if peer?.id == context.account.peerId {
recipientName = presentationData.strings.DialogList_SavedMessages
} else {
recipientName = peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
}
}
let paintStickersContext = LegacyPaintStickersContext(context: context)
paintStickersContext.captionPanelView = {
return getCaptionPanelView()
}
let controller = TGModernGalleryController(context: legacyController.context)!
controller.asyncTransitionIn = true
legacyController.bind(controller: controller)
let (items, focusItem) = galleryItems(account: context.account, results: results, current: current, selectionContext: selectionContext, editingContext: editingContext)
let currentAppConfiguration = context.currentAppConfiguration.with { $0 }
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: false, allowCaptionEntities: true, hasTimer: false, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: false, hasCamera: false, recipientName: recipientName, isScheduledMessages: false, canShowTelescope: currentAppConfiguration.sgWebSettings.global.canShowTelescope, canSendTelescope: currentAppConfiguration.sgWebSettings.user.canSendTelescope)!
model.stickersContext = paintStickersContext
controller.model = model
model.controller = controller
model.useGalleryImageAsEditableItemImage = true
model.storeOriginalImageForItem = { item, image in
editingContext.setOriginalImage(image, for: item, synchronous: false)
}
model.willFinishEditingItem = { item, adjustments, representation, hasChanges in
if hasChanges {
editingContext.setAdjustments(adjustments, for: item)
}
editingContext.setTemporaryRep(representation, for: item)
if let selectionContext = selectionContext, adjustments != nil, let item = item as? TGMediaSelectableItem {
selectionContext.setItem(item, selected: true)
}
}
model.didFinishEditingItem = { item, adjustments, result, thumbnail in
editingContext.setImage(result, thumbnailImage: thumbnail, for: item, synchronous: true)
}
model.saveItemCaption = { item, caption in
editingContext.setCaption(caption, for: item)
if let selectionContext = selectionContext, let caption = caption, caption.length > 0, let item = item as? TGMediaSelectableItem {
selectionContext.setItem(item, selected: true)
}
}
if let selectionContext = selectionContext {
model.interfaceView.updateSelectionInterface(selectionContext.count(), counterVisible: selectionContext.count() > 0, animated: false)
}
model.interfaceView.donePressed = { item in
if let item = item as? LegacyWebSearchGalleryItem {
controller.dismissWhenReady(animated: true)
completed(item.item.result)
}
}
controller.transitionHost = {
return transitionHostView()
}
var transitionedIn = false
controller.itemFocused = { item in
if let item = item as? LegacyWebSearchGalleryItem, transitionedIn {
updateHiddenMedia(item.item.result.id)
}
}
controller.beginTransitionIn = { item, _ in
if let item = item as? LegacyWebSearchGalleryItem {
return transitionView(item.item.result)
} else {
return nil
}
}
controller.startedTransitionIn = {
transitionedIn = true
updateHiddenMedia(current.id)
}
controller.beginTransitionOut = { item, _ in
if let item = item as? LegacyWebSearchGalleryItem {
return transitionView(item.item.result)
} else {
return nil
}
}
controller.completedTransitionOut = { [weak legacyController] in
updateHiddenMedia(nil)
legacyController?.dismiss()
}
present(legacyController, nil)
}
public func legacyEnqueueWebSearchMessages(_ selectionState: TGMediaSelectionContext, _ editingState: TGMediaEditingContext, enqueueChatContextResult: (ChatContextResult) -> Void, enqueueMediaMessages: ([Any]) -> Void)
{
var results: [ChatContextResult] = []
for item in selectionState.selectedItems() {
if let item = item as? LegacyWebSearchItem {
results.append(item.result)
}
}
if !results.isEmpty {
var signals: [Any] = []
for result in results {
let editableItem = LegacyWebSearchItem(result: result)
if let adjustments = editingState.adjustments(for: editableItem) {
let animated = adjustments.paintingData?.hasAnimation ?? false
if let imageSignal = editingState.imageSignal(for: editableItem) {
let signal = imageSignal.map { image -> Any in
if let image = image as? UIImage {
var dict: [AnyHashable: Any] = [
"type": "editedPhoto",
"image": image
]
if animated {
dict["isAnimation"] = true
if let photoEditorValues = adjustments as? PGPhotoEditorValues {
dict["adjustments"] = TGVideoEditAdjustments(photoEditorValues: photoEditorValues, preset: TGMediaVideoConversionPresetAnimation)
}
let filePath = NSTemporaryDirectory().appending("/gifvideo_\(arc4random()).jpg")
let data = image.jpegData(compressionQuality: 0.8)
if let data = data {
let _ = try? data.write(to: URL(fileURLWithPath: filePath), options: [])
}
dict["url"] = NSURL(fileURLWithPath: filePath)
if adjustments.cropApplied(forAvatar: false) || adjustments.hasPainting() || adjustments.toolsApplied() {
var paintingImage: UIImage? = adjustments.paintingData?.stillImage
if paintingImage == nil {
paintingImage = adjustments.paintingData?.image
}
let thumbnailImage = TGPhotoEditorVideoExtCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, TGScaleToFill(image.size, CGSize(width: 512.0, height: 512.0)), adjustments.originalSize, true, true, true, false)
if let thumbnailImage = thumbnailImage {
dict["previewImage"] = thumbnailImage
}
}
}
return legacyAssetPickerItemGenerator()(dict, nil, nil, nil) as Any
} else {
return SSignal.complete()
}
}
signals.append(signal as Any)
}
} else {
enqueueChatContextResult(result)
}
}
if !signals.isEmpty {
enqueueMediaMessages(signals)
}
}
}