mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Various improvements
This commit is contained in:
@@ -277,6 +277,7 @@ public final class ChatTextInputPanelComponent: Component {
|
|||||||
private var panelNode: ChatTextInputPanelNode?
|
private var panelNode: ChatTextInputPanelNode?
|
||||||
|
|
||||||
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||||
|
private var hasPendingInputTextRefresh: Bool = false
|
||||||
|
|
||||||
private var component: ChatTextInputPanelComponent?
|
private var component: ChatTextInputPanelComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
@@ -406,7 +407,10 @@ public final class ChatTextInputPanelComponent: Component {
|
|||||||
if let component = self.component {
|
if let component = self.component {
|
||||||
let currentMode = inputModeFromComponent(component)
|
let currentMode = inputModeFromComponent(component)
|
||||||
let (updatedTextInputState, updatedMode) = f(component.externalState.textInputState, currentMode)
|
let (updatedTextInputState, updatedMode) = f(component.externalState.textInputState, currentMode)
|
||||||
|
if component.externalState.textInputState != updatedTextInputState {
|
||||||
component.externalState.textInputState = updatedTextInputState
|
component.externalState.textInputState = updatedTextInputState
|
||||||
|
self.hasPendingInputTextRefresh = true
|
||||||
|
}
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
@@ -944,7 +948,10 @@ public final class ChatTextInputPanelComponent: Component {
|
|||||||
component.externalState.resetInputState = nil
|
component.externalState.resetInputState = nil
|
||||||
let _ = resetInputState
|
let _ = resetInputState
|
||||||
panelNode.text = ""
|
panelNode.text = ""
|
||||||
|
} else if self.hasPendingInputTextRefresh {
|
||||||
|
panelNode.updateInputTextState(component.externalState.textInputState)
|
||||||
}
|
}
|
||||||
|
self.hasPendingInputTextRefresh = false
|
||||||
|
|
||||||
let panelHeight = panelNode.updateLayout(
|
let panelHeight = panelNode.updateLayout(
|
||||||
width: availableSize.width,
|
width: availableSize.width,
|
||||||
|
|||||||
@@ -529,6 +529,42 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateInputTextState(_ state: ChatTextInputState) {
|
||||||
|
if self.ignoreInputStateUpdates {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if state.inputText.length != 0 && self.textInputNode == nil {
|
||||||
|
self.loadTextInputNode()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let textInputNode = self.textInputNode, let _ = self.presentationInterfaceState, let context = self.context {
|
||||||
|
self.updatingInputState = true
|
||||||
|
|
||||||
|
var textColor: UIColor = .black
|
||||||
|
var accentTextColor: UIColor = .blue
|
||||||
|
var baseFontSize: CGFloat = 17.0
|
||||||
|
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||||
|
textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor
|
||||||
|
accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor
|
||||||
|
baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize)
|
||||||
|
}
|
||||||
|
textInputNode.attributedText = textAttributedStringForStateText(context: context, stateText: state.inputText, fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in
|
||||||
|
return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes)
|
||||||
|
})
|
||||||
|
textInputNode.selectedRange = NSMakeRange(state.selectionRange.lowerBound, state.selectionRange.count)
|
||||||
|
|
||||||
|
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||||
|
refreshChatTextInputAttributes(context: context, textView: textInputNode.textView, theme: presentationInterfaceState.theme, baseFontSize: baseFontSize, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in
|
||||||
|
return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updatingInputState = false
|
||||||
|
self.updateTextNodeText(animated: false)
|
||||||
|
self.updateSpoiler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func updateKeepSendButtonEnabled(keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, animated: Bool) {
|
public func updateKeepSendButtonEnabled(keepSendButtonEnabled: Bool, extendedSearchLayout: Bool, animated: Bool) {
|
||||||
if keepSendButtonEnabled != self.keepSendButtonEnabled || extendedSearchLayout != self.extendedSearchLayout {
|
if keepSendButtonEnabled != self.keepSendButtonEnabled || extendedSearchLayout != self.extendedSearchLayout {
|
||||||
self.keepSendButtonEnabled = keepSendButtonEnabled
|
self.keepSendButtonEnabled = keepSendButtonEnabled
|
||||||
|
|||||||
@@ -1007,6 +1007,9 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
|
self.isAnimatingOut = false
|
||||||
|
self.didAnimateOut = false
|
||||||
|
|
||||||
if let component = self.component {
|
if let component = self.component {
|
||||||
component.focusedItemPromise.set(self.focusedItem.get())
|
component.focusedItemPromise.set(self.focusedItem.get())
|
||||||
}
|
}
|
||||||
@@ -2158,6 +2161,40 @@ public class StoryContainerScreen: ViewControllerComponentContainer {
|
|||||||
self.dismiss(completion: completion)
|
self.dismiss(completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dismissForPictureInPicture() {
|
||||||
|
if !self.isDismissed {
|
||||||
|
self.isDismissed = true
|
||||||
|
self.didAnimateIn = false
|
||||||
|
|
||||||
|
/*if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View {
|
||||||
|
componentView.endEditing(true)
|
||||||
|
|
||||||
|
componentView.animateOut(completion: { [weak self] in
|
||||||
|
self?.dismiss(animated: false)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.dismiss(animated: false)
|
||||||
|
}*/
|
||||||
|
self.dismiss(animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreForPictureInPicture(navigationController: NavigationController, completion: @escaping () -> Void) {
|
||||||
|
if self.isDismissed {
|
||||||
|
self.isDismissed = false
|
||||||
|
|
||||||
|
navigationController.pushViewController(self, animated: false, completion: completion)
|
||||||
|
|
||||||
|
if !self.didAnimateIn {
|
||||||
|
self.didAnimateIn = true
|
||||||
|
|
||||||
|
if let componentView = self.node.hostView.componentView as? StoryContainerScreenComponent.View {
|
||||||
|
componentView.animateIn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
if !self.isDismissed {
|
if !self.isDismissed {
|
||||||
self.isDismissed = true
|
self.isDismissed = true
|
||||||
|
|||||||
@@ -166,6 +166,10 @@ final class StoryItemContentComponent: Component {
|
|||||||
private var liveCallStateDisposable: Disposable?
|
private var liveCallStateDisposable: Disposable?
|
||||||
private var liveCallStatsDisposable: Disposable?
|
private var liveCallStatsDisposable: Disposable?
|
||||||
private var mediaStream: ComponentView<Empty>?
|
private var mediaStream: ComponentView<Empty>?
|
||||||
|
private let activatePictureInPictureAction = ActionSlot<Action<Void>>()
|
||||||
|
private let deactivatePictureInPictureAction = ActionSlot<Void>()
|
||||||
|
private var restorePictureInPicture: ((@escaping () -> Void) -> Void)?
|
||||||
|
private var dismissWhileInPictureInPicture: (() -> Void)?
|
||||||
private var loadingEffectView: StoryItemLoadingEffectView?
|
private var loadingEffectView: StoryItemLoadingEffectView?
|
||||||
private var loadingEffectAppearanceTimer: SwiftSignalKit.Timer?
|
private var loadingEffectAppearanceTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
@@ -534,7 +538,7 @@ final class StoryItemContentComponent: Component {
|
|||||||
if let mediaStreamCall = self.mediaStreamCall {
|
if let mediaStreamCall = self.mediaStreamCall {
|
||||||
//print("call progressMode: \(self.progressMode)")
|
//print("call progressMode: \(self.progressMode)")
|
||||||
var canPlay = true
|
var canPlay = true
|
||||||
if case .pause = self.progressMode.mode, (!self.progressMode.isCentral || !self.hierarchyTrackingLayer.isInHierarchy) {
|
if case .pause = self.progressMode.mode, (!self.progressMode.isCentral || (!self.hierarchyTrackingLayer.isInHierarchy && self.restorePictureInPicture == nil)) {
|
||||||
canPlay = false
|
canPlay = false
|
||||||
}
|
}
|
||||||
if !canPlay {
|
if !canPlay {
|
||||||
@@ -783,6 +787,22 @@ final class StoryItemContentComponent: Component {
|
|||||||
self.isSeeking = false
|
self.isSeeking = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func beginPictureInPicture(dismissController: @escaping () -> (restore: (@escaping () -> Void) -> Void, dismissWhilePictureInPicture: () -> Void)) {
|
||||||
|
self.activatePictureInPictureAction.invoke(Action { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var restorePictureInPictureImpl: ((restore: (@escaping () -> Void) -> Void, dismissWhilePictureInPicture: () -> Void))?
|
||||||
|
self.restorePictureInPicture = { f in
|
||||||
|
restorePictureInPictureImpl?.restore(f)
|
||||||
|
}
|
||||||
|
self.dismissWhileInPictureInPicture = {
|
||||||
|
restorePictureInPictureImpl?.dismissWhilePictureInPicture()
|
||||||
|
}
|
||||||
|
restorePictureInPictureImpl = dismissController()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: StoryItemContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StoryContentItem.Environment>, transition: ComponentTransition) -> CGSize {
|
func update(component: StoryItemContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StoryContentItem.Environment>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
defer {
|
||||||
@@ -1000,13 +1020,30 @@ final class StoryItemContentComponent: Component {
|
|||||||
isFullscreen: false,
|
isFullscreen: false,
|
||||||
videoLoading: false,
|
videoLoading: false,
|
||||||
callPeer: nil,
|
callPeer: nil,
|
||||||
enablePictureInPicture: false,
|
enablePictureInPicture: true,
|
||||||
activatePictureInPicture: ActionSlot(),
|
activatePictureInPicture: self.activatePictureInPictureAction,
|
||||||
deactivatePictureInPicture: ActionSlot(),
|
deactivatePictureInPicture: self.deactivatePictureInPictureAction,
|
||||||
bringBackControllerForPictureInPictureDeactivation: { f in
|
bringBackControllerForPictureInPictureDeactivation: { [weak self] f in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.dismissWhileInPictureInPicture = nil
|
||||||
|
if let restorePictureInPicture = self.restorePictureInPicture {
|
||||||
|
self.restorePictureInPicture = nil
|
||||||
|
restorePictureInPicture(f)
|
||||||
|
} else {
|
||||||
f()
|
f()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
pictureInPictureClosed: {
|
pictureInPictureClosed: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.restorePictureInPicture = nil
|
||||||
|
if let dismissWhileInPictureInPicture = self.dismissWhileInPictureInPicture {
|
||||||
|
self.dismissWhileInPictureInPicture = nil
|
||||||
|
dismissWhileInPictureInPicture()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onVideoSizeRetrieved: { _ in
|
onVideoSizeRetrieved: { _ in
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1426,7 +1426,10 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if self.verticalPanState != nil {
|
if self.verticalPanState != nil {
|
||||||
return .pause
|
return .pause
|
||||||
}
|
}
|
||||||
if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.sendMessageContext.actionSheet != nil || self.sendMessageContext.isViewingAttachedStickers || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.viewListDisplayState != .hidden {
|
if self.contextController != nil {
|
||||||
|
return .blurred
|
||||||
|
}
|
||||||
|
if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.sendMessageContext.actionSheet != nil || self.sendMessageContext.isViewingAttachedStickers || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.viewListDisplayState != .hidden {
|
||||||
return .pause
|
return .pause
|
||||||
}
|
}
|
||||||
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
|
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
|
||||||
@@ -6505,7 +6508,19 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.openItemPrivacySettings()
|
self.openItemPrivacySettings()
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if !isLiveStream {
|
if isLiveStream {
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Minimize", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/pip"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.beginPictureInPicture()
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: component.strings.Story_Context_Edit, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { [weak self] _, a in
|
}, action: { [weak self] _, a in
|
||||||
@@ -6827,7 +6842,19 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isLiveStream {
|
if isLiveStream {
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Minimize", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/pip"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.beginPictureInPicture()
|
||||||
|
})))
|
||||||
|
} else {
|
||||||
let saveText: String = component.strings.Story_Context_SaveToGallery
|
let saveText: String = component.strings.Story_Context_SaveToGallery
|
||||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor)
|
||||||
@@ -7300,7 +7327,19 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !component.slice.item.storyItem.isForwardingDisabled && !isLiveStream {
|
if isLiveStream {
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Minimize", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/pip"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { [weak self] _, a in
|
||||||
|
a(.default)
|
||||||
|
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.beginPictureInPicture()
|
||||||
|
})))
|
||||||
|
} else if !component.slice.item.storyItem.isForwardingDisabled {
|
||||||
let saveText: String = component.strings.Story_Context_SaveToGallery
|
let saveText: String = component.strings.Story_Context_SaveToGallery
|
||||||
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: saveText, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Download" : "Chat/Context Menu/DownloadLocked"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: accountUser.isPremium ? "Chat/Context Menu/Download" : "Chat/Context Menu/DownloadLocked"), color: theme.contextMenu.primaryColor)
|
||||||
@@ -7425,6 +7464,33 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func beginPictureInPicture() {
|
||||||
|
guard let component = self.component, let visibleItem = self.visibleItems[component.slice.item.id] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let itemView = visibleItem.view.view as? StoryItemContentComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
itemView.beginPictureInPicture(dismissController: { [weak self] in
|
||||||
|
guard let self, let component = self.component, let controller = component.controller() as? StoryContainerScreen, let navigationController = controller.navigationController as? NavigationController else {
|
||||||
|
return ({ completion in
|
||||||
|
completion()
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.dismissForPictureInPicture()
|
||||||
|
|
||||||
|
return ({ [weak navigationController] completion in
|
||||||
|
guard let navigationController else {
|
||||||
|
completion()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.restoreForPictureInPicture(navigationController: navigationController, completion: completion)
|
||||||
|
}, {
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private func presentAddStoryFolder(addItems: [EngineStoryItem] = []) {
|
private func presentAddStoryFolder(addItems: [EngineStoryItem] = []) {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user