Attachment menu improvements

This commit is contained in:
Ilya Laktyushin 2022-02-22 01:39:12 +03:00
parent 33f503c41e
commit ac543cb093
6 changed files with 354 additions and 324 deletions

View File

@ -98,7 +98,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
return 210.0 return 210.0
} }
if case .compact = layout.metrics.widthClass { if case .compact = layout.metrics.widthClass {
return min(layout.size.width, layout.size.height) * 0.2488 return max(layout.size.width, layout.size.height) * 0.2488
} else { } else {
return 210.0 return 210.0
} }

View File

@ -0,0 +1,101 @@
import Foundation
import UIKit
import SwiftSignalKit
import Photos
import AVFoundation
class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver {
private var registeredChangeObserver = false
private let changeSink = ValuePipe<PHChange>()
private let mediaAccessSink = ValuePipe<PHAuthorizationStatus>()
private let cameraAccessSink = ValuePipe<AVAuthorizationStatus?>()
override init() {
super.init()
if PHPhotoLibrary.authorizationStatus() == .authorized {
PHPhotoLibrary.shared().register(self)
self.registeredChangeObserver = true
}
}
deinit {
if self.registeredChangeObserver {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
}
func photoLibraryDidChange(_ changeInstance: PHChange) {
self.changeSink.putNext(changeInstance)
}
func fetchResultAssets(_ initialFetchResult: PHFetchResult<PHAsset>) -> Signal<PHFetchResult<PHAsset>?, NoError> {
let fetchResult = Atomic<PHFetchResult<PHAsset>>(value: initialFetchResult)
return .single(initialFetchResult)
|> then(
self.changeSink.signal()
|> mapToSignal { change in
if let updatedFetchResult = change.changeDetails(for: fetchResult.with { $0 })?.fetchResultAfterChanges {
let _ = fetchResult.modify { _ in return updatedFetchResult }
return .single(updatedFetchResult)
} else {
return .complete()
}
}
)
}
func recentAssets() -> Signal<PHFetchResult<PHAsset>?, NoError> {
let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil)
if let collection = collections.firstObject {
let initialFetchResult = PHAsset.fetchAssets(in: collection, options: nil)
return fetchResultAssets(initialFetchResult)
} else {
return .single(nil)
}
}
func mediaAccess() -> Signal<PHAuthorizationStatus, NoError> {
let initialStatus: PHAuthorizationStatus
if #available(iOS 14.0, *) {
initialStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
} else {
initialStatus = PHPhotoLibrary.authorizationStatus()
}
return .single(initialStatus)
|> then(
self.mediaAccessSink.signal()
)
}
func requestMediaAccess() -> Void {
PHPhotoLibrary.requestAuthorization { [weak self] status in
self?.mediaAccessSink.putNext(status)
}
}
func cameraAccess() -> Signal<AVAuthorizationStatus?, NoError> {
#if targetEnvironment(simulator)
return .single(.authorized)
#else
if UIImagePickerController.isSourceTypeAvailable(.camera) {
return .single(AVCaptureDevice.authorizationStatus(for: .video))
|> then(
self.cameraAccessSink.signal()
)
} else {
return .single(nil)
}
#endif
}
func requestCameraAccess() -> Void {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] result in
if result {
self?.cameraAccessSink.putNext(.authorized)
} else {
self?.cameraAccessSink.putNext(.denied)
}
})
}
}

View File

@ -0,0 +1,8 @@
//
// MediaGroupsScreen.swift
// _idx_TelegramUI_F082088E_ios_min9.0
//
// Created by Ilya Laktyushin on 22.02.2022.
//
import Foundation

View File

@ -0,0 +1,131 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ManagedAnimationNode
import ContextUI
final class MediaPickerMoreButtonNode: ASDisplayNode {
class MoreIconNode: ManagedAnimationNode {
enum State: Equatable {
case more
case search
}
private let duration: Double = 0.21
var iconState: State = .search
init() {
super.init(size: CGSize(width: 30.0, height: 30.0))
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moretosearch"), frames: .range(startFrame: 90, endFrame: 90), duration: 0.0))
}
func play() {
if case .more = self.iconState {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moredots"), frames: .range(startFrame: 0, endFrame: 46), duration: 0.76))
}
}
func enqueueState(_ state: State, animated: Bool) {
guard self.iconState != state else {
return
}
let previousState = self.iconState
self.iconState = state
let source = ManagedAnimationSource.local("anim_moretosearch")
let totalLength: Int = 90
if animated {
switch previousState {
case .more:
switch state {
case .more:
break
case .search:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: totalLength), duration: self.duration))
}
case .search:
switch state {
case .more:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: 0), duration: self.duration))
case .search:
break
}
}
} else {
switch state {
case .more:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: 0), duration: 0.0))
case .search:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: totalLength), duration: 0.0))
}
}
}
}
var action: ((ASDisplayNode, ContextGesture?) -> Void)?
private let containerNode: ContextControllerSourceNode
let contextSourceNode: ContextReferenceContentNode
private let buttonNode: HighlightableButtonNode
let iconNode: MoreIconNode
var theme: PresentationTheme {
didSet {
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
}
}
init(theme: PresentationTheme) {
self.theme = theme
self.contextSourceNode = ContextReferenceContentNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.animateScale = false
self.buttonNode = HighlightableButtonNode()
self.iconNode = MoreIconNode()
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
super.init()
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.contextSourceNode)
self.contextSourceNode.addSubnode(self.iconNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
}
strongSelf.action?(strongSelf.contextSourceNode, gesture)
}
}
@objc private func buttonPressed() {
self.action?(self.contextSourceNode, nil)
if case .more = self.iconNode.iconState {
self.iconNode.play()
}
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
let animationSize = CGSize(width: 30.0, height: 30.0)
let inset: CGFloat = 0.0
self.iconNode.frame = CGRect(origin: CGPoint(x: inset + 4.0, y: floor((constrainedSize.height - animationSize.height) / 2.0)), size: animationSize)
let size = CGSize(width: animationSize.width + inset * 2.0, height: constrainedSize.height)
let bounds = CGRect(origin: CGPoint(), size: size)
self.buttonNode.frame = bounds
self.containerNode.frame = bounds
self.contextSourceNode.frame = bounds
return size
}
}

View File

@ -12,109 +12,11 @@ import MergeLists
import Photos import Photos
import PhotosUI import PhotosUI
import LegacyComponents import LegacyComponents
import AttachmentUI
import SegmentedControlNode
import ManagedAnimationNode
import ContextUI
import LegacyMediaPickerUI import LegacyMediaPickerUI
import AttachmentUI
import ContextUI
import WebSearchUI import WebSearchUI
private class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver {
private var registeredChangeObserver = false
private let changeSink = ValuePipe<PHChange>()
private let mediaAccessSink = ValuePipe<PHAuthorizationStatus>()
private let cameraAccessSink = ValuePipe<AVAuthorizationStatus?>()
override init() {
super.init()
if PHPhotoLibrary.authorizationStatus() == .authorized {
PHPhotoLibrary.shared().register(self)
self.registeredChangeObserver = true
}
}
deinit {
if self.registeredChangeObserver {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
}
func photoLibraryDidChange(_ changeInstance: PHChange) {
self.changeSink.putNext(changeInstance)
}
func fetchResultAssets(_ initialFetchResult: PHFetchResult<PHAsset>) -> Signal<PHFetchResult<PHAsset>?, NoError> {
let fetchResult = Atomic<PHFetchResult<PHAsset>>(value: initialFetchResult)
return .single(initialFetchResult)
|> then(
self.changeSink.signal()
|> mapToSignal { change in
if let updatedFetchResult = change.changeDetails(for: fetchResult.with { $0 })?.fetchResultAfterChanges {
let _ = fetchResult.modify { _ in return updatedFetchResult }
return .single(updatedFetchResult)
} else {
return .complete()
}
}
)
}
func recentAssets() -> Signal<PHFetchResult<PHAsset>?, NoError> {
let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil)
if let collection = collections.firstObject {
let initialFetchResult = PHAsset.fetchAssets(in: collection, options: nil)
return fetchResultAssets(initialFetchResult)
} else {
return .single(nil)
}
}
func mediaAccess() -> Signal<PHAuthorizationStatus, NoError> {
let initialStatus: PHAuthorizationStatus
if #available(iOS 14.0, *) {
initialStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite)
} else {
initialStatus = PHPhotoLibrary.authorizationStatus()
}
return .single(initialStatus)
|> then(
self.mediaAccessSink.signal()
)
}
func requestMediaAccess() -> Void {
PHPhotoLibrary.requestAuthorization { [weak self] status in
self?.mediaAccessSink.putNext(status)
}
}
func cameraAccess() -> Signal<AVAuthorizationStatus?, NoError> {
#if targetEnvironment(simulator)
return .single(.authorized)
#else
if UIImagePickerController.isSourceTypeAvailable(.camera) {
return .single(AVCaptureDevice.authorizationStatus(for: .video))
|> then(
self.cameraAccessSink.signal()
)
} else {
return .single(nil)
}
#endif
}
func requestCameraAccess() -> Void {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { [weak self] result in
if result {
self?.cameraAccessSink.putNext(.authorized)
} else {
self?.cameraAccessSink.putNext(.denied)
}
})
}
}
final class MediaPickerInteraction { final class MediaPickerInteraction {
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
@ -166,218 +68,6 @@ private struct MediaPickerGridTransaction {
} }
} }
private final class MediaPickerSegmentedTitleView: UIView {
private let titleNode: ImmediateTextNode
private let segmentedControlNode: SegmentedControlNode
public var theme: PresentationTheme {
didSet {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme))
}
}
public var title: String = "" {
didSet {
if self.title != oldValue {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
self.setNeedsLayout()
}
}
}
public var segmentsHidden = true {
didSet {
if self.segmentsHidden != oldValue {
let transition = ContainedViewLayoutTransition.animated(duration: 0.21, curve: .easeInOut)
transition.updateAlpha(node: self.titleNode, alpha: self.segmentsHidden ? 1.0 : 0.0)
transition.updateAlpha(node: self.segmentedControlNode, alpha: self.segmentsHidden ? 0.0 : 1.0)
self.segmentedControlNode.isUserInteractionEnabled = !self.segmentsHidden
}
}
}
public var segments: [String] {
didSet {
if self.segments != oldValue {
self.segmentedControlNode.items = self.segments.map { SegmentedControlItem(title: $0) }
self.setNeedsLayout()
}
}
}
public var index: Int {
get {
return self.segmentedControlNode.selectedIndex
}
set {
self.segmentedControlNode.selectedIndex = newValue
}
}
public var indexUpdated: ((Int) -> Void)?
public init(theme: PresentationTheme, segments: [String], selectedIndex: Int) {
self.theme = theme
self.segments = segments
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segments.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedIndex)
self.segmentedControlNode.alpha = 0.0
self.segmentedControlNode.isUserInteractionEnabled = false
super.init(frame: CGRect())
self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
self?.indexUpdated?(index)
}
self.addSubnode(self.titleNode)
self.addSubnode(self.segmentedControlNode)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func layoutSubviews() {
super.layoutSubviews()
let size = self.bounds.size
let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: min(300.0, size.width - 36.0)), transition: .immediate)
self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - controlSize.width) / 2.0), y: floorToScreenPixels((size.height - controlSize.height) / 2.0)), size: controlSize)
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: 44.0))
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
}
}
private final class MediaPickerMoreButtonNode: ASDisplayNode {
fileprivate final class MoreIconNode: ManagedAnimationNode {
enum State: Equatable {
case more
case search
}
private let duration: Double = 0.21
var iconState: State = .search
init() {
super.init(size: CGSize(width: 30.0, height: 30.0))
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moretosearch"), frames: .range(startFrame: 90, endFrame: 90), duration: 0.0))
}
func play() {
if case .more = self.iconState {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_moredots"), frames: .range(startFrame: 0, endFrame: 46), duration: 0.76))
}
}
func enqueueState(_ state: State, animated: Bool) {
guard self.iconState != state else {
return
}
let previousState = self.iconState
self.iconState = state
let source = ManagedAnimationSource.local("anim_moretosearch")
let totalLength: Int = 90
if animated {
switch previousState {
case .more:
switch state {
case .more:
break
case .search:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: totalLength), duration: self.duration))
}
case .search:
switch state {
case .more:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: 0), duration: self.duration))
case .search:
break
}
}
} else {
switch state {
case .more:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: 0, endFrame: 0), duration: 0.0))
case .search:
self.trackTo(item: ManagedAnimationItem(source: source, frames: .range(startFrame: totalLength, endFrame: totalLength), duration: 0.0))
}
}
}
}
var action: ((ASDisplayNode, ContextGesture?) -> Void)?
private let containerNode: ContextControllerSourceNode
let contextSourceNode: ContextReferenceContentNode
private let buttonNode: HighlightableButtonNode
let iconNode: MoreIconNode
var theme: PresentationTheme {
didSet {
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
}
}
init(theme: PresentationTheme) {
self.theme = theme
self.contextSourceNode = ContextReferenceContentNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.animateScale = false
self.buttonNode = HighlightableButtonNode()
self.iconNode = MoreIconNode()
self.iconNode.customColor = self.theme.rootController.navigationBar.buttonColor
super.init()
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.contextSourceNode)
self.contextSourceNode.addSubnode(self.iconNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
}
strongSelf.action?(strongSelf.contextSourceNode, gesture)
}
}
@objc private func buttonPressed() {
self.action?(self.contextSourceNode, nil)
if case .more = self.iconNode.iconState {
self.iconNode.play()
}
}
override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
let animationSize = CGSize(width: 30.0, height: 30.0)
let inset: CGFloat = 0.0
self.iconNode.frame = CGRect(origin: CGPoint(x: inset + 4.0, y: floor((constrainedSize.height - animationSize.height) / 2.0)), size: animationSize)
let size = CGSize(width: animationSize.width + inset * 2.0, height: constrainedSize.height)
let bounds = CGRect(origin: CGPoint(), size: size)
self.buttonNode.frame = bounds
self.containerNode.frame = bounds
self.contextSourceNode.frame = bounds
return size
}
}
public final class MediaPickerScreen: ViewController, AttachmentContainable { public final class MediaPickerScreen: ViewController, AttachmentContainable {
private let context: AccountContext private let context: AccountContext
private var presentationData: PresentationData private var presentationData: PresentationData
@ -386,7 +76,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private let peer: EnginePeer? private let peer: EnginePeer?
private let chatLocation: ChatLocation? private let chatLocation: ChatLocation?
private let titleView: MediaPickerSegmentedTitleView private let titleView: MediaPickerTitleView
private let moreButtonNode: MediaPickerMoreButtonNode private let moreButtonNode: MediaPickerMoreButtonNode
public weak var webSearchController: WebSearchController? public weak var webSearchController: WebSearchController?
@ -939,6 +629,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
if case let .noAccess(cameraAccess) = self.state { if case let .noAccess(cameraAccess) = self.state {
var placeholderTransition = transition
let placeholderNode: MediaPickerPlaceholderNode let placeholderNode: MediaPickerPlaceholderNode
if let current = self.placeholderNode { if let current = self.placeholderNode {
placeholderNode = current placeholderNode = current
@ -952,9 +643,14 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
self.insertSubnode(placeholderNode, aboveSubnode: self.gridNode) self.insertSubnode(placeholderNode, aboveSubnode: self.gridNode)
self.placeholderNode = placeholderNode self.placeholderNode = placeholderNode
if transition.isAnimated {
placeholderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
placeholderTransition = .immediate
} }
placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: cameraAccess == .authorized, transition: transition) placeholderNode.update(layout: layout, theme: self.presentationData.theme, strings: self.presentationData.strings, hasCamera: cameraAccess == .authorized, transition: placeholderTransition)
transition.updateFrame(node: placeholderNode, frame: bounds) placeholderTransition.updateFrame(node: placeholderNode, frame: bounds)
} else if let placeholderNode = self.placeholderNode { } else if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil self.placeholderNode = nil
placeholderNode.removeFromSupernode() placeholderNode.removeFromSupernode()
@ -991,7 +687,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.peer = peer self.peer = peer
self.chatLocation = chatLocation self.chatLocation = chatLocation
self.titleView = MediaPickerSegmentedTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
self.titleView.title = self.presentationData.strings.Attachment_Gallery self.titleView.title = self.presentationData.strings.Attachment_Gallery
self.moreButtonNode = MediaPickerMoreButtonNode(theme: self.presentationData.theme) self.moreButtonNode = MediaPickerMoreButtonNode(theme: self.presentationData.theme)
@ -1166,7 +862,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
} }
public var mediaPickerContext: MediaPickerContext? { public var mediaPickerContext: AttachmentMediaPickerContext? {
if let interaction = self.controllerNode.interaction { if let interaction = self.controllerNode.interaction {
return MediaPickerContext(interaction: interaction) return MediaPickerContext(interaction: interaction)
} else { } else {
@ -1175,10 +871,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
} }
} }
public class MediaPickerContext: AttachmentMediaPickerContext { final class MediaPickerContext: AttachmentMediaPickerContext {
private weak var interaction: MediaPickerInteraction? private weak var interaction: MediaPickerInteraction?
public var selectionCount: Signal<Int, NoError> { var selectionCount: Signal<Int, NoError> {
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
let disposable = self?.interaction?.selectionState?.selectionChangedSignal().start(next: { [weak self] value in let disposable = self?.interaction?.selectionState?.selectionChangedSignal().start(next: { [weak self] value in
subscriber.putNext(Int(self?.interaction?.selectionState?.count() ?? 0)) subscriber.putNext(Int(self?.interaction?.selectionState?.count() ?? 0))
@ -1189,7 +885,7 @@ public class MediaPickerContext: AttachmentMediaPickerContext {
} }
} }
public var caption: Signal<NSAttributedString?, NoError> { var caption: Signal<NSAttributedString?, NoError> {
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
let disposable = self?.interaction?.editingState.forcedCaption().start(next: { caption in let disposable = self?.interaction?.editingState.forcedCaption().start(next: { caption in
if let caption = caption as? NSAttributedString { if let caption = caption as? NSAttributedString {
@ -1208,15 +904,15 @@ public class MediaPickerContext: AttachmentMediaPickerContext {
self.interaction = interaction self.interaction = interaction
} }
public func setCaption(_ caption: NSAttributedString) { func setCaption(_ caption: NSAttributedString) {
self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true) self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
} }
public func send(silently: Bool, mode: AttachmentMediaPickerSendMode) { func send(silently: Bool, mode: AttachmentMediaPickerSendMode) {
self.interaction?.sendSelected(nil, silently, nil, true) self.interaction?.sendSelected(nil, silently, nil, true)
} }
public func schedule() { func schedule() {
self.interaction?.schedule() self.interaction?.schedule()
} }
} }

View File

@ -0,0 +1,94 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import SegmentedControlNode
final class MediaPickerTitleView: UIView {
private let titleNode: ImmediateTextNode
private let segmentedControlNode: SegmentedControlNode
public var theme: PresentationTheme {
didSet {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
self.segmentedControlNode.updateTheme(SegmentedControlTheme(theme: self.theme))
}
}
public var title: String = "" {
didSet {
if self.title != oldValue {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: NavigationBar.titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
self.setNeedsLayout()
}
}
}
public var segmentsHidden = true {
didSet {
if self.segmentsHidden != oldValue {
let transition = ContainedViewLayoutTransition.animated(duration: 0.21, curve: .easeInOut)
transition.updateAlpha(node: self.titleNode, alpha: self.segmentsHidden ? 1.0 : 0.0)
transition.updateAlpha(node: self.segmentedControlNode, alpha: self.segmentsHidden ? 0.0 : 1.0)
self.segmentedControlNode.isUserInteractionEnabled = !self.segmentsHidden
}
}
}
public var segments: [String] {
didSet {
if self.segments != oldValue {
self.segmentedControlNode.items = self.segments.map { SegmentedControlItem(title: $0) }
self.setNeedsLayout()
}
}
}
public var index: Int {
get {
return self.segmentedControlNode.selectedIndex
}
set {
self.segmentedControlNode.selectedIndex = newValue
}
}
public var indexUpdated: ((Int) -> Void)?
public init(theme: PresentationTheme, segments: [String], selectedIndex: Int) {
self.theme = theme
self.segments = segments
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.segmentedControlNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segments.map { SegmentedControlItem(title: $0) }, selectedIndex: selectedIndex)
self.segmentedControlNode.alpha = 0.0
self.segmentedControlNode.isUserInteractionEnabled = false
super.init(frame: CGRect())
self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
self?.indexUpdated?(index)
}
self.addSubnode(self.titleNode)
self.addSubnode(self.segmentedControlNode)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func layoutSubviews() {
super.layoutSubviews()
let size = self.bounds.size
let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: min(300.0, size.width - 36.0)), transition: .immediate)
self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - controlSize.width) / 2.0), y: floorToScreenPixels((size.height - controlSize.height) / 2.0)), size: controlSize)
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: 44.0))
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
}
}