mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Initial avatar editor implementation
This commit is contained in:
parent
1bc6d85f0f
commit
89481bdef2
@ -186,6 +186,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
public let panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?
|
||||
public let isTopPanelExpandedUpdated: (Bool, Transition) -> Void
|
||||
public let isTopPanelHiddenUpdated: (Bool, Transition) -> Void
|
||||
public let contentIdUpdated: (AnyHashable) -> Void
|
||||
public let panelHideBehavior: PagerComponentPanelHideBehavior
|
||||
public let clipContentToTopPanel: Bool
|
||||
|
||||
@ -205,6 +206,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
panelStateUpdated: ((PagerComponentPanelState, Transition) -> Void)?,
|
||||
isTopPanelExpandedUpdated: @escaping (Bool, Transition) -> Void,
|
||||
isTopPanelHiddenUpdated: @escaping (Bool, Transition) -> Void,
|
||||
contentIdUpdated: @escaping (AnyHashable) -> Void,
|
||||
panelHideBehavior: PagerComponentPanelHideBehavior,
|
||||
clipContentToTopPanel: Bool
|
||||
) {
|
||||
@ -223,6 +225,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
self.panelStateUpdated = panelStateUpdated
|
||||
self.isTopPanelExpandedUpdated = isTopPanelExpandedUpdated
|
||||
self.isTopPanelHiddenUpdated = isTopPanelHiddenUpdated
|
||||
self.contentIdUpdated = contentIdUpdated
|
||||
self.panelHideBehavior = panelHideBehavior
|
||||
self.clipContentToTopPanel = clipContentToTopPanel
|
||||
}
|
||||
@ -397,12 +400,33 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
} else {
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
if let centralId = self.centralId {
|
||||
self.component?.contentIdUpdated(centralId)
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func navigateToContentId(_ id: AnyHashable) {
|
||||
var updateTopPanelExpanded = false
|
||||
if self.centralId != id {
|
||||
self.centralId = id
|
||||
|
||||
if self.isTopPanelExpanded {
|
||||
updateTopPanelExpanded = true
|
||||
}
|
||||
}
|
||||
|
||||
if updateTopPanelExpanded {
|
||||
self.isTopPanelExpandedUpdated(isExpanded: false, transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
} else {
|
||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
self.component?.contentIdUpdated(id)
|
||||
}
|
||||
|
||||
func update(component: PagerComponent<ChildEnvironmentType, TopPanelEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
let previousPanelHideBehavior = self.component?.panelHideBehavior
|
||||
|
||||
@ -418,21 +442,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
var updateTopPanelExpanded = false
|
||||
if strongSelf.centralId != id {
|
||||
strongSelf.centralId = id
|
||||
|
||||
if strongSelf.isTopPanelExpanded {
|
||||
updateTopPanelExpanded = true
|
||||
}
|
||||
}
|
||||
|
||||
if updateTopPanelExpanded {
|
||||
strongSelf.isTopPanelExpandedUpdated(isExpanded: false, transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
} else {
|
||||
strongSelf.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
strongSelf.navigateToContentId(id)
|
||||
}
|
||||
|
||||
var centralId: AnyHashable?
|
||||
|
@ -195,8 +195,8 @@ final class DrawingStickerEntityView: DrawingEntityView {
|
||||
|
||||
if let animationNode = animationNode {
|
||||
let _ = (animationNode.status
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
self?.started?(status.duration)
|
||||
})
|
||||
}
|
||||
@ -273,7 +273,7 @@ final class DrawingStickerEntityView: DrawingEntityView {
|
||||
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||
|
||||
self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384)
|
||||
|> deliverOn(Queue.concurrentDefaultQueue())).start())
|
||||
|> deliverOn(Queue.concurrentDefaultQueue())).start())
|
||||
}
|
||||
}
|
||||
self.animationNode?.visibility = isPlaying
|
||||
|
@ -154,6 +154,7 @@ private final class StickerSelectionComponent: Component {
|
||||
switchToGifSubject: { _ in },
|
||||
reorderItems: { _, _ in },
|
||||
makeSearchContainerNode: { _ in return nil },
|
||||
contentIdUpdated: { _ in },
|
||||
deviceMetrics: component.deviceMetrics,
|
||||
hiddenInputHeight: 0.0,
|
||||
inputHeight: 0.0,
|
||||
|
@ -26,6 +26,8 @@ typedef void (^TGMediaAvatarPresentImpl)(id<LegacyComponentsContext>, void (^)(U
|
||||
@property (nonatomic, copy) void (^requestSearchController)(TGMediaAssetsController *);
|
||||
@property (nonatomic, copy) CGRect (^sourceRect)(void);
|
||||
|
||||
@property (nonatomic, copy) void (^requestAvatarEditor)(void (^)(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void(^commit)(void)));
|
||||
|
||||
@property (nonatomic, strong) id<TGPhotoPaintStickersContext> stickersContext;
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController hasDeleteButton:(bool)hasDeleteButton saveEditedPhotos:(bool)saveEditedPhotos saveCapturedMedia:(bool)saveCapturedMedia;
|
||||
|
@ -193,24 +193,60 @@
|
||||
}];
|
||||
[itemViews addObject:galleryItem];
|
||||
|
||||
// if (_hasSearchButton)
|
||||
// {
|
||||
// TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SearchWeb") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||
// {
|
||||
// __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
// if (strongSelf == nil)
|
||||
// return;
|
||||
//
|
||||
// __strong TGMenuSheetController *strongController = weakController;
|
||||
// if (strongController == nil)
|
||||
// return;
|
||||
//
|
||||
// [strongController dismissAnimated:true];
|
||||
// if (strongSelf != nil)
|
||||
// strongSelf.requestSearchController(nil);
|
||||
// }];
|
||||
// [itemViews addObject:viewItem];
|
||||
// }
|
||||
if (!_signup) {
|
||||
TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:@"Emoji or Sticker" type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||
{
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
if (strongSelf != nil)
|
||||
strongSelf.requestAvatarEditor(^(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void (^commit)(void)) {
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
if (strongSelf.willFinishWithVideo != nil) {
|
||||
strongSelf.willFinishWithVideo(image, ^{
|
||||
if (strongSelf.didFinishWithVideo != nil)
|
||||
strongSelf.didFinishWithVideo(image, asset, adjustments);
|
||||
|
||||
commit();
|
||||
});
|
||||
} else {
|
||||
if (strongSelf.didFinishWithVideo != nil)
|
||||
strongSelf.didFinishWithVideo(image, asset, adjustments);
|
||||
|
||||
commit();
|
||||
}
|
||||
});
|
||||
}];
|
||||
[itemViews addObject:viewItem];
|
||||
}
|
||||
|
||||
if (_hasSearchButton)
|
||||
{
|
||||
TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SearchWeb") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||
{
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
if (strongSelf != nil)
|
||||
strongSelf.requestSearchController(nil);
|
||||
}];
|
||||
[itemViews addObject:viewItem];
|
||||
}
|
||||
|
||||
if (_hasViewButton)
|
||||
{
|
||||
|
@ -351,6 +351,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ChatInputNode",
|
||||
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||
"//submodules/TelegramUI/Components/StorageUsageScreen",
|
||||
"//submodules/TelegramUI/Components/AvatarEditorScreen",
|
||||
"//submodules/MediaPasteboardUI:MediaPasteboardUI",
|
||||
"//submodules/DrawingUI:DrawingUI",
|
||||
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen",
|
||||
|
43
submodules/TelegramUI/Components/AvatarEditorScreen/BUILD
Normal file
43
submodules/TelegramUI/Components/AvatarEditorScreen/BUILD
Normal file
@ -0,0 +1,43 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AvatarEditorScreen",
|
||||
module_name = "AvatarEditorScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent:ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
"//submodules/Components/SolidRoundedButtonComponent",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode",
|
||||
"//submodules/TelegramNotices:TelegramNotices",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/GradientBackground:GradientBackground",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/DrawingUI:DrawingUI",
|
||||
"//submodules/StickerResources:StickerResources",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,215 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import EmojiStatusComponent
|
||||
import Postbox
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import StickerResources
|
||||
|
||||
final class AvatarPreviewComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let context: AccountContext
|
||||
let background: AvatarBackground
|
||||
let file: TelegramMediaFile?
|
||||
let tapped: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
background: AvatarBackground,
|
||||
file: TelegramMediaFile?,
|
||||
tapped: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.background = background
|
||||
self.file = file
|
||||
self.tapped = tapped
|
||||
}
|
||||
|
||||
static func ==(lhs: AvatarPreviewComponent, rhs: AvatarPreviewComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.background != rhs.background {
|
||||
return false
|
||||
}
|
||||
if lhs.file != rhs.file {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView, UITextFieldDelegate {
|
||||
private let imageView: UIImageView
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
|
||||
private var component: AvatarPreviewComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.imageView = UIImageView()
|
||||
self.imageView.isUserInteractionEnabled = false
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.disablesInteractiveModalDismiss = true
|
||||
self.disablesInteractiveKeyboardGestureRecognizer = true
|
||||
|
||||
self.addSubview(self.imageView)
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapped)))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stickerFetchedDisposable.dispose()
|
||||
}
|
||||
|
||||
@objc func tapped() {
|
||||
self.animationNode?.playOnce()
|
||||
self.component?.tapped()
|
||||
}
|
||||
|
||||
func update(component: AvatarPreviewComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
let previousBackground = self.component?.background
|
||||
|
||||
let hadFile = self.component?.file != nil
|
||||
var fileUpdated = false
|
||||
if self.component?.file?.fileId != component.file?.fileId {
|
||||
self.imageNode.isHidden = false
|
||||
fileUpdated = true
|
||||
}
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let size = CGSize(width: availableSize.width * 0.66, height: availableSize.width * 0.66)
|
||||
|
||||
var dimensions: CGSize?
|
||||
if let file = component.file, fileUpdated, let fileDimensions = file.dimensions?.cgSize {
|
||||
dimensions = fileDimensions
|
||||
|
||||
if !self.imageNode.isHidden && hadFile, let snapshotView = self.imageNode.view.snapshotContentTree() {
|
||||
self.imageNode.view.superview?.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
snapshotView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
if let animationNode = self.animationNode {
|
||||
self.animationNode = nil
|
||||
|
||||
animationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
animationNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false, completion: { [weak animationNode] _ in
|
||||
animationNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" {
|
||||
self.imageNode.isHidden = false
|
||||
|
||||
if self.animationNode == nil {
|
||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
animationNode.autoplay = false
|
||||
self.animationNode = animationNode
|
||||
animationNode.started = { [weak self] in
|
||||
self?.imageNode.isHidden = true
|
||||
}
|
||||
self.addSubnode(animationNode)
|
||||
}
|
||||
|
||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: component.context.account.postbox, userLocation: .other, file: file, small: false, size: fileDimensions.aspectFitted(CGSize(width: 256.0, height: 256.0))))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: component.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: file.resource).start())
|
||||
} else {
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.visibility = false
|
||||
self.animationNode = nil
|
||||
animationNode.removeFromSupernode()
|
||||
self.imageNode.isHidden = false
|
||||
}
|
||||
self.imageNode.setSignal(chatMessageSticker(account: component.context.account, userLocation: .other, file: file, small: false, synchronousLoad: false))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: component.context.account, userLocation: .other, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: false)).start())
|
||||
}
|
||||
|
||||
if fileUpdated && hadFile {
|
||||
self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.imageNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
animationNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let dimensions {
|
||||
let imageSize = dimensions.aspectFitted(size)
|
||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - imageSize.width) / 2.0), y: (availableSize.height - imageSize.height) / 2.0), size: imageSize)
|
||||
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - imageSize.width) / 2.0), y: (availableSize.height - imageSize.height) / 2.0), size: imageSize)
|
||||
animationNode.updateLayout(size: imageSize)
|
||||
}
|
||||
|
||||
if fileUpdated {
|
||||
self.updateVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
self.imageView.frame = CGRect(origin: .zero, size: availableSize)
|
||||
if previousBackground != component.background {
|
||||
if let _ = previousBackground, !transition.animation.isImmediate, let snapshotView = self.imageView.snapshotContentTree() {
|
||||
self.insertSubview(snapshotView, aboveSubview: self.imageView)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
self.imageView.image = component.background.generateImage(size: availableSize)
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
||||
private func updateVisibility() {
|
||||
guard let component = self.component, let file = component.file else {
|
||||
return
|
||||
}
|
||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
|
||||
let source = AnimatedStickerResourceSource(account: component.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm")
|
||||
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .count(2), mode: .direct(cachePathPrefix: nil))
|
||||
self.animationNode?.visibility = true
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -0,0 +1,313 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
|
||||
final class BackgroundColorComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let values: [AvatarBackground]
|
||||
let selectedValue: AvatarBackground
|
||||
let updateValue: (AvatarBackground) -> Void
|
||||
let openColorPicker: () -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
values: [AvatarBackground],
|
||||
selectedValue: AvatarBackground,
|
||||
updateValue: @escaping (AvatarBackground) -> Void,
|
||||
openColorPicker: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.values = values
|
||||
self.selectedValue = selectedValue
|
||||
self.updateValue = updateValue
|
||||
self.openColorPicker = openColorPicker
|
||||
}
|
||||
|
||||
static func ==(lhs: BackgroundColorComponent, rhs: BackgroundColorComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.values != rhs.values {
|
||||
return false
|
||||
}
|
||||
if lhs.selectedValue != rhs.selectedValue {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
class View: UIView {
|
||||
private var views: [Int: ComponentView<Empty>] = [:]
|
||||
|
||||
private var component: BackgroundColorComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: BackgroundColorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var values: [(AvatarBackground?, Bool)] = component.values.map { ($0, false) }
|
||||
if !values.contains(where: { $0.0 == component.selectedValue }) {
|
||||
values.append((component.selectedValue, true))
|
||||
} else {
|
||||
values.append((nil, true))
|
||||
}
|
||||
|
||||
let itemSize = CGSize(width: 30.0, height: 30.0)
|
||||
let sideInset: CGFloat = 12.0
|
||||
let height: CGFloat = 50.0
|
||||
let delta = (availableSize.width - sideInset * 2.0 - CGFloat(values.count) * itemSize.width) / CGFloat(values.count - 1)
|
||||
|
||||
for i in 0 ..< values.count {
|
||||
let view: ComponentView<Empty>
|
||||
if let current = self.views[i] {
|
||||
view = current
|
||||
} else {
|
||||
view = ComponentView<Empty>()
|
||||
self.views[i] = view
|
||||
}
|
||||
|
||||
let itemSize = view.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
BackgroundSwatchComponent(
|
||||
theme: component.theme,
|
||||
background: values[i].0,
|
||||
isCustom: values[i].1,
|
||||
isSelected: component.selectedValue == values[i].0,
|
||||
action: {
|
||||
if !values[i].1, let value = values[i].0 {
|
||||
component.updateValue(value)
|
||||
} else {
|
||||
component.openColorPicker()
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: itemSize
|
||||
)
|
||||
if let itemView = view.view {
|
||||
if itemView.superview == nil {
|
||||
self.addSubview(itemView)
|
||||
}
|
||||
|
||||
let position: CGFloat = sideInset + (delta + itemSize.width) * CGFloat(i)
|
||||
transition.setFrame(view: itemView, frame: CGRect(origin: CGPoint(x: position, y: 10.0), size: itemSize))
|
||||
}
|
||||
}
|
||||
return CGSize(width: availableSize.width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private func generateAddIcon(color: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
context.setStrokeColor(color.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
|
||||
context.move(to: CGPoint(x: 15.0, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 15.0, y: 21.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 9.0, y: 15.0))
|
||||
context.addLine(to: CGPoint(x: 21.0, y: 15.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
private func generateMoreIcon() -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
context.addEllipse(in: CGRect(x: 8.5, y: 13.5, width: 3.0, height: 3.0))
|
||||
context.fillPath()
|
||||
|
||||
context.addEllipse(in: CGRect(x: 13.5, y: 13.5, width: 3.0, height: 3.0))
|
||||
context.fillPath()
|
||||
|
||||
context.addEllipse(in: CGRect(x: 18.5, y: 13.5, width: 3.0, height: 3.0))
|
||||
context.fillPath()
|
||||
})
|
||||
}
|
||||
|
||||
final class BackgroundSwatchComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let background: AvatarBackground?
|
||||
let isCustom: Bool
|
||||
let isSelected: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
background: AvatarBackground?,
|
||||
isCustom: Bool,
|
||||
isSelected: Bool,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.background = background
|
||||
self.isCustom = isCustom
|
||||
self.isSelected = isSelected
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func == (lhs: BackgroundSwatchComponent, rhs: BackgroundSwatchComponent) -> Bool {
|
||||
return lhs.theme === rhs.theme && lhs.background == rhs.background && lhs.isCustom == rhs.isCustom && lhs.isSelected == rhs.isSelected
|
||||
}
|
||||
|
||||
final class View: UIButton {
|
||||
private var component: BackgroundSwatchComponent?
|
||||
|
||||
private let maskLayer: SimpleLayer
|
||||
private let ringMaskLayer: SimpleShapeLayer
|
||||
private let circleMaskLayer: SimpleShapeLayer
|
||||
|
||||
private let iconLayer: SimpleLayer
|
||||
|
||||
private var currentIsHighlighted: Bool = false {
|
||||
didSet {
|
||||
if self.currentIsHighlighted != oldValue {
|
||||
self.alpha = self.currentIsHighlighted ? 0.6 : 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.maskLayer = SimpleLayer()
|
||||
self.ringMaskLayer = SimpleShapeLayer()
|
||||
self.circleMaskLayer = SimpleShapeLayer()
|
||||
self.iconLayer = SimpleLayer()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
self.component?.action()
|
||||
}
|
||||
|
||||
override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||
self.currentIsHighlighted = true
|
||||
|
||||
return super.beginTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||
self.currentIsHighlighted = false
|
||||
|
||||
super.endTracking(touch, with: event)
|
||||
}
|
||||
|
||||
override public func cancelTracking(with event: UIEvent?) {
|
||||
self.currentIsHighlighted = false
|
||||
|
||||
super.cancelTracking(with: event)
|
||||
}
|
||||
|
||||
func update(component: BackgroundSwatchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let previousBackground = self.component?.background
|
||||
self.component = component
|
||||
|
||||
let contentSize = availableSize
|
||||
let bounds = CGRect(origin: .zero, size: contentSize)
|
||||
|
||||
self.layer.allowsGroupOpacity = true
|
||||
|
||||
if self.layer.mask == nil {
|
||||
self.layer.mask = self.maskLayer
|
||||
self.maskLayer.frame = bounds
|
||||
|
||||
self.maskLayer.addSublayer(self.circleMaskLayer)
|
||||
self.maskLayer.addSublayer(self.ringMaskLayer)
|
||||
|
||||
self.circleMaskLayer.frame = bounds
|
||||
if self.circleMaskLayer.path == nil {
|
||||
self.circleMaskLayer.path = UIBezierPath(ovalIn: bounds.insetBy(dx: 3.0, dy: 3.0)).cgPath
|
||||
}
|
||||
|
||||
let ringFrame = bounds
|
||||
self.ringMaskLayer.frame = CGRect(origin: .zero, size: ringFrame.size)
|
||||
self.ringMaskLayer.strokeColor = UIColor.white.cgColor
|
||||
self.ringMaskLayer.fillColor = UIColor.clear.cgColor
|
||||
self.ringMaskLayer.lineWidth = 2.0 - UIScreenPixel
|
||||
self.ringMaskLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: ringFrame.size).insetBy(dx: 1.0, dy: 1.0)).cgPath
|
||||
|
||||
self.layer.addSublayer(self.iconLayer)
|
||||
}
|
||||
|
||||
self.iconLayer.frame = bounds
|
||||
if component.isCustom {
|
||||
if previousBackground != component.background || self.iconLayer.contents == nil {
|
||||
if component.background != nil {
|
||||
self.iconLayer.contents = generateMoreIcon()?.cgImage
|
||||
} else {
|
||||
self.iconLayer.contents = generateAddIcon(color: component.theme.list.itemAccentColor)?.cgImage
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.iconLayer.contents = nil
|
||||
}
|
||||
|
||||
if component.isSelected {
|
||||
transition.setShapeLayerPath(layer: self.circleMaskLayer, path: CGPath(ellipseIn: bounds.insetBy(dx: 3.0, dy: 3.0), transform: nil))
|
||||
} else {
|
||||
transition.setShapeLayerPath(layer: self.circleMaskLayer, path: CGPath(ellipseIn: bounds, transform: nil))
|
||||
}
|
||||
|
||||
if previousBackground != component.background {
|
||||
if let background = component.background {
|
||||
self.layer.backgroundColor = nil
|
||||
self.layer.contents = background.generateImage(size: availableSize).cgImage
|
||||
} else {
|
||||
self.layer.backgroundColor = component.theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor
|
||||
self.layer.contents = nil
|
||||
}
|
||||
} else if component.background == nil {
|
||||
self.layer.backgroundColor = component.theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor
|
||||
self.layer.contents = nil
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1495,6 +1495,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
}
|
||||
)
|
||||
},
|
||||
contentIdUpdated: { _ in },
|
||||
deviceMetrics: deviceMetrics,
|
||||
hiddenInputHeight: hiddenInputHeight,
|
||||
inputHeight: inputHeight,
|
||||
|
@ -185,6 +185,7 @@ public final class EmojiStatusSelectionComponent: Component {
|
||||
switchToGifSubject: { _ in },
|
||||
reorderItems: { _, _ in },
|
||||
makeSearchContainerNode: { _ in return nil },
|
||||
contentIdUpdated: { _ in },
|
||||
deviceMetrics: component.deviceMetrics,
|
||||
hiddenInputHeight: 0.0,
|
||||
inputHeight: 0.0,
|
||||
|
@ -2534,6 +2534,13 @@ public final class EmojiPagerContentComponent: Component {
|
||||
let frame: CGRect
|
||||
let supergroupId: AnyHashable
|
||||
let groupId: AnyHashable
|
||||
let itemsPerRow: Int
|
||||
let nativeItemSize: CGFloat
|
||||
let visibleItemSize: CGFloat
|
||||
let playbackItemSize: CGFloat
|
||||
let horizontalSpacing: CGFloat
|
||||
let verticalSpacing: CGFloat
|
||||
let itemInsets: UIEdgeInsets
|
||||
let headerHeight: CGFloat
|
||||
let itemTopOffset: CGFloat
|
||||
let itemCount: Int
|
||||
@ -2642,6 +2649,30 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var verticalGroupOrigin: CGFloat = self.itemInsets.top
|
||||
self.itemGroupLayouts = []
|
||||
for itemGroup in itemGroups {
|
||||
var itemsPerRow = self.itemsPerRow
|
||||
var nativeItemSize = self.nativeItemSize
|
||||
var visibleItemSize = self.visibleItemSize
|
||||
var playbackItemSize = self.playbackItemSize
|
||||
var horizontalSpacing = self.horizontalSpacing
|
||||
var verticalSpacing = self.verticalSpacing
|
||||
var itemInsets = self.itemInsets
|
||||
|
||||
if itemGroup.groupId == AnyHashable("stickers") {
|
||||
let minItemsPerRow = 5
|
||||
nativeItemSize = 70.0
|
||||
playbackItemSize = 96.0
|
||||
verticalSpacing = 2.0
|
||||
let minSpacing = 12.0
|
||||
|
||||
itemInsets = UIEdgeInsets(top: containerInsets.top, left: containerInsets.left + 10.0, bottom: containerInsets.bottom, right: containerInsets.right + 10.0)
|
||||
|
||||
let itemHorizontalSpace = width - itemInsets.left - itemInsets.right
|
||||
itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (nativeItemSize + minSpacing)))
|
||||
let proposedItemSize = floor((itemHorizontalSpace - minSpacing * (CGFloat(itemsPerRow) - 1.0)) / CGFloat(itemsPerRow))
|
||||
visibleItemSize = proposedItemSize < nativeItemSize ? proposedItemSize : nativeItemSize
|
||||
horizontalSpacing = floorToScreenPixels((itemHorizontalSpace - visibleItemSize * CGFloat(itemsPerRow)) / CGFloat(itemsPerRow - 1))
|
||||
}
|
||||
|
||||
var itemTopOffset: CGFloat = 0.0
|
||||
var headerHeight: CGFloat = 0.0
|
||||
var groupSpacing = self.verticalGroupDefaultSpacing
|
||||
@ -2663,7 +2694,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if itemGroup.isEmbedded {
|
||||
numRowsInGroup = 0
|
||||
} else {
|
||||
numRowsInGroup = (itemGroup.itemCount + (self.itemsPerRow - 1)) / self.itemsPerRow
|
||||
numRowsInGroup = (itemGroup.itemCount + (itemsPerRow - 1)) / itemsPerRow
|
||||
}
|
||||
|
||||
var collapsedItemIndex: Int?
|
||||
@ -2674,7 +2705,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
} else if let collapsedLineCount = itemGroup.collapsedLineCount, !expandedGroupIds.contains(itemGroup.groupId) {
|
||||
let maxLines: Int = collapsedLineCount
|
||||
if numRowsInGroup > maxLines {
|
||||
visibleItemCount = self.itemsPerRow * maxLines - 1
|
||||
visibleItemCount = itemsPerRow * maxLines - 1
|
||||
collapsedItemIndex = visibleItemCount
|
||||
collapsedItemText = "+\(itemGroup.itemCount - visibleItemCount)"
|
||||
} else {
|
||||
@ -2685,10 +2716,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
if !itemGroup.isEmbedded {
|
||||
numRowsInGroup = (visibleItemCount + (self.itemsPerRow - 1)) / self.itemsPerRow
|
||||
numRowsInGroup = (visibleItemCount + (itemsPerRow - 1)) / itemsPerRow
|
||||
}
|
||||
|
||||
var groupContentSize = CGSize(width: width, height: itemTopOffset + CGFloat(numRowsInGroup) * self.visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * self.verticalSpacing)
|
||||
var groupContentSize = CGSize(width: width, height: itemTopOffset + CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing)
|
||||
if (itemGroup.isPremiumLocked || itemGroup.isFeatured), case .compact = layoutType {
|
||||
groupContentSize.height += self.premiumButtonInset + self.premiumButtonHeight
|
||||
}
|
||||
@ -2697,6 +2728,13 @@ public final class EmojiPagerContentComponent: Component {
|
||||
frame: CGRect(origin: CGPoint(x: 0.0, y: verticalGroupOrigin), size: groupContentSize),
|
||||
supergroupId: itemGroup.supergroupId,
|
||||
groupId: itemGroup.groupId,
|
||||
itemsPerRow: itemsPerRow,
|
||||
nativeItemSize: nativeItemSize,
|
||||
visibleItemSize: visibleItemSize,
|
||||
playbackItemSize: playbackItemSize,
|
||||
horizontalSpacing: horizontalSpacing,
|
||||
verticalSpacing: verticalSpacing,
|
||||
itemInsets: itemInsets,
|
||||
headerHeight: headerHeight,
|
||||
itemTopOffset: itemTopOffset,
|
||||
itemCount: visibleItemCount,
|
||||
@ -2705,24 +2743,24 @@ public final class EmojiPagerContentComponent: Component {
|
||||
))
|
||||
verticalGroupOrigin += groupContentSize.height + groupSpacing
|
||||
}
|
||||
verticalGroupOrigin += self.itemInsets.bottom
|
||||
verticalGroupOrigin += itemInsets.bottom
|
||||
self.contentSize = CGSize(width: width, height: verticalGroupOrigin)
|
||||
}
|
||||
|
||||
func frame(groupIndex: Int, itemIndex: Int) -> CGRect {
|
||||
let groupLayout = self.itemGroupLayouts[groupIndex]
|
||||
|
||||
let row = itemIndex / self.itemsPerRow
|
||||
let column = itemIndex % self.itemsPerRow
|
||||
let row = itemIndex / groupLayout.itemsPerRow
|
||||
let column = itemIndex % groupLayout.itemsPerRow
|
||||
|
||||
return CGRect(
|
||||
origin: CGPoint(
|
||||
x: self.itemInsets.left + CGFloat(column) * (self.visibleItemSize + self.horizontalSpacing),
|
||||
y: groupLayout.frame.minY + groupLayout.itemTopOffset + CGFloat(row) * (self.visibleItemSize + self.verticalSpacing)
|
||||
x: groupLayout.itemInsets.left + CGFloat(column) * (groupLayout.visibleItemSize + groupLayout.horizontalSpacing),
|
||||
y: groupLayout.frame.minY + groupLayout.itemTopOffset + CGFloat(row) * (groupLayout.visibleItemSize + groupLayout.verticalSpacing)
|
||||
),
|
||||
size: CGSize(
|
||||
width: self.visibleItemSize,
|
||||
height: self.visibleItemSize
|
||||
width: groupLayout.visibleItemSize,
|
||||
height: groupLayout.visibleItemSize
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -2731,22 +2769,22 @@ public final class EmojiPagerContentComponent: Component {
|
||||
var result: [(supergroupId: AnyHashable, groupId: AnyHashable, groupIndex: Int, groupItems: Range<Int>?)] = []
|
||||
|
||||
for groupIndex in 0 ..< self.itemGroupLayouts.count {
|
||||
let group = self.itemGroupLayouts[groupIndex]
|
||||
let groupLayout = self.itemGroupLayouts[groupIndex]
|
||||
|
||||
if !rect.intersects(group.frame) {
|
||||
if !rect.intersects(groupLayout.frame) {
|
||||
continue
|
||||
}
|
||||
let offsetRect = rect.offsetBy(dx: -self.itemInsets.left, dy: -group.frame.minY - group.itemTopOffset)
|
||||
var minVisibleRow = Int(floor((offsetRect.minY - self.verticalSpacing) / (self.visibleItemSize + self.verticalSpacing)))
|
||||
let offsetRect = rect.offsetBy(dx: -groupLayout.itemInsets.left, dy: -groupLayout.frame.minY - groupLayout.itemTopOffset)
|
||||
var minVisibleRow = Int(floor((offsetRect.minY - groupLayout.verticalSpacing) / (groupLayout.visibleItemSize + groupLayout.verticalSpacing)))
|
||||
minVisibleRow = max(0, minVisibleRow)
|
||||
let maxVisibleRow = Int(ceil((offsetRect.maxY - self.verticalSpacing) / (self.visibleItemSize + self.verticalSpacing)))
|
||||
let maxVisibleRow = Int(ceil((offsetRect.maxY - groupLayout.verticalSpacing) / (groupLayout.visibleItemSize + groupLayout.verticalSpacing)))
|
||||
|
||||
let minVisibleIndex = minVisibleRow * self.itemsPerRow
|
||||
let maxVisibleIndex = min(group.itemCount - 1, (maxVisibleRow + 1) * self.itemsPerRow - 1)
|
||||
let minVisibleIndex = minVisibleRow * groupLayout.itemsPerRow
|
||||
let maxVisibleIndex = min(groupLayout.itemCount - 1, (maxVisibleRow + 1) * groupLayout.itemsPerRow - 1)
|
||||
|
||||
result.append((
|
||||
supergroupId: group.supergroupId,
|
||||
groupId: group.groupId,
|
||||
supergroupId: groupLayout.supergroupId,
|
||||
groupId: groupLayout.groupId,
|
||||
groupIndex: groupIndex,
|
||||
groupItems: maxVisibleIndex >= minVisibleIndex ? (minVisibleIndex ..< (maxVisibleIndex + 1)) : nil
|
||||
))
|
||||
@ -5204,6 +5242,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if !itemGroup.isEmbedded, let groupItemRange = groupItems.groupItems {
|
||||
for index in groupItemRange.lowerBound ..< groupItemRange.upperBound {
|
||||
let item = itemGroup.items[index]
|
||||
|
||||
|
||||
if assignTopVisibleSubgroupId {
|
||||
if let subgroupId = item.subgroupId {
|
||||
@ -5219,9 +5258,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
let itemDimensions: CGSize = item.animationData?.dimensions ?? CGSize(width: 512.0, height: 512.0)
|
||||
|
||||
let itemNativeFitSize = itemDimensions.aspectFitted(CGSize(width: itemLayout.nativeItemSize, height: itemLayout.nativeItemSize))
|
||||
let itemVisibleFitSize = itemDimensions.aspectFitted(CGSize(width: itemLayout.visibleItemSize, height: itemLayout.visibleItemSize))
|
||||
let itemPlaybackSize = itemDimensions.aspectFitted(CGSize(width: itemLayout.playbackItemSize, height: itemLayout.playbackItemSize))
|
||||
let itemNativeFitSize = itemDimensions.aspectFitted(CGSize(width: itemGroupLayout.nativeItemSize, height: itemGroupLayout.nativeItemSize))
|
||||
let itemVisibleFitSize = itemDimensions.aspectFitted(CGSize(width: itemGroupLayout.visibleItemSize, height: itemGroupLayout.visibleItemSize))
|
||||
let itemPlaybackSize = itemDimensions.aspectFitted(CGSize(width: itemGroupLayout.playbackItemSize, height: itemGroupLayout.playbackItemSize))
|
||||
|
||||
var animateItemIn = false
|
||||
var updateItemLayerPlaceholder = false
|
||||
@ -6309,6 +6348,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
isEmojiSelection: Bool,
|
||||
isTopicIconSelection: Bool = false,
|
||||
isQuickReactionSelection: Bool = false,
|
||||
isProfilePhotoEmojiSelection: Bool = false,
|
||||
isGroupPhotoEmojiSelection: Bool = false,
|
||||
topReactionItems: [EmojiComponentReactionItem],
|
||||
areUnicodeEmojiEnabled: Bool,
|
||||
areCustomEmojiEnabled: Bool,
|
||||
@ -6359,6 +6400,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
} else if isProfilePhotoEmojiSelection {
|
||||
//orderedItemListCollectionIds.append(Namespaces.OrderedItemList.CloudFeaturedProfilePhotoEmoji)
|
||||
}
|
||||
|
||||
let availableReactions: Signal<AvailableReactions?, NoError>
|
||||
@ -7071,7 +7114,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
|
||||
var displaySearchWithPlaceholder: String?
|
||||
var searchInitiallyHidden = true
|
||||
let searchInitiallyHidden = true
|
||||
if hasSearch {
|
||||
if isReactionSelection {
|
||||
displaySearchWithPlaceholder = strings.EmojiSearch_SearchReactionsPlaceholder
|
||||
@ -7079,7 +7122,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
displaySearchWithPlaceholder = strings.EmojiSearch_SearchStatusesPlaceholder
|
||||
} else if isEmojiSelection {
|
||||
displaySearchWithPlaceholder = strings.EmojiSearch_SearchEmojiPlaceholder
|
||||
searchInitiallyHidden = false
|
||||
} else if isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection {
|
||||
displaySearchWithPlaceholder = "Search"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7147,7 +7191,8 @@ public final class EmojiPagerContentComponent: Component {
|
||||
chatPeerId: EnginePeer.Id?,
|
||||
hasSearch: Bool,
|
||||
hasTrending: Bool,
|
||||
forceHasPremium: Bool
|
||||
forceHasPremium: Bool,
|
||||
searchIsPlaceholderOnly: Bool = true
|
||||
) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||
@ -7637,7 +7682,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
warpContentsOnEdges: false,
|
||||
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
|
||||
searchInitiallyHidden: true,
|
||||
searchIsPlaceholderOnly: true,
|
||||
searchIsPlaceholderOnly: searchIsPlaceholderOnly,
|
||||
emptySearchResults: nil,
|
||||
enableLongPress: false,
|
||||
selectedItems: Set()
|
||||
|
@ -108,6 +108,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
public let switchToGifSubject: (GifPagerContentComponent.Subject) -> Void
|
||||
public let reorderItems: (ReorderCategory, [EntityKeyboardTopPanelComponent.Item]) -> Void
|
||||
public let makeSearchContainerNode: (EntitySearchContentType) -> EntitySearchContainerNode?
|
||||
public let contentIdUpdated: (AnyHashable) -> Void
|
||||
public let deviceMetrics: DeviceMetrics
|
||||
public let hiddenInputHeight: CGFloat
|
||||
public let inputHeight: CGFloat
|
||||
@ -138,6 +139,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
switchToGifSubject: @escaping (GifPagerContentComponent.Subject) -> Void,
|
||||
reorderItems: @escaping (ReorderCategory, [EntityKeyboardTopPanelComponent.Item]) -> Void,
|
||||
makeSearchContainerNode: @escaping (EntitySearchContentType) -> EntitySearchContainerNode?,
|
||||
contentIdUpdated: @escaping (AnyHashable) -> Void,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
hiddenInputHeight: CGFloat,
|
||||
inputHeight: CGFloat,
|
||||
@ -167,6 +169,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
self.switchToGifSubject = switchToGifSubject
|
||||
self.reorderItems = reorderItems
|
||||
self.makeSearchContainerNode = makeSearchContainerNode
|
||||
self.contentIdUpdated = contentIdUpdated
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.hiddenInputHeight = hiddenInputHeight
|
||||
self.inputHeight = inputHeight
|
||||
@ -744,6 +747,12 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
strongSelf.isTopPanelHiddenUpdated(isTopPanelHidden: isTopPanelHidden, transition: transition)
|
||||
},
|
||||
contentIdUpdated: { [weak self] id in
|
||||
guard let strongSelf = self, let component = strongSelf.component else {
|
||||
return
|
||||
}
|
||||
component.contentIdUpdated(id)
|
||||
},
|
||||
panelHideBehavior: panelHideBehavior,
|
||||
clipContentToTopPanel: component.clipContentToTopPanel
|
||||
)),
|
||||
@ -862,6 +871,15 @@ public final class EntityKeyboardComponent: Component {
|
||||
component.hideTopPanelUpdated(self.isTopPanelHidden, transition)
|
||||
}
|
||||
|
||||
public func scrollToContentId(_ contentId: AnyHashable) {
|
||||
guard let _ = self.component else {
|
||||
return
|
||||
}
|
||||
if let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent<EntityKeyboardChildEnvironment, EntityKeyboardTopContainerPanelEnvironment>.View {
|
||||
pagerView.navigateToContentId(contentId)
|
||||
}
|
||||
}
|
||||
|
||||
public func openSearch() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
|
@ -416,6 +416,7 @@ private final class TopicIconSelectionComponent: Component {
|
||||
switchToGifSubject: { _ in },
|
||||
reorderItems: { _, _ in },
|
||||
makeSearchContainerNode: { _ in return nil },
|
||||
contentIdUpdated: { _ in },
|
||||
deviceMetrics: component.deviceMetrics,
|
||||
hiddenInputHeight: 0.0,
|
||||
inputHeight: 0.0,
|
||||
|
@ -84,6 +84,7 @@ import StickerPackPreviewUI
|
||||
import ChatListHeaderComponent
|
||||
import ChatControllerInteraction
|
||||
import StorageUsageScreen
|
||||
import AvatarEditorScreen
|
||||
|
||||
enum PeerInfoAvatarEditingMode {
|
||||
case generic
|
||||
@ -7208,6 +7209,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
mixin.requestAvatarEditor = { [weak self] completion in
|
||||
guard let strongSelf = self, let completion else {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = AvatarEditorScreen(context: strongSelf.context)
|
||||
controller.completion = completion
|
||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
|
||||
}
|
||||
|
||||
if let confirmationTextPhoto, let confirmationAction {
|
||||
mixin.willFinishWithImage = { [weak self] image, commit in
|
||||
if let strongSelf = self, let image {
|
||||
|
@ -15,7 +15,6 @@ import AppBundle
|
||||
import DatePickerNode
|
||||
import DebugSettingsUI
|
||||
import TabBarUI
|
||||
import DrawingUI
|
||||
|
||||
public final class TelegramRootController: NavigationController {
|
||||
private let context: AccountContext
|
||||
|
@ -8,6 +8,7 @@ import Display
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import LegacyUI
|
||||
import LegacyMediaPickerUI
|
||||
|
||||
func presentLegacyWebSearchEditor(context: AccountContext, theme: PresentationTheme, result: ChatContextResult, initialLayout: ContainerViewLayout?, updateHiddenMedia: @escaping (String?) -> Void, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (UIImage) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
guard let item = legacyWebSearchItem(account: context.account, result: result) else {
|
||||
@ -33,7 +34,10 @@ func presentLegacyWebSearchEditor(context: AccountContext, theme: PresentationTh
|
||||
let legacyController = LegacyController(presentation: .custom, theme: theme, initialLayout: initialLayout)
|
||||
legacyController.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style
|
||||
|
||||
let paintStickersContext = LegacyPaintStickersContext(context: context)
|
||||
|
||||
let controller = TGPhotoEditorController(context: legacyController.context, item: item, intent: TGPhotoEditorControllerAvatarIntent, adjustments: nil, caption: nil, screenImage: screenImage ?? UIImage(), availableTabs: TGPhotoEditorController.defaultTabsForAvatarIntent(), selectedTab: .cropTab)!
|
||||
controller.stickersContext = paintStickersContext
|
||||
legacyController.bind(controller: controller)
|
||||
|
||||
controller.editingContext = TGMediaEditingContext()
|
||||
|
Loading…
x
Reference in New Issue
Block a user