Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-07-14 01:07:09 +03:00
parent a260717c88
commit 8d58a2b239
20 changed files with 513 additions and 167 deletions

View File

@ -1,12 +1,44 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import LegacyComponents
public final class VoiceBlobNode: ASDisplayNode {
public init(
maxLevel: CGFloat,
smallBlobRange: VoiceBlobView.BlobRange,
mediumBlobRange: VoiceBlobView.BlobRange,
bigBlobRange: VoiceBlobView.BlobRange
) {
super.init()
self.setViewBlock({
return VoiceBlobView(frame: CGRect(), maxLevel: maxLevel, smallBlobRange: smallBlobRange, mediumBlobRange: mediumBlobRange, bigBlobRange: bigBlobRange)
})
}
public func startAnimating(immediately: Bool = false) {
(self.view as? VoiceBlobView)?.startAnimating(immediately: immediately)
}
public func stopAnimating(duration: Double = 0.15) {
(self.view as? VoiceBlobView)?.stopAnimating(duration: duration)
}
public func setColor(_ color: UIColor, animated: Bool = false) {
(self.view as? VoiceBlobView)?.setColor(color, animated: animated)
}
public func updateLevel(_ level: CGFloat, immediately: Bool = false) {
(self.view as? VoiceBlobView)?.updateLevel(level, immediately: immediately)
}
}
public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDecoration {
private let smallBlob: BlobView
private let mediumBlob: BlobView
private let bigBlob: BlobView
private let smallBlob: BlobNode
private let mediumBlob: BlobNode
private let bigBlob: BlobNode
private let maxLevel: CGFloat
@ -28,7 +60,7 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
) {
self.maxLevel = maxLevel
self.smallBlob = BlobView(
self.smallBlob = BlobNode(
pointsCount: 8,
minRandomness: 0.1,
maxRandomness: 0.5,
@ -39,23 +71,23 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
scaleSpeed: 0.2,
isCircle: true
)
self.mediumBlob = BlobView(
self.mediumBlob = BlobNode(
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minSpeed: 0.9,
maxSpeed: 4,
minScale: mediumBlobRange.min,
maxScale: mediumBlobRange.max,
scaleSpeed: 0.2,
isCircle: false
)
self.bigBlob = BlobView(
self.bigBlob = BlobNode(
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minSpeed: 0.9,
maxSpeed: 4,
minScale: bigBlobRange.min,
maxScale: bigBlobRange.max,
scaleSpeed: 0.2,
@ -64,9 +96,9 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
super.init(frame: frame)
addSubview(bigBlob)
addSubview(mediumBlob)
addSubview(smallBlob)
self.addSubnode(self.bigBlob)
self.addSubnode(self.mediumBlob)
self.addSubnode(self.smallBlob)
displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in
guard let strongSelf = self else { return }
@ -148,8 +180,8 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
}
private func updateBlobsState() {
if isAnimating {
if smallBlob.frame.size != .zero {
if self.isAnimating {
if self.smallBlob.frame.size != .zero {
smallBlob.startAnimating()
mediumBlob.startAnimating()
bigBlob.startAnimating()
@ -164,15 +196,15 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
override public func layoutSubviews() {
super.layoutSubviews()
smallBlob.frame = bounds
mediumBlob.frame = bounds
bigBlob.frame = bounds
self.smallBlob.frame = bounds
self.mediumBlob.frame = bounds
self.bigBlob.frame = bounds
updateBlobsState()
self.updateBlobsState()
}
}
final class BlobView: UIView {
final class BlobNode: ASDisplayNode {
let pointsCount: Int
let smoothness: CGFloat
@ -186,8 +218,6 @@ final class BlobView: UIView {
let maxScale: CGFloat
let scaleSpeed: CGFloat
var scaleLevelsToBalance = [CGFloat]()
let isCircle: Bool
var level: CGFloat = 0 {
@ -261,9 +291,9 @@ final class BlobView: UIView {
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
super.init(frame: .zero)
super.init()
layer.addSublayer(shapeLayer)
self.layer.addSublayer(self.shapeLayer)
self.shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1)
}
@ -282,10 +312,6 @@ final class BlobView: UIView {
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
self.speedLevel = max(self.speedLevel, newSpeedLevel)
// if abs(lastSpeedLevel - newSpeedLevel) > 0.5 {
// animateToNewShape()
// }
}
func startAnimating() {
@ -368,16 +394,16 @@ final class BlobView: UIView {
return points
}
override func layoutSubviews() {
super.layoutSubviews()
override func layout() {
super.layout()
CATransaction.begin()
CATransaction.setDisableActions(true)
shapeLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
if isCircle {
let halfWidth = bounds.width * 0.5
shapeLayer.path = UIBezierPath(
roundedRect: bounds.offsetBy(dx: -halfWidth, dy: -halfWidth),
self.shapeLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
if self.isCircle {
let halfWidth = self.bounds.width * 0.5
self.shapeLayer.path = UIBezierPath(
roundedRect: self.bounds.offsetBy(dx: -halfWidth, dy: -halfWidth),
cornerRadius: halfWidth
).cgPath
}
@ -386,7 +412,6 @@ final class BlobView: UIView {
}
private extension UIBezierPath {
static func smoothCurve(
through points: [CGPoint],
length: CGFloat,
@ -439,7 +464,6 @@ private extension UIBezierPath {
}
struct SmoothPoint {
let point: CGPoint
let inAngle: CGFloat
@ -464,4 +488,3 @@ private extension UIBezierPath {
}
}
}

View File

@ -1272,6 +1272,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
if !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing {
var isEditing = false
strongSelf.chatListDisplayNode.containerNode.updateState { state in
isEditing = state.editing
return state
}
if !isEditing {
strongSelf.editPressed()
}
strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing = true
if let layout = strongSelf.validLayout {
strongSelf.updateLayout(layout: layout, transition: .animated(duration: 0.2, curve: .easeInOut))

View File

@ -182,7 +182,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
let peer1 = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt32Value(0)), accessHash: nil, firstName: "FirstName", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
let timestamp1: Int32 = 100000
let peers = SimpleDictionary<PeerId, Peer>()
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in
gesture?.cancel()
}, present: { _ in })

View File

@ -1217,6 +1217,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self?.listNode.clearHighlightAnimated(true)
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, togglePeersSelection: { _, _ in
}, additionalCategorySelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
interaction.dismissInput()
@ -2324,7 +2325,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
let timestamp1: Int32 = 100000
var peers = SimpleDictionary<PeerId, Peer>()
peers[peer1.id] = peer1
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in }, activateChatPreview: { _, _, gesture in
gesture?.cancel()
}, present: { _ in })

View File

@ -696,7 +696,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
}
self.contextContainer.isGestureEnabled = enablePreview
self.contextContainer.isGestureEnabled = enablePreview && !item.editing
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
@ -1509,7 +1509,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.onlineIsVoiceChat = onlineIsVoiceChat
strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
if case .groupReference = item.content {
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0)
}

View File

@ -48,10 +48,16 @@ final class ChatListHighlightedLocation {
}
public final class ChatListNodeInteraction {
public enum PeerEntry {
case peerId(PeerId)
case peer(Peer)
}
let activateSearch: () -> Void
let peerSelected: (Peer, ChatListNodeEntryPromoInfo?) -> Void
let disabledPeerSelected: (Peer) -> Void
let togglePeerSelected: (Peer) -> Void
let togglePeersSelection: ([PeerEntry], Bool) -> Void
let additionalCategorySelected: (Int) -> Void
let messageSelected: (Peer, Message, ChatListNodeEntryPromoInfo?) -> Void
let groupSelected: (PeerGroupId) -> Void
@ -70,11 +76,12 @@ public final class ChatListNodeInteraction {
public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation?
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (Peer) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (Peer, Message, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId, Bool) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (PeerId) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) {
public init(activateSearch: @escaping () -> Void, peerSelected: @escaping (Peer, ChatListNodeEntryPromoInfo?) -> Void, disabledPeerSelected: @escaping (Peer) -> Void, togglePeerSelected: @escaping (Peer) -> Void, togglePeersSelection: @escaping ([PeerEntry], Bool) -> Void, additionalCategorySelected: @escaping (Int) -> Void, messageSelected: @escaping (Peer, Message, ChatListNodeEntryPromoInfo?) -> Void, groupSelected: @escaping (PeerGroupId) -> Void, addContact: @escaping (String) -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, setItemPinned: @escaping (PinnedItemId, Bool) -> Void, setPeerMuted: @escaping (PeerId, Bool) -> Void, deletePeer: @escaping (PeerId, Bool) -> Void, updatePeerGrouping: @escaping (PeerId, Bool) -> Void, togglePeerMarkedUnread: @escaping (PeerId, Bool) -> Void, toggleArchivedFolderHiddenByDefault: @escaping () -> Void, hidePsa: @escaping (PeerId) -> Void, activateChatPreview: @escaping (ChatListItem, ASDisplayNode, ContextGesture?) -> Void, present: @escaping (ViewController) -> Void) {
self.activateSearch = activateSearch
self.peerSelected = peerSelected
self.disabledPeerSelected = disabledPeerSelected
self.togglePeerSelected = togglePeerSelected
self.togglePeersSelection = togglePeersSelection
self.additionalCategorySelected = additionalCategorySelected
self.messageSelected = messageSelected
self.groupSelected = groupSelected
@ -565,6 +572,8 @@ public final class ChatListNode: ListView {
var didBeginSelectingChats: (() -> Void)?
public var selectionCountChanged: ((Int) -> Void)?
var isSelectionGestureEnabled = true
public init(context: AccountContext, groupId: PeerGroupId, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
self.context = context
self.groupId = groupId
@ -625,6 +634,34 @@ public final class ChatListNode: ListView {
if didBeginSelecting {
self?.didBeginSelectingChats?()
}
}, togglePeersSelection: { [weak self] peers, selected in
self?.updateState { state in
var state = state
if selected {
for peerEntry in peers {
switch peerEntry {
case let .peer(peer):
state.selectedPeerIds.insert(peer.id)
state.selectedPeerMap[peer.id] = peer
case let .peerId(peerId):
state.selectedPeerIds.insert(peerId)
}
}
} else {
for peerEntry in peers {
switch peerEntry {
case let .peer(peer):
state.selectedPeerIds.remove(peer.id)
case let .peerId(peerId):
state.selectedPeerIds.remove(peerId)
}
}
}
return state
}
if selected && !peers.isEmpty {
self?.didBeginSelectingChats?()
}
}, additionalCategorySelected: { [weak self] id in
self?.additionalCategorySelected?(id)
}, messageSelected: { [weak self] peer, message, promoInfo in
@ -1315,6 +1352,15 @@ public final class ChatListNode: ListView {
}
self.resetFilter()
let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:)))
selectionRecognizer.shouldBegin = { [weak self] in
guard let strongSelf = self else {
return false
}
return strongSelf.isSelectionGestureEnabled
}
self.view.addGestureRecognizer(selectionRecognizer)
}
deinit {
@ -1898,6 +1944,140 @@ public final class ChatListNode: ListView {
}
return nil
}
private func peerAtPoint(_ point: CGPoint) -> Peer? {
var resultPeer: Peer?
self.forEachVisibleItemNode { itemNode in
if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) {
if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item {
switch item.content {
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _):
resultPeer = peer.peer
default:
break
}
}
}
}
return resultPeer
}
private var selectionPanState: (selecting: Bool, initialPeerId: PeerId, toggledPeerIds: [[PeerId]])?
private var selectionScrollActivationTimer: SwiftSignalKit.Timer?
private var selectionScrollDisplayLink: ConstantDisplayLinkAnimator?
private var selectionScrollDelta: CGFloat?
private var selectionLastLocation: CGPoint?
@objc private func selectionPanGesture(_ recognizer: UIGestureRecognizer) -> Void {
let location = recognizer.location(in: self.view)
switch recognizer.state {
case .began:
if let peer = self.peerAtPoint(location) {
let selecting = !self.currentState.selectedPeerIds.contains(peer.id)
self.selectionPanState = (selecting, peer.id, [])
self.interaction?.togglePeersSelection([.peer(peer)], selecting)
}
case .changed:
self.handlePanSelection(location: location)
self.selectionLastLocation = location
case .ended, .failed, .cancelled:
self.selectionPanState = nil
self.selectionScrollDisplayLink = nil
self.selectionScrollActivationTimer?.invalidate()
self.selectionScrollActivationTimer = nil
self.selectionScrollDelta = nil
self.selectionLastLocation = nil
self.selectionScrollSkipUpdate = false
case .possible:
break
@unknown default:
fatalError()
}
}
private func handlePanSelection(location: CGPoint) {
if let state = self.selectionPanState {
if let peer = self.peerAtPoint(location) {
if peer.id == state.initialPeerId {
if !state.toggledPeerIds.isEmpty {
self.interaction?.togglePeersSelection(state.toggledPeerIds.flatMap { $0.compactMap({ .peerId($0) }) }, !state.selecting)
self.selectionPanState = (state.selecting, state.initialPeerId, [])
}
} else if state.toggledPeerIds.last?.first != peer.id {
var updatedToggledPeerIds: [[PeerId]] = []
var previouslyToggled = false
for i in (0 ..< state.toggledPeerIds.count) {
if let peerId = state.toggledPeerIds[i].first {
if peerId == peer.id {
previouslyToggled = true
updatedToggledPeerIds = Array(state.toggledPeerIds.prefix(i + 1))
let peerIdsToToggle = Array(state.toggledPeerIds.suffix(state.toggledPeerIds.count - i - 1)).flatMap { $0 }
self.interaction?.togglePeersSelection(peerIdsToToggle.compactMap { .peerId($0) }, !state.selecting)
break
}
}
}
if !previouslyToggled {
updatedToggledPeerIds = state.toggledPeerIds
let isSelected = self.currentState.selectedPeerIds.contains(peer.id)
if state.selecting != isSelected {
updatedToggledPeerIds.append([peer.id])
self.interaction?.togglePeersSelection([.peer(peer)], state.selecting)
}
}
self.selectionPanState = (state.selecting, state.initialPeerId, updatedToggledPeerIds)
}
}
let scrollingAreaHeight: CGFloat = 50.0
if location.y < scrollingAreaHeight + self.insets.top || location.y > self.frame.height - scrollingAreaHeight - self.insets.bottom {
if location.y < self.frame.height / 2.0 {
self.selectionScrollDelta = (scrollingAreaHeight - (location.y - self.insets.top)) / scrollingAreaHeight
} else {
self.selectionScrollDelta = -(scrollingAreaHeight - min(scrollingAreaHeight, max(0.0, (self.frame.height - self.insets.bottom - location.y)))) / scrollingAreaHeight
}
if let displayLink = self.selectionScrollDisplayLink {
displayLink.isPaused = false
} else {
if let _ = self.selectionScrollActivationTimer {
} else {
let timer = SwiftSignalKit.Timer(timeout: 0.45, repeat: false, completion: { [weak self] in
self?.setupSelectionScrolling()
}, queue: .mainQueue())
timer.start()
self.selectionScrollActivationTimer = timer
}
}
} else {
self.selectionScrollDisplayLink?.isPaused = true
self.selectionScrollActivationTimer?.invalidate()
self.selectionScrollActivationTimer = nil
}
}
}
private var selectionScrollSkipUpdate = false
private func setupSelectionScrolling() {
self.selectionScrollDisplayLink = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.selectionScrollActivationTimer = nil
if let strongSelf = self, let delta = strongSelf.selectionScrollDelta {
let distance: CGFloat = 15.0 * min(1.0, 0.15 + abs(delta * delta))
let direction: ListViewScrollDirection = delta > 0.0 ? .up : .down
let _ = strongSelf.scrollWithDirection(direction, distance: distance)
if let location = strongSelf.selectionLastLocation {
if !strongSelf.selectionScrollSkipUpdate {
strongSelf.handlePanSelection(location: location)
}
strongSelf.selectionScrollSkipUpdate = !strongSelf.selectionScrollSkipUpdate
}
}
})
self.selectionScrollDisplayLink?.isPaused = false
}
}
private func statusStringForPeerType(accountPeerId: PeerId, strings: PresentationStrings, peer: Peer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> (String, Bool)? {
@ -1951,3 +2131,65 @@ private func statusStringForPeerType(accountPeerId: PeerId, strings: Presentatio
}
return (strings.ChatList_PeerTypeNonContact, false)
}
public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
private let selectionGestureActivationThreshold: CGFloat = 5.0
var recognized: Bool? = nil
var initialLocation: CGPoint = CGPoint()
public var shouldBegin: (() -> Bool)?
public override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.minimumNumberOfTouches = 2
self.maximumNumberOfTouches = 2
}
public override func reset() {
super.reset()
self.recognized = nil
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let shouldBegin = self.shouldBegin, !shouldBegin() {
self.state = .failed
} else {
let touch = touches.first!
self.initialLocation = touch.location(in: self.view)
}
}
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
let location = touches.first!.location(in: self.view)
let translation = location.offsetBy(dx: -self.initialLocation.x, dy: -self.initialLocation.y)
let touchesArray = Array(touches)
if self.recognized == nil, touchesArray.count == 2 {
if let firstTouch = touchesArray.first, let secondTouch = touchesArray.last {
let firstLocation = firstTouch.location(in: self.view)
let secondLocation = secondTouch.location(in: self.view)
func distance(_ v1: CGPoint, _ v2: CGPoint) -> CGFloat {
let dx = v1.x - v2.x
let dy = v1.y - v2.y
return sqrt(dx * dx + dy * dy)
}
if distance(firstLocation, secondLocation) > 200.0 {
self.state = .failed
}
}
if self.state != .failed && (abs(translation.y) >= selectionGestureActivationThreshold) {
self.recognized = true
}
}
if let recognized = self.recognized, recognized {
super.touchesMoved(touches, with: event)
}
}
}

View File

@ -52,6 +52,7 @@ public final class HashtagSearchController: TelegramBaseController {
}, peerSelected: { _, _ in
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, togglePeersSelection: { _, _ in
}, additionalCategorySelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
if let strongSelf = self {

View File

@ -1414,7 +1414,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
- (CGFloat)_brushWeightForSize:(CGFloat)size
{
return [self _brushBaseWeightForCurrentPainting] + [self _brushWeightRangeForCurrentPainting] * size;
return ([self _brushBaseWeightForCurrentPainting] + [self _brushWeightRangeForCurrentPainting] * size) / _scrollView.zoomScale;
}
+ (CGSize)maximumPaintingSize
@ -1739,6 +1739,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
{
[self adjustZoom];
TGPaintSwatch *currentSwatch = _portraitSettingsView.swatch;
[_canvasView setBrushWeight:[self _brushWeightForSize:currentSwatch.brushWeight]];
if (_scrollView.zoomScale < _scrollView.normalZoomScale - FLT_EPSILON)
{
[TGHacks setAnimationDurationFactor:0.5f];

View File

@ -132,6 +132,11 @@ open class ManagedAnimationNode: ASDisplayNode {
private let imageNode: ASImageNode
private let displayLink: CADisplayLink
public var imageUpdated: ((UIImage) -> Void)?
public var image: UIImage? {
return self.imageNode.image
}
public var state: ManagedAnimationState?
public var trackStack: [ManagedAnimationItem] = []
public var didTryAdvancingState = false
@ -260,6 +265,7 @@ open class ManagedAnimationNode: ASDisplayNode {
} else {
self.imageNode.image = image
}
self.imageUpdated?(image)
}
for (callbackFrame, callback) in state.item.callbacks {

View File

@ -317,13 +317,17 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
if value != self._statusValue {
if let value = value, value.seekId == self.ignoreSeekId {
} else {
let previousStatusValue = self._statusValue
self._statusValue = value
self.updateProgressAnimations()
let playbackStatus = value?.status
var playbackStatus = value?.status
if self.playbackStatusValue != playbackStatus {
self.playbackStatusValue = playbackStatus
if let playbackStatusUpdated = self.playbackStatusUpdated {
if playbackStatus == .paused, previousStatusValue?.status == .playing, let value = value, value.timestamp > value.duration - 0.1 {
playbackStatus = .playing
}
playbackStatusUpdated(playbackStatus)
}
}

View File

@ -362,6 +362,10 @@ final class PasscodeEntryControllerNode: ASDisplayNode {
if !iconFrame.isEmpty {
self.iconNode.animateIn(fromScale: 0.416)
self.iconNode.layer.animatePosition(from: iconFrame.center.offsetBy(dx: 6.0, dy: 6.0), to: self.iconNode.layer.position, duration: 0.45)
Queue.mainQueue().after(0.45) {
self.hapticFeedback.impact(.medium)
}
}
self.subtitleNode.isHidden = true

View File

@ -13,6 +13,7 @@ swift_library(
"//submodules/GZip:GZip",
"//submodules/rlottie:RLottieBinding",
"//submodules/AppBundle:AppBundle",
"//submodules/ManagedAnimationNode:ManagedAnimationNode"
],
visibility = [
"//visibility:public",

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import RLottieBinding
import GZip
import AppBundle
import ManagedAnimationNode
public enum SemanticStatusNodeState: Equatable {
public struct ProgressAppearance: Equatable {
@ -41,6 +42,7 @@ private protocol SemanticStatusNodeStateDrawingState: NSObjectProtocol {
private protocol SemanticStatusNodeStateContext: class {
var isAnimating: Bool { get }
var requestUpdate: () -> Void { get set }
func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState
}
@ -90,10 +92,12 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
final class DrawingState: NSObject, SemanticStatusNodeStateDrawingState {
let transitionFraction: CGFloat
let icon: SemanticStatusNodeIcon
let iconImage: UIImage?
init(transitionFraction: CGFloat, icon: SemanticStatusNodeIcon) {
init(transitionFraction: CGFloat, icon: SemanticStatusNodeIcon, iconImage: UIImage?) {
self.transitionFraction = transitionFraction
self.icon = icon
self.iconImage = iconImage
super.init()
}
@ -119,38 +123,65 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
break
case .play:
let diameter = size.width
let factor = diameter / 50.0
let size = CGSize(width: 15.0, height: 18.0)
context.translateBy(x: (diameter - size.width) / 2.0 + 1.5, y: (diameter - size.height) / 2.0)
let size: CGSize
var offset: CGFloat = 0.0
if let iconImage = self.iconImage {
size = iconImage.size
} else {
offset = 1.5
size = CGSize(width: 15.0, height: 18.0)
}
context.translateBy(x: (diameter - size.width) / 2.0 + offset, y: (diameter - size.height) / 2.0)
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: factor, y: factor)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
}
let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ")
context.fillPath()
if let iconImage = self.iconImage {
context.saveGState()
let iconRect = CGRect(origin: CGPoint(), size: iconImage.size)
context.clip(to: iconRect, mask: iconImage.cgImage!)
context.fill(iconRect)
context.restoreGState()
} else {
let _ = try? drawSvgPath(context, path: "M1.71891969,0.209353049 C0.769586558,-0.350676705 0,0.0908839327 0,1.18800046 L0,16.8564753 C0,17.9569971 0.750549162,18.357187 1.67393713,17.7519379 L14.1073836,9.60224049 C15.0318735,8.99626906 15.0094718,8.04970371 14.062401,7.49100858 L1.71891969,0.209353049 ")
context.fillPath()
}
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
}
context.translateBy(x: -(diameter - size.width) / 2.0 - 1.5, y: -(diameter - size.height) / 2.0)
context.translateBy(x: -(diameter - size.width) / 2.0 - offset, y: -(diameter - size.height) / 2.0)
case .pause:
let diameter = size.width
let factor = diameter / 50.0
let size = CGSize(width: 15.0, height: 16.0)
let size: CGSize
if let iconImage = self.iconImage {
size = iconImage.size
} else {
size = CGSize(width: 15.0, height: 16.0)
}
context.translateBy(x: (diameter - size.width) / 2.0, y: (diameter - size.height) / 2.0)
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: factor, y: factor)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
}
let _ = try? drawSvgPath(context, path: "M0,1.00087166 C0,0.448105505 0.443716645,0 0.999807492,0 L4.00019251,0 C4.55237094,0 5,0.444630861 5,1.00087166 L5,14.9991283 C5,15.5518945 4.55628335,16 4.00019251,16 L0.999807492,16 C0.447629061,16 0,15.5553691 0,14.9991283 L0,1.00087166 Z M10,1.00087166 C10,0.448105505 10.4437166,0 10.9998075,0 L14.0001925,0 C14.5523709,0 15,0.444630861 15,1.00087166 L15,14.9991283 C15,15.5518945 14.5562834,16 14.0001925,16 L10.9998075,16 C10.4476291,16 10,15.5553691 10,14.9991283 L10,1.00087166 ")
context.fillPath()
if let iconImage = self.iconImage {
context.saveGState()
let iconRect = CGRect(origin: CGPoint(), size: iconImage.size)
context.clip(to: iconRect, mask: iconImage.cgImage!)
context.fill(iconRect)
context.restoreGState()
} else {
let _ = try? drawSvgPath(context, path: "M0,1.00087166 C0,0.448105505 0.443716645,0 0.999807492,0 L4.00019251,0 C4.55237094,0 5,0.444630861 5,1.00087166 L5,14.9991283 C5,15.5518945 4.55628335,16 4.00019251,16 L0.999807492,16 C0.447629061,16 0,15.5553691 0,14.9991283 L0,1.00087166 Z M10,1.00087166 C10,0.448105505 10.4437166,0 10.9998075,0 L14.0001925,0 C14.5523709,0 15,0.444630861 15,1.00087166 L15,14.9991283 C15,15.5518945 14.5562834,16 14.0001925,16 L10.9998075,16 C10.4476291,16 10,15.5553691 10,14.9991283 L10,1.00087166 ")
context.fillPath()
}
if (diameter < 40.0) {
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0 / 0.8, y: 1.0 / 0.8)
@ -159,7 +190,6 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
context.translateBy(x: -(diameter - size.width) / 2.0, y: -(diameter - size.height) / 2.0)
case let .custom(image):
let diameter = size.width
let imageRect = CGRect(origin: CGPoint(x: floor((diameter - image.size.width) / 2.0), y: floor((diameter - image.size.height) / 2.0)), size: image.size)
context.saveGState()
@ -210,18 +240,36 @@ private final class SemanticStatusNodeIconContext: SemanticStatusNodeStateContex
}
}
let icon: SemanticStatusNodeIcon
var icon: SemanticStatusNodeIcon {
didSet {
self.animationNode?.enqueueState(self.icon == .play ? .play : .pause, animated: self.iconImage != nil)
}
}
var animationNode: PlayPauseIconNode?
var iconImage: UIImage?
init(icon: SemanticStatusNodeIcon) {
self.icon = icon
if [.play, .pause].contains(icon) {
self.animationNode = PlayPauseIconNode()
self.animationNode?.imageUpdated = { [weak self] image in
self?.iconImage = image
self?.requestUpdate()
}
self.iconImage = self.animationNode?.image
}
}
var isAnimating: Bool {
return false
}
var requestUpdate: () -> Void = {}
func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeStateDrawingState {
return DrawingState(transitionFraction: transitionFraction, icon: self.icon)
return DrawingState(transitionFraction: transitionFraction, icon: self.icon, iconImage: self.iconImage)
}
}
@ -376,6 +424,8 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
return true
}
var requestUpdate: () -> Void = {}
init(value: CGFloat?, displayCancel: Bool, appearance: SemanticStatusNodeState.ProgressAppearance?) {
self.value = value
self.displayCancel = displayCancel
@ -402,6 +452,10 @@ private final class SemanticStatusNodeProgressContext: SemanticStatusNodeStateCo
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, displayCancel: self.displayCancel, appearance: self.appearance, timestamp: timestamp)
}
func maskView() -> UIView? {
return nil
}
func updateValue(value: CGFloat?) {
if value != self.value {
let previousValue = self.value
@ -501,6 +555,8 @@ private final class SemanticStatusNodeCheckContext: SemanticStatusNodeStateConte
return true
}
var requestUpdate: () -> Void = {}
init(value: CGFloat, appearance: SemanticStatusNodeState.CheckAppearance?) {
self.value = value
self.appearance = appearance
@ -524,6 +580,10 @@ private final class SemanticStatusNodeCheckContext: SemanticStatusNodeStateConte
return DrawingState(transitionFraction: transitionFraction, value: resolvedValue, appearance: self.appearance)
}
func maskView() -> UIView? {
return nil
}
func animate() {
guard self.value < 1.0 else {
return
@ -553,8 +613,15 @@ private extension SemanticStatusNodeState {
default:
preconditionFailure()
}
if let current = current as? SemanticStatusNodeIconContext, current.icon == icon {
return current
if let current = current as? SemanticStatusNodeIconContext {
if current.icon == icon {
return current
} else if (current.icon == .play && icon == .pause) || (current.icon == .pause && icon == .play) {
current.icon = icon
return current
} else {
return SemanticStatusNodeIconContext(icon: icon)
}
} else {
return SemanticStatusNodeIconContext(icon: icon)
}
@ -874,6 +941,9 @@ public final class SemanticStatusNode: ASControlNode {
self.state = state
let previousStateContext = self.stateContext
self.stateContext = self.state.context(current: self.stateContext)
self.stateContext.requestUpdate = { [weak self] in
self?.setNeedsDisplay()
}
if animated && previousStateContext !== self.stateContext {
self.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: previousStateContext, previousAppearanceContext: nil, completion: completion)
@ -947,3 +1017,53 @@ public final class SemanticStatusNode: ASControlNode {
parameters.appearanceState.drawForeground(context: context, size: bounds.size)
}
}
private enum PlayPauseIconNodeState: Equatable {
case play
case pause
}
private final class PlayPauseIconNode: ManagedAnimationNode {
private let duration: Double = 0.35
private var iconState: PlayPauseIconNodeState = .play
init() {
super.init(size: CGSize(width: 36.0, height: 36.0))
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
}
func enqueueState(_ state: PlayPauseIconNodeState, animated: Bool) {
guard self.iconState != state else {
return
}
let previousState = self.iconState
self.iconState = state
switch previousState {
case .pause:
switch state {
case .play:
if animated {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 83), duration: self.duration))
} else {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.01))
}
case .pause:
break
}
case .play:
switch state {
case .pause:
if animated {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 0, endFrame: 41), duration: self.duration))
} else {
self.trackTo(item: ManagedAnimationItem(source: .local("anim_playpause"), frames: .range(startFrame: 41, endFrame: 41), duration: 0.01))
}
case .play:
break
}
}
}
}

View File

@ -211,7 +211,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in
}, activateChatPreview: { _, _, gesture in
gesture?.cancel()

View File

@ -779,7 +779,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in
}, activateChatPreview: { _, _, gesture in
gesture?.cancel()

View File

@ -356,7 +356,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
private func updateChatsLayout(layout: ContainerViewLayout, topInset: CGFloat, transition: ContainedViewLayoutTransition) {
var items: [ChatListItem] = []
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, additionalCategorySelected: { _ in
let interaction = ChatListNodeInteraction(activateSearch: {}, peerSelected: { _, _ in }, disabledPeerSelected: { _ in }, togglePeerSelected: { _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, deletePeer: { _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, hidePsa: { _ in
}, activateChatPreview: { _, _, gesture in
gesture?.cancel()

View File

@ -17,6 +17,7 @@ import AppBundle
import ListMessageItem
import AccountContext
import ChatInterfaceState
import ChatListUI
extension ChatReplyThreadMessage {
var effectiveTopId: MessageId {
@ -30,69 +31,6 @@ struct ChatTopVisibleMessageRange: Equatable {
var isLast: Bool
}
private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
private let selectionGestureActivationThreshold: CGFloat = 5.0
var recognized: Bool? = nil
var initialLocation: CGPoint = CGPoint()
var shouldBegin: (() -> Bool)?
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.minimumNumberOfTouches = 2
self.maximumNumberOfTouches = 2
}
override func reset() {
super.reset()
self.recognized = nil
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let shouldBegin = self.shouldBegin, !shouldBegin() {
self.state = .failed
} else {
let touch = touches.first!
self.initialLocation = touch.location(in: self.view)
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
let location = touches.first!.location(in: self.view)
let translation = location.offsetBy(dx: -self.initialLocation.x, dy: -self.initialLocation.y)
let touchesArray = Array(touches)
if self.recognized == nil, touchesArray.count == 2 {
if let firstTouch = touchesArray.first, let secondTouch = touchesArray.last {
let firstLocation = firstTouch.location(in: self.view)
let secondLocation = secondTouch.location(in: self.view)
func distance(_ v1: CGPoint, _ v2: CGPoint) -> CGFloat {
let dx = v1.x - v2.x
let dy = v1.y - v2.y
return sqrt(dx * dx + dy * dy)
}
if distance(firstLocation, secondLocation) > 200.0 {
self.state = .failed
}
}
if self.state != .failed && (abs(translation.y) >= selectionGestureActivationThreshold) {
self.recognized = true
}
}
if let recognized = self.recognized, recognized {
super.touchesMoved(touches, with: event)
}
}
}
private let historyMessageCount: Int = 90
public enum ChatHistoryListDisplayHeaders {

View File

@ -710,20 +710,19 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
func animateFromMicInput(micInputNode: UIView, transition: CombinedTransition) -> ContextExtractedContentContainingNode? {
for contentNode in self.contentNodes {
if let contentNode = contentNode as? ChatMessageFileBubbleContentNode {
if let statusContainerNode = contentNode.interactiveFileNode.statusContainerNode {
let scale = statusContainerNode.contentRect.height / 100.0
micInputNode.transform = CGAffineTransform(scaleX: scale, y: scale)
micInputNode.center = CGPoint(x: statusContainerNode.contentRect.midX, y: statusContainerNode.contentRect.midY)
statusContainerNode.contentNode.view.addSubview(micInputNode)
let statusContainerNode = contentNode.interactiveFileNode.statusContainerNode
let scale = statusContainerNode.contentRect.height / 100.0
micInputNode.transform = CGAffineTransform(scaleX: scale, y: scale)
micInputNode.center = CGPoint(x: statusContainerNode.contentRect.midX, y: statusContainerNode.contentRect.midY)
statusContainerNode.contentNode.view.addSubview(micInputNode)
transition.horizontal.updateAlpha(layer: micInputNode.layer, alpha: 0.0, completion: { [weak micInputNode] _ in
micInputNode?.removeFromSuperview()
})
transition.horizontal.updateAlpha(layer: micInputNode.layer, alpha: 0.0, completion: { [weak micInputNode] _ in
micInputNode?.removeFromSuperview()
})
transition.horizontal.animateTransformScale(node: statusContainerNode.contentNode, from: 1.0 / scale)
transition.horizontal.animateTransformScale(node: statusContainerNode.contentNode, from: 1.0 / scale)
return statusContainerNode
}
return statusContainerNode
}
}
return nil

View File

@ -39,9 +39,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
private let consumableContentNode: ASImageNode
private var iconNode: TransformImageNode?
private(set) var statusContainerNode: ContextExtractedContentContainingNode?
let statusContainerNode: ContextExtractedContentContainingNode
private var statusNode: SemanticStatusNode?
private var playbackAudioLevelView: VoiceBlobView?
private var playbackAudioLevelNode: VoiceBlobNode?
private var streamingStatusNode: SemanticStatusNode?
private var tapRecognizer: UITapGestureRecognizer?
@ -73,7 +73,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
guard self.visibility != oldValue else { return }
if !self.visibility {
self.playbackAudioLevelView?.stopAnimating()
self.playbackAudioLevelNode?.stopAnimating()
}
}
}
@ -140,9 +140,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
self.addSubnode(self.descriptionNode)
self.addSubnode(self.fetchingTextNode)
self.addSubnode(self.fetchingCompactTextNode)
if let statusContainerNode = self.statusContainerNode {
self.addSubnode(statusContainerNode)
}
self.addSubnode(self.statusContainerNode)
}
deinit {
@ -673,7 +671,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
return
}
strongSelf.inputAudioLevel = CGFloat(value)
strongSelf.playbackAudioLevelView?.updateLevel(CGFloat(value))
strongSelf.playbackAudioLevelNode?.updateLevel(CGFloat(value))
}))
}
@ -692,19 +690,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
strongSelf.statusNode?.displaysAsynchronously = !presentationData.isPreview
strongSelf.statusNode?.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
strongSelf.statusContainerNode?.frame = progressFrame
strongSelf.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size)
strongSelf.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
strongSelf.statusContainerNode.frame = progressFrame
strongSelf.statusContainerNode.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size)
strongSelf.statusContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
strongSelf.playbackAudioLevelView?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0)
strongSelf.playbackAudioLevelNode?.frame = progressFrame.insetBy(dx: -12.0, dy: -12.0)
strongSelf.progressFrame = progressFrame
strongSelf.streamingCacheStatusFrame = streamingCacheStatusFrame
strongSelf.fileIconImage = fileIconImage
strongSelf.statusContainerNode?.frame = progressFrame
strongSelf.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size)
strongSelf.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
if let updatedFetchControls = updatedFetchControls {
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
if automaticDownload {
@ -954,26 +948,27 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let statusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor, image: image, overlayForegroundNodeColor: presentationData.theme.theme.chat.message.mediaOverlayControlColors.foregroundColor)
self.statusNode = statusNode
self.statusContainerNode?.contentNode.insertSubnode(statusNode, at: 0)
self.statusContainerNode?.frame = progressFrame
self.statusContainerNode?.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size)
self.statusContainerNode?.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
self.statusContainerNode.contentNode.insertSubnode(statusNode, at: 0)
self.statusContainerNode.frame = progressFrame
self.statusContainerNode.contentRect = CGRect(origin: CGPoint(), size: progressFrame.size)
self.statusContainerNode.contentNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
statusNode.frame = CGRect(origin: CGPoint(), size: progressFrame.size)
} else if let statusNode = self.statusNode {
statusNode.backgroundNodeColor = backgroundNodeColor
}
if state != .none && isVoice && self.playbackAudioLevelView == nil && false {
if state != .none && isVoice && self.playbackAudioLevelNode == nil {
let blobFrame = progressFrame.insetBy(dx: -12.0, dy: -12.0)
let playbackAudioLevelView = VoiceBlobView(
frame: blobFrame,
let playbackAudioLevelNode = VoiceBlobNode(
maxLevel: 0.3,
smallBlobRange: (0, 0),
mediumBlobRange: (0.7, 0.8),
bigBlobRange: (0.8, 0.9)
)
self.playbackAudioLevelView = playbackAudioLevelView
self.view.addSubview(playbackAudioLevelView)
playbackAudioLevelNode.isUserInteractionEnabled = false
playbackAudioLevelNode.frame = blobFrame
self.playbackAudioLevelNode = playbackAudioLevelNode
self.insertSubnode(playbackAudioLevelNode, belowSubnode: self.statusContainerNode)
let maskRect = CGRect(origin: .zero, size: blobFrame.size)
let playbackMaskLayer = CAShapeLayer()
@ -983,9 +978,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
maskPath.append(UIBezierPath(roundedRect: maskRect.insetBy(dx: 12, dy: 12), cornerRadius: 22))
maskPath.append(UIBezierPath(rect: maskRect))
playbackMaskLayer.path = maskPath.cgPath
playbackAudioLevelView.layer.mask = playbackMaskLayer
playbackAudioLevelNode.layer.mask = playbackMaskLayer
}
self.playbackAudioLevelView?.setColor(presentationData.theme.theme.chat.inputPanel.actionControlFillColor)
self.playbackAudioLevelNode?.setColor(messageTheme.mediaActiveControlColor)
if streamingState != .none && self.streamingStatusNode == nil {
let streamingStatusNode = SemanticStatusNode(backgroundNodeColor: backgroundNodeColor, foregroundNodeColor: foregroundNodeColor)
@ -1012,9 +1007,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
switch state {
case .pause:
self.playbackAudioLevelView?.startAnimating()
self.playbackAudioLevelNode?.startAnimating()
default:
self.playbackAudioLevelView?.stopAnimating()
self.playbackAudioLevelNode?.stopAnimating()
}
}

View File

@ -175,6 +175,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
}, peerSelected: { _, _ in
}, disabledPeerSelected: { _ in
}, togglePeerSelected: { _ in
}, togglePeersSelection: { _, _ in
}, additionalCategorySelected: { _ in
}, messageSelected: { [weak self] peer, message, _ in
if let strongSelf = self {