Additional gallery options

This commit is contained in:
Ali 2021-10-08 23:03:22 +04:00
parent 90650e71c7
commit a7140ac206
15 changed files with 384 additions and 123 deletions

View File

@ -142,7 +142,38 @@ private class Scroller: UIScrollView {
}
}
private final class ImageCache: Equatable {
static func ==(lhs: ImageCache, rhs: ImageCache) -> Bool {
return lhs === rhs
}
private struct FilledCircle: Hashable {
var diameter: CGFloat
var color: UInt32
}
private var items: [AnyHashable: UIImage] = [:]
func filledCircle(diameter: CGFloat, color: UIColor) -> UIImage {
let key = AnyHashable(FilledCircle(diameter: diameter, color: color.argb))
if let image = self.items[key] {
return image
}
let image = generateImage(CGSize(width: diameter, height: diameter), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})!.stretchableImage(withLeftCapWidth: Int(diameter) / 2, topCapHeight: Int(diameter) / 2)
self.items[key] = image
return image
}
}
private final class DayComponent: Component {
typealias EnvironmentType = ImageCache
let title: String
let isCurrent: Bool
let isEnabled: Bool
@ -198,9 +229,6 @@ private final class DayComponent: Component {
private let titleNode: ImmediateTextNode
private var mediaPreviewNode: MediaPreviewNode?
private var currentTheme: PresentationTheme?
private var currentDiameter: CGFloat?
private var currentIsCurrent: Bool?
private var action: (() -> Void)?
private var currentMedia: DayMedia?
@ -227,40 +255,24 @@ private final class DayComponent: Component {
self.action?()
}
func update(component: DayComponent, availableSize: CGSize, transition: Transition) -> CGSize {
func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
self.action = component.action
let shadowInset: CGFloat = 0.0
let diameter = min(availableSize.width, availableSize.height)
var updated = false
if self.currentTheme !== component.theme || self.currentIsCurrent != component.isCurrent {
updated = true
}
if self.currentDiameter != diameter || updated {
self.currentDiameter = diameter
self.currentTheme = component.theme
self.currentIsCurrent = component.isCurrent
if component.isCurrent || component.media != nil {
self.highlightNode.image = generateImage(CGSize(width: diameter + shadowInset * 2.0, height: diameter + shadowInset * 2.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
if component.media != nil {
context.setFillColor(UIColor(white: 0.0, alpha: 0.2).cgColor)
} else {
context.setFillColor(component.theme.list.itemAccentColor.cgColor)
}
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: CGSize(width: size.width - shadowInset * 2.0, height: size.height - shadowInset * 2.0)))
})?.stretchableImage(withLeftCapWidth: Int(diameter + shadowInset * 2.0) / 2, topCapHeight: Int(diameter + shadowInset * 2.0) / 2)
} else {
self.highlightNode.image = nil
}
let imageCache = environment[ImageCache.self]
if component.media != nil {
self.highlightNode.image = imageCache.value.filledCircle(diameter: diameter, color: UIColor(white: 0.0, alpha: 0.2))
} else if component.isCurrent {
self.highlightNode.image = imageCache.value.filledCircle(diameter: diameter, color: component.theme.list.itemAccentColor)
} else {
self.highlightNode.image = nil
}
if self.currentMedia != component.media {
self.currentMedia = component.media
if let mediaPreviewNode = self.mediaPreviewNode {
self.mediaPreviewNode = nil
mediaPreviewNode.removeFromSupernode()
@ -308,12 +320,14 @@ private final class DayComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
func update(view: View, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}
private final class MonthComponent: CombinedComponent {
typealias EnvironmentType = ImageCache
let context: AccountContext
let model: MonthModel
let foregroundColor: UIColor
@ -359,7 +373,7 @@ private final class MonthComponent: CombinedComponent {
static var body: Body {
let title = Child(Text.self)
let weekdayTitles = ChildMap(environment: Empty.self, keyedBy: Int.self)
let days = ChildMap(environment: Empty.self, keyedBy: Int.self)
let days = ChildMap(environment: ImageCache.self, keyedBy: Int.self)
return { context in
let sideInset: CGFloat = 14.0
@ -423,6 +437,9 @@ private final class MonthComponent: CombinedComponent {
navigateToDay(dayTimestamp)
}
)),
environment: {
context.environment[ImageCache.self]
},
availableSize: CGSize(width: weekdaySize, height: weekdaySize),
transition: .immediate
)
@ -545,7 +562,9 @@ public final class CalendarMessageScreen: ViewController {
private var initialMonthIndex: Int = 0
private var months: [MonthModel] = []
private var monthViews: [Int: ComponentHostView<Empty>] = [:]
private var monthViews: [Int: ComponentHostView<ImageCache>] = [:]
private let imageCache = ImageCache()
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
private var scrollLayout: (width: CGFloat, contentHeight: CGFloat, frames: [Int: CGRect])?
@ -642,6 +661,10 @@ public final class CalendarMessageScreen: ViewController {
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let indicator = scrollView.value(forKey: "_verticalScrollIndicator") as? UIView {
indicator.transform = CGAffineTransform(scaleX: -1.0, y: 1.0)
}
self.updateMonthViews()
}
@ -656,7 +679,8 @@ public final class CalendarMessageScreen: ViewController {
var contentHeight: CGFloat = layout.intrinsicInsets.bottom
var frames: [Int: CGRect] = [:]
let measureView = ComponentHostView<Empty>()
let measureView = ComponentHostView<ImageCache>()
let imageCache = ImageCache()
for i in 0 ..< self.months.count {
let monthSize = measureView.update(
transition: .immediate,
@ -669,7 +693,9 @@ public final class CalendarMessageScreen: ViewController {
navigateToDay: { _ in
}
)),
environment: {},
environment: {
imageCache
},
containerSize: CGSize(width: layout.size.width, height: 10000.0
))
let monthFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: monthSize)
@ -684,7 +710,7 @@ public final class CalendarMessageScreen: ViewController {
self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight))
self.scrollView.contentSize = CGSize(width: layout.size.width, height: contentHeight)
self.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: layout.intrinsicInsets.bottom, left: 0.0, bottom: 0.0, right: 0.0)
self.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: layout.intrinsicInsets.bottom, left: 0.0, bottom: 0.0, right: layout.size.width - 3.0 - 6.0)
return true
}
@ -706,7 +732,7 @@ public final class CalendarMessageScreen: ViewController {
}
validMonths.insert(i)
let monthView: ComponentHostView<Empty>
let monthView: ComponentHostView<ImageCache>
if let current = self.monthViews[i] {
monthView = current
} else {
@ -730,7 +756,9 @@ public final class CalendarMessageScreen: ViewController {
strongSelf.navigateToDay(timestamp)
}
)),
environment: {},
environment: {
self.imageCache
},
containerSize: CGSize(width: width, height: 10000.0
))
monthView.frame = monthFrame

View File

@ -54,6 +54,7 @@ private func updateChildAnyComponent<EnvironmentType>(
let size = component._update(
view: view,
availableSize: availableSize,
environment: context.environment,
transition: transition
)
context.layoutResult.size = size
@ -609,7 +610,7 @@ public extension CombinedComponent {
return UIView()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
let context = view.getCombinedComponentContext(Self.self)
let storedBody: Body

View File

@ -100,7 +100,7 @@ public final class EmptyComponentState: ComponentState {
public protocol _TypeErasedComponent {
func _makeView() -> UIView
func _makeContext() -> _TypeErasedComponentContext
func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize
func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize
func _isEqual(to other: _TypeErasedComponent) -> Bool
}
@ -111,7 +111,7 @@ public protocol Component: _TypeErasedComponent, Equatable {
func makeView() -> View
func makeState() -> State
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize
func update(view: View, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize
}
public extension Component {
@ -123,8 +123,8 @@ public extension Component {
return ComponentContext<Self>(component: self, environment: Environment<EnvironmentType>(), state: self.makeState())
}
func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
return self.update(view: view as! Self.View, availableSize: availableSize, transition: transition)
func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
return self.update(view: view as! Self.View, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition)
}
func _isEqual(to other: _TypeErasedComponent) -> Bool {
@ -173,8 +173,8 @@ public class AnyComponent<EnvironmentType>: _TypeErasedComponent, Equatable {
return self.wrapped._makeContext()
}
public func _update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
return self.wrapped._update(view: view, availableSize: availableSize, transition: transition)
public func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
return self.wrapped._update(view: view, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition)
}
public func _isEqual(to other: _TypeErasedComponent) -> Bool {

View File

@ -55,7 +55,7 @@ public class _EnvironmentValue {
public final class EnvironmentValue<T: Equatable>: _EnvironmentValue, Equatable {
private var storage: EnvironmentValueStorage<T>
fileprivate var value: T {
public var value: T {
switch self.storage {
case let .direct(value):
return value

View File

@ -25,7 +25,7 @@ public final class Rectangle: Component {
return true
}
public func update(view: UIView, availableSize: CGSize, transition: Transition) -> CGSize {
public func update(view: UIView, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
var size = availableSize
if let width = self.width {
size.width = min(size.width, width)

View File

@ -95,7 +95,7 @@ public final class Text: Component {
return View()
}
public func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize)
}
}

View File

@ -54,7 +54,7 @@ public final class ComponentHostView<EnvironmentType>: UIView {
} as () -> Environment<EnvironmentType>, updateEnvironment: false, containerSize: containerSize)
}
let updatedSize = component._update(view: componentView, availableSize: containerSize, transition: transition)
let updatedSize = component._update(view: componentView, availableSize: containerSize, environment: context.erasedEnvironment, transition: transition)
transition.setFrame(view: componentView, frame: CGRect(origin: CGPoint(), size: updatedSize))
self.isUpdating = false

View File

@ -37,7 +37,7 @@ private final class RoundedRectangle: Component {
preconditionFailure()
}
func update(component: RoundedRectangle, availableSize: CGSize, transition: Transition) -> CGSize {
func update(component: RoundedRectangle, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
let shadowInset: CGFloat = 0.0
let diameter = min(availableSize.width, availableSize.height)
@ -76,8 +76,8 @@ private final class RoundedRectangle: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}
@ -113,7 +113,7 @@ private final class ShadowRoundedRectangle: Component {
preconditionFailure()
}
func update(component: ShadowRoundedRectangle, availableSize: CGSize, transition: Transition) -> CGSize {
func update(component: ShadowRoundedRectangle, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
let shadowInset: CGFloat = 10.0
let diameter = min(availableSize.width, availableSize.height)
@ -150,8 +150,8 @@ private final class ShadowRoundedRectangle: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
}
}

View File

@ -108,25 +108,20 @@ public final class SparseMessageList {
|> map { result -> SparseItems in
switch result {
case let .searchResultsPositions(totalCount, positions):
struct Position {
struct Position: Equatable {
var id: Int32
var date: Int32
var offset: Int
}
let positions: [Position] = positions.sorted(by: { lhs, rhs in
switch lhs {
case let .searchResultPosition(lhsId, _, _):
switch rhs {
case let .searchResultPosition(rhsId, _, _):
return lhsId > rhsId
}
}
}).map { position -> Position in
var positions: [Position] = positions.map { position -> Position in
switch position {
case let .searchResultPosition(id, date, offset):
return Position(id: id, date: date, offset: Int(offset))
}
}
positions.sort(by: { lhs, rhs in
return lhs.id > rhs.id
})
var result = SparseItems(items: [])
for i in 0 ..< positions.count {

View File

@ -22,6 +22,7 @@ public enum PresentationResourceKey: Int32 {
case navigationCompactSearchIcon
case navigationCalendarIcon
case navigationMoreIcon
case navigationMoreCircledIcon
case navigationAddIcon
case navigationPlayerCloseButton

View File

@ -92,6 +92,12 @@ public struct PresentationResourcesRootController {
})
})
}
public static func navigationMoreCircledIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationMoreCircledIcon.rawValue, { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.rootController.navigationBar.accentTextColor)
})
}
public static func navigationAddIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.navigationAddIcon.rawValue, { theme in

View File

@ -52,7 +52,7 @@ final class BlurredRoundedRectangle: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
@ -166,7 +166,7 @@ final class RadialProgressComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
@ -308,7 +308,7 @@ final class CheckComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
@ -558,7 +558,7 @@ final class AvatarComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
@ -656,7 +656,7 @@ private final class WallpaperBlurComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}
@ -979,7 +979,7 @@ final class OverscrollContentsComponent: Component {
return View()
}
func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize {
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -743,6 +743,10 @@ private func tagMaskForType(_ type: PeerInfoVisualMediaPaneNode.ContentType) ->
switch type {
case .photoOrVideo:
return .photoOrVideo
case .photo:
return .photo
case .video:
return .video
case .gifs:
return .gif
}
@ -758,11 +762,27 @@ private enum ItemsLayout {
let rowCount: Int
let contentHeight: CGFloat
init(containerWidth: CGFloat, itemCount: Int, bottomInset: CGFloat) {
init(containerWidth: CGFloat, zoomLevel: PeerInfoVisualMediaPaneNode.ZoomLevel, itemCount: Int, bottomInset: CGFloat) {
self.containerWidth = containerWidth
self.itemCount = itemCount
self.itemSpacing = 1.0
self.itemsInRow = max(3, min(6, Int(containerWidth / 140.0)))
let minItemsInRow: Int
let maxItemsInRow: Int
switch zoomLevel {
case .level2:
minItemsInRow = 2
maxItemsInRow = 4
case .level3:
minItemsInRow = 3
maxItemsInRow = 6
case .level4:
minItemsInRow = 4
maxItemsInRow = 8
case .level5:
minItemsInRow = 5
maxItemsInRow = 10
}
self.itemsInRow = max(minItemsInRow, min(maxItemsInRow, Int(containerWidth / 140.0)))
self.itemSize = floor(containerWidth / CGFloat(itemsInRow))
self.rowCount = itemCount / self.itemsInRow + (itemCount % self.itemsInRow == 0 ? 0 : 1)
@ -817,13 +837,48 @@ private enum ItemsLayout {
final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
enum ContentType {
case photoOrVideo
case photo
case video
case gifs
}
enum ZoomLevel {
case level2
case level3
case level4
case level5
func incremented() -> ZoomLevel {
switch self {
case .level2:
return .level3
case .level3:
return .level4
case .level4:
return .level5
case .level5:
return .level5
}
}
func decremented() -> ZoomLevel {
switch self {
case .level2:
return .level2
case .level3:
return .level2
case .level4:
return .level3
case .level5:
return .level4
}
}
}
private let context: AccountContext
private let peerId: PeerId
private let chatControllerInteraction: ChatControllerInteraction
private let contentType: ContentType
private(set) var contentType: ContentType
weak var parentController: ViewController?
@ -860,8 +915,10 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
private var animationTimer: SwiftSignalKit.Timer?
private let listSource: SparseMessageList
private var listSource: SparseMessageList
private var requestedPlaceholderIds = Set<MessageId>()
private(set) var zoomLevel: ZoomLevel = .level3
init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, contentType: ContentType) {
self.context = context
@ -908,7 +965,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
self.addSubnode(self.scrollNode)
self.addSubnode(self.scrollingArea)
self.requestHistoryAroundVisiblePosition()
self.requestHistoryAroundVisiblePosition(synchronous: false, reloadAtTop: false)
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
guard let strongSelf = self else {
@ -943,6 +1000,37 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
self.hiddenMediaDisposable?.dispose()
self.animationTimer?.invalidate()
}
func updateContentType(contentType: ContentType) {
if self.contentType == contentType {
return
}
self.contentType = contentType
self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, tag: tagMaskForType(self.contentType))
self.isRequestingView = false
self.requestHistoryAroundVisiblePosition(synchronous: true, reloadAtTop: true)
}
func updateZoomLevel(level: ZoomLevel) {
if self.zoomLevel == level {
return
}
self.zoomLevel = level
self.itemsLayout = nil
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
if let copyView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
copyView.backgroundColor = self.context.sharedContext.currentPresentationData.with({ $0 }).theme.list.plainBackgroundColor
self.view.addSubview(copyView)
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in
copyView?.removeFromSuperview()
})
}
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .immediate)
}
}
func ensureMessageIsVisible(id: MessageId) {
let activeRect = self.scrollNode.bounds
@ -959,22 +1047,26 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
}
}
private func requestHistoryAroundVisiblePosition() {
private func requestHistoryAroundVisiblePosition(synchronous: Bool, reloadAtTop: Bool) {
if self.isRequestingView {
return
}
self.isRequestingView = true
var firstTime = true
self.listDisposable.set((self.listSource.state
|> deliverOnMainQueue).start(next: { [weak self] list in
guard let strongSelf = self else {
return
}
strongSelf.updateHistory(list: list)
let currentSynchronous = synchronous && firstTime
let currentReloadAtTop = reloadAtTop && firstTime
firstTime = false
strongSelf.updateHistory(list: list, synchronous: currentSynchronous, reloadAtTop: currentReloadAtTop)
strongSelf.isRequestingView = false
}))
}
private func updateHistory(list: SparseMessageList.State) {
private func updateHistory(list: SparseMessageList.State, synchronous: Bool, reloadAtTop: Bool) {
self.mediaItems = VisualMediaItemCollection(items: [], totalCount: list.totalCount)
for item in list.items {
switch item.content {
@ -990,7 +1082,21 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
self.isFirstHistoryView = false
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate)
if synchronous {
if let copyView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
copyView.backgroundColor = self.context.sharedContext.currentPresentationData.with({ $0 }).theme.list.plainBackgroundColor
self.view.addSubview(copyView)
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in
copyView?.removeFromSuperview()
})
}
}
self.ignoreScrolling = true
if reloadAtTop {
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: false)
}
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView || synchronous, transition: .immediate)
self.ignoreScrolling = false
if !self.didSetReady {
self.didSetReady = true
self.ready.set(.single(true))
@ -1108,8 +1214,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
itemsLayout = current
} else {
switch self.contentType {
case .photoOrVideo, .gifs:
itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.totalCount, bottomInset: bottomInset))
case .photoOrVideo, .photo, .video, .gifs:
itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, zoomLevel: self.zoomLevel, itemCount: self.mediaItems.totalCount, bottomInset: bottomInset))
}
self.itemsLayout = itemsLayout
}
@ -1140,8 +1246,12 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
}
private var previousDidScrollTimestamp: Double = 0.0
private var ignoreScrolling: Bool = false
func scrollViewDidScroll(_ scrollView: UIScrollView) {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.ignoreScrolling {
return
}
if let (size, sideInset, bottomInset, visibleHeight, _, _, presentationData) = self.currentParams {
self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false)

View File

@ -916,6 +916,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
}
final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
let contextSourceNode: ContextReferenceContentNode
private let regularTextNode: ImmediateTextNode
private let whiteTextNode: ImmediateTextNode
private let iconNode: ASImageNode
@ -935,6 +936,8 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
var action: (() -> Void)?
init() {
self.contextSourceNode = ContextReferenceContentNode()
self.regularTextNode = ImmediateTextNode()
self.whiteTextNode = ImmediateTextNode()
self.whiteTextNode.isHidden = true
@ -948,9 +951,11 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
self.isAccessibilityElement = true
self.accessibilityTraits = .button
self.addSubnode(self.regularTextNode)
self.addSubnode(self.whiteTextNode)
self.addSubnode(self.iconNode)
self.contextSourceNode.addSubnode(self.regularTextNode)
self.contextSourceNode.addSubnode(self.whiteTextNode)
self.contextSourceNode.addSubnode(self.iconNode)
self.addSubnode(self.contextSourceNode)
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
}
@ -983,9 +988,9 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
text = presentationData.strings.Settings_EditPhoto
case .editVideo:
text = presentationData.strings.Settings_EditVideo
case .calendar:
text = ""
icon = PresentationResourcesRootController.navigationCalendarIcon(presentationData.theme)
case .more:
text = ""
icon = PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme)
}
self.accessibilityLabel = text
@ -1010,9 +1015,13 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(x: inset, y: floor((height - image.size.height) / 2.0)), size: image.size)
return CGSize(width: image.size.width + inset * 2.0, height: height)
let size = CGSize(width: image.size.width + inset * 2.0, height: height)
self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size)
return size
} else {
return CGSize(width: textSize.width + inset * 2.0, height: height)
let size = CGSize(width: textSize.width + inset * 2.0, height: height)
self.contextSourceNode.frame = CGRect(origin: CGPoint(), size: size)
return size
}
}
}
@ -1026,7 +1035,7 @@ enum PeerInfoHeaderNavigationButtonKey {
case search
case editPhoto
case editVideo
case calendar
case more
}
struct PeerInfoHeaderNavigationButtonSpec: Equatable {
@ -1049,7 +1058,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: ASDisplayNode {
}
}
var performAction: ((PeerInfoHeaderNavigationButtonKey) -> Void)?
var performAction: ((PeerInfoHeaderNavigationButtonKey, ContextReferenceContentNode?) -> Void)?
override init() {
super.init()
@ -1075,7 +1084,10 @@ final class PeerInfoHeaderNavigationButtonContainerNode: ASDisplayNode {
self.addSubnode(buttonNode)
buttonNode.isWhite = self.isWhite
buttonNode.action = { [weak self] in
self?.performAction?(spec.key)
guard let strongSelf = self, let buttonNode = strongSelf.buttonNodes[spec.key] else {
return
}
strongSelf.performAction?(spec.key, buttonNode.contextSourceNode)
}
}
let buttonSize = buttonNode.update(key: spec.key, presentationData: presentationData, height: size.height)

View File

@ -1834,7 +1834,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start()
}
})
@ -1853,7 +1853,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).start()
}
})
@ -1968,7 +1968,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
items.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { c, f in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start()
}
})
@ -1987,7 +1987,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
items.append(.action(ContextMenuActionItem(text: localOptionText, textColor: .destructive, icon: { _ in nil }, action: { c, f in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).start()
}
})
@ -2441,7 +2441,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
self.headerNode.navigationButtonContainer.performAction = { [weak self] key in
self.headerNode.navigationButtonContainer.performAction = { [weak self] key, source in
guard let strongSelf = self else {
return
}
@ -2482,7 +2482,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
strongSelf.view.endEditing(true)
if case .done = key {
guard let data = strongSelf.data else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
return
}
if let peer = data.peer as? TelegramUser {
@ -2525,10 +2525,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}))
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}
} else if data.isContact {
let firstName = strongSelf.headerNode.editingContentNode.editingTextForKey(.firstName) ?? ""
@ -2559,7 +2559,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}, completed: {
dismissStatus?()
@ -2588,14 +2588,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
}).start()
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}))
}
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}
} else if let group = data.peer as? TelegramGroup, canEditPeerInfo(context: strongSelf.context, peer: group) {
let title = strongSelf.headerNode.editingContentNode.editingTextForKey(.title) ?? ""
@ -2646,14 +2646,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}, completed: {
dismissStatus?()
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}))
}
} else if let channel = data.peer as? TelegramChannel, canEditPeerInfo(context: strongSelf.context, peer: channel) {
@ -2705,21 +2705,21 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}, completed: {
dismissStatus?()
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}))
}
}
proceed()
} else {
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}
} else {
strongSelf.state = strongSelf.state.withIsEditing(false)
@ -2748,8 +2748,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
case .search:
strongSelf.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
strongSelf.activateSearch()
case .calendar:
strongSelf.openCalendarSearch()
case .more:
if let source = source {
strongSelf.displayMediaGalleryContextMenu(source: source)
}
case .editPhoto, .editVideo:
break
}
@ -3001,7 +3003,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
@objc private func editingCancelPressed() {
self.headerNode.navigationButtonContainer.performAction?(.cancel)
self.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
}
private func openMessage(id: MessageId) -> Bool {
@ -4063,7 +4065,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self else {
return
}
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel)
strongSelf.headerNode.navigationButtonContainer.performAction?(.cancel, nil)
let text: String
switch error {
@ -5403,7 +5405,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
case .avatar:
self.openAvatarForEditing()
case .edit:
self.headerNode.navigationButtonContainer.performAction?(.edit)
self.headerNode.navigationButtonContainer.performAction?(.edit, nil)
case .proxy:
self.controller?.push(proxySettingsController(context: self.context))
case .savedMessages:
@ -5662,7 +5664,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start()
}
}))
@ -5679,7 +5681,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).start()
}
}))
@ -5795,7 +5797,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
return .forward(source: id, grouping: .auto, attributes: [], correlationId: nil)
@ -5829,7 +5831,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
})
|> deliverOnMainQueue).start(completed: {
if let strongSelf = self {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
if let navigationController = strongSelf.controller?.navigationController as? NavigationController {
let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId))
@ -5946,7 +5948,113 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
private func openCalendarSearch() {
private func displayMediaGalleryContextMenu(source: ContextReferenceContentNode) {
guard let controller = self.controller else {
return
}
guard let pane = self.paneContainerNode.currentPane?.node as? PeerInfoVisualMediaPaneNode else {
return
}
var items: [ContextMenuItem] = []
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Zoom In", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor)
}, action: { [weak pane] _, a in
a(.default)
guard let pane = pane else {
return
}
pane.updateZoomLevel(level: pane.zoomLevel.decremented())
})))
items.append(.action(ContextMenuActionItem(text: "Zoom Out", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor)
}, action: { [weak pane] _, a in
a(.default)
guard let pane = pane else {
return
}
pane.updateZoomLevel(level: pane.zoomLevel.incremented())
})))
items.append(.action(ContextMenuActionItem(text: "Show Calendar", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/Calendar"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
a(.default)
self?.openMediaCalendar()
})))
items.append(.separator)
let showPhotos: Bool
switch pane.contentType {
case .photo, .photoOrVideo:
showPhotos = true
default:
showPhotos = false
}
let showVideos: Bool
switch pane.contentType {
case .video, .photoOrVideo:
showVideos = true
default:
showVideos = false
}
items.append(.action(ContextMenuActionItem(text: "Show Photos", icon: { theme in
if !showPhotos {
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak pane] _, a in
a(.default)
guard let pane = pane else {
return
}
let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType
switch pane.contentType {
case .photoOrVideo:
updatedContentType = .video
case .photo:
updatedContentType = .photo
case .video:
updatedContentType = .photoOrVideo
case .gifs:
updatedContentType = .gifs
}
pane.updateContentType(contentType: updatedContentType)
})))
items.append(.action(ContextMenuActionItem(text: "Show Videos", icon: { theme in
if !showVideos {
return nil
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak pane] _, a in
a(.default)
guard let pane = pane else {
return
}
let updatedContentType: PeerInfoVisualMediaPaneNode.ContentType
switch pane.contentType {
case .photoOrVideo:
updatedContentType = .photo
case .photo:
updatedContentType = .photoOrVideo
case .video:
updatedContentType = .video
case .gifs:
updatedContentType = .gifs
}
pane.updateContentType(contentType: updatedContentType)
})))
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: source)), items: .single(ContextController.Items(items: items)), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
private func openMediaCalendar() {
var initialTimestamp = Int32(Date().timeIntervalSince1970)
if let pane = self.paneContainerNode.currentPane?.node as? PeerInfoVisualMediaPaneNode, let timestamp = pane.currentTopTimestamp() {
@ -6156,7 +6264,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
|> deliverOnMainQueue).start(next: { messages in
if let strongSelf = self, !messages.isEmpty {
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil)
let shareController = ShareController(context: strongSelf.context, subject: .messages(messages.sorted(by: { lhs, rhs in
return lhs.index < rhs.index
@ -6303,7 +6411,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
case .files, .music, .links, .members:
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
case .media:
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .calendar, isForExpandedView: true))
navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true))
default:
break
}