Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2026-03-27 18:45:59 +01:00
commit d0fce97c93
20 changed files with 304 additions and 190 deletions

View file

@ -16101,3 +16101,57 @@ Error: %8$@";
"MediaPicker.LivePhoto.Tooltip.LiveOff" = "Live Photo turned off";
"AuthConfirmation.UnverifiedApp" = "Unverified App";
"Chat.ManagedBot.CreatedTitle" = "**%@** is ready!";
"Chat.ManagedBot.CreatedText" = "Tap **Start** below to test your new chatbot. Its behavior is defined by **%@**.";
"Chat.CreateBotLink" = "Create Bot";
"Chat.InlineTopicMenu.TabAll" = "All";
"CreateBot.Title" = "Create Bot";
"CreateBot.Text" = "[%@]() wound like to create and manage a chatbot on your behalf.";
"CreateBot.SectionName" = "BOT NAME";
"CreateBot.SectionUsername" = "BOT USERNAME";
"CreateBot.NamePlaceholder" = "Name";
"CreateBot.UsernameStatus.Checking" = "Checking...";
"CreateBot.UsernameStatus.Invalid" = "You can only use **a-z**, **0-9** and underscores.";
"CreateBot.UsernameStatus.Taken" = "This username is already taken.";
"CreateBot.UsernameStatus.Short" = "A username must have at least 5 characters.";
"CreateBot.UsernameStatus.Number" = "A username can't start with a number";
"CreateBot.UsernameStatus.LimitExceeded_1" = "You can't create more than %d bot.";
"CreateBot.UsernameStatus.LimitExceeded_any" = "You can't create more than %d bots.";
"CreateBot.UsernameStatus.Link" = "Link: t.me/%@";
"CreateBot.UnsavedAlert.Title" = "Unsaved Changes";
"CreateBot.UnsavedAlert.Text" = "You have not finished creating a bot.";
"CreateBot.UnsavedAlert.Discard" = "Discard";
"CreateBot.ActionButton" = "Create";
"CreateBot.InputBadge" = "edit";
"PeerInfo.ManagedBotFooter" = "Created and managed by [@%@]()";
"Conversation.ReadAllPollVotes" = "Read All Poll Votes";
"Chat.PanelSetBotPhoto" = "Set Profile Photo";
"TextProcessing.TitleEdit" = "AI Editor";
"TextProcessing.TitleTranslate" = "Translation";
"TextProcessing.TabTranslate" = "Translate";
"TextProcessing.TabStylize" = "Style";
"TextProcessing.TabFix" = "Fix";
"TextProcessing.ActionTitleNonPremium" = "Increase Limit";
"TextProcessing.ActionValueNonPremium" = "X50";
"TextProcessing.ActionApply" = "Apply";
"TextProcessing.ActionClose" = "Close";
"TextProcessing.LimitToast.Title" = "Daily limit reached";
"TextProcessing.LimitToast.Text" = "Get **Telegram Premium** for **50x** more text edits per day.";
"TextProcessing.Emojify" = "Emojify";
"TextProcessing.TextExpand" = "more";
"TextProcessing.TextCollapse" = "less";
"TextProcessing.Translate.FromLanguage" = "From {}";
"TextProcessing.Translate.ToLanguage" = "To {}";
"TextProcessing.OriginalBadge" = "Original:";
"TextProcessing.OriginalStyleBadge" = "Original";
"TextProcessing.ResultBadge" = "Result";
"TextProcessing.Translate.LanguageStyle" = "%1$@ (%2$@)";
"TextProcessing.StyleTooltip" = "Select Style";
"Bot.AlertCanNotCreateBots" = "%@ can't manage other bots.";

View file

@ -19,6 +19,7 @@ import ChatSendMessageActionUI
import MinimizedContainer
import ComponentFlow
import GlassBackgroundComponent
import TextFormat
public enum AttachmentButtonType: Equatable {
case gallery
@ -775,19 +776,17 @@ public class AttachmentController: ViewController, MinimizableController {
mode: .edit(
saveRestoreStateId: nil,
completion: { [weak self] text in
//TODO:localize
guard let self, let mediaPickerContext = self.mediaPickerContext else {
return
}
self.panel.updateCaption(NSAttributedString(string: text.text))
mediaPickerContext.setCaption(NSAttributedString(string: text.text))
self.panel.updateCaption(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))
mediaPickerContext.setCaption(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))
},
send: { [weak self] text in
//TODO:localize
guard let self, let mediaPickerContext = self.mediaPickerContext else {
return
}
mediaPickerContext.setCaption(NSAttributedString(string: text.text))
mediaPickerContext.setCaption(chatInputStateStringWithAppliedEntities(text.text, entities: text.entities))
mediaPickerContext.send(mode: .generic, attachmentMode: .media, parameters: nil)
},
sendContextActions: nil

View file

@ -29,6 +29,7 @@ public struct UserLimitsConfiguration: Equatable {
public var maxGiveawayPeriodSeconds: Int32
public var maxChannelRecommendationsCount: Int32
public var maxConferenceParticipantCount: Int32
public var maxBotsCreated: Int32
public static var defaultValue: UserLimitsConfiguration {
return UserLimitsConfiguration(
@ -58,7 +59,8 @@ public struct UserLimitsConfiguration: Equatable {
maxGiveawayCountriesCount: 10,
maxGiveawayPeriodSeconds: 86400 * 31,
maxChannelRecommendationsCount: 10,
maxConferenceParticipantCount: 100
maxConferenceParticipantCount: 100,
maxBotsCreated: 20
)
}
@ -89,7 +91,8 @@ public struct UserLimitsConfiguration: Equatable {
maxGiveawayCountriesCount: Int32,
maxGiveawayPeriodSeconds: Int32,
maxChannelRecommendationsCount: Int32,
maxConferenceParticipantCount: Int32
maxConferenceParticipantCount: Int32,
maxBotsCreated: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
@ -118,6 +121,7 @@ public struct UserLimitsConfiguration: Equatable {
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
self.maxConferenceParticipantCount = maxConferenceParticipantCount
self.maxBotsCreated = maxBotsCreated
}
}
@ -172,5 +176,6 @@ extension UserLimitsConfiguration {
self.maxGiveawayPeriodSeconds = getGeneralValue("giveaway_period_max", orElse: defaultValue.maxGiveawayPeriodSeconds)
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)
}
}

View file

@ -63,6 +63,7 @@ public enum EngineConfiguration {
public let maxGiveawayPeriodSeconds: Int32
public let maxChannelRecommendationsCount: Int32
public let maxConferenceParticipantCount: Int32
public let maxBotsCreated: Int32
public static var defaultValue: UserLimits {
return UserLimits(UserLimitsConfiguration.defaultValue)
@ -95,7 +96,8 @@ public enum EngineConfiguration {
maxGiveawayCountriesCount: Int32,
maxGiveawayPeriodSeconds: Int32,
maxChannelRecommendationsCount: Int32,
maxConferenceParticipantCount: Int32
maxConferenceParticipantCount: Int32,
maxBotsCreated: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
@ -124,6 +126,7 @@ public enum EngineConfiguration {
self.maxGiveawayPeriodSeconds = maxGiveawayPeriodSeconds
self.maxChannelRecommendationsCount = maxChannelRecommendationsCount
self.maxConferenceParticipantCount = maxConferenceParticipantCount
self.maxBotsCreated = maxBotsCreated
}
}
}
@ -187,7 +190,8 @@ public extension EngineConfiguration.UserLimits {
maxGiveawayCountriesCount: userLimitsConfiguration.maxGiveawayCountriesCount,
maxGiveawayPeriodSeconds: userLimitsConfiguration.maxGiveawayPeriodSeconds,
maxChannelRecommendationsCount: userLimitsConfiguration.maxChannelRecommendationsCount,
maxConferenceParticipantCount: userLimitsConfiguration.maxConferenceParticipantCount
maxConferenceParticipantCount: userLimitsConfiguration.maxConferenceParticipantCount,
maxBotsCreated: userLimitsConfiguration.maxBotsCreated
)
}
}

View file

@ -251,8 +251,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
let textString: NSAttributedString
let textSpacing: CGFloat
if let peer = item.peer, let managedByBot = item.managedByBot {
//TODO:localize
titleString = parseMarkdownIntoAttributedString("**\(peer.compactDisplayTitle)** is ready!", attributes: MarkdownAttributes(
titleString = parseMarkdownIntoAttributedString(item.presentationData.strings.Chat_ManagedBot_CreatedTitle(peer.compactDisplayTitle).string, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: messageFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor),
bold: MarkdownAttributeSet(font: messageBoldFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor),
link: MarkdownAttributeSet(font: messageFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor),
@ -260,7 +259,7 @@ public final class ChatBotInfoItemNode: ListViewItemNode {
return ("URL", url)
}
))
let rawTextString = "Tap **Start** below to test your new chatbot. Its behavior is defined by **\(managedByBot.compactDisplayTitle)**."
let rawTextString = item.presentationData.strings.Chat_ManagedBot_CreatedText(managedByBot.compactDisplayTitle).string
textString = parseMarkdownIntoAttributedString(rawTextString, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: messageFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor),
bold: MarkdownAttributeSet(font: messageBoldFont, textColor: item.presentationData.theme.theme.chat.message.infoPrimaryTextColor),

View file

@ -516,8 +516,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
case "telegram_channel_direct":
actionTitle = item.presentationData.strings.Chat_ContactChannel
case "telegram_newbot":
//TODO:localize
actionTitle = "Create Bot"
actionTitle = item.presentationData.strings.Chat_CreateBotLink
default:
break
}

View file

@ -1263,8 +1263,7 @@ public final class ChatSideTopicsPanel: Component {
if forumManagedByUser {
titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab
} else {
//TODO:localize
titleText = "All"
titleText = component.strings.Chat_InlineTopicMenu_TabAll
}
} else {
titleText = component.strings.Chat_InlineTopicMenu_AllTab
@ -1405,8 +1404,7 @@ public final class ChatSideTopicsPanel: Component {
if forumManagedByUser {
titleText = component.strings.Chat_InlineTopicMenu_NewForumThreadTab
} else {
//TODO:localize
titleText = "All"
titleText = component.strings.Chat_InlineTopicMenu_TabAll
}
} else {
titleText = component.strings.Chat_InlineTopicMenu_AllTab

View file

@ -200,7 +200,7 @@ final class CreateBotContentComponent: Component {
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .plain(NSAttributedString(string: "Create Bot", font: Font.bold(24.0), textColor: environment.theme.list.itemPrimaryTextColor)),
text: .plain(NSAttributedString(string: environment.strings.CreateBot_Title, font: Font.bold(24.0), textColor: environment.theme.list.itemPrimaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.12
@ -230,7 +230,7 @@ final class CreateBotContentComponent: Component {
let subtitleSize = self.subtitle.update(
transition: .immediate,
component: AnyComponent(BalancedTextComponent(
text: .markdown(text: "[\(component.parentPeer.debugDisplayTitle)]() wound like to create and manage a chatbot on your behalf.", attributes: markdownAttributes),
text: .markdown(text: environment.strings.CreateBot_Text(component.parentPeer.debugDisplayTitle).string, attributes: markdownAttributes),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.12
@ -256,7 +256,7 @@ final class CreateBotContentComponent: Component {
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "BOT NAME",
string: environment.strings.CreateBot_SectionName,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
@ -272,7 +272,7 @@ final class CreateBotContentComponent: Component {
strings: environment.strings,
initialText: "",
resetText: isFirstTime ? ListMultilineTextFieldItemComponent.ResetText(value: component.initialTitle ?? "") : nil,
placeholder: "Name",
placeholder: environment.strings.CreateBot_NamePlaceholder,
autocapitalizationType: .words,
autocorrectionType: .no,
characterLimit: 64,
@ -327,23 +327,23 @@ final class CreateBotContentComponent: Component {
switch self.usernameCheckingStatus?.status ?? .valid {
case .checking:
usernameFooterString = NSAttributedString(
string: "Checking...",
string: environment.strings.CreateBot_UsernameStatus_Checking,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)
case .invalid:
let errorText = "You can only use **a-z**, **0-9** and underscores."
let errorText = environment.strings.CreateBot_UsernameStatus_Invalid
usernameFooterString = parseMarkdownIntoAttributedString(errorText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemDestructiveColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), linkAttribute: { contents in
return ("URL", contents)
}))
case .taken:
let errorText = "This username is already taken."
let errorText = environment.strings.CreateBot_UsernameStatus_Taken
usernameFooterString = parseMarkdownIntoAttributedString(errorText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemDestructiveColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), linkAttribute: { contents in
return ("URL", contents)
}))
case .valid:
usernameFooterString = NSAttributedString(
string: "Link: t.me/\(value)",
string: environment.strings.CreateBot_UsernameStatus_Link(value).string,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)
@ -352,11 +352,11 @@ final class CreateBotContentComponent: Component {
let errorText: String
switch error {
case .insufficientLength:
errorText = "A username must have at least 5 characters."
errorText = environment.strings.CreateBot_UsernameStatus_Short
case .startsWithNumber:
errorText = "A username can't start with a number"
errorText = environment.strings.CreateBot_UsernameStatus_Number
case .unsupportedCharacters:
errorText = "You can only use **a-z**, **0-9** and underscores."
errorText = environment.strings.CreateBot_UsernameStatus_Invalid
}
usernameFooterString = parseMarkdownIntoAttributedString(errorText, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.itemDestructiveColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemDestructiveColor), linkAttribute: { contents in
return ("URL", contents)
@ -370,7 +370,7 @@ final class CreateBotContentComponent: Component {
style: .glass,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "BOT USERNAME",
string: environment.strings.CreateBot_SectionUsername,
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
@ -513,17 +513,16 @@ private final class CreateBotSheetComponent: Component {
return false
}
//TODO:localize
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let _ = presentationData
let alertController = textAlertController(
context: component.context,
title: "Unsaved Changes",
text: "You have not finished creating a bot.",
title: presentationData.strings.CreateBot_UnsavedAlert_Title,
text: presentationData.strings.CreateBot_UnsavedAlert_Text,
actions: [
TextAlertAction(type: .genericAction, title: "Cancel", action: {
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
}),
TextAlertAction(type: .destructiveAction, title: "Discard", action: { [weak self] in
TextAlertAction(type: .destructiveAction, title: presentationData.strings.CreateBot_UnsavedAlert_Discard, action: { [weak self] in
guard let self, let component = self.component else {
return
}
@ -638,33 +637,40 @@ private final class CreateBotSheetComponent: Component {
}
})
}, error: { [weak self] error in
guard let self, let environment = self.environment, let component = self.component else {
return
Task { @MainActor in
guard let self, let environment = self.environment, let component = self.component else {
return
}
self.isCreating = false
self.state?.updated(transition: .immediate)
let text: String
switch error {
case .generic:
text = environment.strings.Login_UnknownError
case .occupied:
text = environment.strings.CreateBot_UsernameStatus_Taken
case .limitExceeded:
let isPremium = (await component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId)
).get())?.isPremium ?? false
let limits = await component.context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: isPremium)
).get()
text = environment.strings.CreateBot_UsernameStatus_LimitExceeded(Int32(limits.maxBotsCreated))
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
self.environment?.controller()?.push(textAlertController(
context: component.context,
title: nil,
text: text,
actions: [
.init(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
]
))
}
self.isCreating = false
self.state?.updated(transition: .immediate)
let text: String
switch error {
case .generic:
text = environment.strings.Login_UnknownError
case .occupied:
text = "This username already exists."
case .limitExceeded:
text = "Please try again later."
}
//TODO:localize
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
self.environment?.controller()?.push(textAlertController(
context: component.context,
title: nil,
text: text,
actions: [
.init(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
]
))
})
}
@ -961,7 +967,7 @@ private final class ActionButtonsComponent: Component {
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(ButtonTextContentComponent(
text: "Create", //TODO:localize
text: component.strings.CreateBot_ActionButton,
badge: 0,
textColor: component.theme.list.itemCheckColors.foregroundColor,
badgeBackground: component.theme.list.itemCheckColors.foregroundColor,
@ -1070,11 +1076,10 @@ private final class EditLabelComponent: Component {
let verticalInset: CGFloat = 4.0
let rightInset: CGFloat = 16.0
//TODO:localize
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "edit", font: Font.regular(11.0), textColor: component.theme.list.itemAccentColor))
text: .plain(NSAttributedString(string: component.strings.CreateBot_InputBadge, font: Font.regular(11.0), textColor: component.theme.list.itemAccentColor))
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)

View file

@ -528,6 +528,18 @@ public final class InteractiveTextNodeLayout: NSObject {
}
}
public var trailingLineIsBlock: Bool {
if let lastSegment = self.segments.last {
if let _ = lastSegment.blockQuote {
return true
} else {
return false
}
} else {
return false
}
}
public func attributesAtPoint(_ point: CGPoint, orNearest: Bool) -> (Int, [NSAttributedString.Key: Any])? {
if let attributedString = self.attributedString {
let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top)

View file

@ -513,8 +513,7 @@ func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationD
}))
if let managedByBot = data.managedByBot {
//TODO:localize
items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, icon: .managedBot, text: "Created and managed by [@\(managedByBot.compactDisplayTitle)]()", linkAction: { _ in
items[currentPeerInfoSection]!.append(PeerInfoScreenCommentItem(id: ItemBotAddToChatInfo, icon: .managedBot, text: presentationData.strings.PeerInfo_ManagedBotFooter(managedByBot.compactDisplayTitle).string, linkAction: { _ in
interaction.openPeerInfo(managedByBot._asPeer(), false)
}))
} else {

View file

@ -689,7 +689,6 @@ extension PeerInfoScreenNode {
hasPaidFee = true
}
if !hasPaidFee {
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Return Fee", icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Paid"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in

View file

@ -410,7 +410,7 @@ final class TextProcessingLanguageSelectionComponent: Component {
if component.ignoredTranslationLanguages.contains(item) {
return nil
}
return Language(id: item, languageCode: item, name: localizedLanguageName(strings: component.strings, language: item))
return Language(id: item, languageCode: item, name: localizedLanguageName(strings: component.strings, language: item, kind: .neutral))
}
var topIds: [String] = []
if !topIds.contains(component.selectedLanguageCode), let item = self.mainItems.first(where: { $0.languageCode == component.selectedLanguageCode }) {

View file

@ -229,7 +229,7 @@ final class TextProcessingContentComponent: Component {
tabs.append(TabBarComponent.Item(
content: .customItem(TabBarComponent.Item.Content.CustomItem(
id: "translate",
title: "Translate",
title: environment.strings.TextProcessing_TabTranslate,
icon: .bundleIcon(name: "TextProcessing/TabTranslate")
)),
action: { [weak self] _ in
@ -251,7 +251,7 @@ final class TextProcessingContentComponent: Component {
tabs.append(TabBarComponent.Item(
content: .customItem(TabBarComponent.Item.Content.CustomItem(
id: "stylize",
title: "Style",
title: environment.strings.TextProcessing_TabStylize,
icon: .bundleIcon(name: "TextProcessing/TabStylize")
)),
action: { [weak self] _ in
@ -274,7 +274,7 @@ final class TextProcessingContentComponent: Component {
tabs.append(TabBarComponent.Item(
content: .customItem(TabBarComponent.Item.Content.CustomItem(
id: "fix",
title: "Fix",
title: environment.strings.TextProcessing_TabFix,
icon: .bundleIcon(name: "TextProcessing/TabFix")
)),
action: { [weak self] _ in
@ -437,31 +437,6 @@ final class TextProcessingContentComponent: Component {
var actionsSectionItems: [AnyComponentWithIdentity<Empty>] = []
if case .translate = component.mode {
/*if let replaceText = component.replaceText {
//TODO:localize
actionsSectionItems.append(AnyComponentWithIdentity(id: "replace", component: AnyComponent(ListActionItemComponent(
theme: theme,
style: .glass,
title: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Replace with Translation",
font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize),
textColor: theme.list.itemAccentColor
)),
maximumNumberOfLines: 1
)
),
leftIcon: .custom(AnyComponentWithIdentity(id: "icon", component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Replace", tintColor: theme.list.itemAccentColor))), false),
action: { [weak state] _ in
guard let state else {
return
}
replaceText(state.translatedText?.0 ?? state.text, state.translatedText?.1 ?? state.entities)
component.dismiss()
}
))))
}*/
if let copyTranslation = component.copyCurrentResult {
actionsSectionItems.append(AnyComponentWithIdentity(id: "copy", component: AnyComponent(ListActionItemComponent(
theme: environment.theme,
@ -489,7 +464,7 @@ final class TextProcessingContentComponent: Component {
title: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Translate Entire Chat",
string: environment.strings.Localization_TranslateEntireChat,
font: Font.regular(17.0),
textColor: environment.theme.list.itemAccentColor
)),
@ -765,13 +740,12 @@ private final class TextProcessingSheetComponent: Component {
var performSendAction: ((TextWithEntities) -> Void)?
var hasLongPressActions = false
let isMainActionEnabled: Bool
//TODO:localize (for AI: don't remove this line)
let actionButtonTitle: String
var actionButtonShowsIncreaseLimit = false
if self.contentExternalState.nonPremiumFloodTriggered {
isMainActionEnabled = true
actionButtonTitle = "Increase Limit"
actionButtonTitle = environmentValue.strings.TextProcessing_ActionTitleNonPremium
actionButtonShowsIncreaseLimit = true
performMainAction = { [weak self] in
guard let self, let component = self.component else {
@ -780,8 +754,8 @@ private final class TextProcessingSheetComponent: Component {
let context = component.context
var replaceImpl: ((ViewController) -> Void)?
let controller = component.context.sharedContext.makePremiumDemoController(context: component.context, subject: .doubleLimits, forceDark: false, action: {
let controller = component.context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
let controller = component.context.sharedContext.makePremiumDemoController(context: component.context, subject: .aiTools, forceDark: false, action: {
let controller = component.context.sharedContext.makePremiumIntroController(context: context, source: .aiTools, forceDark: false, dismissed: nil)
replaceImpl?(controller)
}, dismissed: nil)
replaceImpl = { [weak controller] c in
@ -792,7 +766,7 @@ private final class TextProcessingSheetComponent: Component {
} else {
switch component.mode {
case let .edit(_, completion, send, sendContextActions):
actionButtonTitle = "Apply"
actionButtonTitle = environmentValue.strings.TextProcessing_ActionApply
performSendAction = send
isMainActionEnabled = !self.contentExternalState.isProcessing
hasLongPressActions = sendContextActions != nil
@ -806,7 +780,7 @@ private final class TextProcessingSheetComponent: Component {
dismiss(true)
}
case .translate:
actionButtonTitle = "Close"
actionButtonTitle = environmentValue.strings.TextProcessing_ActionClose
isMainActionEnabled = true
performMainAction = {
dismiss(true)
@ -827,9 +801,9 @@ private final class TextProcessingSheetComponent: Component {
let titleString: String
switch component.mode {
case .edit:
titleString = "AI Editor"
titleString = environmentValue.strings.TextProcessing_TitleEdit
case .translate:
titleString = "Translation"
titleString = environmentValue.strings.TextProcessing_TitleTranslate
}
let sheetSize = self.sheet.update(
@ -903,6 +877,7 @@ private final class TextProcessingSheetComponent: Component {
bottomItem: AnyComponent(
ActionButtonsComponent(
theme: theme,
strings: environmentValue.strings,
actionTitle: actionButtonTitle,
actionButtonShowsIncreaseLimit: actionButtonShowsIncreaseLimit,
action: isMainActionEnabled ? performMainAction : nil,
@ -982,10 +957,10 @@ private final class TextProcessingSheetComponent: Component {
)),
content: AnyComponent(VStack([
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Daily limit reached", font: Font.semibold(14.0), textColor: .white)),
text: .plain(NSAttributedString(string: environmentValue.strings.TextProcessing_LimitToast_Title, font: Font.semibold(14.0), textColor: .white)),
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
text: .markdown(text: "Get **Telegram Premium** for **50x** more text edits per day.", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })),
text: .markdown(text: environmentValue.strings.TextProcessing_LimitToast_Text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })),
maximumNumberOfLines: 0
)))
], alignment: .left, spacing: 6.0)),
@ -1302,6 +1277,7 @@ private final class TitleComponent: Component {
private final class ActionButtonsComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let actionTitle: String
let actionButtonShowsIncreaseLimit: Bool
let action: (() -> Void)?
@ -1310,6 +1286,7 @@ private final class ActionButtonsComponent: Component {
init(
theme: PresentationTheme,
strings: PresentationStrings,
actionTitle: String,
actionButtonShowsIncreaseLimit: Bool,
action: (() -> Void)?,
@ -1317,6 +1294,7 @@ private final class ActionButtonsComponent: Component {
longPressSendAction: ((UIView) -> Void)?
) {
self.theme = theme
self.strings = strings
self.actionTitle = actionTitle
self.actionButtonShowsIncreaseLimit = actionButtonShowsIncreaseLimit
self.action = action
@ -1328,6 +1306,9 @@ private final class ActionButtonsComponent: Component {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.actionTitle != rhs.actionTitle {
return false
}
@ -1380,6 +1361,7 @@ private final class ActionButtonsComponent: Component {
))))
if component.actionButtonShowsIncreaseLimit {
actionButtonContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(IncreaseLimitBadgeComponent(
title: component.strings.TextProcessing_ActionValueNonPremium,
fillColor: component.theme.list.itemCheckColors.foregroundColor,
foregroundColor: .clear
))))
@ -1484,18 +1466,24 @@ private final class ActionButtonsComponent: Component {
}
private final class IncreaseLimitBadgeComponent: Component {
let title: String
let fillColor: UIColor
let foregroundColor: UIColor
init(
title: String,
fillColor: UIColor,
foregroundColor: UIColor
) {
self.title = title
self.fillColor = fillColor
self.foregroundColor = foregroundColor
}
static func ==(lhs: IncreaseLimitBadgeComponent, rhs: IncreaseLimitBadgeComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.fillColor != rhs.fillColor {
return false
}
@ -1532,7 +1520,7 @@ private final class IncreaseLimitBadgeComponent: Component {
let topInset: CGFloat = 1.0
let bottomInset: CGFloat = 0.0
let text = NSAttributedString(string: "X50", font: Font.with(size: 14.0, design: .round, weight: .semibold), textColor: .clear)
let text = NSAttributedString(string: component.title, font: Font.with(size: 14.0, design: .round, weight: .semibold), textColor: .clear)
let rawTextSize = text.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
let textSize = CGSize(width: ceil(rawTextSize.width), height: ceil(rawTextSize.height))
let backgroundSize = CGSize(width: leftInset + rightInset + textSize.width, height: topInset + bottomInset + textSize.height)

View file

@ -13,7 +13,7 @@ func localizedStyleName(strings: PresentationStrings, styleId: TelegramComposeAI
case .neutral:
return strings.TextProcessingStyle_Neutral
case let .style(name):
if let value = strings.primaryComponent.dict["TextProcessingStyle_\(name)"] {
if let value = strings.primaryComponent.dict["TextProcessingStyle.\(name)"] {
return value
} else {
return name.capitalized

View file

@ -21,7 +21,7 @@ final class TextProcessingTextAreaComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let titlePrefix: String
let titleFormat: String
let title: String
let titleAction: ((UIView) -> Void)?
let isExpanded: (value: Bool, toggle: () -> Void)?
@ -37,7 +37,7 @@ final class TextProcessingTextAreaComponent: Component {
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
titlePrefix: String,
titleFormat: String,
title: String,
titleAction: ((UIView) -> Void)?,
isExpanded: (value: Bool, toggle: () -> Void)?,
@ -52,7 +52,7 @@ final class TextProcessingTextAreaComponent: Component {
self.context = context
self.theme = theme
self.strings = strings
self.titlePrefix = titlePrefix
self.titleFormat = titleFormat
self.isExpanded = isExpanded
self.copyAction = copyAction
self.title = title
@ -75,7 +75,7 @@ final class TextProcessingTextAreaComponent: Component {
if lhs.strings !== rhs.strings {
return false
}
if lhs.titlePrefix != rhs.titlePrefix {
if lhs.titleFormat != rhs.titleFormat {
return false
}
if lhs.title != rhs.title {
@ -110,7 +110,6 @@ final class TextProcessingTextAreaComponent: Component {
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private let titlePrefix = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private var titleArrow: ComponentView<Empty>?
private var emojify: ComponentView<Empty>?
@ -190,11 +189,18 @@ final class TextProcessingTextAreaComponent: Component {
guard let result = super.hitTest(point, with: event) else {
return nil
}
if result == self.textSelectionNode?.view && !self.displayContentsUnderSpoilers.value {
if result == self.textSelectionNode?.view {
if let textView = self.text.view as? InteractiveTextComponent.View {
let textPoint = self.convert(point, to: textView.textNode.view)
if let attributes = textView.textNode.attributesAtPoint(textPoint, orNearest: false)?.1 {
if attributes[NSAttributedString.Key(rawValue: "TelegramSpoiler")] != nil || attributes[NSAttributedString.Key(rawValue: "Attribute__Spoiler")] != nil {
if !self.displayContentsUnderSpoilers.value {
if let value = textView.textNode.view.hitTest(textPoint, with: event) {
return value
}
}
}
if attributes[NSAttributedString.Key(rawValue: "TelegramBlockQuote")] != nil || attributes[NSAttributedString.Key(rawValue: "Attribute__Blockquote")] != nil {
if let value = textView.textNode.view.hitTest(textPoint, with: event) {
return value
}
@ -225,40 +231,32 @@ final class TextProcessingTextAreaComponent: Component {
var contentHeight: CGFloat = 0.0
contentHeight += topInset
let titlePrefixSize = self.titlePrefix.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.titlePrefix, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0)
)
let titleString = NSMutableAttributedString()
if let range = component.titleFormat.range(of: "{}") {
if range.lowerBound != component.titleFormat.startIndex {
titleString.append(NSAttributedString(string: String(component.titleFormat[component.titleFormat.startIndex ..< range.lowerBound]), font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
}
titleString.append(NSAttributedString(string: component.title, font: Font.semibold(13.0), textColor: component.theme.list.itemAccentColor))
if range.upperBound != component.titleFormat.endIndex {
titleString.append(NSAttributedString(string: String(component.titleFormat[range.upperBound...]), font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
}
} else {
titleString.append(NSAttributedString(string: component.titleFormat, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
}
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(13.0), textColor: component.theme.list.itemAccentColor))
text: .plain(titleString)
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 10.0, height: 100.0)
)
let titlePrefixFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: titlePrefixSize)
var titleFrame = CGRect(origin: CGPoint(x: titlePrefixFrame.maxX, y: titlePrefixFrame.minY), size: titleSize)
if !component.titlePrefix.isEmpty {
titleFrame.origin.x += 3.0
}
let titleFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: titleSize)
transition.setFrame(view: self.titleButton, frame: titleFrame.insetBy(dx: -10.0, dy: -10.0))
if let titlePrefixView = self.titlePrefix.view {
if titlePrefixView.superview == nil {
titlePrefixView.layer.anchorPoint = CGPoint()
titlePrefixView.isUserInteractionEnabled = false
self.addSubview(titlePrefixView)
}
titlePrefixView.bounds = CGRect(origin: CGPoint(), size: titlePrefixFrame.size)
transition.setPosition(view: titlePrefixView, position: titlePrefixFrame.origin)
}
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.layer.anchorPoint = CGPoint()
@ -332,7 +330,7 @@ final class TextProcessingTextAreaComponent: Component {
selected: emojifyValue.value
))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Emojify", font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
text: .plain(NSAttributedString(string: component.strings.TextProcessing_Emojify, font: Font.semibold(13.0), textColor: component.theme.list.itemSecondaryTextColor))
)))
], spacing: 7.0)),
effectAlignment: .center,
@ -368,6 +366,7 @@ final class TextProcessingTextAreaComponent: Component {
entities: component.text?.entities ?? self.previousText?.entities ?? [],
baseColor: component.theme.list.itemPrimaryTextColor,
linkColor: component.theme.list.itemAccentColor,
baseQuoteTintColor: component.theme.list.itemAccentColor,
baseFont: Font.regular(fontSize),
linkFont: Font.regular(fontSize),
boldFont: Font.semibold(fontSize),
@ -415,7 +414,7 @@ final class TextProcessingTextAreaComponent: Component {
textStroke: nil,
displayContentsUnderSpoilers: self.displayContentsUnderSpoilers.value,
customTruncationToken: nil,
expandedBlocks: Set(),
expandedBlocks: self.expandedBlockIds,
context: component.context,
cache: component.context.animationCache,
renderer: component.context.animationRenderer,
@ -516,7 +515,7 @@ final class TextProcessingTextAreaComponent: Component {
transition: expandButtonTransition,
component: AnyComponent(PlainButtonComponent(
content: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: isExpanded.value ? "less" : "more", font: Font.regular(17.0), textColor: component.theme.list.itemAccentColor))
text: .plain(NSAttributedString(string: isExpanded.value ? component.strings.TextProcessing_TextCollapse : component.strings.TextProcessing_TextExpand, font: Font.regular(17.0), textColor: component.theme.list.itemAccentColor))
)),
effectAlignment: .right,
action: {
@ -558,7 +557,7 @@ final class TextProcessingTextAreaComponent: Component {
}
if component.copyAction != nil, let textLayout = self.textState.layout {
if textLayout.trailingLineWidth >= availableSize.width - sideInset - 32.0 || textLayout.trailingLineIsRTL {
if textLayout.trailingLineWidth >= availableSize.width - sideInset - 32.0 || textLayout.trailingLineIsRTL || textLayout.trailingLineIsBlock {
textContainerFrame.size.height += 28.0
}
}
@ -705,7 +704,7 @@ final class TextProcessingTextAreaComponent: Component {
textStroke: nil,
displayContentsUnderSpoilers: true,
customTruncationToken: nil,
expandedBlocks: Set(),
expandedBlocks: self.expandedBlockIds,
context: component.context,
cache: component.context.animationCache,
renderer: component.context.animationRenderer,

View file

@ -14,18 +14,39 @@ import TooltipComponent
private let languageRecognizer = NLLanguageRecognizer()
func localizedLanguageName(strings: PresentationStrings, language: String) -> String {
let toLang = language
let key = "Translation.Language.\(toLang)"
let translateTitle: String
if let string = strings.primaryComponent.dict[key] {
translateTitle = string
} else {
let languageLocale = Locale(identifier: language)
let toLanguage = languageLocale.localizedString(forLanguageCode: toLang)?.capitalized ?? ""
return toLanguage
enum LocalizedLanguageNameKind {
case neutral
case translateFrom
case translateTo
}
func localizedLanguageName(strings: PresentationStrings, language: String, kind: LocalizedLanguageNameKind) -> String {
var result: String?
switch kind {
case .neutral:
if let value = strings.primaryComponent.dict["Translation.Language.\(language)"] {
result = value
}
case .translateFrom:
if let value = strings.primaryComponent.dict["Translation.LanguageFrom.\(language)"] {
result = value
}
case .translateTo:
if let value = strings.primaryComponent.dict["Translation.LanguageTo.\(language)"] {
result = value
}
}
return translateTitle
if result == nil {
if let value = strings.primaryComponent.dict["Translation.Language.\(language)"] {
result = value
}
}
if result == nil {
let languageLocale = Locale(identifier: language)
result = languageLocale.localizedString(forLanguageCode: language)?.capitalized
}
return result ?? ""
}
final class TextProcessingTranslateContentComponent: Component {
@ -139,6 +160,8 @@ final class TextProcessingTranslateContentComponent: Component {
final class View: UIView {
private let sourceText = ComponentView<Empty>()
private let styleSelection = ComponentView<Empty>()
private let styleSelectionContainer: UIView
private let targetText = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
@ -152,6 +175,9 @@ final class TextProcessingTranslateContentComponent: Component {
override init(frame: CGRect) {
self.separatorLayer = SimpleLayer()
self.styleSelectionContainer = SparseContainerView()
self.styleSelectionContainer.clipsToBounds = true
self.styleSelectionContainer.layer.cornerRadius = 26.0
super.init(frame: frame)
@ -277,37 +303,34 @@ final class TextProcessingTranslateContentComponent: Component {
let bottomInset: CGFloat = 14.0
let blockSpacing: CGFloat = 30.0
let fromPrefix: String
let toPrefix: String
let fromFormat: String
let toFormat: String
var toTitle: String
switch component.mode {
case .translate:
fromPrefix = "From"
toPrefix = "To"
toTitle = localizedLanguageName(strings: component.strings, language: component.externalState.result?.language ?? "")
if component.externalState.style != .neutral {
toTitle.append(" (")
let styleName = localizedStyleName(strings: component.strings, styleId: component.externalState.style)
toTitle.append(styleName)
toTitle.append(")")
fromFormat = component.strings.TextProcessing_Translate_FromLanguage
toFormat = component.strings.TextProcessing_Translate_ToLanguage
toTitle = localizedLanguageName(strings: component.strings, language: component.externalState.result?.language ?? "", kind: .translateTo)
if case .style = component.externalState.style {
toTitle = component.strings.TextProcessing_Translate_LanguageStyle(toTitle, localizedStyleName(strings: component.strings, styleId: component.externalState.style)).string
}
case .stylize, .fix:
fromPrefix = "Original:"
fromFormat = component.strings.TextProcessing_OriginalBadge
if case .stylize = component.mode {
if component.externalState.style == .neutral {
toPrefix = "Original"
toFormat = component.strings.TextProcessing_OriginalStyleBadge
} else {
toPrefix = "Result"
toFormat = component.strings.TextProcessing_ResultBadge
}
} else {
toPrefix = "Result"
toFormat = component.strings.TextProcessing_ResultBadge
}
toTitle = ""
}
contentHeight += topInset
if case .stylize = component.mode {
let sourceTextSize = self.sourceText.update(
let styleSelectionSize = self.styleSelection.update(
transition: transition,
component: AnyComponent(TextProcessingStyleSelectionComponent(
context: component.context,
@ -335,15 +358,16 @@ final class TextProcessingTranslateContentComponent: Component {
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 46.0)
)
let sourceTextFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: sourceTextSize)
contentHeight += sourceTextSize.height
let styleSelectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: styleSelectionSize)
contentHeight += styleSelectionSize.height
if let sourceTextView = self.sourceText.view {
if sourceTextView.superview == nil {
self.sourceText.parentState = state
self.addSubview(sourceTextView)
if let styleSelectionView = self.styleSelection.view {
if styleSelectionView.superview == nil {
self.styleSelection.parentState = state
self.styleSelectionContainer.addSubview(styleSelectionView)
self.addSubview(self.styleSelectionContainer)
}
transition.setFrame(view: sourceTextView, frame: sourceTextFrame)
transition.setFrame(view: styleSelectionView, frame: styleSelectionFrame)
}
} else {
let sourceTextSize = self.sourceText.update(
@ -352,8 +376,8 @@ final class TextProcessingTranslateContentComponent: Component {
context: component.context,
theme: component.theme,
strings: component.strings,
titlePrefix: fromPrefix,
title: localizedLanguageName(strings: component.strings, language: component.externalState.sourceLanguage ?? ""),
titleFormat: fromFormat,
title: localizedLanguageName(strings: component.strings, language: component.externalState.sourceLanguage ?? "", kind: .translateFrom),
titleAction: nil,
isExpanded: (
component.externalState.isSourceTextExpanded,
@ -397,7 +421,7 @@ final class TextProcessingTranslateContentComponent: Component {
context: component.context,
theme: component.theme,
strings: component.strings,
titlePrefix: toPrefix,
titleFormat: toFormat,
title: toTitle,
titleAction: component.mode == .translate ? { [weak self] sourceView in
guard let self, let component = self.component, let result = component.externalState.result else {
@ -494,7 +518,7 @@ final class TextProcessingTranslateContentComponent: Component {
transition: tooltipTransition,
component: AnyComponent(TooltipComponent(
content: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Select Style", font: Font.regular(15.0), textColor: .white))
text: .plain(NSAttributedString(string: component.strings.TextProcessing_StyleTooltip, font: Font.regular(15.0), textColor: .white))
))
)),
environment: {},
@ -521,6 +545,8 @@ final class TextProcessingTranslateContentComponent: Component {
}
}
}
transition.setFrame(view: self.styleSelectionContainer, frame: CGRect(origin: CGPoint(), size: size))
return size
}

View file

@ -1611,10 +1611,9 @@ extension ChatControllerImpl {
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
var menuItems: [ContextMenuItem] = []
//TODO:localize
menuItems.append(.action(ContextMenuActionItem(
id: nil,
text: "Read All Poll Votes",
text: strongSelf.presentationData.strings.Conversation_ReadAllPollVotes,
textColor: .primary,
textLayout: .singleLine,
icon: { theme in

View file

@ -2341,9 +2341,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
edgeEffectAlpha = self.chatPresentationInterfaceState.chatWallpaper.singleColor != nil ? 0.85 : 0.75
}
var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
if self.historyNode.rotated {
if self.historyNode.rotated && !isOverlay {
if let current = self.bottomBackgroundEdgeEffectNode {
bottomBackgroundEdgeEffectNode = current
} else {
@ -2354,6 +2353,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.messageTransitionNode.view)
}
}
} else {
if let bottomBackgroundEdgeEffectNode = self.bottomBackgroundEdgeEffectNode {
self.bottomBackgroundEdgeEffectNode = nil
bottomBackgroundEdgeEffectNode.view.removeFromSuperview()
}
}
if let bottomBackgroundEdgeEffectNode {
var blurFrame = inputBackgroundFrame
@ -2406,7 +2410,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let containerNode = self.containerNode {
let containerNodeFrame = CGRect(origin: CGPoint(x: wrappingInsets.left, y: wrappingInsets.top), size: CGSize(width: contentBounds.size.width, height: contentBounds.size.height - containerInsets.bottom - inputPanelsHeight - 8.0))
var containerNodeFrame = CGRect(origin: CGPoint(x: wrappingInsets.left, y: wrappingInsets.top), size: CGSize(width: contentBounds.size.width, height: contentBounds.size.height - containerInsets.bottom - inputPanelsHeight - 8.0))
if isOverlay {
containerNodeFrame.size.height -= 8.0
}
transition.updateFrame(node: containerNode, frame: containerNodeFrame)
if let containerBackgroundNode = self.containerBackgroundNode {
@ -2544,7 +2551,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
})
var topBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
if self.historyNode.rotated {
if self.historyNode.rotated && !isOverlay {
if let current = self.topBackgroundEdgeEffectNode {
topBackgroundEdgeEffectNode = current
} else {
@ -2555,6 +2562,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.messageTransitionNode.view)
}
}
} else {
if let topBackgroundEdgeEffectNode = self.topBackgroundEdgeEffectNode {
self.topBackgroundEdgeEffectNode = nil
topBackgroundEdgeEffectNode.view.removeFromSuperview()
}
}
if let topBackgroundEdgeEffectNode {
let topExtent: CGFloat = 34.0

View file

@ -57,8 +57,7 @@ private enum ChatReportPeerTitleButton: Equatable {
case .restartTopic:
return strings.Chat_PanelRestartTopic
case .setPhoto:
//TODO:localize
return "Set Profile Photo"
return strings.Chat_PanelSetBotPhoto
}
}
}

View file

@ -1938,11 +1938,29 @@ func openResolvedUrlImpl(
}
}
})
case let .createBot(parentBot, username, title):
case let .createBot(parentBotId, username, title):
Task { @MainActor in
guard let parentBot = await context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: parentBotId)
).get() else {
return
}
guard case let .user(user) = parentBot, let botInfo = user.botInfo, botInfo.flags.contains(.canManageBots) else {
let alertController = textAlertController(
context: context,
title: nil,
text: presentationData.strings.Bot_AlertCanNotCreateBots(parentBot.debugDisplayTitle).string,
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})
]
)
present(alertController, nil)
return
}
guard let controller = await CreateBotScreen(
context: context,
parentBot: parentBot,
parentBot: parentBot.id,
initialUsername: username,
initialTitle: title,
openAutomatically: true,