mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Various improvements
This commit is contained in:
parent
6c2f370a8f
commit
68a95f53f2
10 changed files with 259 additions and 75 deletions
|
|
@ -220,6 +220,10 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public var contentViewValue: UIView? {
|
||||
return self.contentView.view
|
||||
}
|
||||
|
||||
private let dimView: UIView
|
||||
public let containerView: UIView
|
||||
|
|
|
|||
|
|
@ -3319,7 +3319,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
animation.completion = { [weak self] _ in
|
||||
self?.updateItemNodesVisibilities(onlyPositive: false)
|
||||
}
|
||||
self.layer.add(animation, forKey: nil)
|
||||
self.layer.add(animation, forKey: "animation-\(ObjectIdentifier(animation))")
|
||||
if !completeOffset.isZero {
|
||||
for itemNode in self.itemNodes {
|
||||
itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -completeOffset), animationCurve: animationCurve, duration: animationDuration)
|
||||
|
|
@ -3747,7 +3747,7 @@ open class ListViewImpl: ASDisplayNode, ListView, ASScrollViewDelegate, ASGestur
|
|||
headerNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
self.layer.add(animation, forKey: nil)
|
||||
self.layer.add(animation, forKey: "animation-\(ObjectIdentifier(animation))")
|
||||
}
|
||||
|
||||
for itemNode in self.itemNodes {
|
||||
|
|
|
|||
|
|
@ -6078,12 +6078,16 @@ func replayFinalState(
|
|||
if let current, current.id > update.id {
|
||||
return current
|
||||
}
|
||||
var timestamp = update.timestamp
|
||||
if let current, current.id == update.id {
|
||||
timestamp = current.timestamp
|
||||
}
|
||||
return (
|
||||
update.id,
|
||||
Namespaces.Message.Cloud,
|
||||
update.threadId,
|
||||
update.authorId,
|
||||
update.timestamp,
|
||||
timestamp,
|
||||
update.text,
|
||||
[
|
||||
TypingDraftMessageAttribute(),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||
public var maxChannelRecommendationsCount: Int32
|
||||
public var maxConferenceParticipantCount: Int32
|
||||
public var maxBotsCreated: Int32
|
||||
public var maxOwnedAITextStyles: Int32
|
||||
|
||||
public static var defaultValue: UserLimitsConfiguration {
|
||||
return UserLimitsConfiguration(
|
||||
|
|
@ -60,7 +61,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||
maxGiveawayPeriodSeconds: 86400 * 31,
|
||||
maxChannelRecommendationsCount: 10,
|
||||
maxConferenceParticipantCount: 100,
|
||||
maxBotsCreated: 20
|
||||
maxBotsCreated: 20,
|
||||
maxOwnedAITextStyles: 5
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +94,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||
maxGiveawayPeriodSeconds: Int32,
|
||||
maxChannelRecommendationsCount: Int32,
|
||||
maxConferenceParticipantCount: Int32,
|
||||
maxBotsCreated: Int32
|
||||
maxBotsCreated: Int32,
|
||||
maxOwnedAITextStyles: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||
|
|
@ -122,6 +125,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
|
||||
self.maxConferenceParticipantCount = maxConferenceParticipantCount
|
||||
self.maxBotsCreated = maxBotsCreated
|
||||
self.maxOwnedAITextStyles = maxOwnedAITextStyles
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,5 +181,6 @@ extension UserLimitsConfiguration {
|
|||
self.maxChannelRecommendationsCount = getValue("recommended_channels_limit", orElse: defaultValue.maxChannelRecommendationsCount)
|
||||
self.maxConferenceParticipantCount = getGeneralValue("conference_call_size_limit", orElse: defaultValue.maxConferenceParticipantCount)
|
||||
self.maxBotsCreated = getValue("bots_create_limit", orElse: defaultValue.maxBotsCreated)
|
||||
self.maxOwnedAITextStyles = getValue("aicompose_tone_saved_limit", orElse: defaultValue.maxBotsCreated)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ public enum EngineConfiguration {
|
|||
public let maxChannelRecommendationsCount: Int32
|
||||
public let maxConferenceParticipantCount: Int32
|
||||
public let maxBotsCreated: Int32
|
||||
public let maxOwnedAITextStyles: Int32
|
||||
|
||||
public static var defaultValue: UserLimits {
|
||||
return UserLimits(UserLimitsConfiguration.defaultValue)
|
||||
|
|
@ -97,7 +98,8 @@ public enum EngineConfiguration {
|
|||
maxGiveawayPeriodSeconds: Int32,
|
||||
maxChannelRecommendationsCount: Int32,
|
||||
maxConferenceParticipantCount: Int32,
|
||||
maxBotsCreated: Int32
|
||||
maxBotsCreated: Int32,
|
||||
maxOwnedAITextStyles: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||
|
|
@ -127,6 +129,7 @@ public enum EngineConfiguration {
|
|||
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
|
||||
self.maxConferenceParticipantCount = maxConferenceParticipantCount
|
||||
self.maxBotsCreated = maxBotsCreated
|
||||
self.maxOwnedAITextStyles = maxOwnedAITextStyles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -191,7 +194,8 @@ public extension EngineConfiguration.UserLimits {
|
|||
maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds,
|
||||
maxChannelRecommendationsCount: userLimitsConfiguration.maxChannelRecommendationsCount,
|
||||
maxConferenceParticipantCount: userLimitsConfiguration.maxConferenceParticipantCount,
|
||||
maxBotsCreated: userLimitsConfiguration.maxBotsCreated
|
||||
maxBotsCreated: userLimitsConfiguration.maxBotsCreated,
|
||||
maxOwnedAITextStyles: userLimitsConfiguration.maxOwnedAITextStyles
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -798,7 +798,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
textFrameWithoutInsets = textFrameWithoutInsets.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: topInset)
|
||||
|
||||
var suggestedBoundingWidth: CGFloat = textFrameWithoutInsets.width
|
||||
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue {
|
||||
if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue, !hasDraft {
|
||||
suggestedBoundingWidth = max(suggestedBoundingWidth, statusSuggestedWidthAndContinue.0)
|
||||
}
|
||||
let sideInsets = layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
|
||||
|
|
@ -810,7 +810,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
|
||||
|
||||
boundingSize = textFrameWithoutInsets.size
|
||||
if let statusSizeAndApply = statusSizeAndApply {
|
||||
if let statusSizeAndApply = statusSizeAndApply, !hasDraft {
|
||||
boundingSize.height += statusSizeAndApply.0.height
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1106,6 +1106,9 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
|
|||
|
||||
titleViewTransition.updateFrame(view: titleView, frame: titleFrame)
|
||||
} else {
|
||||
leftTitleInset = max(leftTitleInset, rightTitleInset)
|
||||
rightTitleInset = leftTitleInset
|
||||
|
||||
let titleSize = CGSize(width: max(1.0, size.width - leftTitleInset - rightTitleInset), height: nominalHeight)
|
||||
var titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
|
||||
if titleFrame.origin.x + titleFrame.width > size.width - rightTitleInset {
|
||||
|
|
|
|||
|
|
@ -285,21 +285,39 @@ final class TextProcessingContentComponent: Component {
|
|||
return
|
||||
}
|
||||
|
||||
environment.controller()?.push(textAlertController(
|
||||
context: component.context,
|
||||
title: "Delete Style",
|
||||
text: "Are you sure you want to delete this style? It will be removed for everyone who installed it.",
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let _ = component.context.engine.messages.deleteAITextStyle(id: style.id, accessHash: style.accessHash).startStandalone()
|
||||
component.styleDeleted(id.id)
|
||||
}),
|
||||
]
|
||||
))
|
||||
if style.isCreator {
|
||||
environment.controller()?.push(textAlertController(
|
||||
context: component.context,
|
||||
title: "Delete Style",
|
||||
text: "Are you sure you want to delete this style? It will be removed for everyone who installed it.",
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let _ = component.context.engine.messages.deleteAITextStyle(id: style.id, accessHash: style.accessHash).startStandalone()
|
||||
component.styleDeleted(id.id)
|
||||
}),
|
||||
]
|
||||
))
|
||||
} else {
|
||||
environment.controller()?.push(textAlertController(
|
||||
context: component.context,
|
||||
title: "Delete Style",
|
||||
text: "Are you sure you want to delete this style?",
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .destructiveAction, title: environment.strings.Common_Delete, action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let _ = component.context.engine.messages.unsaveAITextStyle(id: style.id, accessHash: style.accessHash).startStandalone()
|
||||
component.styleDeleted(id.id)
|
||||
}),
|
||||
]
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
private func openStyleContextMenu(id: TelegramComposeAIMessageMode.StyleReference, gesture: ContextGesture, sourceView: ContextExtractedContentContainingView) {
|
||||
|
|
@ -749,6 +767,53 @@ final class TextProcessingContentComponent: Component {
|
|||
guard let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
|
||||
let hasPremium = await (component.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId)
|
||||
)
|
||||
|> map { peer -> Bool in
|
||||
guard case let .user(user) = peer else {
|
||||
return false
|
||||
}
|
||||
return user.isPremium
|
||||
}).get()
|
||||
let userLimits = await component.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: hasPremium)
|
||||
).get()
|
||||
|
||||
let maxStyles = Int(userLimits.maxOwnedAITextStyles)
|
||||
|
||||
if component.styles.filter({ $0.isAuthor }).count >= maxStyles {
|
||||
if !hasPremium {
|
||||
//TODO:localize
|
||||
let context = component.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .aiTools, forceDark: false, action: {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .storiesStealthMode, forceDark: false, dismissed: nil)
|
||||
replaceImpl?(controller)
|
||||
}, dismissed: nil)
|
||||
replaceImpl = { [weak self, weak controller] c in
|
||||
controller?.dismiss(animated: true, completion: {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.push(c)
|
||||
})
|
||||
}
|
||||
environment.controller()?.push(controller)
|
||||
} else {
|
||||
environment.controller()?.push(textAlertController(
|
||||
context: component.context,
|
||||
title: "Too Many Styles",
|
||||
text: "Please delete some of your saved styles to create a new one.",
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: environment.strings.Common_OK, action: {}),
|
||||
]
|
||||
))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
environment.controller()?.push(await TextStyleEditScreen(
|
||||
context: component.context,
|
||||
mode: .create,
|
||||
|
|
@ -1101,7 +1166,6 @@ private final class TextProcessingSheetComponent: Component {
|
|||
final class View: UIView {
|
||||
private let sheet = ComponentView<(ViewControllerComponentContainer.Environment, ResizableSheetComponentEnvironment)>()
|
||||
private var toast: ComponentView<Empty>?
|
||||
private var languageSelectionMenu: ComponentView<Empty>?
|
||||
private let animateOut = ActionSlot<Action<Void>>()
|
||||
private let contentExternalState = TextProcessingContentComponent.ExternalState()
|
||||
|
||||
|
|
@ -1123,6 +1187,10 @@ private final class TextProcessingSheetComponent: Component {
|
|||
}
|
||||
}
|
||||
private var languageSelectionMenuData: LanguageSelectionMenuData?
|
||||
private var languageSelectionMenu: ComponentView<Empty>?
|
||||
|
||||
private var styleCreatedToastData: (timer: Foundation.Timer, emojiFile: TelegramMediaFile, style: TelegramComposeAIMessageMode.CloudStyle.Custom)?
|
||||
private var styleCreatedToast: ComponentView<Empty>?
|
||||
|
||||
private var component: TextProcessingSheetComponent?
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
|
|
@ -1142,6 +1210,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
|
||||
deinit {
|
||||
self.actionDisposable?.dispose()
|
||||
self.styleCreatedToastData?.timer.invalidate()
|
||||
}
|
||||
|
||||
private func displayLongPressSendMenu(sourceSendButton: UIView) {
|
||||
|
|
@ -1433,6 +1502,21 @@ private final class TextProcessingSheetComponent: Component {
|
|||
TelegramEngine.EngineData.Item.Peer.Peer(id: authorId)
|
||||
).get()
|
||||
}
|
||||
|
||||
if case let .custom(style) = style.content, let emojiFileId = style.emojiFileId {
|
||||
let emojiFile = await component.context.engine.stickers.resolveInlineStickersLocal(fileIds: [emojiFileId]).get().first?.value
|
||||
|
||||
if let emojiFile {
|
||||
self.styleCreatedToastData = (Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.styleCreatedToastData = nil
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}), emojiFile, style)
|
||||
}
|
||||
}
|
||||
|
||||
self.styles.insert(TextProcessingScreen.Style(cloudStyle: style, authorPeer: authorPeer), at: 0)
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
|
|
@ -1636,6 +1720,73 @@ private final class TextProcessingSheetComponent: Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let styleCreatedToastData = self.styleCreatedToastData, !self.contentExternalState.nonPremiumFloodTriggered {
|
||||
let sideInset: CGFloat = 8.0
|
||||
|
||||
let styleCreatedToast: ComponentView<Empty>
|
||||
var styleCreatedToastTransition = transition
|
||||
if let current = self.styleCreatedToast {
|
||||
styleCreatedToast = current
|
||||
} else {
|
||||
styleCreatedToastTransition = styleCreatedToastTransition.withAnimation(.none)
|
||||
styleCreatedToast = ComponentView()
|
||||
self.styleCreatedToast = styleCreatedToast
|
||||
}
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let styleCreatedToastSize = styleCreatedToast.update(
|
||||
transition: styleCreatedToastTransition,
|
||||
component: AnyComponent(ToastContentComponent(
|
||||
icon: AnyComponent(EmojiStatusComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
content: .animation(
|
||||
content: .file(file: styleCreatedToastData.emojiFile),
|
||||
size: CGSize(width: 32.0, height: 32.0),
|
||||
placeholderColor: environmentValue.theme.list.mediaPlaceholderColor,
|
||||
themeColor: environmentValue.theme.list.itemPrimaryTextColor,
|
||||
loopMode: .count(1)
|
||||
),
|
||||
size: CGSize(width: 32.0, height: 32.0),
|
||||
isVisibleForAnimations: true,
|
||||
action: nil
|
||||
)),
|
||||
content: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "\(styleCreatedToastData.style.title) style created!", font: Font.semibold(14.0), textColor: .white)),
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(text: "Press and hold a style to edit or share the link.", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })),
|
||||
maximumNumberOfLines: 0
|
||||
)))
|
||||
], alignment: .left, spacing: 6.0)),
|
||||
insets: UIEdgeInsets(top: 10.0, left: 12.0, bottom: 10.0, right: 10.0),
|
||||
iconSpacing: 12.0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
if let styleCreatedToastView = styleCreatedToast.view {
|
||||
if styleCreatedToastView.superview == nil, let sheetView = self.sheet.view as? ResizableSheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
sheetView.containerView.addSubview(styleCreatedToastView)
|
||||
if !transition.animation.isImmediate {
|
||||
styleCreatedToastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
styleCreatedToastTransition.setFrame(view: styleCreatedToastView, frame: CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - 94.0 - styleCreatedToastSize.height), size: styleCreatedToastSize))
|
||||
}
|
||||
} else {
|
||||
if let styleCreatedToast = self.styleCreatedToast {
|
||||
self.styleCreatedToast = nil
|
||||
if let styleCreatedToastView = styleCreatedToast.view {
|
||||
styleCreatedToastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak styleCreatedToastView] _ in
|
||||
styleCreatedToastView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let languageSelectionMenuDataValue = self.languageSelectionMenuData {
|
||||
let languageSelectionMenu: ComponentView<Empty>
|
||||
|
|
|
|||
|
|
@ -152,6 +152,53 @@ final class TextStyleEditContentComponent: Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func activateEmojiSelection() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard let iconBackgroundView = self.iconBackground.view else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.present(component.context.sharedContext.makeEmojiStatusSelectionController(
|
||||
context: component.context,
|
||||
mode: .backgroundSelection(completion: { [weak self] file in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.externalState.emojiFile = file
|
||||
self.state?.updated(transition: .immediate)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}),
|
||||
sourceView: iconBackgroundView,
|
||||
emojiContent: EmojiPagerContentComponent.emojiInputData(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
isStandalone: false,
|
||||
subject: .emoji,
|
||||
hasTrending: false,
|
||||
topReactionItems: [],
|
||||
areUnicodeEmojiEnabled: false,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: component.context.account.peerId,
|
||||
selectedItems: Set()
|
||||
) |> map { $0 },
|
||||
currentSelection: nil,
|
||||
color: nil,
|
||||
destinationItemView: { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
return self.emojiIcon?.view
|
||||
}
|
||||
), in: .window(.root))
|
||||
}
|
||||
|
||||
func update(component: TextStyleEditContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
|
|
@ -195,50 +242,10 @@ final class TextStyleEditContentComponent: Component {
|
|||
smoothCorners: false
|
||||
)),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let iconBackgroundView = self.iconBackground.view else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.present(component.context.sharedContext.makeEmojiStatusSelectionController(
|
||||
context: component.context,
|
||||
mode: .backgroundSelection(completion: { [weak self] file in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.externalState.emojiFile = file
|
||||
self.state?.updated(transition: .immediate)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
}),
|
||||
sourceView: iconBackgroundView,
|
||||
emojiContent: EmojiPagerContentComponent.emojiInputData(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
isStandalone: false,
|
||||
subject: .emoji,
|
||||
hasTrending: false,
|
||||
topReactionItems: [],
|
||||
areUnicodeEmojiEnabled: false,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: component.context.account.peerId,
|
||||
selectedItems: Set()
|
||||
) |> map { $0 },
|
||||
currentSelection: nil,
|
||||
color: nil,
|
||||
destinationItemView: { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
}
|
||||
return self.emojiIcon?.view
|
||||
}
|
||||
), in: .window(.root))
|
||||
self.activateEmojiSelection()
|
||||
},
|
||||
animateAlpha: false,
|
||||
animateScale: false,
|
||||
|
|
@ -338,9 +345,9 @@ final class TextStyleEditContentComponent: Component {
|
|||
return ListMultilineTextFieldItemComponent.ResetText(value: resetTitle)
|
||||
},
|
||||
placeholder: "Style Name (for example, \"Pirate\")",
|
||||
autocapitalizationType: .none,
|
||||
autocorrectionType: .no,
|
||||
characterLimit: 256,
|
||||
autocapitalizationType: .sentences,
|
||||
autocorrectionType: .default,
|
||||
characterLimit: 12,
|
||||
emptyLineHandling: .notAllowed,
|
||||
updated: nil,
|
||||
textUpdateTransition: .spring(duration: 0.4),
|
||||
|
|
@ -383,9 +390,9 @@ final class TextStyleEditContentComponent: Component {
|
|||
},
|
||||
placeholder: "Instructions (for example, \"Write like a swashbuckling pirate. Use arr, ye, matey, and talk about treasure, the sea, and rum\")",
|
||||
placeholderDefinesMinHeight: true,
|
||||
autocapitalizationType: .none,
|
||||
autocorrectionType: .no,
|
||||
characterLimit: 4096,
|
||||
autocapitalizationType: .sentences,
|
||||
autocorrectionType: .default,
|
||||
characterLimit: 1024,
|
||||
emptyLineHandling: .allowed,
|
||||
updated: nil,
|
||||
textUpdateTransition: .spring(duration: 0.4),
|
||||
|
|
@ -615,6 +622,11 @@ private final class TextStyleEditSheetComponent: Component {
|
|||
return
|
||||
}
|
||||
guard let emojiFile = self.contentState.emojiFile else {
|
||||
if let sheetView = self.sheet.view as? ResizableSheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
if let contentView = sheetView.contentViewValue as? TextStyleEditContentComponent.View {
|
||||
contentView.activateEmojiSelection()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -731,7 +743,7 @@ private final class TextStyleEditSheetComponent: Component {
|
|||
}
|
||||
self.performCreateStyle()
|
||||
}
|
||||
let isMainActionEnabled = !self.contentState.titleInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !self.contentState.textInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && self.contentState.emojiFile != nil && !self.isActionInProgress
|
||||
let isMainActionEnabled = !self.contentState.titleInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !self.contentState.textInputState.text.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !self.isActionInProgress
|
||||
let actionButtonTitle: String
|
||||
let titleString: String
|
||||
switch component.mode {
|
||||
|
|
|
|||
|
|
@ -1177,6 +1177,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
|
|||
}
|
||||
self.isInteractivelyScrollingValue = true
|
||||
self.isInteractivelyScrollingPromise.set(true)
|
||||
//self.pinToTopStableId = nil
|
||||
self.beganDragging?()
|
||||
}
|
||||
|
||||
|
|
@ -2150,7 +2151,7 @@ public final class ChatHistoryListNodeImpl: ListViewImpl, ChatHistoryNode, ChatH
|
|||
if message.attributes.contains(where: { $0 is TypingDraftMessageAttribute }) {
|
||||
for j in (0 ..< i).reversed() {
|
||||
let otherMessage = view.entries[j].message
|
||||
if !otherMessage.effectivelyIncoming(context.account.peerId) {
|
||||
if !otherMessage.effectivelyIncoming(context.account.peerId) && otherMessage.id.namespace == Namespaces.Message.Cloud {
|
||||
if strongSelf.pinToTopStableId != otherMessage.stableId {
|
||||
strongSelf.pinToTopStableId = otherMessage.stableId
|
||||
scrollToPinToTopStableId = true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue