mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various Fixes
This commit is contained in:
parent
f8890b9734
commit
80426cdf8f
@ -361,7 +361,7 @@ public final class DeviceAccess {
|
||||
switch status {
|
||||
case .restricted, .denied, .notDetermined:
|
||||
value = false
|
||||
case .authorized:
|
||||
case .authorized, .limited:
|
||||
value = true
|
||||
@unknown default:
|
||||
fatalError()
|
||||
@ -416,8 +416,6 @@ public final class DeviceAccess {
|
||||
locationManager?.requestAlwaysAuthorization(completion: { status in
|
||||
completion(status == .authorizedAlways)
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
@unknown default:
|
||||
fatalError()
|
||||
|
@ -2,6 +2,12 @@ import Foundation
|
||||
import UIKit
|
||||
import Postbox
|
||||
|
||||
enum StickerVerificationStatus {
|
||||
case loading
|
||||
case verified
|
||||
case declined
|
||||
}
|
||||
|
||||
public class ImportStickerPack {
|
||||
public class Sticker: Equatable {
|
||||
public enum Content {
|
||||
@ -32,6 +38,14 @@ public class ImportStickerPack {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
var isAnimated: Bool {
|
||||
if case .animation = self.content {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let software: String
|
||||
|
@ -29,8 +29,8 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
|
||||
|
||||
private let stickerPack: ImportStickerPack
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
|
||||
private var verificationDisposable: Disposable?
|
||||
|
||||
public init(context: AccountContext, stickerPack: ImportStickerPack, parentNavigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.parentNavigationController = parentNavigationController
|
||||
@ -57,6 +57,7 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
self.verificationDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
@ -77,8 +78,65 @@ public final class ImportStickerPackController: ViewController, StandalonePresen
|
||||
self.controllerNode.navigationController = self.parentNavigationController
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
self.controllerNode.updateStickerPack(self.stickerPack)
|
||||
self.controllerNode.updateStickerPack(self.stickerPack, verifiedStickers: Set(), declinedStickers: Set(), uploadedStickerResources: [:])
|
||||
|
||||
if self.stickerPack.isAnimated {
|
||||
let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var signals: [Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError>] = []
|
||||
for sticker in strongSelf.stickerPack.stickers {
|
||||
if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] {
|
||||
signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource, alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), isAnimated: true)
|
||||
|> map { result -> (UUID, StickerVerificationStatus, MediaResource?) in
|
||||
switch result {
|
||||
case .progress:
|
||||
return (sticker.uuid, .loading, nil)
|
||||
case let .complete(resource, mimeType):
|
||||
if mimeType == "application/x-tgsticker" {
|
||||
return (sticker.uuid, .verified, resource)
|
||||
} else {
|
||||
return (sticker.uuid, .declined, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<(UUID, StickerVerificationStatus, MediaResource?), NoError> in
|
||||
return .single((sticker.uuid, .declined, nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
strongSelf.verificationDisposable = (combineLatest(signals)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] results in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var verifiedStickers = Set<UUID>()
|
||||
var declinedStickers = Set<UUID>()
|
||||
var uploadedStickerResources: [UUID: MediaResource] = [:]
|
||||
for (uuid, result, resource) in results {
|
||||
switch result {
|
||||
case .verified:
|
||||
if let resource = resource {
|
||||
verifiedStickers.insert(uuid)
|
||||
uploadedStickerResources[uuid] = resource
|
||||
} else {
|
||||
declinedStickers.insert(uuid)
|
||||
}
|
||||
case .declined:
|
||||
declinedStickers.insert(uuid)
|
||||
case .loading:
|
||||
break
|
||||
}
|
||||
}
|
||||
strongSelf.controllerNode.updateStickerPack(strongSelf.stickerPack, verifiedStickers: verifiedStickers, declinedStickers: declinedStickers, uploadedStickerResources: uploadedStickerResources)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.ready.set(self.controllerNode.ready.get())
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,13 @@ import RadialStatusNode
|
||||
import UndoUI
|
||||
import StickerPackPreviewUI
|
||||
|
||||
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
private struct StickerPackPreviewGridEntry: Comparable, Equatable, Identifiable {
|
||||
let index: Int
|
||||
let stickerItem: ImportStickerPack.Sticker
|
||||
let isVerified: Bool
|
||||
|
||||
var stableId: Int {
|
||||
return self.index
|
||||
// return self.stickerItem.file.fileId
|
||||
}
|
||||
|
||||
static func <(lhs: StickerPackPreviewGridEntry, rhs: StickerPackPreviewGridEntry) -> Bool {
|
||||
@ -31,8 +31,10 @@ private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
}
|
||||
|
||||
func item(account: Account, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) -> StickerPackPreviewGridItem {
|
||||
return StickerPackPreviewGridItem(account: account, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isEmpty: false)
|
||||
return StickerPackPreviewGridItem(account: account, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isVerified: self.isVerified)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private struct StickerPackPreviewGridTransaction {
|
||||
@ -53,6 +55,9 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
private var stickerPack: ImportStickerPack?
|
||||
var stickerResources: [UUID: MediaResource] = [:]
|
||||
private var uploadedStickerResources: [UUID: MediaResource] = [:]
|
||||
private var stickerPackReady = true
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
@ -130,6 +135,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
self.contentTitleNode = ImmediateTextNode()
|
||||
self.contentTitleNode.displaysAsynchronously = false
|
||||
self.contentTitleNode.maximumNumberOfLines = 1
|
||||
self.contentTitleNode.alpha = 0.0
|
||||
|
||||
self.contentSeparatorNode = ASDisplayNode()
|
||||
self.contentSeparatorNode.isLayerBacked = true
|
||||
@ -205,6 +211,8 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
self.wrappingScrollNode.addSubnode(self.progressText)
|
||||
self.wrappingScrollNode.addSubnode(self.infoText)
|
||||
|
||||
self.installActionButtonNode.setTitle(self.presentationData.strings.ImportStickerPack_CreateStickerSet, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
|
||||
self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
|
||||
}
|
||||
@ -345,16 +353,14 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
let contentFrame = contentContainerFrame.insetBy(dx: 12.0, dy: 0.0)
|
||||
|
||||
var transaction: StickerPackPreviewGridTransaction?
|
||||
|
||||
var animateIn = false
|
||||
|
||||
|
||||
var forceTitleUpdate = false
|
||||
if self.progress != nil && !self.hadProgress {
|
||||
self.hadProgress = true
|
||||
forceTitleUpdate = true
|
||||
}
|
||||
|
||||
if let _ = self.stickerPack, self.currentItems.isEmpty || self.currentItems.count != self.pendingItems.count || forceTitleUpdate {
|
||||
if let _ = self.stickerPack, self.currentItems.isEmpty || self.currentItems.count != self.pendingItems.count || self.pendingItems != self.currentItems || forceTitleUpdate {
|
||||
let previousItems = self.currentItems
|
||||
self.currentItems = self.pendingItems
|
||||
|
||||
@ -366,7 +372,6 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
title = self.presentationData.strings.ImportStickerPack_StickerCount(Int32(self.currentItems.count))
|
||||
}
|
||||
self.contentTitleNode.attributedText = stringWithAppliedEntities(title, entities: [], baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleFont, italicFont: titleFont, boldItalicFont: titleFont, fixedFont: titleFont, blockQuoteFont: titleFont)
|
||||
animateIn = true
|
||||
|
||||
if !forceTitleUpdate {
|
||||
transaction = StickerPackPreviewGridTransaction(previousList: previousItems, list: self.currentItems, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme)
|
||||
@ -405,13 +410,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
|
||||
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||
transition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
|
||||
|
||||
if animateIn {
|
||||
self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
|
||||
transition.updateAlpha(node: self.contentGridNode, alpha: self.progress == nil ? 1.0 : 0.0)
|
||||
|
||||
var effectiveProgress: CGFloat = 0.0
|
||||
@ -563,7 +562,12 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
if case let .image(data) = item.stickerItem.content, let image = UIImage(data: data) {
|
||||
dimensions = PixelDimensions(image.size)
|
||||
}
|
||||
if let resource = item.stickerItem.resource {
|
||||
if let resource = self.uploadedStickerResources[item.stickerItem.uuid] {
|
||||
if let localResource = item.stickerItem.resource {
|
||||
self.context.account.postbox.mediaBox.copyResourceData(from: localResource.id, to: resource.id)
|
||||
}
|
||||
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions))
|
||||
} else if let resource = item.stickerItem.resource {
|
||||
stickers.append(ImportSticker(resource: resource, emojis: item.stickerItem.emojis, dimensions: dimensions))
|
||||
}
|
||||
}
|
||||
@ -592,6 +596,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
if case let .complete(info, items) = status {
|
||||
if let (_, _, count) = strongSelf.progress {
|
||||
strongSelf.progress = (1.0, count, count)
|
||||
strongSelf.radialStatus.transitionToState(.progress(color: strongSelf.presentationData.theme.list.itemAccentColor, lineWidth: 6.0, value: 1.0, cancelEnabled: false, animateRotation: false), animated: !stickerPack.isAnimated, synchronous: true, completion: {})
|
||||
if let (layout, navigationBarHeight) = strongSelf.containerLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
@ -631,9 +636,11 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
if let firstStickerItem = firstStickerItem, let resource = firstStickerItem.resource as? TelegramMediaResource {
|
||||
firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: stickerPack.isAnimated ? "application/x-tgsticker": "image/png", size: nil, attributes: [.FileName(fileName: stickerPack.isAnimated ? "sticker.tgs" : "sticker.png"), .ImageSize(size: firstStickerItem.dimensions)]), indexKeys: [])
|
||||
}
|
||||
strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: firstItem ?? items.first, context: strongSelf.context), elevatedLayout: false, action: { _ in
|
||||
// (navigationController?.viewControllers.last as? ViewController)?.present(StickerPackScreen(context: context, mode: .settings, mainStickerPack: .id(id: info.id.id, accessHash: info.accessHash), stickerPacks: [], parentNavigationController: navigationController, actionPerformed: { _, _, _ in
|
||||
// }), in: .window(.root))
|
||||
strongSelf.presentInGlobalOverlay?(UndoOverlayController(presentationData: strongSelf.presentationData, content: .stickersModified(title: strongSelf.presentationData.strings.StickerPackActionInfo_AddedTitle, text: strongSelf.presentationData.strings.StickerPackActionInfo_AddedText(info.title).0, undo: false, info: info, topItem: firstItem ?? items.first, context: strongSelf.context), elevatedLayout: false, action: { action in
|
||||
if case .info = action {
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(StickerPackScreen(context: context, mode: .settings, mainStickerPack: .id(id: info.id.id, accessHash: info.accessHash), stickerPacks: [], parentNavigationController: navigationController, actionPerformed: { _, _, _ in
|
||||
}), in: .window(.root))
|
||||
}
|
||||
return true
|
||||
}), nil)
|
||||
strongSelf.cancel?()
|
||||
@ -716,23 +723,35 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
})
|
||||
}
|
||||
|
||||
func updateStickerPack(_ stickerPack: ImportStickerPack) {
|
||||
func updateStickerPack(_ stickerPack: ImportStickerPack, verifiedStickers: Set<UUID>, declinedStickers: Set<UUID>, uploadedStickerResources: [UUID: MediaResource]) {
|
||||
self.stickerPack = stickerPack
|
||||
self.uploadedStickerResources = uploadedStickerResources
|
||||
var updatedItems: [StickerPackPreviewGridEntry] = []
|
||||
for item in stickerPack.stickers {
|
||||
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
||||
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.data)
|
||||
item.resource = resource
|
||||
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
|
||||
if declinedStickers.contains(item.uuid) {
|
||||
continue
|
||||
}
|
||||
if let resource = self.stickerResources[item.uuid] {
|
||||
item.resource = resource
|
||||
} else {
|
||||
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
|
||||
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: item.data)
|
||||
item.resource = resource
|
||||
self.stickerResources[item.uuid] = resource
|
||||
}
|
||||
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item, isVerified: !item.isAnimated || verifiedStickers.contains(item.uuid)))
|
||||
}
|
||||
self.pendingItems = updatedItems
|
||||
|
||||
self.interaction.playAnimatedStickers = true
|
||||
if stickerPack.isAnimated {
|
||||
self.stickerPackReady = stickerPack.stickers.count == (verifiedStickers.count + declinedStickers.count) && updatedItems.count > 0
|
||||
}
|
||||
|
||||
self.interaction.playAnimatedStickers = true
|
||||
|
||||
if let _ = self.containerLayout {
|
||||
self.dequeueUpdateStickerPack()
|
||||
}
|
||||
self.installActionButtonNode.setTitle(self.presentationData.strings.ImportStickerPack_CreateStickerSet, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
}
|
||||
|
||||
func dequeueUpdateStickerPack() {
|
||||
@ -742,11 +761,17 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
} else {
|
||||
transition = .immediate
|
||||
|
||||
self.contentTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
self.contentTitleNode.alpha = 1.0
|
||||
self.contentTitleNode.layer.animateAlpha(from: self.contentTitleNode.alpha, to: 1.0, duration: 0.2)
|
||||
|
||||
self.contentGridNode.alpha = 1.0
|
||||
self.contentGridNode.layer.animateAlpha(from: self.contentGridNode.alpha, to: 1.0, duration: 0.2)
|
||||
|
||||
let buttonTransition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
buttonTransition.updateAlpha(node: self.installActionButtonNode, alpha: self.stickerPackReady ? 1.0 : 0.3)
|
||||
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
if !self.didSetReady {
|
||||
|
@ -27,21 +27,21 @@ final class StickerPackPreviewGridItem: GridItem {
|
||||
let stickerItem: ImportStickerPack.Sticker
|
||||
let interaction: StickerPackPreviewInteraction
|
||||
let theme: PresentationTheme
|
||||
let isEmpty: Bool
|
||||
let isVerified: Bool
|
||||
|
||||
let section: GridSection? = nil
|
||||
|
||||
init(account: Account, stickerItem: ImportStickerPack.Sticker, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isEmpty: Bool) {
|
||||
init(account: Account, stickerItem: ImportStickerPack.Sticker, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isVerified: Bool) {
|
||||
self.account = account
|
||||
self.stickerItem = stickerItem
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
self.isEmpty = isEmpty
|
||||
self.isVerified = isVerified
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
let node = StickerPackPreviewGridItemNode()
|
||||
node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isEmpty: self.isEmpty)
|
||||
node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isVerified: self.isVerified)
|
||||
return node
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ final class StickerPackPreviewGridItem: GridItem {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isEmpty: self.isEmpty)
|
||||
node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isVerified: self.isVerified)
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,9 +58,10 @@ private let textFont = Font.regular(20.0)
|
||||
|
||||
final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
private var currentState: (Account, ImportStickerPack.Sticker?, CGSize)?
|
||||
private var isEmpty: Bool?
|
||||
private var isVerified: Bool?
|
||||
private let imageNode: ASImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
@ -84,7 +85,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
|
||||
override init() {
|
||||
self.imageNode = ASImageNode()
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
@ -100,11 +101,11 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
}
|
||||
|
||||
func setup(account: Account, stickerItem: ImportStickerPack.Sticker?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isEmpty: Bool) {
|
||||
func setup(account: Account, stickerItem: ImportStickerPack.Sticker?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isVerified: Bool) {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 !== stickerItem || self.isEmpty != isEmpty {
|
||||
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 !== stickerItem || self.isVerified != isVerified {
|
||||
var dimensions = CGSize(width: 512.0, height: 512.0)
|
||||
if let stickerItem = stickerItem {
|
||||
switch stickerItem.content {
|
||||
@ -121,15 +122,35 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
}
|
||||
case .animation:
|
||||
self.imageNode.isHidden = true
|
||||
let animationNode = AnimatedStickerNode()
|
||||
self.animationNode = animationNode
|
||||
self.addSubnode(animationNode)
|
||||
|
||||
let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
||||
if let resource = stickerItem.resource {
|
||||
animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
||||
if isVerified {
|
||||
let animationNode = AnimatedStickerNode()
|
||||
self.animationNode = animationNode
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak placeholderNode] _ in
|
||||
placeholderNode?.removeFromSupernode()
|
||||
})
|
||||
self.insertSubnode(animationNode, belowSubnode: placeholderNode)
|
||||
} else {
|
||||
self.addSubnode(animationNode)
|
||||
}
|
||||
|
||||
let fittedDimensions = dimensions.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
||||
if let resource = stickerItem.resource {
|
||||
animationNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
||||
}
|
||||
animationNode.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
||||
} else {
|
||||
let placeholderNode = ShimmerEffectNode()
|
||||
self.placeholderNode = placeholderNode
|
||||
|
||||
self.addSubnode(placeholderNode)
|
||||
if let (absoluteRect, containerSize) = self.absoluteLocation {
|
||||
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||
}
|
||||
}
|
||||
animationNode.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
||||
}
|
||||
} else {
|
||||
dimensions = CGSize()
|
||||
@ -137,7 +158,7 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
self.currentState = (account, stickerItem, dimensions)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
self.isEmpty = isEmpty
|
||||
self.isVerified = isVerified
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
@ -154,6 +175,11 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
animationNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
animationNode.updateLayout(size: imageSize)
|
||||
}
|
||||
|
||||
if let placeholderNode = self.placeholderNode, let theme = self.theme {
|
||||
placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: [.roundedRect(rect: CGRect(origin: CGPoint(), size: imageSize), cornerRadius: 11.0)], horizontal: true, size: imageSize)
|
||||
placeholderNode.frame = self.imageNode.frame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,5 +211,13 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var absoluteLocation: (CGRect, CGSize)?
|
||||
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||
self.absoluteLocation = (absoluteRect, containerSize)
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,14 +26,10 @@ typedef enum {
|
||||
|
||||
@protocol LegacyComponentsAccessChecker <NSObject>
|
||||
|
||||
- (bool)checkAddressBookAuthorizationStatusWithAlertDismissComlpetion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkPhotoAuthorizationStatusForIntent:(TGPhotoAccessIntent)intent alertDismissCompletion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkMicrophoneAuthorizationStatusForIntent:(TGMicrophoneAccessIntent)intent alertDismissCompletion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkCameraAuthorizationStatusForIntent:(TGCameraAccessIntent)intent alertDismissCompletion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
- (bool)checkLocationAuthorizationStatusForIntent:(TGLocationAccessIntent)intent alertDismissComlpetion:(void (^)(void))alertDismissCompletion;
|
||||
- (bool)checkCameraAuthorizationStatusForIntent:(TGCameraAccessIntent)intent completion:(void (^)(BOOL))completion alertDismissCompletion:(void (^)(void))alertDismissCompletion;
|
||||
|
||||
@end
|
||||
|
@ -270,101 +270,102 @@
|
||||
|
||||
- (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController
|
||||
{
|
||||
if (![[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault alertDismissCompletion:nil])
|
||||
return;
|
||||
|
||||
if ([_context currentlyInSplitView])
|
||||
return;
|
||||
|
||||
TGCameraController *controller = nil;
|
||||
CGSize screenSize = TGScreenSize();
|
||||
|
||||
id<LegacyComponentsOverlayWindowManager> windowManager = [_context makeOverlayWindowManager];
|
||||
|
||||
if (cameraView.previewView != nil)
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:_saveEditedPhotos saveCapturedMedia:_saveCapturedMedia camera:cameraView.previewView.camera previewView:cameraView.previewView intent:_signup ? TGCameraControllerSignupAvatarIntent : TGCameraControllerAvatarIntent];
|
||||
else
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:_saveEditedPhotos saveCapturedMedia:_saveCapturedMedia intent:_signup ? TGCameraControllerSignupAvatarIntent : TGCameraControllerAvatarIntent];
|
||||
controller.stickersContext = _stickersContext;
|
||||
controller.shouldStoreCapturedAssets = true;
|
||||
|
||||
TGCameraControllerWindow *controllerWindow = [[TGCameraControllerWindow alloc] initWithManager:windowManager parentController:_parentController contentController:controller];
|
||||
controllerWindow.hidden = false;
|
||||
controllerWindow.clipsToBounds = true;
|
||||
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
|
||||
controllerWindow.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
|
||||
else
|
||||
controllerWindow.frame = [_context fullscreenBounds];
|
||||
|
||||
bool standalone = true;
|
||||
CGRect startFrame = CGRectMake(0, screenSize.height, screenSize.width, screenSize.height);
|
||||
if (cameraView != nil)
|
||||
{
|
||||
standalone = false;
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
|
||||
startFrame = CGRectZero;
|
||||
else
|
||||
startFrame = [controller.view convertRect:cameraView.previewView.frame fromView:cameraView];
|
||||
}
|
||||
|
||||
[cameraView detachPreviewView];
|
||||
[controller beginTransitionInFromRect:startFrame];
|
||||
|
||||
__weak TGMediaAvatarMenuMixin *weakSelf = self;
|
||||
__weak TGCameraController *weakCameraController = controller;
|
||||
__weak TGAttachmentCameraView *weakCameraView = cameraView;
|
||||
|
||||
controller.beginTransitionOut = ^CGRect
|
||||
{
|
||||
__strong TGCameraController *strongCameraController = weakCameraController;
|
||||
if (strongCameraController == nil)
|
||||
return CGRectZero;
|
||||
|
||||
__strong TGAttachmentCameraView *strongCameraView = weakCameraView;
|
||||
if (strongCameraView != nil)
|
||||
{
|
||||
[strongCameraView willAttachPreviewView];
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
|
||||
return CGRectZero;
|
||||
[[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault completion:^(BOOL allowed) {
|
||||
if (!allowed)
|
||||
return;
|
||||
if ([_context currentlyInSplitView])
|
||||
return;
|
||||
|
||||
return [strongCameraController.view convertRect:strongCameraView.frame fromView:strongCameraView.superview];
|
||||
TGCameraController *controller = nil;
|
||||
CGSize screenSize = TGScreenSize();
|
||||
|
||||
id<LegacyComponentsOverlayWindowManager> windowManager = [_context makeOverlayWindowManager];
|
||||
|
||||
if (cameraView.previewView != nil)
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:_saveEditedPhotos saveCapturedMedia:_saveCapturedMedia camera:cameraView.previewView.camera previewView:cameraView.previewView intent:_signup ? TGCameraControllerSignupAvatarIntent : TGCameraControllerAvatarIntent];
|
||||
else
|
||||
controller = [[TGCameraController alloc] initWithContext:[windowManager context] saveEditedPhotos:_saveEditedPhotos saveCapturedMedia:_saveCapturedMedia intent:_signup ? TGCameraControllerSignupAvatarIntent : TGCameraControllerAvatarIntent];
|
||||
controller.stickersContext = _stickersContext;
|
||||
controller.shouldStoreCapturedAssets = true;
|
||||
|
||||
TGCameraControllerWindow *controllerWindow = [[TGCameraControllerWindow alloc] initWithManager:windowManager parentController:_parentController contentController:controller];
|
||||
controllerWindow.hidden = false;
|
||||
controllerWindow.clipsToBounds = true;
|
||||
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)
|
||||
controllerWindow.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
|
||||
else
|
||||
controllerWindow.frame = [_context fullscreenBounds];
|
||||
|
||||
bool standalone = true;
|
||||
CGRect startFrame = CGRectMake(0, screenSize.height, screenSize.width, screenSize.height);
|
||||
if (cameraView != nil)
|
||||
{
|
||||
standalone = false;
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
|
||||
startFrame = CGRectZero;
|
||||
else
|
||||
startFrame = [controller.view convertRect:cameraView.previewView.frame fromView:cameraView];
|
||||
}
|
||||
|
||||
return CGRectZero;
|
||||
};
|
||||
|
||||
controller.finishedTransitionOut = ^
|
||||
{
|
||||
__strong TGAttachmentCameraView *strongCameraView = weakCameraView;
|
||||
if (strongCameraView == nil)
|
||||
return;
|
||||
[cameraView detachPreviewView];
|
||||
[controller beginTransitionInFromRect:startFrame];
|
||||
|
||||
[strongCameraView attachPreviewViewAnimated:true];
|
||||
};
|
||||
|
||||
controller.finishedWithPhoto = ^(__unused TGOverlayController *controller, UIImage *resultImage, __unused NSString *caption, __unused NSArray *entities, __unused NSArray *stickers, __unused NSNumber *timer)
|
||||
{
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
__weak TGMediaAvatarMenuMixin *weakSelf = self;
|
||||
__weak TGCameraController *weakCameraController = controller;
|
||||
__weak TGAttachmentCameraView *weakCameraView = cameraView;
|
||||
|
||||
if (strongSelf.didFinishWithImage != nil)
|
||||
strongSelf.didFinishWithImage(resultImage);
|
||||
controller.beginTransitionOut = ^CGRect
|
||||
{
|
||||
__strong TGCameraController *strongCameraController = weakCameraController;
|
||||
if (strongCameraController == nil)
|
||||
return CGRectZero;
|
||||
|
||||
__strong TGAttachmentCameraView *strongCameraView = weakCameraView;
|
||||
if (strongCameraView != nil)
|
||||
{
|
||||
[strongCameraView willAttachPreviewView];
|
||||
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
|
||||
return CGRectZero;
|
||||
|
||||
return [strongCameraController.view convertRect:strongCameraView.frame fromView:strongCameraView.superview];
|
||||
}
|
||||
|
||||
return CGRectZero;
|
||||
};
|
||||
|
||||
[menuController dismissAnimated:false];
|
||||
};
|
||||
|
||||
controller.finishedWithVideo = ^(__unused TGOverlayController *controller, NSURL *url, UIImage *previewImage, __unused NSTimeInterval duration, __unused CGSize dimensions, TGVideoEditAdjustments *adjustments, __unused NSString *caption, __unused NSArray *entities, __unused NSArray *stickers, __unused NSNumber *timer){
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
controller.finishedTransitionOut = ^
|
||||
{
|
||||
__strong TGAttachmentCameraView *strongCameraView = weakCameraView;
|
||||
if (strongCameraView == nil)
|
||||
return;
|
||||
|
||||
[strongCameraView attachPreviewViewAnimated:true];
|
||||
};
|
||||
|
||||
if (strongSelf.didFinishWithVideo != nil)
|
||||
strongSelf.didFinishWithVideo(previewImage, [[AVURLAsset alloc] initWithURL:url options:nil], adjustments);
|
||||
controller.finishedWithPhoto = ^(__unused TGOverlayController *controller, UIImage *resultImage, __unused NSString *caption, __unused NSArray *entities, __unused NSArray *stickers, __unused NSNumber *timer)
|
||||
{
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
if (strongSelf.didFinishWithImage != nil)
|
||||
strongSelf.didFinishWithImage(resultImage);
|
||||
|
||||
[menuController dismissAnimated:false];
|
||||
};
|
||||
|
||||
[menuController dismissAnimated:false];
|
||||
};
|
||||
controller.finishedWithVideo = ^(__unused TGOverlayController *controller, NSURL *url, UIImage *previewImage, __unused NSTimeInterval duration, __unused CGSize dimensions, TGVideoEditAdjustments *adjustments, __unused NSString *caption, __unused NSArray *entities, __unused NSArray *stickers, __unused NSNumber *timer){
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
if (strongSelf.didFinishWithVideo != nil)
|
||||
strongSelf.didFinishWithVideo(previewImage, [[AVURLAsset alloc] initWithURL:url options:nil], adjustments);
|
||||
|
||||
[menuController dismissAnimated:false];
|
||||
};
|
||||
} alertDismissCompletion:nil];
|
||||
}
|
||||
|
||||
- (void)_displayMediaPicker
|
||||
|
@ -271,7 +271,7 @@
|
||||
|
||||
+ (void)_displayCameraWithView:(TGAttachmentCameraView *)cameraView menuController:(TGMenuSheetController *)menuController parentController:(TGViewController *)parentController context:(id<LegacyComponentsContext>)context intent:(TGPassportAttachIntent)intent uploadAction:(void (^)(SSignal *, void (^)(void)))uploadAction
|
||||
{
|
||||
if (![[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault alertDismissCompletion:nil])
|
||||
if (![[[LegacyComponentsGlobals provider] accessChecker] checkCameraAuthorizationStatusForIntent:TGCameraAccessIntentDefault completion:^(BOOL allowed) { } alertDismissCompletion:nil])
|
||||
return;
|
||||
|
||||
if ([context currentlyInSplitView])
|
||||
|
@ -39,10 +39,6 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent
|
||||
self.context = context
|
||||
}
|
||||
|
||||
public func checkAddressBookAuthorizationStatus(alertDismissComlpetion alertDismissCompletion: (() -> Void)!) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func checkPhotoAuthorizationStatus(for intent: TGPhotoAccessIntent, alertDismissCompletion: (() -> Void)!) -> Bool {
|
||||
if let context = self.context {
|
||||
DeviceAccess.authorizeAccess(to: .mediaLibrary(.send), presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
|
||||
@ -58,24 +54,10 @@ private final class LegacyComponentsAccessCheckerImpl: NSObject, LegacyComponent
|
||||
return true
|
||||
}
|
||||
|
||||
public func checkCameraAuthorizationStatus(for intent: TGCameraAccessIntent, alertDismissCompletion: (() -> Void)!) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public func checkLocationAuthorizationStatus(for intent: TGLocationAccessIntent, alertDismissComlpetion alertDismissCompletion: (() -> Void)!) -> Bool {
|
||||
let subject: DeviceAccessLocationSubject
|
||||
if intent == TGLocationAccessIntentSend {
|
||||
subject = .send
|
||||
} else if intent == TGLocationAccessIntentLiveLocation {
|
||||
subject = .live
|
||||
} else if intent == TGLocationAccessIntentTracking {
|
||||
subject = .tracking
|
||||
} else {
|
||||
assertionFailure()
|
||||
subject = .send
|
||||
}
|
||||
public func checkCameraAuthorizationStatus(for intent: TGCameraAccessIntent, completion: ((Bool) -> Void)!, alertDismissCompletion: (() -> Void)!) -> Bool {
|
||||
if let context = self.context {
|
||||
DeviceAccess.authorizeAccess(to: .location(subject), presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
|
||||
DeviceAccess.authorizeAccess(to: .camera(.video), presentationData: context.sharedContext.currentPresentationData.with { $0 }, present: context.sharedContext.presentGlobalController, openSettings: context.sharedContext.applicationBindings.openSettings, { value in
|
||||
completion(value)
|
||||
if !value {
|
||||
alertDismissCompletion?()
|
||||
}
|
||||
|
@ -2,9 +2,10 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||
final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private var currentHorizontal: Bool?
|
||||
private let imageNodeContainer: ASDisplayNode
|
||||
private let imageNode: ASImageNode
|
||||
|
||||
@ -45,31 +46,56 @@ private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
||||
func update(backgroundColor: UIColor, foregroundColor: UIColor, horizontal: Bool = false) {
|
||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), self.currentHorizontal == horizontal {
|
||||
return
|
||||
}
|
||||
self.currentBackgroundColor = backgroundColor
|
||||
self.currentForegroundColor = foregroundColor
|
||||
self.currentHorizontal = horizontal
|
||||
|
||||
self.imageNode.image = generateImage(CGSize(width: 16.0, height: 320.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foregroundColor.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
let image: UIImage?
|
||||
if horizontal {
|
||||
image = generateImage(CGSize(width: 320.0, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foregroundColor.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
} else {
|
||||
image = generateImage(CGSize(width: 16.0, height: 320.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foregroundColor.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
}
|
||||
self.imageNode.image = image
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
@ -95,7 +121,7 @@ private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func updateAnimation() {
|
||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil && self.currentHorizontal != nil
|
||||
if shouldBeAnimating != self.shouldBeAnimating {
|
||||
self.shouldBeAnimating = shouldBeAnimating
|
||||
if shouldBeAnimating {
|
||||
@ -107,15 +133,25 @@ private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func addImageAnimation() {
|
||||
guard let containerSize = self.absoluteLocation?.1 else {
|
||||
guard let containerSize = self.absoluteLocation?.1, let horizontal = self.currentHorizontal else {
|
||||
return
|
||||
}
|
||||
let gradientHeight: CGFloat = 250.0
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||
|
||||
if horizontal {
|
||||
let gradientHeight: CGFloat = 320.0
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: -gradientHeight, y: 0.0), size: CGSize(width: gradientHeight, height: containerSize.height))
|
||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.width + gradientHeight) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||
} else {
|
||||
let gradientHeight: CGFloat = 250.0
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,6 +171,7 @@ public final class ShimmerEffectNode: ASDisplayNode {
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private var currentShimmeringColor: UIColor?
|
||||
private var currentHorizontal: Bool?
|
||||
private var currentSize = CGSize()
|
||||
|
||||
override public init() {
|
||||
@ -157,8 +194,8 @@ public final class ShimmerEffectNode: ASDisplayNode {
|
||||
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
|
||||
public func update(backgroundColor: UIColor, foregroundColor: UIColor, shimmeringColor: UIColor, shapes: [Shape], size: CGSize) {
|
||||
if self.currentShapes == shapes, let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor), self.currentSize == size {
|
||||
public func update(backgroundColor: UIColor, foregroundColor: UIColor, shimmeringColor: UIColor, shapes: [Shape], horizontal: Bool = false, size: CGSize) {
|
||||
if self.currentShapes == shapes, let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), let currentShimmeringColor = self.currentShimmeringColor, currentShimmeringColor.isEqual(shimmeringColor), horizontal == self.currentHorizontal, self.currentSize == size {
|
||||
return
|
||||
}
|
||||
|
||||
@ -166,11 +203,12 @@ public final class ShimmerEffectNode: ASDisplayNode {
|
||||
self.currentForegroundColor = foregroundColor
|
||||
self.currentShimmeringColor = shimmeringColor
|
||||
self.currentShapes = shapes
|
||||
self.currentHorizontal = horizontal
|
||||
self.currentSize = size
|
||||
|
||||
self.backgroundNode.backgroundColor = foregroundColor
|
||||
|
||||
self.effectNode.update(backgroundColor: foregroundColor, foregroundColor: shimmeringColor)
|
||||
self.effectNode.update(backgroundColor: foregroundColor, foregroundColor: shimmeringColor, horizontal: horizontal)
|
||||
|
||||
self.foregroundNode.image = generateImage(size, rotatedContext: { size, context in
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
|
@ -2,124 +2,6 @@ import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
private final class ShimmerEffectForegroundNode: ASDisplayNode {
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private let imageNodeContainer: ASDisplayNode
|
||||
private let imageNode: ASImageNode
|
||||
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
private var isCurrentlyInHierarchy = false
|
||||
private var shouldBeAnimating = false
|
||||
|
||||
override init() {
|
||||
self.imageNodeContainer = ASDisplayNode()
|
||||
self.imageNodeContainer.isLayerBacked = true
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.imageNode.displayWithoutProcessing = true
|
||||
self.imageNode.contentMode = .scaleToFill
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.imageNodeContainer.addSubnode(self.imageNode)
|
||||
self.addSubnode(self.imageNodeContainer)
|
||||
}
|
||||
|
||||
override func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = true
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = false
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor) {
|
||||
return
|
||||
}
|
||||
self.currentBackgroundColor = backgroundColor
|
||||
self.currentForegroundColor = foregroundColor
|
||||
|
||||
let image = generateImage(CGSize(width: 320.0, height: 16.0), opaque: false, scale: 1.0, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foregroundColor.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
self.imageNode.image = image
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||
return
|
||||
}
|
||||
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
||||
let frameUpdated = self.absoluteLocation?.0 != rect
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
|
||||
if sizeUpdated {
|
||||
if self.shouldBeAnimating {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
self.addImageAnimation()
|
||||
} else {
|
||||
self.updateAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
if frameUpdated {
|
||||
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAnimation() {
|
||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
||||
if shouldBeAnimating != self.shouldBeAnimating {
|
||||
self.shouldBeAnimating = shouldBeAnimating
|
||||
if shouldBeAnimating {
|
||||
self.addImageAnimation()
|
||||
} else {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addImageAnimation() {
|
||||
guard let containerSize = self.absoluteLocation?.1 else {
|
||||
return
|
||||
}
|
||||
let gradientHeight: CGFloat = 320.0
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: -gradientHeight, y: 0.0), size: CGSize(width: gradientHeight, height: containerSize.height))
|
||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.width + gradientHeight) as NSNumber, keyPath: "position.x", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
|
||||
private let decodingMap: [String] = ["A", "A", "C", "A", "A", "A", "A", "H", "A", "A", "A", "L", "M", "A", "A", "A", "Q", "A", "S", "T", "A", "V", "A", "A", "A", "Z", "a", "a", "c", "a", "a", "a", "a", "h", "a", "a", "a", "l", "m", "a", "a", "a", "q", "a", "s", "t", "a", "v", "a", ".", "a", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-", ","]
|
||||
private func decodeStickerThumbnailData(_ data: Data) -> String {
|
||||
var string = "M"
|
||||
@ -199,7 +81,7 @@ public class StickerShimmerEffectNode: ASDisplayNode {
|
||||
|
||||
self.backgroundNode.backgroundColor = foregroundColor
|
||||
|
||||
self.effectNode.update(backgroundColor: backgroundColor == nil ? .clear : foregroundColor, foregroundColor: shimmeringColor)
|
||||
self.effectNode.update(backgroundColor: backgroundColor == nil ? .clear : foregroundColor, foregroundColor: shimmeringColor, horizontal: true)
|
||||
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
let image = generateImage(size, rotatedContext: { size, context in
|
||||
|
@ -4370,8 +4370,8 @@ public final class VoiceChatController: ViewController {
|
||||
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: self.isFullscreen, small: smallButtons, animated: true)
|
||||
|
||||
var hasCameraButton = self.callState?.isVideoEnabled ?? false
|
||||
if let joinedVideo = self.joinedVideo, !joinedVideo {
|
||||
hasCameraButton = false
|
||||
if let joinedVideo = self.joinedVideo {
|
||||
hasCameraButton = joinedVideo
|
||||
}
|
||||
switch actionButtonState {
|
||||
case let .active(state):
|
||||
@ -4790,7 +4790,7 @@ public final class VoiceChatController: ViewController {
|
||||
displayPanelVideos = self.displayPanelVideos
|
||||
}
|
||||
|
||||
var joinedVideo = true
|
||||
var joinedVideo = self.joinedVideo ?? true
|
||||
|
||||
var myEntry: VoiceChatPeerEntry?
|
||||
var mainEntry: VoiceChatPeerEntry?
|
||||
@ -4929,7 +4929,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
if !isTile || (isTablet) {
|
||||
if !isTile || isTablet || !joinedVideo {
|
||||
entries.append(.peer(peerEntry, index))
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,12 @@ import TelegramApi
|
||||
|
||||
import SyncCore
|
||||
|
||||
private enum UploadStickerStatus {
|
||||
public enum UploadStickerStatus {
|
||||
case progress(Float)
|
||||
case complete(TelegramMediaFile)
|
||||
case complete(CloudDocumentMediaResource, String)
|
||||
}
|
||||
|
||||
private enum UploadStickerError {
|
||||
public enum UploadStickerError {
|
||||
case generic
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ private func uploadedSticker(postbox: Postbox, network: Network, resource: Media
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, isAnimated: Bool) -> Signal<UploadStickerStatus, UploadStickerError> {
|
||||
func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, isAnimated: Bool) -> Signal<UploadStickerStatus, UploadStickerError> {
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
@ -59,8 +59,8 @@ private func uploadSticker(account: Account, peer: Peer, resource: MediaResource
|
||||
|> mapToSignal { media -> Signal<UploadStickerStatus, UploadStickerError> in
|
||||
switch media {
|
||||
case let .messageMediaDocument(_, document, _):
|
||||
if let document = document, let file = telegramMediaFileFromApiDocument(document) {
|
||||
return .single(.complete(file))
|
||||
if let document = document, let file = telegramMediaFileFromApiDocument(document), let resource = file.resource as? CloudDocumentMediaResource {
|
||||
return .single(.complete(resource, file.mimeType))
|
||||
}
|
||||
default:
|
||||
break
|
||||
@ -108,35 +108,37 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
stickers.append(thumbnail)
|
||||
}
|
||||
for sticker in stickers {
|
||||
uploadStickers.append(uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, isAnimated: isAnimated)
|
||||
|> mapError { _ -> CreateStickerSetError in
|
||||
return .generic
|
||||
})
|
||||
if let resource = sticker.resource as? CloudDocumentMediaResource {
|
||||
uploadStickers.append(.single(.complete(resource, isAnimated ? "application/x-tgsticker": "image/png")))
|
||||
} else {
|
||||
uploadStickers.append(_internal_uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, isAnimated: isAnimated)
|
||||
|> mapError { _ -> CreateStickerSetError in
|
||||
return .generic
|
||||
})
|
||||
}
|
||||
}
|
||||
return combineLatest(uploadStickers)
|
||||
|> mapToSignal { uploadedStickers -> Signal<CreateStickerSetStatus, CreateStickerSetError> in
|
||||
var documents: [TelegramMediaFile] = []
|
||||
var resources: [CloudDocumentMediaResource] = []
|
||||
for sticker in uploadedStickers {
|
||||
if case let .complete(document) = sticker {
|
||||
documents.append(document)
|
||||
if case let .complete(resource, _) = sticker {
|
||||
resources.append(resource)
|
||||
}
|
||||
}
|
||||
if documents.count == stickers.count {
|
||||
if resources.count == stickers.count {
|
||||
var flags: Int32 = 0
|
||||
if isAnimated {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
var inputStickers: [Api.InputStickerSetItem] = []
|
||||
let stickerDocuments = thumbnail != nil ? documents.dropLast() : documents
|
||||
let stickerDocuments = thumbnail != nil ? resources.dropLast() : resources
|
||||
for i in 0 ..< stickerDocuments.count {
|
||||
let sticker = stickers[i]
|
||||
let document = documents[i]
|
||||
if let resource = document.resource as? CloudDocumentMediaResource {
|
||||
inputStickers.append(.inputStickerSetItem(flags: 0, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil))
|
||||
}
|
||||
let resource = resources[i]
|
||||
inputStickers.append(.inputStickerSetItem(flags: 0, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil))
|
||||
}
|
||||
var thumbnailDocument: Api.InputDocument?
|
||||
if thumbnail != nil, let document = documents.last, let resource = document.resource as? CloudDocumentMediaResource {
|
||||
if thumbnail != nil, let resource = resources.last {
|
||||
flags |= (1 << 2)
|
||||
thumbnailDocument = .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))
|
||||
}
|
||||
|
@ -70,6 +70,10 @@ public extension TelegramEngine {
|
||||
return _internal_stickerPacksAttachedToMedia(account: self.account, media: media)
|
||||
}
|
||||
|
||||
public func uploadSticker(peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, isAnimated: Bool) -> Signal<UploadStickerStatus, UploadStickerError> {
|
||||
return _internal_uploadSticker(account: self.account, peer: peer, resource: resource, alt: alt, dimensions: dimensions, isAnimated: isAnimated)
|
||||
}
|
||||
|
||||
public func createStickerSet(title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, isAnimated: Bool, software: String?) -> Signal<CreateStickerSetStatus, CreateStickerSetError> {
|
||||
return _internal_createStickerSet(account: self.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnail, isAnimated: isAnimated, software: software)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user