Media caption input panel improvements

This commit is contained in:
Ilya Laktyushin 2023-08-30 19:26:07 +04:00
parent 9fd6b9369f
commit b1f40bf0aa
19 changed files with 722 additions and 156 deletions

View File

@ -446,8 +446,9 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
public var sendPressed: ((NSAttributedString?) -> Void)?
public var focusUpdated: ((Bool) -> Void)?
public var heightUpdated: ((Bool) -> Void)?
public var timerUpdated: ((NSNumber?) -> Void)?
public func updateLayoutSize(_ size: CGSize, sideInset: CGFloat, animated: Bool) -> CGFloat {
public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat {
guard let presentationInterfaceState = self.presentationInterfaceState else {
return 0.0
}
@ -460,6 +461,16 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
}
public func setTimeout(_ timeout: Int32) {
}
public func animate(_ view: UIView, frame: CGRect) {
}
public func onAnimateOut() {
}
public func dismissInput() {
self.ensureUnfocused()
}

View File

@ -89,7 +89,7 @@ extension UIView {
}
open class ComponentState {
var _updated: ((Transition) -> Void)?
open var _updated: ((Transition) -> Void)?
var isUpdated: Bool = false
public init() {

View File

@ -22,18 +22,22 @@
@property (nonatomic, copy) void (^panelFocused)(void);
@property (nonatomic, copy) void (^finishedWithCaption)(NSAttributedString *caption);
@property (nonatomic, copy) void (^keyboardHeightChanged)(CGFloat keyboardHeight, NSTimeInterval duration, NSInteger animationCurve);
@property (nonatomic, copy) void (^timerUpdated)(NSNumber *timeout);
- (void)createInputPanelIfNeeded;
- (void)beginEditing;
- (void)enableDismissal;
- (void)onAnimateOut;
- (void)destroy;
@property (nonatomic, strong) NSAttributedString *caption;
- (void)setCaption:(NSAttributedString *)caption animated:(bool)animated;
- (void)setCaptionPanelHidden:(bool)hidden animated:(bool)animated;
- (void)setTimeout:(int32_t)timeout;
- (void)updateLayoutWithFrame:(CGRect)frame edgeInsets:(UIEdgeInsets)edgeInsets animated:(bool)animated;
@end

View File

@ -22,15 +22,22 @@
@property (nonatomic, readonly) UIView * _Nonnull view;
- (void)setTimeout:(int32_t)timeout;
- (NSAttributedString * _Nonnull)caption;
- (void)setCaption:(NSAttributedString * _Nullable)caption;
- (void)dismissInput;
- (void)animateView:(UIView * _Nonnull)view frame:(CGRect)frame;
- (void)onAnimateOut;
@property (nonatomic, copy) void(^ _Nullable sendPressed)(NSAttributedString * _Nullable string);
@property (nonatomic, copy) void(^ _Nullable focusUpdated)(BOOL focused);
@property (nonatomic, copy) void(^ _Nullable heightUpdated)(BOOL animated);
@property (nonatomic, copy) void(^ _Nullable timerUpdated)(NSNumber * _Nullable value);
- (CGFloat)updateLayoutSize:(CGSize)size sideInset:(CGFloat)sideInset animated:(bool)animated;
- (CGFloat)updateLayoutSize:(CGSize)size keyboardHeight:(CGFloat)keyboardHeight sideInset:(CGFloat)sideInset animated:(bool)animated;
- (CGFloat)baseHeight;
@end

View File

@ -42,6 +42,28 @@
#import <LegacyComponents/TGPhotoCaptionInputMixin.h>
@interface TGMediaPickerGalleryWrapperView: UIView
{
}
@end
@implementation TGMediaPickerGalleryWrapperView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
__block UIView *result = nil;
[self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *hitTestView = [view hitTest:[self convertPoint:point toView:view] withEvent:event];
if (hitTestView != nil) {
*stop = true;
result = hitTestView;
}
}];
return result;
}
@end
@interface TGMediaPickerGalleryInterfaceView () <ASWatcher>
{
id<TGModernGalleryItem> _currentItem;
@ -121,7 +143,7 @@
_itemHeaderViews = [[NSMutableArray alloc] init];
_itemFooterViews = [[NSMutableArray alloc] init];
_wrapperView = [[UIView alloc] initWithFrame:CGRectZero];
_wrapperView = [[TGMediaPickerGalleryWrapperView alloc] initWithFrame:CGRectZero];
[self addSubview:_wrapperView];
_headerWrapperView = [[UIView alloc] init];
@ -148,6 +170,8 @@
strongSelf->_portraitToolbarView.doneButton.userInteractionEnabled = false;
strongSelf->_landscapeToolbarView.doneButton.userInteractionEnabled = false;
strongSelf->_donePressed(strongSelf->_currentItem);
[strongSelf->_captionMixin onAnimateOut];
};
void(^toolbarDoneLongPressed)(id) = ^(id sender)
{
@ -350,6 +374,18 @@
} completion:nil];
};
_captionMixin.timerUpdated = ^(NSNumber *timeout) {
__strong TGMediaPickerGalleryInterfaceView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (![strongSelf->_currentItem conformsToProtocol:@protocol(TGModernGalleryEditableItem)])
return;
id<TGModernGalleryEditableItem> galleryEditableItem = (id<TGModernGalleryEditableItem>)strongSelf->_currentItem;
[strongSelf->_editingContext setTimer:timeout forItem:galleryEditableItem.editableMediaItem];
};
_captionMixin.stickersContext = stickersContext;
[_captionMixin createInputPanelIfNeeded];
@ -800,6 +836,8 @@
id<TGMediaEditAdjustments> adjustments = dict[@"adjustments"];
NSNumber *timer = dict[@"timer"];
[strongSelf->_captionMixin setTimeout:[timer intValue]];
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]])
{
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
@ -1285,7 +1323,7 @@
- (void)animateTransitionOutWithDuration:(NSTimeInterval)__unused duration
{
[_captionMixin onAnimateOut];
}
- (void)setTransitionOutProgress:(CGFloat)transitionOutProgress manual:(bool)manual

View File

@ -87,6 +87,14 @@
[strongSelf updateLayoutWithFrame:strongSelf->_currentFrame edgeInsets:strongSelf->_currentEdgeInsets animated:animated];
};
_inputPanel.timerUpdated = ^(NSNumber *value) {
__strong TGPhotoCaptionInputMixin *strongSelf = weakSelf;
if (strongSelf.timerUpdated != nil) {
strongSelf.timerUpdated(value);
}
};
_inputPanelView = inputPanel.view;
_backgroundView = [[UIView alloc] init];
@ -95,6 +103,10 @@
[parentView addSubview:_inputPanelView];
}
- (void)onAnimateOut {
[_inputPanel onAnimateOut];
}
- (void)destroy
{
[_inputPanelView removeFromSuperview];
@ -129,6 +141,10 @@
[_inputPanel setCaption:caption];
}
- (void)setTimeout:(int32_t)timeout {
[_inputPanel setTimeout:timeout];
}
- (void)setCaptionPanelHidden:(bool)hidden animated:(bool)__unused animated
{
_inputPanelView.hidden = hidden;
@ -204,7 +220,7 @@
CGRect frame = _currentFrame;
UIEdgeInsets edgeInsets = _currentEdgeInsets;
CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size sideInset:0.0 animated:false];
CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size keyboardHeight:keyboardHeight sideInset:0.0 animated:false];
[UIView animateWithDuration:duration delay:0.0f options:(curve << 16) animations:^{
_inputPanelView.frame = CGRectMake(edgeInsets.left, frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight), frame.size.width, panelHeight);
@ -224,7 +240,7 @@
_currentFrame = frame;
_currentEdgeInsets = edgeInsets;
CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size sideInset:0.0 animated:animated];
CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size keyboardHeight:_keyboardHeight sideInset:0.0 animated:animated];
CGFloat y = 0.0;
if (frame.size.width > frame.size.height && !TGIsPad()) {
@ -238,14 +254,15 @@
backgroundHeight += _keyboardHeight - edgeInsets.bottom;
}
CGRect panelFrame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight);
CGRect backgroundFrame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight);
if (animated) {
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_inputPanelView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight);
_backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight);
} completion:nil];
[_inputPanel animateView:_inputPanelView frame:panelFrame];
[_inputPanel animateView:_backgroundView frame:backgroundFrame];
} else {
_inputPanelView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, panelHeight);
_backgroundView.frame = CGRectMake(edgeInsets.left, y, frame.size.width, backgroundHeight);
_inputPanelView.frame = panelFrame;
_backgroundView.frame = backgroundFrame;
}
}

View File

@ -459,7 +459,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
}
let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: imageFlags)
if let timer = item.timer, timer > 0 && timer <= 60 {
if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
if let spoiler = item.spoiler, spoiler {
@ -531,7 +531,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
])
var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
if let spoiler = item.spoiler, spoiler {
@ -581,7 +581,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: representations, immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
var attributes: [MessageAttribute] = []
if let timer = item.timer, timer > 0 && timer <= 60 {
if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
if let spoiler = item.spoiler, spoiler {
@ -827,7 +827,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: fileAttributes)
if let timer = item.timer, timer > 0 && timer <= 60 {
if let timer = item.timer, timer > 0 && (timer <= 60 || timer == viewOnceTimeout) {
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
if let spoiler = item.spoiler, spoiler {

View File

@ -339,6 +339,7 @@ swift_library(
"//submodules/TelegramUI/Components/PeerSelectionController",
"//submodules/TelegramUI/Components/Chat/AccessoryPanelNode",
"//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode",
"//submodules/TelegramUI/Components/LegacyMessageInputPanel",
] + select({
"@build_bazel_rules_apple//apple:ios_armv7": [],
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View File

@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "LegacyMessageInputPanel",
module_name = "LegacyMessageInputPanel",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramCore",
"//submodules/Postbox",
"//submodules/AccountContext",
"//submodules/LegacyComponents",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/ContextUI",
"//submodules/TooltipUI",
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,370 @@
import Foundation
import UIKit
import AsyncDisplayKit
import LegacyComponents
import Display
import TelegramCore
import Postbox
import SwiftSignalKit
import AccountContext
import LegacyComponents
import ComponentFlow
import MessageInputPanelComponent
import TelegramPresentationData
import ContextUI
import TooltipUI
public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
private let context: AccountContext
private let chatLocation: ChatLocation
private let present: (ViewController) -> Void
private let presentInGlobalOverlay: (ViewController) -> Void
private let state = ComponentState()
private let inputPanelExternalState = MessageInputPanelComponent.ExternalState()
private let inputPanel = ComponentView<Empty>()
private var currentTimeout: Int32?
private var currentIsEditing = false
private var currentHeight: CGFloat?
private let hapticFeedback = HapticFeedback()
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, metrics: LayoutMetrics)?
public init(
context: AccountContext,
chatLocation: ChatLocation,
present: @escaping (ViewController) -> Void,
presentInGlobalOverlay: @escaping (ViewController) -> Void
) {
self.context = context
self.chatLocation = chatLocation
self.present = present
self.presentInGlobalOverlay = presentInGlobalOverlay
super.init()
self.state._updated = { [weak self] transition in
if let self {
self.update(transition: transition.containedViewLayoutTransition)
}
}
}
public var sendPressed: ((NSAttributedString?) -> Void)?
public var focusUpdated: ((Bool) -> Void)?
public var heightUpdated: ((Bool) -> Void)?
public var timerUpdated: ((NSNumber?) -> Void)?
public func updateLayoutSize(_ size: CGSize, keyboardHeight: CGFloat, sideInset: CGFloat, animated: Bool) -> CGFloat {
return self.updateLayout(width: size.width, leftInset: sideInset, rightInset: sideInset, bottomInset: 0.0, keyboardHeight: keyboardHeight, additionalSideInsets: UIEdgeInsets(), maxHeight: size.height, isSecondary: false, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), isMediaInputExpanded: false)
}
public func caption() -> NSAttributedString {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View, case let .text(caption) = view.getSendMessageInput() {
return caption
} else {
return NSAttributedString()
}
}
public func setCaption(_ caption: NSAttributedString?) {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
view.setSendMessageInput(value: .text(caption ?? NSAttributedString()), updateState: true)
}
}
public func animate(_ view: UIView, frame: CGRect) {
let transition = Transition.spring(duration: 0.4)
transition.setFrame(view: view, frame: frame)
}
public func setTimeout(_ timeout: Int32) {
var timeout: Int32? = timeout
if timeout == 0 {
timeout = nil
}
self.currentTimeout = timeout
}
public func dismissInput() {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
view.deactivateInput()
}
}
public func onAnimateOut() {
self.tooltipController?.dismiss()
}
public func baseHeight() -> CGFloat {
return 52.0
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func update(transition: ContainedViewLayoutTransition) {
if let (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics) = self.validLayout {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, keyboardHeight: keyboardHeight, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, metrics: metrics, isMediaInputExpanded: false)
}
}
public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, keyboardHeight: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let previousLayout = self.validLayout
self.validLayout = (width, leftInset, rightInset, bottomInset, keyboardHeight, additionalSideInsets, maxHeight, isSecondary, metrics)
var transition = transition
if keyboardHeight.isZero, let previousKeyboardHeight = previousLayout?.keyboardHeight, previousKeyboardHeight > 0.0, !transition.isAnimated {
transition = .animated(duration: 0.4, curve: .spring)
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let theme = defaultDarkColorPresentationTheme
var timeoutValue: String
var timeoutSelected = false
if let timeout = self.currentTimeout {
if timeout == viewOnceTimeout {
timeoutValue = "1"
} else {
timeoutValue = "\(timeout)"
}
timeoutSelected = true
} else {
timeoutValue = "1"
}
var maxInputPanelHeight = maxHeight
if keyboardHeight.isZero {
maxInputPanelHeight = 60.0
}
self.inputPanel.parentState = self.state
let inputPanelSize = self.inputPanel.update(
transition: Transition(transition),
component: AnyComponent(
MessageInputPanelComponent(
externalState: self.inputPanelExternalState,
context: self.context,
theme: theme,
strings: presentationData.strings,
style: .media,
placeholder: .plain(presentationData.strings.MediaPicker_AddCaption),
maxLength: 1024,
queryTypes: [.mention],
alwaysDarkWhenHasText: false,
resetInputContents: nil,
nextInputMode: { _ in
return .emoji
},
areVoiceMessagesAvailable: false,
presentController: self.present,
presentInGlobalOverlay: self.presentInGlobalOverlay,
sendMessageAction: { [weak self] in
if let self {
self.sendPressed?(self.caption())
self.dismissInput()
}
},
sendMessageOptionsAction: nil,
sendStickerAction: { _ in },
setMediaRecordingActive: nil,
lockMediaRecording: nil,
stopAndPreviewMediaRecording: nil,
discardMediaRecordingPreview: nil,
attachmentAction: nil,
myReaction: nil,
likeAction: nil,
likeOptionsAction: nil,
inputModeAction: nil,
timeoutAction: { [weak self] sourceView in
if let self {
self.presentTimeoutSetup(sourceView: sourceView)
}
},
forwardAction: nil,
moreAction: nil,
presentVoiceMessagesUnavailableTooltip: nil,
presentTextLengthLimitTooltip: nil,
presentTextFormattingTooltip: nil,
paste: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil,
isRecordingLocked: false,
recordedAudioPreview: nil,
hasRecordedVideoPreview: false,
wasRecordingDismissed: false,
timeoutValue: timeoutValue,
timeoutSelected: timeoutSelected,
displayGradient: false,
bottomInset: 0.0,
isFormattingLocked: false,
hideKeyboard: false,
forceIsEditing: false,
disabledPlaceholder: nil,
isChannel: false,
storyItem: nil,
chatLocation: self.chatLocation
)
),
environment: {},
containerSize: CGSize(width: width, height: maxInputPanelHeight)
)
if let view = self.inputPanel.view {
if view.superview == nil {
self.view.addSubview(view)
}
let inputPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: -8.0), size: inputPanelSize)
transition.updateFrame(view: view, frame: inputPanelFrame)
}
if self.currentIsEditing != self.inputPanelExternalState.isEditing {
self.currentIsEditing = self.inputPanelExternalState.isEditing
self.focusUpdated?(self.currentIsEditing)
}
if self.currentHeight != inputPanelSize.height {
self.currentHeight = inputPanelSize.height
self.heightUpdated?(transition.isAnimated)
}
return inputPanelSize.height - 8.0
}
private func presentTimeoutSetup(sourceView: UIView) {
self.hapticFeedback.impact(.light)
var items: [ContextMenuItem] = []
let updateTimeout: (Int32?) -> Void = { [weak self] timeout in
if let self {
self.currentTimeout = timeout
self.timerUpdated?(timeout as? NSNumber)
self.update(transition: .immediate)
self.presentTimeoutTooltip(sourceView: sourceView, timeout: timeout)
}
}
let currentValue = self.currentTimeout
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkPresentationTheme)
let title = "Choose how long the media will be kept after opening."
let emptyAction: ((ContextMenuActionItem.Action) -> Void)? = nil
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
items.append(.action(ContextMenuActionItem(text: "View Once", icon: { theme in
return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(viewOnceTimeout)
})))
items.append(.action(ContextMenuActionItem(text: "3 Seconds", icon: { theme in
return currentValue == 3 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(3)
})))
items.append(.action(ContextMenuActionItem(text: "10 Seconds", icon: { theme in
return currentValue == 10 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(10)
})))
items.append(.action(ContextMenuActionItem(text: "30 Seconds", icon: { theme in
return currentValue == 30 ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(30)
})))
items.append(.action(ContextMenuActionItem(text: "Do Not Delete", icon: { theme in
return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
}, action: { _, a in
a(.default)
updateTimeout(nil)
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
self.present(contextController)
}
private weak var tooltipController: TooltipScreen?
private func presentTimeoutTooltip(sourceView: UIView, timeout: Int32?) {
guard let superview = self.view.superview?.superview else {
return
}
if let tooltipController = self.tooltipController {
self.tooltipController = nil
tooltipController.dismiss()
}
let parentFrame = superview.convert(superview.bounds, to: nil)
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 2.0), size: CGSize())
let text: String
let iconName: String
if timeout == viewOnceTimeout {
text = "Photo set to view once."
iconName = "anim_autoremove_on"
} else if let timeout {
text = "Photo will be deleted in \(timeout) seconds after opening."
iconName = "anim_autoremove_on"
} else {
text = "Photo will be kept in chat."
iconName = "anim_autoremove_off"
}
let tooltipController = TooltipScreen(
account: self.context.account,
sharedContext: self.context.sharedContext,
text: .plain(text: text),
balancedTextLayout: true,
style: .customBlur(UIColor(rgb: 0x18181a), 0.0),
arrowStyle: .small,
icon: .animation(name: iconName, delay: 0.1, tintColor: nil),
location: .point(location, .bottom),
displayDuration: .default,
inset: 8.0,
shouldDismissOnTouch: { _, _ in
return .ignore
}
)
self.tooltipController = tooltipController
self.present(tooltipController)
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if let view = self.inputPanel.view, let panelResult = view.hitTest(self.view.convert(point, to: view), with: event) {
return panelResult
}
return result
}
}
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let sourceView: UIView
var keepInPlace: Bool {
return true
}
init(sourceView: UIView) {
self.sourceView = sourceView
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: .top)
}
}

View File

@ -415,7 +415,6 @@ final class MediaEditorScreenComponent: Component {
guard let _ = self.inputPanel.view as? MessageInputPanelComponent.View else {
return
}
// if view.canDeactivateInput() {
self.currentInputMode = .text
if hasFirstResponder(self) {
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
@ -429,12 +428,6 @@ final class MediaEditorScreenComponent: Component {
} else {
self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(kind: .textFocusChanged)))
}
// } else {
// if let controller = self.environment?.controller() as? MediaEditorScreen {
// controller.presentCaptionLimitPremiumSuggestion(isPremium: self.sta)
// }
// view.animateError()
// }
}
private var animatingButtons = false
@ -1198,7 +1191,8 @@ final class MediaEditorScreenComponent: Component {
forceIsEditing: self.currentInputMode == .emoji,
disabledPlaceholder: nil,
isChannel: false,
storyItem: nil
storyItem: nil,
chatLocation: nil
)),
environment: {},
containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight)
@ -2594,6 +2588,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
controller.statusBar.statusBarStyle = .Ignore
self.isUserInteractionEnabled = false
if self.entitiesView.hasSelection {
self.entitiesView.selectEntity(nil)
}
let previousDimAlpha = self.backgroundDimView.alpha
self.backgroundDimView.alpha = 0.0
self.backgroundDimView.layer.animateAlpha(from: previousDimAlpha, to: 0.0, duration: 0.15)

View File

@ -293,7 +293,8 @@ final class StoryPreviewComponent: Component {
forceIsEditing: false,
disabledPlaceholder: nil,
isChannel: false,
storyItem: nil
storyItem: nil,
chatLocation: nil
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 200.0)

View File

@ -36,6 +36,7 @@ swift_library(
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/AnimatedCountLabelNode",
"//submodules/TelegramUI/Components/MessageInputActionButtonComponent",
"//submodules/SearchPeerMembers",
],
visibility = [
"//visibility:public",

View File

@ -5,6 +5,7 @@ import TextFieldComponent
import ChatContextQuery
import AccountContext
import TelegramUIPreferences
import SearchPeerMembers
func textInputStateContextQueryRangeAndType(inputState: TextFieldComponent.InputState) -> [(NSRange, PossibleContextQueryTypes, NSRange?)] {
return textInputStateContextQueryRangeAndType(inputText: inputState.inputText, selectionRange: inputState.selectionRange)
@ -38,7 +39,7 @@ func inputContextQueries(_ inputState: TextFieldComponent.InputState) -> [ChatPr
return result
}
func contextQueryResultState(context: AccountContext, inputState: TextFieldComponent.InputState, availableTypes: [ChatPresentationInputQueryKind], currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
func contextQueryResultState(context: AccountContext, inputState: TextFieldComponent.InputState, availableTypes: [ChatPresentationInputQueryKind], chatLocation: ChatLocation?, currentQueryStates: inout [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)]) -> [ChatPresentationInputQueryKind: ChatContextQueryUpdate] {
let inputQueries = inputContextQueries(inputState).filter({ query in
return availableTypes.contains(query.kind)
})
@ -48,7 +49,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo
for query in inputQueries {
let previousQuery = currentQueryStates[query.kind]?.0
if previousQuery != query {
let signal = updatedContextQueryResultStateForQuery(context: context, inputQuery: query, previousQuery: previousQuery)
let signal = updatedContextQueryResultStateForQuery(context: context, chatLocation: chatLocation, inputQuery: query, previousQuery: previousQuery)
updates[query.kind] = .update(query, signal)
}
}
@ -69,7 +70,7 @@ func contextQueryResultState(context: AccountContext, inputState: TextFieldCompo
return updates
}
private func updatedContextQueryResultStateForQuery(context: AccountContext, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
private func updatedContextQueryResultStateForQuery(context: AccountContext, chatLocation: ChatLocation?, inputQuery: ChatPresentationInputQuery, previousQuery: ChatPresentationInputQuery?) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> {
switch inputQuery {
case let .emoji(query):
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
@ -149,7 +150,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
|> castError(ChatContextQueryError.self)
return signal |> then(hashtags)
case let .mention(query, _):
case let .mention(query, types):
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
if let previousQuery = previousQuery {
switch previousQuery {
@ -163,6 +164,50 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
}
let normalizedQuery = query.lowercased()
if let chatLocation, let peerId = chatLocation.peerId {
let inlineBots: Signal<[(EnginePeer, Double)], NoError> = types.contains(.contextBots) ? context.engine.peers.recentlyUsedInlineBots() : .single([])
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
let participants = combineLatest(inlineBots, searchPeerMembers(context: context, peerId: peerId, chatLocation: chatLocation, query: query, scope: .mention))
|> map { inlineBots, peers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
let filteredInlineBots = inlineBots.sorted(by: { $0.1 > $1.1 }).filter { peer, rating in
if rating < 0.14 {
return false
}
if peer.indexName.matchesByTokens(normalizedQuery) {
return true
}
if let addressName = peer.addressName, addressName.lowercased().hasPrefix(normalizedQuery) {
return true
}
return false
}.map { $0.0 }
let inlineBotPeerIds = Set(filteredInlineBots.map { $0.id })
let filteredPeers = peers.filter { peer in
if inlineBotPeerIds.contains(peer.id) {
return false
}
if !types.contains(.accountPeer) && peer.id == context.account.peerId {
return false
}
return true
}
var sortedPeers = filteredInlineBots
sortedPeers.append(contentsOf: filteredPeers.sorted(by: { lhs, rhs in
let result = lhs.indexName.stringRepresentation(lastNameFirst: true).compare(rhs.indexName.stringRepresentation(lastNameFirst: true))
return result == .orderedAscending
}))
sortedPeers = sortedPeers.filter { peer in
return !peer.displayTitle(strings: strings, displayOrder: .firstLast).isEmpty
}
return { _ in return .mentions(sortedPeers) }
}
|> castError(ChatContextQueryError.self)
return signal |> then(participants)
} else {
if normalizedQuery.isEmpty {
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers()
|> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
@ -192,6 +237,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
|> castError(ChatContextQueryError.self)
return signal |> then(peers)
}
}
case let .emojiSearch(query, languageCode, range):
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in

View File

@ -143,6 +143,7 @@ public final class MessageInputPanelComponent: Component {
public let disabledPlaceholder: String?
public let isChannel: Bool
public let storyItem: EngineStoryItem?
public let chatLocation: ChatLocation?
public init(
externalState: ExternalState,
@ -193,7 +194,8 @@ public final class MessageInputPanelComponent: Component {
forceIsEditing: Bool,
disabledPlaceholder: String?,
isChannel: Bool,
storyItem: EngineStoryItem?
storyItem: EngineStoryItem?,
chatLocation: ChatLocation?
) {
self.externalState = externalState
self.context = context
@ -244,6 +246,7 @@ public final class MessageInputPanelComponent: Component {
self.disabledPlaceholder = disabledPlaceholder
self.isChannel = isChannel
self.storyItem = storyItem
self.chatLocation = chatLocation
}
public static func ==(lhs: MessageInputPanelComponent, rhs: MessageInputPanelComponent) -> Bool {
@ -346,6 +349,9 @@ public final class MessageInputPanelComponent: Component {
if lhs.storyItem != rhs.storyItem {
return false
}
if lhs.chatLocation != rhs.chatLocation {
return false
}
return true
}
@ -559,7 +565,7 @@ public final class MessageInputPanelComponent: Component {
if component.queryTypes.contains(.emoji) {
availableTypes.append(.emoji)
}
let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, currentQueryStates: &self.contextQueryStates)
let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, chatLocation: component.chatLocation, currentQueryStates: &self.contextQueryStates)
for (kind, update) in contextQueryUpdates {
switch update {
@ -705,6 +711,7 @@ public final class MessageInputPanelComponent: Component {
return value
}
},
resetScrollOnFocusChange: component.style == .media,
formatMenuAvailability: component.isFormattingLocked ? .locked : .available,
lockedFormatAction: {
component.presentTextFormattingTooltip?()
@ -780,6 +787,7 @@ public final class MessageInputPanelComponent: Component {
}
transition.setFrame(view: self.vibrancyEffectView, frame: CGRect(origin: CGPoint(), size: fieldBackgroundFrame.size))
self.vibrancyEffectView.isHidden = component.style == .media
transition.setFrame(view: self.fieldBackgroundView, frame: fieldBackgroundFrame)
self.fieldBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, transition: transition.containedViewLayoutTransition)
@ -1446,6 +1454,7 @@ public final class MessageInputPanelComponent: Component {
}
}
let accentColor = component.theme.chat.inputPanel.panelControlAccentColor
if let timeoutAction = component.timeoutAction, let timeoutValue = component.timeoutValue {
func generateIcon(value: String, selected: Bool) -> UIImage? {
let image = UIImage(bundleImageName: "Media Editor/Timeout")!
@ -1456,7 +1465,7 @@ public final class MessageInputPanelComponent: Component {
context.clear(bounds)
if selected {
context.setFillColor(UIColor(rgb: 0x3478f6).cgColor)
context.setFillColor(accentColor.cgColor)
context.fillEllipse(in: CGRect(origin: .zero, size: size))
} else {
if let cgImage = image.cgImage {
@ -1682,8 +1691,12 @@ public final class MessageInputPanelComponent: Component {
self.updateContextQueries()
let panelLeftInset: CGFloat = max(insets.left, 7.0)
let panelRightInset: CGFloat = max(insets.right, 41.0)
var panelLeftInset: CGFloat = max(insets.left, 7.0)
var panelRightInset: CGFloat = max(insets.right, 41.0)
if case .media = component.style {
panelLeftInset = 0.0
panelRightInset = 0.0
}
var contextResults: ContextResultPanelComponent.Results?
if let result = self.contextQueryResults[.mention], case let .mentions(mentions) = result, !mentions.isEmpty {
@ -1815,7 +1828,13 @@ public final class MessageInputPanelComponent: Component {
containerSize: CGSize(width: availableSize.width - panelLeftInset - panelRightInset, height: availablePanelHeight)
)
let panelFrame = CGRect(origin: CGPoint(x: insets.left, y: -panelSize.height + 14.0), size: CGSize(width: panelSize.width, height: panelSize.height + 19.0))
var panelOriginY = -panelSize.height + 14.0
var panelHeight = panelSize.height + 19.0
if case .media = component.style {
panelOriginY -= 6.0
panelHeight = panelSize.height
}
let panelFrame = CGRect(origin: CGPoint(x: panelLeftInset, y: panelOriginY), size: CGSize(width: panelSize.width, height: panelHeight))
if let panelView = panel.view as? ContextResultPanelComponent.View {
if panelView.superview == nil {
self.insertSubview(panelView, at: 0)

View File

@ -2864,7 +2864,8 @@ public final class StoryItemSetContainerComponent: Component {
forceIsEditing: self.sendMessageContext.currentInputMode == .media,
disabledPlaceholder: disabledPlaceholder,
isChannel: isChannel,
storyItem: component.slice.item.storyItem
storyItem: component.slice.item.storyItem,
chatLocation: nil
)),
environment: {},
containerSize: CGSize(width: inputPanelAvailableWidth, height: 200.0)

View File

@ -91,6 +91,7 @@ public final class TextFieldComponent: Component {
public let insets: UIEdgeInsets
public let hideKeyboard: Bool
public let resetText: NSAttributedString?
public let resetScrollOnFocusChange: Bool
public let formatMenuAvailability: FormatMenuAvailability
public let lockedFormatAction: () -> Void
public let present: (ViewController) -> Void
@ -105,6 +106,7 @@ public final class TextFieldComponent: Component {
insets: UIEdgeInsets,
hideKeyboard: Bool,
resetText: NSAttributedString?,
resetScrollOnFocusChange: Bool,
formatMenuAvailability: FormatMenuAvailability,
lockedFormatAction: @escaping () -> Void,
present: @escaping (ViewController) -> Void,
@ -118,6 +120,7 @@ public final class TextFieldComponent: Component {
self.insets = insets
self.hideKeyboard = hideKeyboard
self.resetText = resetText
self.resetScrollOnFocusChange = resetScrollOnFocusChange
self.formatMenuAvailability = formatMenuAvailability
self.lockedFormatAction = lockedFormatAction
self.present = present
@ -146,6 +149,9 @@ public final class TextFieldComponent: Component {
if lhs.resetText != rhs.resetText {
return false
}
if lhs.resetScrollOnFocusChange != rhs.resetScrollOnFocusChange {
return false
}
if lhs.formatMenuAvailability != rhs.formatMenuAvailability {
return false
}
@ -235,6 +241,10 @@ public final class TextFieldComponent: Component {
self.textContainer.widthTracksTextView = false
self.textContainer.heightTracksTextView = false
if #available(iOS 13.0, *) {
self.textView.overrideUserInterfaceStyle = .dark
}
self.textView.typingAttributes = [
NSAttributedString.Key.font: Font.regular(17.0),
NSAttributedString.Key.foregroundColor: UIColor.white
@ -823,7 +833,10 @@ public final class TextFieldComponent: Component {
let wasEditing = component.externalState.isEditing
let isEditing = self.textView.isFirstResponder
let refreshScrolling = self.textView.bounds.size != size
var refreshScrolling = self.textView.bounds.size != size
if component.resetScrollOnFocusChange && !isEditing && isEditing != wasEditing {
refreshScrolling = true
}
self.textView.frame = CGRect(origin: CGPoint(), size: size)
self.textView.panGestureRecognizer.isEnabled = isEditing

View File

@ -41,6 +41,7 @@ import HashtagSearchUI
import PeerInfoStoryGridScreen
import TelegramAccountAuxiliaryMethods
import PeerSelectionController
import LegacyMessageInputPanel
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
@ -1626,98 +1627,107 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
public func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? {
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
// var presentationData = context.sharedContext.currentPresentationData.with { $0 }
// presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
//
// var presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil)
//
// var updateChatPresentationInterfaceStateImpl: (((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void)?
// var ensureFocusedImpl: (() -> Void)?
//
// let interfaceInteraction = ChatPanelInterfaceInteraction(updateTextInputStateAndMode: { f in
// updateChatPresentationInterfaceStateImpl?({
// let (updatedState, updatedMode) = f($0.interfaceState.effectiveInputState, $0.inputMode)
// return $0.updatedInterfaceState { interfaceState in
// return interfaceState.withUpdatedEffectiveInputState(updatedState)
// }.updatedInputMode({ _ in updatedMode })
// })
// }, updateInputModeAndDismissedButtonKeyboardMessageId: { f in
// updateChatPresentationInterfaceStateImpl?({
// let (updatedInputMode, updatedClosedButtonKeyboardMessageId) = f($0)
// return $0.updatedInputMode({ _ in return updatedInputMode }).updatedInterfaceState({
// $0.withUpdatedMessageActionsState({ value in
// var value = value
// value.closedButtonKeyboardMessageId = updatedClosedButtonKeyboardMessageId
// return value
// })
// })
// })
// }, openLinkEditing: {
// var selectionRange: Range<Int>?
// var text: NSAttributedString?
// var inputMode: ChatInputMode?
// updateChatPresentationInterfaceStateImpl?({ state in
// selectionRange = state.interfaceState.effectiveInputState.selectionRange
// if let selectionRange = selectionRange {
// text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count))
// }
// inputMode = state.inputMode
// return state
// })
//
// var link: String?
// if let text {
// text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in
// if let linkAttribute = attributes[ChatTextInputAttributes.textUrl] as? ChatTextInputTextUrlAttribute {
// link = linkAttribute.url
// }
// }
// }
//
// let controller = chatTextLinkEditController(sharedContext: context.sharedContext, updatedPresentationData: (presentationData, .never()), account: context.account, text: text?.string ?? "", link: link, apply: { link in
// if let inputMode = inputMode, let selectionRange = selectionRange {
// if let link = link {
// updateChatPresentationInterfaceStateImpl?({
// return $0.updatedInterfaceState({
// $0.withUpdatedEffectiveInputState(chatTextInputAddLinkAttribute($0.effectiveInputState, selectionRange: selectionRange, url: link))
// })
// })
// }
// ensureFocusedImpl?()
// updateChatPresentationInterfaceStateImpl?({
// return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
// $0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex))
// })
// })
// }
// })
// present(controller)
// })
//
// let inputPanelNode = AttachmentTextInputPanelNode(context: context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { c in
// presentInGlobalOverlay(c)
// }, makeEntityInputView: {
// return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable)
// })
// inputPanelNode.interfaceInteraction = interfaceInteraction
// inputPanelNode.effectivePresentationInterfaceState = {
// return presentationInterfaceState
// }
//
// updateChatPresentationInterfaceStateImpl = { [weak inputPanelNode] f in
// let updatedPresentationInterfaceState = f(presentationInterfaceState)
// let updateInputTextState = presentationInterfaceState.interfaceState.effectiveInputState != updatedPresentationInterfaceState.interfaceState.effectiveInputState
//
// presentationInterfaceState = updatedPresentationInterfaceState
//
// if let inputPanelNode = inputPanelNode, updateInputTextState {
// inputPanelNode.updateInputTextState(updatedPresentationInterfaceState.interfaceState.effectiveInputState, animated: true)
// }
// }
//
// ensureFocusedImpl = { [weak inputPanelNode] in
// inputPanelNode?.ensureFocused()
// }
//
// return inputPanelNode
var presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: presentationData.chatFontSize, bubbleCorners: presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil)
var updateChatPresentationInterfaceStateImpl: (((ChatPresentationInterfaceState) -> ChatPresentationInterfaceState) -> Void)?
var ensureFocusedImpl: (() -> Void)?
let interfaceInteraction = ChatPanelInterfaceInteraction(updateTextInputStateAndMode: { f in
updateChatPresentationInterfaceStateImpl?({
let (updatedState, updatedMode) = f($0.interfaceState.effectiveInputState, $0.inputMode)
return $0.updatedInterfaceState { interfaceState in
return interfaceState.withUpdatedEffectiveInputState(updatedState)
}.updatedInputMode({ _ in updatedMode })
})
}, updateInputModeAndDismissedButtonKeyboardMessageId: { f in
updateChatPresentationInterfaceStateImpl?({
let (updatedInputMode, updatedClosedButtonKeyboardMessageId) = f($0)
return $0.updatedInputMode({ _ in return updatedInputMode }).updatedInterfaceState({
$0.withUpdatedMessageActionsState({ value in
var value = value
value.closedButtonKeyboardMessageId = updatedClosedButtonKeyboardMessageId
return value
})
})
})
}, openLinkEditing: {
var selectionRange: Range<Int>?
var text: NSAttributedString?
var inputMode: ChatInputMode?
updateChatPresentationInterfaceStateImpl?({ state in
selectionRange = state.interfaceState.effectiveInputState.selectionRange
if let selectionRange = selectionRange {
text = state.interfaceState.effectiveInputState.inputText.attributedSubstring(from: NSRange(location: selectionRange.startIndex, length: selectionRange.count))
}
inputMode = state.inputMode
return state
})
var link: String?
if let text {
text.enumerateAttributes(in: NSMakeRange(0, text.length)) { attributes, _, _ in
if let linkAttribute = attributes[ChatTextInputAttributes.textUrl] as? ChatTextInputTextUrlAttribute {
link = linkAttribute.url
}
}
}
let controller = chatTextLinkEditController(sharedContext: context.sharedContext, updatedPresentationData: (presentationData, .never()), account: context.account, text: text?.string ?? "", link: link, apply: { link in
if let inputMode = inputMode, let selectionRange = selectionRange {
if let link = link {
updateChatPresentationInterfaceStateImpl?({
return $0.updatedInterfaceState({
$0.withUpdatedEffectiveInputState(chatTextInputAddLinkAttribute($0.effectiveInputState, selectionRange: selectionRange, url: link))
})
})
}
ensureFocusedImpl?()
updateChatPresentationInterfaceStateImpl?({
return $0.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
$0.withUpdatedEffectiveInputState(ChatTextInputState(inputText: $0.effectiveInputState.inputText, selectionRange: selectionRange.endIndex ..< selectionRange.endIndex))
})
})
}
})
present(controller)
})
let inputPanelNode = AttachmentTextInputPanelNode(context: context, presentationInterfaceState: presentationInterfaceState, isCaption: true, presentController: { c in
presentInGlobalOverlay(c)
}, makeEntityInputView: {
return EntityInputView(context: context, isDark: true, areCustomEmojiEnabled: customEmojiAvailable)
})
inputPanelNode.interfaceInteraction = interfaceInteraction
inputPanelNode.effectivePresentationInterfaceState = {
return presentationInterfaceState
}
updateChatPresentationInterfaceStateImpl = { [weak inputPanelNode] f in
let updatedPresentationInterfaceState = f(presentationInterfaceState)
let updateInputTextState = presentationInterfaceState.interfaceState.effectiveInputState != updatedPresentationInterfaceState.interfaceState.effectiveInputState
presentationInterfaceState = updatedPresentationInterfaceState
if let inputPanelNode = inputPanelNode, updateInputTextState {
inputPanelNode.updateInputTextState(updatedPresentationInterfaceState.interfaceState.effectiveInputState, animated: true)
}
}
ensureFocusedImpl = { [weak inputPanelNode] in
inputPanelNode?.ensureFocused()
}
let inputPanelNode = LegacyMessageInputPanelNode(
context: context,
chatLocation: chatLocation,
present: present,
presentInGlobalOverlay: presentInGlobalOverlay
)
return inputPanelNode
}

View File

@ -477,7 +477,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
} else {
animationSize = CGSize(width: 32.0, height: 32.0)
}
if animationName == "anim_autoremove_on" {
if ["anim_autoremove_on", "anim_autoremove_off"].contains(animationName) {
animationOffset = -3.0
} else if animationName == "ChatListFoldersTooltip" {
animationInset = (70.0 - animationSize.width) / 2.0