mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 14:45:21 +00:00
Add story pinch-to-zoom
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)?
|
||||||
|
|||||||
Reference in New Issue
Block a user