mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
Media caption input panel improvements
This commit is contained in:
parent
9fd6b9369f
commit
b1f40bf0aa
@ -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()
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ extension UIView {
|
||||
}
|
||||
|
||||
open class ComponentState {
|
||||
var _updated: ((Transition) -> Void)?
|
||||
open var _updated: ((Transition) -> Void)?
|
||||
var isUpdated: Bool = false
|
||||
|
||||
public init() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -36,6 +36,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/AnimatedCountLabelNode",
|
||||
"//submodules/TelegramUI/Components/MessageInputActionButtonComponent",
|
||||
"//submodules/SearchPeerMembers",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user