mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Various improvements
This commit is contained in:
parent
1b85d8ccb0
commit
55b2b47797
8 changed files with 165 additions and 26 deletions
|
|
@ -1308,7 +1308,7 @@ public final class TextProcessingScreenSendContextActions {
|
|||
public enum TextProcessingScreenMode {
|
||||
case edit(saveRestoreStateId: EnginePeer.Id?, completion: (TextWithEntities) -> Void, send: ((TextWithEntities) -> Void)?, sendContextActions: TextProcessingScreenSendContextActions?)
|
||||
case translate(fromLanguage: String?)
|
||||
case preview(style: TelegramComposeAIMessageMode.CloudStyle.Custom, authorPeer: EnginePeer?, initialPreview: AIMessageStylePreview?, added: () -> Void)
|
||||
case preview(style: TelegramComposeAIMessageMode.CloudStyle.Custom, authorPeer: EnginePeer?, initialPreview: AIMessageStylePreview?, isAlreadyAdded: Bool, added: () -> Void)
|
||||
}
|
||||
|
||||
public enum EmojiStatusSelectionControllerMode {
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||
initialFilter = .topics
|
||||
}
|
||||
|
||||
var folder = folder
|
||||
folder = nil
|
||||
|
||||
self.context = context
|
||||
self.peersFilter = filter
|
||||
self.requestPeerType = requestPeerType
|
||||
|
|
|
|||
|
|
@ -661,6 +661,7 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.endEditing(true)
|
||||
self.animateOut {
|
||||
completion(Void())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -902,7 +902,9 @@ func _internal_editAITextStyle(account: Account, id: Int64, accessHash: Int64, d
|
|||
|
||||
let styles = _internal_cachedCloudAITextStyles(transaction: transaction)
|
||||
var items = styles?.items ?? []
|
||||
items.append(style)
|
||||
if let index = items.firstIndex(where: { $0.id == .style(.custom(id)) }) {
|
||||
items[index] = style
|
||||
}
|
||||
_internal_setCachedCloudAITextStyles(transaction: transaction, styles: CachedCloudAITextStyles(items: items, hash: 0))
|
||||
return style
|
||||
}
|
||||
|
|
|
|||
|
|
@ -356,7 +356,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||
contentMediaValue = file
|
||||
} else if file.isVideo {
|
||||
contentMediaValue = file
|
||||
} else if file.isSticker || file.isAnimatedSticker {
|
||||
} else if file.isSticker || file.isAnimatedSticker || file.isCustomEmoji {
|
||||
contentMediaValue = file
|
||||
} else {
|
||||
contentFileValue = file
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ final class TextProcessingContentComponent: Component {
|
|||
let newStyleAdded: (TelegramComposeAIMessageMode.CloudStyle) -> Void
|
||||
let styleUpdated: (TelegramComposeAIMessageMode.CloudStyle) -> Void
|
||||
let styleDeleted: (TelegramComposeAIMessageMode.StyleId) -> Void
|
||||
let displayToast: (String) -> Void
|
||||
let dismiss: (@escaping () -> Void) -> Void
|
||||
|
||||
init(
|
||||
|
|
@ -72,6 +73,7 @@ final class TextProcessingContentComponent: Component {
|
|||
newStyleAdded: @escaping (TelegramComposeAIMessageMode.CloudStyle) -> Void,
|
||||
styleUpdated: @escaping (TelegramComposeAIMessageMode.CloudStyle) -> Void,
|
||||
styleDeleted: @escaping (TelegramComposeAIMessageMode.StyleId) -> Void,
|
||||
displayToast: @escaping (String) -> Void,
|
||||
dismiss: @escaping (@escaping () -> Void) -> Void
|
||||
) {
|
||||
self.externalState = externalState
|
||||
|
|
@ -89,6 +91,7 @@ final class TextProcessingContentComponent: Component {
|
|||
self.newStyleAdded = newStyleAdded
|
||||
self.styleUpdated = styleUpdated
|
||||
self.styleDeleted = styleDeleted
|
||||
self.displayToast = displayToast
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +243,26 @@ final class TextProcessingContentComponent: Component {
|
|||
guard let slug = style.slug else {
|
||||
return
|
||||
}
|
||||
let shareController = component.context.sharedContext.makeShareController(context: component.context, params: ShareControllerParams(subject: .url("https://t.me/addstyle/\(slug)")))
|
||||
let shareController = component.context.sharedContext.makeShareController(context: component.context, params: ShareControllerParams(
|
||||
subject: .url("https://t.me/addstyle/\(slug)"),
|
||||
completed: { [weak self] peerIds in
|
||||
Task { @MainActor in
|
||||
guard let self, let component = self.component, let environment = self.environment, let peerId = peerIds.first else {
|
||||
return
|
||||
}
|
||||
let text: String
|
||||
if peerId == component.context.account.peerId {
|
||||
text = environment.strings.WebBrowser_LinkForwardTooltip_SavedMessages_One
|
||||
} else {
|
||||
guard let peer = await component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)).get() else {
|
||||
return
|
||||
}
|
||||
text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(peer.displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
|
||||
}
|
||||
component.displayToast(text)
|
||||
}
|
||||
}
|
||||
))
|
||||
self.environment?.controller()?.present(shareController, in: .window(.root))
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +433,7 @@ final class TextProcessingContentComponent: Component {
|
|||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard case let .preview(style, _, _, _) = component.mode else {
|
||||
guard case let .preview(style, _, _, _, _) = component.mode else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -463,7 +485,7 @@ final class TextProcessingContentComponent: Component {
|
|||
switch component.mode {
|
||||
case .edit:
|
||||
self.currentMode = .stylize
|
||||
case let .preview(_, _, initialPreview, _):
|
||||
case let .preview(_, _, initialPreview, _, _):
|
||||
self.currentMode = .stylize
|
||||
self.currentStylePreview = initialPreview
|
||||
self.requestStylePreview()
|
||||
|
|
@ -476,7 +498,7 @@ final class TextProcessingContentComponent: Component {
|
|||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
if case let .preview(style, _, _, _) = component.mode {
|
||||
if case let .preview(style, _, _, _, _) = component.mode {
|
||||
contentHeight += 40.0
|
||||
if let previewIconFile = component.previewIconFile {
|
||||
let previewIcon: ComponentView<Empty>
|
||||
|
|
@ -736,7 +758,7 @@ final class TextProcessingContentComponent: Component {
|
|||
var isRequestingPreview: Bool = false
|
||||
var authorPeer: EnginePeer?
|
||||
var userCount: Int = 0
|
||||
if case let .preview(style, authorPeerValue, _, _) = component.mode {
|
||||
if case let .preview(style, authorPeerValue, _, _, _) = component.mode {
|
||||
isPreview = true
|
||||
inputText = TextWithEntities(text: "", entities: [])
|
||||
authorPeer = authorPeerValue
|
||||
|
|
@ -1191,6 +1213,9 @@ private final class TextProcessingSheetComponent: Component {
|
|||
|
||||
private var styleCreatedToastData: (timer: Foundation.Timer, emojiFile: TelegramMediaFile, style: TelegramComposeAIMessageMode.CloudStyle.Custom)?
|
||||
private var styleCreatedToast: ComponentView<Empty>?
|
||||
|
||||
private var customToastData: (timer: Foundation.Timer, text: String)?
|
||||
private var customToast: ComponentView<Empty>?
|
||||
|
||||
private var component: TextProcessingSheetComponent?
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
|
|
@ -1211,6 +1236,7 @@ private final class TextProcessingSheetComponent: Component {
|
|||
deinit {
|
||||
self.actionDisposable?.dispose()
|
||||
self.styleCreatedToastData?.timer.invalidate()
|
||||
self.customToastData?.timer.invalidate()
|
||||
}
|
||||
|
||||
private func displayLongPressSendMenu(sourceSendButton: UIView) {
|
||||
|
|
@ -1410,8 +1436,9 @@ private final class TextProcessingSheetComponent: Component {
|
|||
performMainAction = {
|
||||
dismiss(true)
|
||||
}
|
||||
case let .preview(style, _, _, added):
|
||||
actionButtonTitle = "Add Style"
|
||||
case let .preview(style, _, _, isAlreadyAdded, added):
|
||||
//TODO:localize
|
||||
actionButtonTitle = isAlreadyAdded ? "Done" : "Add Style"
|
||||
isMainActionEnabled = !self.isPerformingMainAction
|
||||
performMainAction = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
|
|
@ -1422,19 +1449,23 @@ private final class TextProcessingSheetComponent: Component {
|
|||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
|
||||
self.actionDisposable?.dispose()
|
||||
self.actionDisposable = (component.context.engine.messages.installAIMessageStyle(style: style) |> deliverOnMainQueue).startStrict(error: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isPerformingMainAction = false
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
}, completed: {
|
||||
if isAlreadyAdded {
|
||||
dismiss(true)
|
||||
added()
|
||||
})
|
||||
} else {
|
||||
self.actionDisposable?.dispose()
|
||||
self.actionDisposable = (component.context.engine.messages.installAIMessageStyle(style: style) |> deliverOnMainQueue).startStrict(error: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isPerformingMainAction = false
|
||||
if !self.isUpdating {
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
}, completed: {
|
||||
dismiss(true)
|
||||
added()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1549,6 +1580,19 @@ private final class TextProcessingSheetComponent: Component {
|
|||
self.styles.remove(at: index)
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
},
|
||||
displayToast: { [weak self] text in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.customToastData = (Foundation.Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false, block: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.customToastData = nil
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}), text)
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
},
|
||||
dismiss: { [weak self] completion in
|
||||
self?.animateOut.invoke(Action { _ in
|
||||
if let controller = controller() {
|
||||
|
|
@ -1787,6 +1831,63 @@ private final class TextProcessingSheetComponent: Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let customToastData = self.customToastData, !self.contentExternalState.nonPremiumFloodTriggered, self.styleCreatedToastData == nil {
|
||||
let sideInset: CGFloat = 8.0
|
||||
|
||||
let customToast: ComponentView<Empty>
|
||||
var customToastTransition = transition
|
||||
if let current = self.customToast {
|
||||
customToast = current
|
||||
} else {
|
||||
customToastTransition = customToastTransition.withAnimation(.none)
|
||||
customToast = ComponentView()
|
||||
self.customToast = customToast
|
||||
}
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let playOnce = ActionSlot<Void>()
|
||||
let customToastSize = customToast.update(
|
||||
transition: customToastTransition,
|
||||
component: AnyComponent(ToastContentComponent(
|
||||
icon: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "anim_infotip"),
|
||||
startingPosition: .begin,
|
||||
size: CGSize(width: 32.0, height: 32.0),
|
||||
playOnce: playOnce
|
||||
)),
|
||||
content: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(text: customToastData.text, 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 customToastView = customToast.view {
|
||||
if customToastView.superview == nil, let sheetView = self.sheet.view as? ResizableSheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
sheetView.containerView.addSubview(customToastView)
|
||||
if !transition.animation.isImmediate {
|
||||
customToastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
playOnce.invoke(())
|
||||
}
|
||||
customToastTransition.setFrame(view: customToastView, frame: CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - 94.0 - customToastSize.height), size: customToastSize))
|
||||
}
|
||||
} else {
|
||||
if let customToast = self.customToast {
|
||||
self.customToast = nil
|
||||
if let customToastView = customToast.view {
|
||||
customToastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak customToastView] _ in
|
||||
customToastView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let languageSelectionMenuDataValue = self.languageSelectionMenuData {
|
||||
let languageSelectionMenu: ComponentView<Empty>
|
||||
|
|
@ -1988,7 +2089,7 @@ public class TextProcessingScreen: ViewControllerComponentContainer {
|
|||
|
||||
var shouldDisplayStyleNotice = false
|
||||
var previewIconFile: TelegramMediaFile?
|
||||
if case let .preview(style, _, _, _) = mode {
|
||||
if case let .preview(style, _, _, _, _) = mode {
|
||||
if let emojiFileId = style.emojiFileId {
|
||||
previewIconFile = await context.engine.stickers.resolveInlineStickersLocal(fileIds: [emojiFileId]).get().first?.value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ final class TextStyleEditContentComponent: Component {
|
|||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var isUpdating: Bool = false
|
||||
private var previousEnvironment: ViewControllerComponentContainer.Environment?
|
||||
|
||||
private let iconBackground = ComponentView<Empty>()
|
||||
private let emptyIcon = ComponentView<Empty>()
|
||||
|
|
@ -209,6 +210,8 @@ final class TextStyleEditContentComponent: Component {
|
|||
let alphaTransition: ComponentTransition = transition.animation.isImmediate ? .immediate : .easeInOut(duration: 0.2)
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let previousEnvironment = self.previousEnvironment
|
||||
self.previousEnvironment = environment
|
||||
|
||||
var resetTitle: String?
|
||||
var resetText: String?
|
||||
|
|
@ -534,8 +537,36 @@ final class TextStyleEditContentComponent: Component {
|
|||
|
||||
let _ = alphaTransition
|
||||
|
||||
if let hint = transition.userData(TextFieldComponent.AnimationHint.self), case .textChanged = hint.kind, let hintView = hint.view {
|
||||
self.recenterCaret(hintView: hintView, transition: transition)
|
||||
if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let hintView = hint.view {
|
||||
switch hint.kind {
|
||||
case .textChanged:
|
||||
self.recenterCaret(hintView: hintView, transition: transition)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if let previousEnvironment {
|
||||
var targetView: UIView?
|
||||
if component.externalState.titleInputState.isEditing {
|
||||
if let view = self.titleSection.findTaggedView(tag: self.titleFieldTag) as? ListMultilineTextFieldItemComponent.View {
|
||||
targetView = view
|
||||
}
|
||||
} else if component.externalState.textInputState.isEditing {
|
||||
if let view = self.textSection.findTaggedView(tag: self.textFieldTag) as? ListMultilineTextFieldItemComponent.View {
|
||||
targetView = view
|
||||
}
|
||||
}
|
||||
|
||||
if let targetView {
|
||||
if (environment.inputHeight == 0.0) != (previousEnvironment.inputHeight == 0.0) {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.recenterCaret(hintView: targetView, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: contentHeight)
|
||||
|
|
|
|||
|
|
@ -2046,8 +2046,9 @@ func openResolvedUrlImpl(
|
|||
TelegramEngine.EngineData.Item.Peer.Peer(id: authorId)
|
||||
).get()
|
||||
}
|
||||
let isAlreadyAdded = await context.engine.messages.composeAIMessageStyles().get().contains(where: { $0.id == .style(.custom(style.id)) })
|
||||
|
||||
let controller = await context.sharedContext.makeTextProcessingScreen(context: context, theme: nil, mode: .preview(style: style, authorPeer: authorPeer, initialPreview: initialPreview, added: {
|
||||
let controller = await context.sharedContext.makeTextProcessingScreen(context: context, theme: nil, mode: .preview(style: style, authorPeer: authorPeer, initialPreview: initialPreview, isAlreadyAdded: isAlreadyAdded, added: {
|
||||
Task { @MainActor in
|
||||
guard let emojiFileId = style.emojiFileId, let file = await context.engine.stickers.resolveInlineStickers(fileIds: [emojiFileId]).get().first?.value else {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue