mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Stories
This commit is contained in:
parent
62f92ee5af
commit
bf5a8709cc
@ -222,3 +222,8 @@ public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func scrollingRubberBandingOffset(offset: CGFloat, bandingStart: CGFloat, range: CGFloat, coefficient: CGFloat = 0.4) -> CGFloat {
|
||||||
|
let bandedOffset = offset - bandingStart
|
||||||
|
return bandingStart + (1.0 - (1.0 / ((bandedOffset * coefficient / range) + 1.0))) * range
|
||||||
|
}
|
||||||
|
@ -741,13 +741,13 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
stopAndPreviewMediaRecording: nil,
|
stopAndPreviewMediaRecording: nil,
|
||||||
discardMediaRecordingPreview: nil,
|
discardMediaRecordingPreview: nil,
|
||||||
attachmentAction: nil,
|
attachmentAction: nil,
|
||||||
reactionAction: nil,
|
|
||||||
timeoutAction: { [weak self] view in
|
timeoutAction: { [weak self] view in
|
||||||
guard let self, let controller = self.environment?.controller() as? MediaEditorScreen else {
|
guard let self, let controller = self.environment?.controller() as? MediaEditorScreen else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.presentTimeoutSetup(sourceView: view)
|
controller.presentTimeoutSetup(sourceView: view)
|
||||||
},
|
},
|
||||||
|
forwardAction: nil,
|
||||||
audioRecorder: nil,
|
audioRecorder: nil,
|
||||||
videoRecordingStatus: nil,
|
videoRecordingStatus: nil,
|
||||||
isRecordingLocked: false,
|
isRecordingLocked: false,
|
||||||
|
@ -260,8 +260,8 @@ final class StoryPreviewComponent: Component {
|
|||||||
stopAndPreviewMediaRecording: nil,
|
stopAndPreviewMediaRecording: nil,
|
||||||
discardMediaRecordingPreview: nil,
|
discardMediaRecordingPreview: nil,
|
||||||
attachmentAction: { },
|
attachmentAction: { },
|
||||||
reactionAction: { _ in },
|
|
||||||
timeoutAction: nil,
|
timeoutAction: nil,
|
||||||
|
forwardAction: nil,
|
||||||
audioRecorder: nil,
|
audioRecorder: nil,
|
||||||
videoRecordingStatus: nil,
|
videoRecordingStatus: nil,
|
||||||
isRecordingLocked: false,
|
isRecordingLocked: false,
|
||||||
|
@ -15,6 +15,8 @@ private extension MessageInputActionButtonComponent.Mode {
|
|||||||
return "Chat/Context Menu/Delete"
|
return "Chat/Context Menu/Delete"
|
||||||
case .attach:
|
case .attach:
|
||||||
return "Chat/Input/Text/IconAttachment"
|
return "Chat/Input/Text/IconAttachment"
|
||||||
|
case .forward:
|
||||||
|
return "Chat/Input/Text/IconForward"
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -30,6 +32,7 @@ public final class MessageInputActionButtonComponent: Component {
|
|||||||
case videoInput
|
case videoInput
|
||||||
case delete
|
case delete
|
||||||
case attach
|
case attach
|
||||||
|
case forward
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Action {
|
public enum Action {
|
||||||
@ -270,7 +273,7 @@ public final class MessageInputActionButtonComponent: Component {
|
|||||||
switch component.mode {
|
switch component.mode {
|
||||||
case .none:
|
case .none:
|
||||||
break
|
break
|
||||||
case .send, .apply, .attach, .delete:
|
case .send, .apply, .attach, .delete, .forward:
|
||||||
sendAlpha = 1.0
|
sendAlpha = 1.0
|
||||||
case .videoInput, .voiceInput:
|
case .videoInput, .voiceInput:
|
||||||
microphoneAlpha = 1.0
|
microphoneAlpha = 1.0
|
||||||
@ -300,7 +303,7 @@ public final class MessageInputActionButtonComponent: Component {
|
|||||||
|
|
||||||
if previousComponent?.mode != component.mode {
|
if previousComponent?.mode != component.mode {
|
||||||
switch component.mode {
|
switch component.mode {
|
||||||
case .none, .send, .apply, .voiceInput, .attach, .delete:
|
case .none, .send, .apply, .voiceInput, .attach, .delete, .forward:
|
||||||
micButton.updateMode(mode: .audio, animated: !transition.animation.isImmediate)
|
micButton.updateMode(mode: .audio, animated: !transition.animation.isImmediate)
|
||||||
case .videoInput:
|
case .videoInput:
|
||||||
micButton.updateMode(mode: .video, animated: !transition.animation.isImmediate)
|
micButton.updateMode(mode: .video, animated: !transition.animation.isImmediate)
|
||||||
|
@ -37,8 +37,8 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
public let stopAndPreviewMediaRecording: (() -> Void)?
|
public let stopAndPreviewMediaRecording: (() -> Void)?
|
||||||
public let discardMediaRecordingPreview: (() -> Void)?
|
public let discardMediaRecordingPreview: (() -> Void)?
|
||||||
public let attachmentAction: (() -> Void)?
|
public let attachmentAction: (() -> Void)?
|
||||||
public let reactionAction: ((UIView) -> Void)?
|
|
||||||
public let timeoutAction: ((UIView) -> Void)?
|
public let timeoutAction: ((UIView) -> Void)?
|
||||||
|
public let forwardAction: (() -> Void)?
|
||||||
public let audioRecorder: ManagedAudioRecorder?
|
public let audioRecorder: ManagedAudioRecorder?
|
||||||
public let videoRecordingStatus: InstantVideoControllerRecordingStatus?
|
public let videoRecordingStatus: InstantVideoControllerRecordingStatus?
|
||||||
public let isRecordingLocked: Bool
|
public let isRecordingLocked: Bool
|
||||||
@ -64,8 +64,8 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
stopAndPreviewMediaRecording: (() -> Void)?,
|
stopAndPreviewMediaRecording: (() -> Void)?,
|
||||||
discardMediaRecordingPreview: (() -> Void)?,
|
discardMediaRecordingPreview: (() -> Void)?,
|
||||||
attachmentAction: (() -> Void)?,
|
attachmentAction: (() -> Void)?,
|
||||||
reactionAction: ((UIView) -> Void)?,
|
|
||||||
timeoutAction: ((UIView) -> Void)?,
|
timeoutAction: ((UIView) -> Void)?,
|
||||||
|
forwardAction: (() -> Void)?,
|
||||||
audioRecorder: ManagedAudioRecorder?,
|
audioRecorder: ManagedAudioRecorder?,
|
||||||
videoRecordingStatus: InstantVideoControllerRecordingStatus?,
|
videoRecordingStatus: InstantVideoControllerRecordingStatus?,
|
||||||
isRecordingLocked: Bool,
|
isRecordingLocked: Bool,
|
||||||
@ -90,8 +90,8 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
self.stopAndPreviewMediaRecording = stopAndPreviewMediaRecording
|
self.stopAndPreviewMediaRecording = stopAndPreviewMediaRecording
|
||||||
self.discardMediaRecordingPreview = discardMediaRecordingPreview
|
self.discardMediaRecordingPreview = discardMediaRecordingPreview
|
||||||
self.attachmentAction = attachmentAction
|
self.attachmentAction = attachmentAction
|
||||||
self.reactionAction = reactionAction
|
|
||||||
self.timeoutAction = timeoutAction
|
self.timeoutAction = timeoutAction
|
||||||
|
self.forwardAction = forwardAction
|
||||||
self.audioRecorder = audioRecorder
|
self.audioRecorder = audioRecorder
|
||||||
self.videoRecordingStatus = videoRecordingStatus
|
self.videoRecordingStatus = videoRecordingStatus
|
||||||
self.isRecordingLocked = isRecordingLocked
|
self.isRecordingLocked = isRecordingLocked
|
||||||
@ -152,6 +152,9 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
if lhs.bottomInset != rhs.bottomInset {
|
if lhs.bottomInset != rhs.bottomInset {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (lhs.forwardAction == nil) != (rhs.forwardAction == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,6 +241,12 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func activateInput() {
|
||||||
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
|
textFieldView.activateInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
let result = super.hitTest(point, with: event)
|
let result = super.hitTest(point, with: event)
|
||||||
|
|
||||||
@ -520,7 +529,13 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
if hasMediaEditing {
|
if hasMediaEditing {
|
||||||
inputActionButtonMode = .send
|
inputActionButtonMode = .send
|
||||||
} else {
|
} else {
|
||||||
inputActionButtonMode = self.textFieldExternalState.hasText ? .send : (self.currentMediaInputIsVoice ? .voiceInput : .videoInput)
|
if self.textFieldExternalState.hasText {
|
||||||
|
inputActionButtonMode = .send
|
||||||
|
} else if !self.textFieldExternalState.isEditing && component.forwardAction != nil {
|
||||||
|
inputActionButtonMode = .forward
|
||||||
|
} else {
|
||||||
|
inputActionButtonMode = self.currentMediaInputIsVoice ? .voiceInput : .videoInput
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let inputActionButtonSize = self.inputActionButton.update(
|
let inputActionButtonSize = self.inputActionButton.update(
|
||||||
@ -546,10 +561,14 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
case .apply:
|
case .apply:
|
||||||
if case .up = action {
|
if case .up = action {
|
||||||
self.component?.sendMessageAction()
|
component.sendMessageAction()
|
||||||
}
|
}
|
||||||
case .voiceInput, .videoInput:
|
case .voiceInput, .videoInput:
|
||||||
component.setMediaRecordingActive?(action == .down, mode == .videoInput, sendAction)
|
component.setMediaRecordingActive?(action == .down, mode == .videoInput, sendAction)
|
||||||
|
case .forward:
|
||||||
|
if case .up = action {
|
||||||
|
component.forwardAction?()
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -639,39 +658,6 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let reactionAction = component.reactionAction {
|
|
||||||
let reactionButtonSize = self.reactionButton.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(Button(
|
|
||||||
content: AnyComponent(BundleIconComponent(
|
|
||||||
name: "Chat/Input/Text/AccessoryIconReaction",
|
|
||||||
tintColor: .white
|
|
||||||
)),
|
|
||||||
action: { [weak self] in
|
|
||||||
guard let self, let reactionButtonView = self.reactionButton.view else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reactionAction(reactionButtonView)
|
|
||||||
}
|
|
||||||
).minSize(CGSize(width: 32.0, height: 32.0))),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
|
||||||
)
|
|
||||||
if let reactionButtonView = self.reactionButton.view {
|
|
||||||
if reactionButtonView.superview == nil {
|
|
||||||
self.addSubview(reactionButtonView)
|
|
||||||
}
|
|
||||||
let reactionIconFrame = CGRect(origin: CGPoint(x: fieldIconNextX - reactionButtonSize.width, y: fieldBackgroundFrame.minY + 1.0 + floor((fieldBackgroundFrame.height - reactionButtonSize.height) * 0.5)), size: reactionButtonSize)
|
|
||||||
transition.setPosition(view: reactionButtonView, position: reactionIconFrame.center)
|
|
||||||
transition.setBounds(view: reactionButtonView, bounds: CGRect(origin: CGPoint(), size: reactionIconFrame.size))
|
|
||||||
|
|
||||||
transition.setAlpha(view: reactionButtonView, alpha: (self.textFieldExternalState.hasText || hasMediaRecording || hasMediaEditing || self.textFieldExternalState.isEditing) ? 0.0 : 1.0)
|
|
||||||
transition.setScale(view: reactionButtonView, scale: (self.textFieldExternalState.hasText || hasMediaRecording || hasMediaEditing || self.textFieldExternalState.isEditing) ? 0.1 : 1.0)
|
|
||||||
|
|
||||||
fieldIconNextX -= reactionButtonSize.width + 2.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
|
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
|
||||||
func generateIcon(value: String) -> UIImage? {
|
func generateIcon(value: String) -> UIImage? {
|
||||||
let image = UIImage(bundleImageName: "Media Editor/Timeout")!
|
let image = UIImage(bundleImageName: "Media Editor/Timeout")!
|
||||||
|
@ -165,7 +165,7 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
private var visibleItemSetViews: [EnginePeer.Id: ItemSetView] = [:]
|
private var visibleItemSetViews: [EnginePeer.Id: ItemSetView] = [:]
|
||||||
|
|
||||||
private var itemSetPanState: ItemSetPanState?
|
private var itemSetPanState: ItemSetPanState?
|
||||||
private var dismissPanState: ItemSetPanState?
|
private var verticalPanState: ItemSetPanState?
|
||||||
private var isHoldingTouch: Bool = false
|
private var isHoldingTouch: Bool = false
|
||||||
|
|
||||||
private var isAnimatingOut: Bool = false
|
private var isAnimatingOut: Bool = false
|
||||||
@ -345,21 +345,27 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
@objc private func dismissPanGesture(_ recognizer: UIPanGestureRecognizer) {
|
@objc private func dismissPanGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
self.dismissPanState = ItemSetPanState(fraction: 0.0, didBegin: true)
|
self.verticalPanState = ItemSetPanState(fraction: 0.0, didBegin: true)
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
case .changed:
|
case .changed:
|
||||||
let translation = recognizer.translation(in: self)
|
let translation = recognizer.translation(in: self)
|
||||||
self.dismissPanState = ItemSetPanState(fraction: max(0.0, min(1.0, translation.y / self.bounds.height)), didBegin: true)
|
self.verticalPanState = ItemSetPanState(fraction: max(-1.0, min(1.0, translation.y / self.bounds.height)), didBegin: true)
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
case .cancelled, .ended:
|
case .cancelled, .ended:
|
||||||
let translation = recognizer.translation(in: self)
|
let translation = recognizer.translation(in: self)
|
||||||
let velocity = recognizer.velocity(in: self)
|
let velocity = recognizer.velocity(in: self)
|
||||||
|
|
||||||
self.dismissPanState = nil
|
self.verticalPanState = nil
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||||
|
|
||||||
if translation.y > 100.0 || velocity.y > 10.0 {
|
if translation.y > 100.0 || velocity.y > 10.0 {
|
||||||
self.environment?.controller()?.dismiss()
|
self.environment?.controller()?.dismiss()
|
||||||
|
} else if translation.y < -100.0 || velocity.y < -40.0 {
|
||||||
|
if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id] {
|
||||||
|
if let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View {
|
||||||
|
itemSetComponentView.activateInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -470,7 +476,7 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
focusedItemPromise.set(.single(nil))
|
focusedItemPromise.set(.single(nil))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.dismissPanState = ItemSetPanState(fraction: 1.0, didBegin: true)
|
self.verticalPanState = ItemSetPanState(fraction: 1.0, didBegin: true)
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
||||||
|
|
||||||
let focusedItemPromise = self.component?.focusedItemPromise
|
let focusedItemPromise = self.component?.focusedItemPromise
|
||||||
@ -570,7 +576,7 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
if self.itemSetPanState != nil {
|
if self.itemSetPanState != nil {
|
||||||
isProgressPaused = true
|
isProgressPaused = true
|
||||||
}
|
}
|
||||||
if self.dismissPanState != nil {
|
if self.verticalPanState != nil {
|
||||||
isProgressPaused = true
|
isProgressPaused = true
|
||||||
}
|
}
|
||||||
if self.isAnimatingOut {
|
if self.isAnimatingOut {
|
||||||
@ -583,10 +589,14 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
var dismissPanOffset: CGFloat = 0.0
|
var dismissPanOffset: CGFloat = 0.0
|
||||||
var dismissPanScale: CGFloat = 1.0
|
var dismissPanScale: CGFloat = 1.0
|
||||||
var dismissAlphaScale: CGFloat = 1.0
|
var dismissAlphaScale: CGFloat = 1.0
|
||||||
if let dismissPanState = self.dismissPanState {
|
var verticalPanFraction: CGFloat = 0.0
|
||||||
dismissPanOffset = dismissPanState.fraction * availableSize.height
|
if let verticalPanState = self.verticalPanState {
|
||||||
dismissPanScale = 1.0 * (1.0 - dismissPanState.fraction) + 0.6 * dismissPanState.fraction
|
let dismissFraction = max(0.0, verticalPanState.fraction)
|
||||||
dismissAlphaScale = 1.0 * (1.0 - dismissPanState.fraction) + 0.2 * dismissPanState.fraction
|
verticalPanFraction = max(0.0, min(1.0, -verticalPanState.fraction))
|
||||||
|
|
||||||
|
dismissPanOffset = dismissFraction * availableSize.height
|
||||||
|
dismissPanScale = 1.0 * (1.0 - dismissFraction) + 0.6 * dismissFraction
|
||||||
|
dismissAlphaScale = 1.0 * (1.0 - dismissFraction) + 0.2 * dismissFraction
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setAlpha(layer: self.backgroundLayer, alpha: max(0.5, dismissAlphaScale))
|
transition.setAlpha(layer: self.backgroundLayer, alpha: max(0.5, dismissAlphaScale))
|
||||||
@ -685,6 +695,7 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
hideUI: i == focusedIndex && self.itemSetPanState?.didBegin == false,
|
hideUI: i == focusedIndex && self.itemSetPanState?.didBegin == false,
|
||||||
visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction),
|
visibilityFraction: 1.0 - abs(panFraction + cubeAdditionalRotationFraction),
|
||||||
isPanning: self.itemSetPanState?.didBegin == true,
|
isPanning: self.itemSetPanState?.didBegin == true,
|
||||||
|
verticalPanFraction: verticalPanFraction,
|
||||||
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
|
||||||
|
@ -47,6 +47,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
public let hideUI: Bool
|
public let hideUI: Bool
|
||||||
public let visibilityFraction: CGFloat
|
public let visibilityFraction: CGFloat
|
||||||
public let isPanning: Bool
|
public let isPanning: Bool
|
||||||
|
public let verticalPanFraction: CGFloat
|
||||||
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
|
||||||
@ -68,6 +69,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
hideUI: Bool,
|
hideUI: Bool,
|
||||||
visibilityFraction: CGFloat,
|
visibilityFraction: CGFloat,
|
||||||
isPanning: Bool,
|
isPanning: Bool,
|
||||||
|
verticalPanFraction: CGFloat,
|
||||||
presentController: @escaping (ViewController) -> Void,
|
presentController: @escaping (ViewController) -> Void,
|
||||||
close: @escaping () -> Void,
|
close: @escaping () -> Void,
|
||||||
navigate: @escaping (NavigationDirection) -> Void,
|
navigate: @escaping (NavigationDirection) -> Void,
|
||||||
@ -88,6 +90,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.hideUI = hideUI
|
self.hideUI = hideUI
|
||||||
self.visibilityFraction = visibilityFraction
|
self.visibilityFraction = visibilityFraction
|
||||||
self.isPanning = isPanning
|
self.isPanning = isPanning
|
||||||
|
self.verticalPanFraction = verticalPanFraction
|
||||||
self.presentController = presentController
|
self.presentController = presentController
|
||||||
self.close = close
|
self.close = close
|
||||||
self.navigate = navigate
|
self.navigate = navigate
|
||||||
@ -133,6 +136,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if lhs.isPanning != rhs.isPanning {
|
if lhs.isPanning != rhs.isPanning {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.verticalPanFraction != rhs.verticalPanFraction {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +211,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let closeButtonIconView: UIImageView
|
let closeButtonIconView: UIImageView
|
||||||
|
|
||||||
let navigationStrip = ComponentView<MediaNavigationStripComponent.EnvironmentType>()
|
let navigationStrip = ComponentView<MediaNavigationStripComponent.EnvironmentType>()
|
||||||
let inlineActions = ComponentView<Empty>()
|
|
||||||
|
|
||||||
var centerInfoItem: InfoItem?
|
var centerInfoItem: InfoItem?
|
||||||
var rightInfoItem: InfoItem?
|
var rightInfoItem: InfoItem?
|
||||||
@ -228,7 +233,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
var preloadContexts: [AnyHashable: Disposable] = [:]
|
var preloadContexts: [AnyHashable: Disposable] = [:]
|
||||||
|
|
||||||
var displayReactions: Bool = false
|
|
||||||
var reactionItems: [ReactionItem]?
|
var reactionItems: [ReactionItem]?
|
||||||
var reactionContextNode: ReactionContextNode?
|
var reactionContextNode: ReactionContextNode?
|
||||||
weak var disappearingReactionContextNode: ReactionContextNode?
|
weak var disappearingReactionContextNode: ReactionContextNode?
|
||||||
@ -423,11 +427,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||||
if case .ended = recognizer.state, let component = self.component, let itemLayout = self.itemLayout {
|
if case .ended = recognizer.state, let component = self.component, let itemLayout = self.itemLayout {
|
||||||
if hasFirstResponder(self) {
|
if hasFirstResponder(self) {
|
||||||
self.displayReactions = false
|
|
||||||
self.endEditing(true)
|
self.endEditing(true)
|
||||||
} else if self.displayReactions {
|
|
||||||
self.displayReactions = false
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
|
||||||
} else if self.displayViewList {
|
} else if self.displayViewList {
|
||||||
self.displayViewList = false
|
self.displayViewList = false
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||||
@ -476,7 +476,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.displayReactions || 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
|
||||||
}
|
}
|
||||||
if let captionItem = self.captionItem, captionItem.externalState.expandFraction > 0.0 {
|
if let captionItem = self.captionItem, captionItem.externalState.expandFraction > 0.0 {
|
||||||
@ -576,6 +576,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func activateInput() {
|
||||||
|
if let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||||
|
inputPanelView.activateInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func animateIn(transitionIn: StoryContainerScreen.TransitionIn) {
|
func animateIn(transitionIn: StoryContainerScreen.TransitionIn) {
|
||||||
self.closeButton.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, delay: 0.12, timingFunction: kCAMediaTimingFunctionSpring)
|
self.closeButton.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2, delay: 0.12, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
@ -835,9 +841,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.reactionItems = reactionItems
|
self.reactionItems = reactionItems
|
||||||
if self.displayReactions {
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -966,29 +969,13 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
self.sendMessageContext.presentAttachmentMenu(view: self, subject: .default)
|
self.sendMessageContext.presentAttachmentMenu(view: self, subject: .default)
|
||||||
},
|
},
|
||||||
reactionAction: { [weak self] sourceView in
|
timeoutAction: nil,
|
||||||
guard let self, let component = self.component else {
|
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
|
||||||
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.sendMessageContext.performShareAction(view: self)
|
||||||
let _ = (allowedStoryReactions(context: component.context)
|
} : nil,
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] reactionItems in
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
component.controller()?.forEachController { c in
|
|
||||||
if let c = c as? UndoOverlayController {
|
|
||||||
c.dismiss()
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
self.displayReactions = !self.displayReactions
|
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
timeoutAction: nil,
|
|
||||||
audioRecorder: self.sendMessageContext.audioRecorderValue,
|
audioRecorder: self.sendMessageContext.audioRecorderValue,
|
||||||
videoRecordingStatus: self.sendMessageContext.videoRecorderValue?.audioStatus,
|
videoRecordingStatus: self.sendMessageContext.videoRecorderValue?.audioStatus,
|
||||||
isRecordingLocked: self.sendMessageContext.isMediaRecordingLocked,
|
isRecordingLocked: self.sendMessageContext.isMediaRecordingLocked,
|
||||||
@ -1457,7 +1444,14 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if inputPanelView.superview == nil {
|
if inputPanelView.superview == nil {
|
||||||
self.addSubview(inputPanelView)
|
self.addSubview(inputPanelView)
|
||||||
}
|
}
|
||||||
inputPanelTransition.setFrame(view: inputPanelView, frame: inputPanelFrame)
|
|
||||||
|
var inputPanelOffset: CGFloat = 0.0
|
||||||
|
if focusedItem?.isMy == false && !self.inputPanelExternalState.isEditing {
|
||||||
|
let bandingOffset = scrollingRubberBandingOffset(offset: component.verticalPanFraction * availableSize.height, bandingStart: 0.0, range: 10.0)
|
||||||
|
inputPanelOffset = -max(0.0, min(10.0, bandingOffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPanelTransition.setFrame(view: inputPanelView, frame: inputPanelFrame.offsetBy(dx: 0.0, dy: inputPanelOffset))
|
||||||
transition.setAlpha(view: inputPanelView, alpha: inputPanelAlpha)
|
transition.setAlpha(view: inputPanelView, alpha: inputPanelAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1503,7 +1497,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrame.maxX - 40.0, y: inputPanelFrame.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
|
let reactionsAnchorRect = CGRect(origin: CGPoint(x: inputPanelFrame.maxX - 40.0, y: inputPanelFrame.minY + 9.0), size: CGSize(width: 32.0, height: 32.0)).insetBy(dx: -4.0, dy: -4.0)
|
||||||
|
|
||||||
var effectiveDisplayReactions = self.displayReactions
|
var effectiveDisplayReactions = false
|
||||||
if self.inputPanelExternalState.isEditing && !self.inputPanelExternalState.hasText {
|
if self.inputPanelExternalState.isEditing && !self.inputPanelExternalState.hasText {
|
||||||
effectiveDisplayReactions = true
|
effectiveDisplayReactions = true
|
||||||
}
|
}
|
||||||
@ -1619,7 +1613,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.displayReactions = false
|
|
||||||
if hasFirstResponder(self) {
|
if hasFirstResponder(self) {
|
||||||
self.endEditing(true)
|
self.endEditing(true)
|
||||||
}
|
}
|
||||||
@ -1781,50 +1774,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: navigationStripSideInset, y: navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0)))
|
transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: navigationStripSideInset, y: navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0)))
|
||||||
transition.setAlpha(view: navigationStripView, alpha: (component.hideUI || self.displayViewList) ? 0.0 : 1.0)
|
transition.setAlpha(view: navigationStripView, alpha: (component.hideUI || self.displayViewList) ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var items: [StoryActionsComponent.Item] = []
|
|
||||||
let _ = focusedItem
|
|
||||||
items.append(StoryActionsComponent.Item(
|
|
||||||
kind: .share,
|
|
||||||
isActivated: false
|
|
||||||
))
|
|
||||||
|
|
||||||
let inlineActionsSize = self.inlineActions.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(StoryActionsComponent(
|
|
||||||
items: items,
|
|
||||||
action: { [weak self] item in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.sendMessageContext.performInlineAction(view: self, item: item)
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: contentFrame.size
|
|
||||||
)
|
|
||||||
if let inlineActionsView = self.inlineActions.view {
|
|
||||||
if inlineActionsView.superview == nil {
|
|
||||||
self.contentContainerView.addSubview(inlineActionsView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: inlineActionsView, frame: CGRect(origin: CGPoint(x: contentFrame.width - 10.0 - inlineActionsSize.width, y: contentFrame.height - 10.0 - inlineActionsSize.height), size: inlineActionsSize))
|
|
||||||
|
|
||||||
var inlineActionsAlpha: CGFloat = inputPanelIsOverlay ? 0.0 : 1.0
|
|
||||||
if self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil {
|
|
||||||
inlineActionsAlpha = 0.0
|
|
||||||
}
|
|
||||||
if self.displayReactions {
|
|
||||||
inlineActionsAlpha = 0.0
|
|
||||||
}
|
|
||||||
if component.hideUI || self.displayViewList {
|
|
||||||
inlineActionsAlpha = 0.0
|
|
||||||
}
|
|
||||||
if !component.slice.item.storyItem.isPublic {
|
|
||||||
inlineActionsAlpha = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.setAlpha(view: inlineActionsView, alpha: inlineActionsAlpha)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
component.externalState.derivedMediaSize = contentFrame.size
|
component.externalState.derivedMediaSize = contentFrame.size
|
||||||
|
@ -272,7 +272,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func performInlineAction(view: StoryItemSetContainerComponent.View, item: StoryActionsComponent.Item) {
|
func performShareAction(view: StoryItemSetContainerComponent.View) {
|
||||||
guard let component = view.component else {
|
guard let component = view.component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -284,23 +284,18 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch item.kind {
|
/*let linkPromise = Promise<String?, NoError>()
|
||||||
case .like:
|
linkPromise.set(component.context.engine.messages.exportStoryLink(peerId: peerId, id: focusedItem.storyItem.id))*/
|
||||||
return
|
|
||||||
case .share:
|
let shareController = ShareController(
|
||||||
/*let linkPromise = Promise<String?, NoError>()
|
context: component.context,
|
||||||
linkPromise.set(component.context.engine.messages.exportStoryLink(peerId: peerId, id: focusedItem.storyItem.id))*/
|
subject: .media(AnyMediaReference.standalone(media: TelegramMediaStory(storyId: StoryId(peerId: peerId, id: focusedItem.storyItem.id)))),
|
||||||
|
externalShare: false,
|
||||||
let shareController = ShareController(
|
immediateExternalShare: false,
|
||||||
context: component.context,
|
updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }),
|
||||||
subject: .media(AnyMediaReference.standalone(media: TelegramMediaStory(storyId: StoryId(peerId: peerId, id: focusedItem.storyItem.id)))),
|
component.context.sharedContext.presentationData)
|
||||||
externalShare: false,
|
)
|
||||||
immediateExternalShare: false,
|
controller.present(shareController, in: .window(.root))
|
||||||
updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }),
|
|
||||||
component.context.sharedContext.presentationData)
|
|
||||||
)
|
|
||||||
controller.present(shareController, in: .window(.root))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func clearInputText(view: StoryItemSetContainerComponent.View) {
|
private func clearInputText(view: StoryItemSetContainerComponent.View) {
|
||||||
|
@ -126,6 +126,10 @@ public final class TextFieldComponent: Component {
|
|||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged)))
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func activateInput() {
|
||||||
|
self.textView.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: TextFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: TextFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForward.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForward.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ic_forward.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForward.imageset/ic_forward.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Input/Text/IconForward.imageset/ic_forward.pdf
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user