Add story pinch-to-zoom

This commit is contained in:
Ilya Laktyushin
2023-06-18 01:58:44 +04:00
parent e639dab7a3
commit 21b76b0857
3 changed files with 69 additions and 6 deletions

View File

@@ -151,6 +151,7 @@ private final class StoryContainerScreenComponent: Component {
} }
} }
final class View: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate { final class View: UIView, UIScrollViewDelegate, UIGestureRecognizerDelegate {
private var component: StoryContainerScreenComponent? private var component: StoryContainerScreenComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@@ -165,6 +166,7 @@ private final class StoryContainerScreenComponent: Component {
private let storyItemSharedState = StoryContentItem.SharedState() private let storyItemSharedState = StoryContentItem.SharedState()
private var visibleItemSetViews: [EnginePeer.Id: ItemSetView] = [:] private var visibleItemSetViews: [EnginePeer.Id: ItemSetView] = [:]
private var itemSetPinchState: StoryItemSetContainerComponent.PinchState?
private var itemSetPanState: ItemSetPanState? private var itemSetPanState: ItemSetPanState?
private var verticalPanState: ItemSetPanState? private var verticalPanState: ItemSetPanState?
private var isHoldingTouch: Bool = false private var isHoldingTouch: Bool = false
@@ -226,6 +228,9 @@ private final class StoryContainerScreenComponent: Component {
} }
self.addGestureRecognizer(longPressRecognizer) self.addGestureRecognizer(longPressRecognizer)
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchGesture(_:)))
self.addGestureRecognizer(pinchRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.backgroundEffectView.addGestureRecognizer(tapGestureRecognizer) self.backgroundEffectView.addGestureRecognizer(tapGestureRecognizer)
} }
@@ -435,6 +440,26 @@ private final class StoryContainerScreenComponent: Component {
} }
} }
@objc private func pinchGesture(_ recognizer: UIPinchGestureRecognizer) {
switch recognizer.state {
case .began, .changed:
let location = recognizer.location(in: self)
let scale = recognizer.scale
if let itemSetPinchState = self.itemSetPinchState {
let offset = CGPoint(x: location.x - itemSetPinchState.location.x , y: location.y - itemSetPinchState.location.y)
self.itemSetPinchState = StoryItemSetContainerComponent.PinchState(scale: scale, location: itemSetPinchState.location, offset: offset)
} else {
self.itemSetPinchState = StoryItemSetContainerComponent.PinchState(scale: scale, location: location, offset: .zero)
}
self.state?.updated(transition: .immediate)
case .cancelled, .ended:
self.itemSetPinchState = nil
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
default:
break
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for subview in self.subviews.reversed() { for subview in self.subviews.reversed() {
if !subview.isUserInteractionEnabled || subview.isHidden || subview.alpha == 0.0 { if !subview.isUserInteractionEnabled || subview.isHidden || subview.alpha == 0.0 {
@@ -718,10 +743,11 @@ private final class StoryContainerScreenComponent: Component {
inputHeight: environment.inputHeight, inputHeight: environment.inputHeight,
metrics: environment.metrics, metrics: environment.metrics,
isProgressPaused: isProgressPaused || i != focusedIndex, isProgressPaused: isProgressPaused || i != focusedIndex,
hideUI: i == focusedIndex && self.itemSetPanState?.didBegin == false, hideUI: (i == focusedIndex && (self.itemSetPanState?.didBegin == false || self.itemSetPinchState != nil)),
visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction), visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction),
isPanning: self.itemSetPanState?.didBegin == true, isPanning: self.itemSetPanState?.didBegin == true,
verticalPanFraction: verticalPanFraction, verticalPanFraction: verticalPanFraction,
pinchState: self.itemSetPinchState,
presentController: { [weak self] c in presentController: { [weak self] c in
guard let self, let environment = self.environment else { guard let self, let environment = self.environment else {
return return

View File

@@ -36,6 +36,18 @@ public final class StoryItemSetContainerComponent: Component {
case next case next
} }
public struct PinchState: Equatable {
var scale: CGFloat
var location: CGPoint
var offset: CGPoint
init(scale: CGFloat, location: CGPoint, offset: CGPoint) {
self.scale = scale
self.location = location
self.offset = offset
}
}
public let context: AccountContext public let context: AccountContext
public let externalState: ExternalState public let externalState: ExternalState
public let storyItemSharedState: StoryContentItem.SharedState public let storyItemSharedState: StoryContentItem.SharedState
@@ -51,6 +63,7 @@ public final class StoryItemSetContainerComponent: Component {
public let visibilityFraction: CGFloat public let visibilityFraction: CGFloat
public let isPanning: Bool public let isPanning: Bool
public let verticalPanFraction: CGFloat public let verticalPanFraction: CGFloat
public let pinchState: PinchState?
public let presentController: (ViewController) -> Void public let presentController: (ViewController) -> Void
public let close: () -> Void public let close: () -> Void
public let navigate: (NavigationDirection) -> Void public let navigate: (NavigationDirection) -> Void
@@ -74,6 +87,7 @@ public final class StoryItemSetContainerComponent: Component {
visibilityFraction: CGFloat, visibilityFraction: CGFloat,
isPanning: Bool, isPanning: Bool,
verticalPanFraction: CGFloat, verticalPanFraction: CGFloat,
pinchState: PinchState?,
presentController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController) -> Void,
close: @escaping () -> Void, close: @escaping () -> Void,
navigate: @escaping (NavigationDirection) -> Void, navigate: @escaping (NavigationDirection) -> Void,
@@ -96,6 +110,7 @@ public final class StoryItemSetContainerComponent: Component {
self.visibilityFraction = visibilityFraction self.visibilityFraction = visibilityFraction
self.isPanning = isPanning self.isPanning = isPanning
self.verticalPanFraction = verticalPanFraction self.verticalPanFraction = verticalPanFraction
self.pinchState = pinchState
self.presentController = presentController self.presentController = presentController
self.close = close self.close = close
self.navigate = navigate self.navigate = navigate
@@ -144,6 +159,9 @@ public final class StoryItemSetContainerComponent: Component {
if lhs.verticalPanFraction != rhs.verticalPanFraction { if lhs.verticalPanFraction != rhs.verticalPanFraction {
return false return false
} }
if lhs.pinchState != rhs.pinchState {
return false
}
return true return true
} }
@@ -492,6 +510,9 @@ public final class StoryItemSetContainerComponent: Component {
guard let component = self.component else { guard let component = self.component else {
return false return false
} }
if component.pinchState != nil {
return true
}
if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.displayViewList { if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.displayViewList {
return true return true
} }
@@ -1359,7 +1380,7 @@ public final class StoryItemSetContainerComponent: Component {
actionSheet.setItemGroups([ actionSheet.setItemGroups([
ActionSheetItemGroup(items: [ ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: "Delete", color: .destructive, action: { [weak self, weak actionSheet] in ActionSheetButtonItem(title: "Delete Story", color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
guard let self, let component = self.component else { guard let self, let component = self.component else {
@@ -1607,7 +1628,24 @@ public final class StoryItemSetContainerComponent: Component {
transition.setPosition(view: self.contentContainerView, position: contentFrame.center) transition.setPosition(view: self.contentContainerView, position: contentFrame.center)
transition.setBounds(view: self.contentContainerView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size)) transition.setBounds(view: self.contentContainerView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size))
transition.setScale(view: self.contentContainerView, scale: contentVisualScale)
var transform = CATransform3DMakeScale(contentVisualScale, contentVisualScale, 1.0)
if let pinchState = component.pinchState {
let pinchOffset = CGPoint(
x: pinchState.location.x - contentFrame.width / 2.0,
y: pinchState.location.y - contentFrame.height / 2.0
)
transform = CATransform3DTranslate(
transform,
pinchState.offset.x - pinchOffset.x * (pinchState.scale - 1.0),
pinchState.offset.y - pinchOffset.y * (pinchState.scale - 1.0),
0.0
)
transform = CATransform3DScale(transform, pinchState.scale, pinchState.scale, 0.0)
}
transition.setTransform(view: self.contentContainerView, transform: transform)
//transition.setScale(view: self.contentContainerView, scale: contentVisualScale)
transition.setCornerRadius(layer: self.contentContainerView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale)) transition.setCornerRadius(layer: self.contentContainerView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale))
if self.closeButtonIconView.image == nil { if self.closeButtonIconView.image == nil {
@@ -1731,7 +1769,7 @@ public final class StoryItemSetContainerComponent: Component {
self.itemLayout = itemLayout self.itemLayout = itemLayout
let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize) let inputPanelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - inputPanelSize.width) / 2.0), y: availableSize.height - inputPanelBottomInset - inputPanelSize.height), size: inputPanelSize)
var inputPanelAlpha: CGFloat = focusedItem?.isMy == true ? 0.0 : 1.0 var inputPanelAlpha: CGFloat = focusedItem?.isMy == true || component.hideUI ? 0.0 : 1.0
if case .regular = component.metrics.widthClass { if case .regular = component.metrics.widthClass {
inputPanelAlpha *= component.visibilityFraction inputPanelAlpha *= component.visibilityFraction
} }

View File

@@ -255,7 +255,6 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
controller.view.endEditing(true) controller.view.endEditing(true)
let context = self.context let context = self.context
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var presentImpl: ((ViewController) -> Void)? var presentImpl: ((ViewController) -> Void)?
var returnToCameraImpl: (() -> Void)? var returnToCameraImpl: (() -> Void)?