Swipe to reply

This commit is contained in:
Ali 2023-09-29 16:13:34 +04:00
parent 99fa059e08
commit b704bdaa9b
7 changed files with 191 additions and 41 deletions

View File

@ -70,6 +70,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case redactSensitiveData(PresentationTheme, Bool)
case keepChatNavigationStack(PresentationTheme, Bool)
case skipReadHistory(PresentationTheme, Bool)
case unidirectionalSwipeToReply(Bool)
case crashOnSlowQueries(PresentationTheme, Bool)
case crashOnMemoryPressure(PresentationTheme, Bool)
case clearTips(PresentationTheme)
@ -118,7 +119,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logs.rawValue
case .logToFile, .logToConsole, .redactSensitiveData:
return DebugControllerSection.logging.rawValue
case .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries, .crashOnMemoryPressure:
case .keepChatNavigationStack, .skipReadHistory, .unidirectionalSwipeToReply, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .inlineForums, .localTranscription, .enableReactionOverrides, .restorePurchases:
return DebugControllerSection.experiments.rawValue
@ -165,46 +166,48 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 14
case .skipReadHistory:
return 15
case .crashOnSlowQueries:
case .unidirectionalSwipeToReply:
return 16
case .crashOnMemoryPressure:
case .crashOnSlowQueries:
return 17
case .clearTips:
case .crashOnMemoryPressure:
return 18
case .resetNotifications:
case .clearTips:
return 19
case .crash:
case .resetNotifications:
return 20
case .resetData:
case .crash:
return 21
case .resetDatabase:
case .resetData:
return 22
case .resetDatabaseAndCache:
case .resetDatabase:
return 23
case .resetHoles:
case .resetDatabaseAndCache:
return 24
case .reindexUnread:
case .resetHoles:
return 25
case .resetCacheIndex:
case .reindexUnread:
return 26
case .reindexCache:
case .resetCacheIndex:
return 27
case .resetBiometricsData:
case .reindexCache:
return 28
case .resetWebViewCache:
case .resetBiometricsData:
return 29
case .optimizeDatabase:
case .resetWebViewCache:
return 30
case .photoPreview:
case .optimizeDatabase:
return 31
case .knockoutWallpaper:
case .photoPreview:
return 32
case .experimentalCompatibility:
case .knockoutWallpaper:
return 33
case .enableDebugDataDisplay:
case .experimentalCompatibility:
return 34
case .acceleratedStickers:
case .enableDebugDataDisplay:
return 35
case .acceleratedStickers:
return 36
case .inlineForums:
return 37
case .localTranscription:
@ -928,6 +931,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return settings
}).start()
})
case let .unidirectionalSwipeToReply(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Legacy swipe to reply", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
var settings = settings
settings.unidirectionalSwipeToReply = value
return settings
}).start()
})
case let .crashOnSlowQueries(_, value):
return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
@ -1375,6 +1386,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
#if DEBUG
entries.append(.skipReadHistory(presentationData.theme, experimentalSettings.skipReadHistory))
#endif
entries.append(.unidirectionalSwipeToReply(experimentalSettings.unidirectionalSwipeToReply))
}
entries.append(.crashOnSlowQueries(presentationData.theme, experimentalSettings.crashOnLongQueries))
entries.append(.crashOnMemoryPressure(presentationData.theme, experimentalSettings.crashOnMemoryPressure))

View File

@ -284,6 +284,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var appliedForwardInfo: (Peer?, String?)?
private var replyRecognizer: ChatSwipeToReplyRecognizer?
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
private var wasPending: Bool = false
@ -450,6 +451,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.view.addGestureRecognizer(recognizer)
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
if let item = self.item {
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
self.view.disablesInteractiveTransitionGestureRecognizer = true
}
replyRecognizer.shouldBegin = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
if strongSelf.selectionNode != nil {
@ -470,6 +475,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
return false
}
self.replyRecognizer = replyRecognizer
self.view.addGestureRecognizer(replyRecognizer)
}
@ -510,6 +516,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var setupTimestamp: Double?
private func setupNode(item: ChatMessageItem) {
self.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
guard self.animationNode == nil else {
return
}
@ -2419,11 +2428,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var playedSwipeToReplyHaptic = false
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
var offset: CGFloat = 0.0
var leftOffset: CGFloat = 0.0
var swipeOffset: CGFloat = 45.0
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
offset = -24.0
leftOffset = -10.0
} else {
offset = 10.0
leftOffset = -10.0
swipeOffset = 60.0
}
@ -2451,7 +2463,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if translation.x < 0.0 {
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
translation.x = 0.0
if recognizer.allowBothDirections {
translation.x = -max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
translation.x = 0.0
}
}
if let item = self.item, self.swipeToReplyNode == nil {
@ -2469,7 +2485,13 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let swipeToReplyNode = self.swipeToReplyNode {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
if translation.x < 0.0 {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
} else {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
}
if let (rect, containerSize) = self.absoluteRect {
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
@ -2488,7 +2510,15 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.swipeToReplyFeedback = nil
let translation = recognizer.translation(in: self.view)
if case .ended = recognizer.state, translation.x < -swipeOffset {
let gestureRecognized: Bool
if recognizer.allowBothDirections {
gestureRecognized = abs(translation.x) > swipeOffset
} else {
gestureRecognized = translation.x < -swipeOffset
}
if case .ended = recognizer.state, gestureRecognized {
if let item = self.item {
if let currentSwipeAction = currentSwipeAction {
switch currentSwipeAction {

View File

@ -581,6 +581,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
private var replyRecognizer: ChatSwipeToReplyRecognizer?
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
//private let debugNode: ASDisplayNode
@ -1073,6 +1074,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.view.isExclusiveTouch = true
let replyRecognizer = ChatSwipeToReplyRecognizer(target: self, action: #selector(self.swipeToReplyGesture(_:)))
if let item = self.item {
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
}
replyRecognizer.shouldBegin = { [weak self] in
if let strongSelf = self, let item = strongSelf.item {
if strongSelf.selectionNode != nil {
@ -1104,6 +1109,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
return false
}
self.replyRecognizer = replyRecognizer
self.view.addGestureRecognizer(replyRecognizer)
}
@ -2664,6 +2670,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
strongSelf.updateAccessibilityData(accessibilityData)
strongSelf.disablesComments = disablesComments
strongSelf.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
strongSelf.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
var animation = animation
if strongSelf.mainContextSourceNode.isExtractedToContextPreview {
animation = .System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .easeInOut, interactive: false))
@ -4382,11 +4391,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
private var playedSwipeToReplyHaptic = false
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
var offset: CGFloat = 0.0
var leftOffset: CGFloat = 0.0
var swipeOffset: CGFloat = 45.0
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
offset = -24.0
leftOffset = -10.0
} else {
offset = 10.0
leftOffset = -10.0
swipeOffset = 60.0
}
@ -4414,7 +4426,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if translation.x < 0.0 {
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
translation.x = 0.0
if recognizer.allowBothDirections {
translation.x = -max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
translation.x = 0.0
}
}
if let item = self.item, self.swipeToReplyNode == nil {
@ -4434,8 +4450,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.updateAttachedAvatarNodeOffset(offset: translation.x, transition: .immediate)
if let swipeToReplyNode = self.swipeToReplyNode {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
if translation.x < 0.0 {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
} else {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
}
if let (rect, containerSize) = self.absoluteRect {
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
@ -4454,7 +4475,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.swipeToReplyFeedback = nil
let translation = recognizer.translation(in: self.view)
if case .ended = recognizer.state, translation.x < -swipeOffset {
let gestureRecognized: Bool
if recognizer.allowBothDirections {
gestureRecognized = abs(translation.x) > swipeOffset
} else {
gestureRecognized = translation.x < -swipeOffset
}
if case .ended = recognizer.state, gestureRecognized {
if let item = self.item {
if let currentSwipeAction = currentSwipeAction {
switch currentSwipeAction {

View File

@ -57,7 +57,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
var currentSwipeToReplyTranslation: CGFloat = 0.0
var recognizer: TapLongTapOrDoubleTapGestureRecognizer?
private var replyRecognizer: ChatSwipeToReplyRecognizer?
var currentSwipeAction: ChatControllerInteractionSwipeAction?
override var visibility: ListViewItemNodeVisibility {
@ -220,7 +221,14 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
}
return false
}
if let item = self.item {
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
}
self.replyRecognizer = replyRecognizer
self.view.addGestureRecognizer(replyRecognizer)
self.view.disablesInteractiveTransitionGestureRecognizer = true
}
override func updateAccessibilityData(_ accessibilityData: ChatMessageAccessibilityData) {
@ -617,6 +625,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
strongSelf.appliedCurrentlyPlaying = isPlaying
strongSelf.appliedAutomaticDownload = automaticDownload
strongSelf.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
strongSelf.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
strongSelf.updateAccessibilityData(accessibilityData)
let videoLayoutData: ChatMessageInstantVideoItemLayoutData
@ -1006,11 +1017,14 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
private var playedSwipeToReplyHaptic = false
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
var offset: CGFloat = 0.0
var leftOffset: CGFloat = 0.0
var swipeOffset: CGFloat = 45.0
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
offset = -24.0
leftOffset = -10.0
} else {
offset = 10.0
leftOffset = -10.0
swipeOffset = 60.0
}
@ -1024,8 +1038,26 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
}
self.item?.controllerInteraction.cancelInteractiveKeyboardGestures()
case .changed:
func rubberBandingOffset(offset: CGFloat, bandingStart: CGFloat) -> CGFloat {
let bandedOffset = offset - bandingStart
if offset < bandingStart {
return offset
}
let range: CGFloat = 100.0
let coefficient: CGFloat = 0.4
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
}
var translation = recognizer.translation(in: self.view)
translation.x = max(-80.0, min(0.0, translation.x))
if translation.x < 0.0 {
translation.x = max(-80.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
if recognizer.allowBothDirections {
translation.x = -max(-80.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
translation.x = 0.0
}
}
if let item = self.item, self.swipeToReplyNode == nil {
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
@ -1042,7 +1074,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
if let swipeToReplyNode = self.swipeToReplyNode {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
if translation.x < 0.0 {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
} else {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
}
if let (rect, containerSize) = self.absoluteRect {
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
@ -1061,7 +1099,13 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
self.swipeToReplyFeedback = nil
let translation = recognizer.translation(in: self.view)
if case .ended = recognizer.state, translation.x < -swipeOffset {
let gestureRecognized: Bool
if recognizer.allowBothDirections {
gestureRecognized = abs(translation.x) > swipeOffset
} else {
gestureRecognized = translation.x < -swipeOffset
}
if case .ended = recognizer.state, gestureRecognized {
if let item = self.item {
if let currentSwipeAction = currentSwipeAction {
switch currentSwipeAction {

View File

@ -55,6 +55,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var currentSwipeToReplyTranslation: CGFloat = 0.0
private var replyRecognizer: ChatSwipeToReplyRecognizer?
private var currentSwipeAction: ChatControllerInteractionSwipeAction?
private var appliedForwardInfo: (Peer?, String?)?
@ -254,12 +255,22 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
return false
}
if let item = self.item {
replyRecognizer.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
}
self.replyRecognizer = replyRecognizer
self.view.addGestureRecognizer(replyRecognizer)
self.view.disablesInteractiveTransitionGestureRecognizer = true
}
override func setupItem(_ item: ChatMessageItem, synchronousLoad: Bool) {
super.setupItem(item, synchronousLoad: synchronousLoad)
self.replyRecognizer?.allowBothDirections = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
self.view.disablesInteractiveTransitionGestureRecognizer = !item.context.sharedContext.immediateExperimentalUISettings.unidirectionalSwipeToReply
for media in item.message.media {
if let telegramFile = media as? TelegramMediaFile {
if self.telegramFile != telegramFile {
@ -1393,11 +1404,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var playedSwipeToReplyHaptic = false
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
var offset: CGFloat = 0.0
var leftOffset: CGFloat = 0.0
var swipeOffset: CGFloat = 45.0
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
offset = -24.0
leftOffset = -10.0
} else {
offset = 10.0
leftOffset = -10.0
swipeOffset = 60.0
}
@ -1425,7 +1439,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if translation.x < 0.0 {
translation.x = max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
translation.x = 0.0
if recognizer.allowBothDirections {
translation.x = -max(-180.0, min(0.0, -rubberBandingOffset(offset: abs(translation.x), bandingStart: swipeOffset)))
} else {
translation.x = 0.0
}
}
if let item = self.item, self.swipeToReplyNode == nil {
@ -1443,7 +1461,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let swipeToReplyNode = self.swipeToReplyNode {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
if translation.x < 0.0 {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0)
} else {
swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0)
}
if let (rect, containerSize) = self.absoluteRect {
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
@ -1462,7 +1486,13 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.swipeToReplyFeedback = nil
let translation = recognizer.translation(in: self.view)
if case .ended = recognizer.state, translation.x < -swipeOffset {
let gestureRecognized: Bool
if recognizer.allowBothDirections {
gestureRecognized = abs(translation.x) > swipeOffset
} else {
gestureRecognized = translation.x < -swipeOffset
}
if case .ended = recognizer.state, gestureRecognized {
if let item = self.item {
if let currentSwipeAction = currentSwipeAction {
switch currentSwipeAction {

View File

@ -4,6 +4,7 @@ import UIKit
class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer {
var validatedGesture = false
var firstLocation: CGPoint = CGPoint()
var allowBothDirections: Bool = true
var shouldBegin: (() -> Bool)?
@ -37,17 +38,17 @@ class ChatSwipeToReplyRecognizer: UIPanGestureRecognizer {
let absTranslationX: CGFloat = abs(translation.x)
let absTranslationY: CGFloat = abs(translation.y)
if !validatedGesture {
if translation.x > 0.0 {
if !self.validatedGesture {
if !self.allowBothDirections && translation.x > 0.0 {
self.state = .failed
} else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
self.state = .failed
} else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
validatedGesture = true
self.validatedGesture = true
}
}
if validatedGesture {
if self.validatedGesture {
super.touchesMoved(touches, with: event)
}
}

View File

@ -53,6 +53,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var storiesExperiment: Bool
public var storiesJpegExperiment: Bool
public var crashOnMemoryPressure: Bool
public var unidirectionalSwipeToReply: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -83,7 +84,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
logLanguageRecognition: false,
storiesExperiment: false,
storiesJpegExperiment: false,
crashOnMemoryPressure: false
crashOnMemoryPressure: false,
unidirectionalSwipeToReply: false
)
}
@ -115,7 +117,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
logLanguageRecognition: Bool,
storiesExperiment: Bool,
storiesJpegExperiment: Bool,
crashOnMemoryPressure: Bool
crashOnMemoryPressure: Bool,
unidirectionalSwipeToReply: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -145,6 +148,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.storiesExperiment = storiesExperiment
self.storiesJpegExperiment = storiesJpegExperiment
self.crashOnMemoryPressure = crashOnMemoryPressure
self.unidirectionalSwipeToReply = unidirectionalSwipeToReply
}
public init(from decoder: Decoder) throws {
@ -178,6 +182,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.storiesExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesExperiment") ?? false
self.storiesJpegExperiment = try container.decodeIfPresent(Bool.self, forKey: "storiesJpegExperiment") ?? false
self.crashOnMemoryPressure = try container.decodeIfPresent(Bool.self, forKey: "crashOnMemoryPressure") ?? false
self.unidirectionalSwipeToReply = try container.decodeIfPresent(Bool.self, forKey: "unidirectionalSwipeToReply") ?? false
}
public func encode(to encoder: Encoder) throws {
@ -211,6 +216,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encode(self.storiesExperiment, forKey: "storiesExperiment")
try container.encode(self.storiesJpegExperiment, forKey: "storiesJpegExperiment")
try container.encode(self.crashOnMemoryPressure, forKey: "crashOnMemoryPressure")
try container.encode(self.unidirectionalSwipeToReply, forKey: "unidirectionalSwipeToReply")
}
}