mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
d0fce97c93
20 changed files with 304 additions and 190 deletions
|
|
@ -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.";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue