mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Swipe to reply
This commit is contained in:
parent
99fa059e08
commit
b704bdaa9b
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user