From 11a4dbf0a0a285e88f53e6da635633e85ee71904 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 2 Jun 2026 13:31:23 +0200 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 13 +++++ .../Sources/AttachmentPanel.swift | 6 +- .../BotCheckoutPaymentMethodScreen.swift | 25 ++++---- .../BotCheckoutShippingOptionScreen.swift | 25 ++++---- .../Sources/ChatListController.swift | 53 +---------------- .../MtProtoKit/Sources/MTApiEnvironment.m | 8 +++ .../Sources/ComposePollScreen.swift | 38 ++++++++++++ .../Sources/PollAttachmentScreen.swift | 58 ++++++++++--------- .../Sources/OpenUserGeneratedUrl.swift | 5 +- .../ChatControllerOpenLinkContextMenu.swift | 3 +- 10 files changed, 120 insertions(+), 114 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a626678a02..43e6681e57 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -16342,3 +16342,16 @@ Error: %8$@"; "MediaPicker.SetNewGroupPhoto" = "Set new group photo"; "MediaPicker.SetNewChannelPhoto" = "Set new channel photo"; + +"Attachment.Link" = "Link"; + +"Checkout.PaymentMethod.Proceed" = "Proceed"; +"Checkout.ShippingMethod.Proceed" = "Proceed"; + +"OpenLinkConfirmation.Title" = "Open link?"; +"OpenLinkConfirmation.Open" = "Open"; + +"Chat.ContextMenu.OpenInBrowser" = "Open in Browser"; +"Chat.ContextMenu.OpenInApp" = "Open In-App"; + +"CreatePoll.Link.Description" = "Attach a link to this option."; diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index f60228bde9..340a97f14a 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -244,8 +244,7 @@ private final class AttachButtonComponent: CombinedComponent { name = strings.Attachment_Audio imageName = "Chat/Attach Menu/Audio" case .link: - //TODO:localize - name = "Link" + name = strings.Attachment_Link imageName = "Chat/Attach Menu/Link" case let .app(bot): botPeer = bot.peer @@ -2187,8 +2186,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate, ASGestureRecog case .audio: accessibilityTitle = self.presentationData.strings.Attachment_Audio case .link: - //TODO:localize - accessibilityTitle = "Link" + accessibilityTitle = self.presentationData.strings.Attachment_Link case let .app(bot): accessibilityTitle = bot.shortName case .standalone: diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift index 1e85b5f263..e9aff0ba7c 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutPaymentMethodScreen.swift @@ -328,9 +328,9 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { self.component = component self.state = state - let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value - let controller = environmentValue.controller - let theme = environmentValue.theme.withModalBlocksBackground() + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + let theme = environment.theme.withModalBlocksBackground() let dismiss: (Bool, (() -> Void)?) -> Void = { [weak self] animated, completion in guard let self, !self.isDismissing else { @@ -379,7 +379,7 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { )), titleItem: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environmentValue.strings.Checkout_PaymentMethod, + string: environment.strings.Checkout_PaymentMethod, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor )), @@ -402,7 +402,6 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { } ) ), - //TODO:localize bottomItem: AnyComponent(ButtonComponent( background: ButtonComponent.Background( style: .glass, @@ -413,7 +412,7 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { content: AnyComponentWithIdentity( id: AnyHashable("proceed"), component: AnyComponent(ButtonTextContentComponent( - text: "Proceed", + text: environment.strings.Checkout_PaymentMethod_Proceed, badge: 0, textColor: theme.list.itemCheckColors.foregroundColor, badgeBackground: theme.list.itemCheckColors.foregroundColor, @@ -440,16 +439,16 @@ private final class BotCheckoutPaymentMethodScreenComponent: Component { animateOut: self.animateOut )), environment: { - environmentValue + environment ResizableSheetComponentEnvironment( theme: theme, - statusBarHeight: environmentValue.statusBarHeight, - safeInsets: environmentValue.safeInsets, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, inputHeight: 0.0, - metrics: environmentValue.metrics, - deviceMetrics: environmentValue.deviceMetrics, - isDisplaying: environmentValue.isVisible, - isCentered: environmentValue.metrics.widthClass == .regular, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, screenSize: availableSize, regularMetricsSize: nil, dismiss: { animated in diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift index aa848c41fc..f86a1496e5 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutShippingOptionScreen.swift @@ -225,9 +225,9 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { self.component = component self.state = state - let environmentValue = environment[ViewControllerComponentContainer.Environment.self].value - let controller = environmentValue.controller - let theme = environmentValue.theme.withModalBlocksBackground() + let environment = environment[ViewControllerComponentContainer.Environment.self].value + let controller = environment.controller + let theme = environment.theme.withModalBlocksBackground() let dismiss: (Bool) -> Void = { [weak self] animated in guard let self, !self.isDismissing else { @@ -268,7 +268,7 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { )), titleItem: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environmentValue.strings.Checkout_ShippingMethod, + string: environment.strings.Checkout_ShippingMethod, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor )), @@ -291,7 +291,6 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { } ) ), - //TODO:localize bottomItem: AnyComponent(ButtonComponent( background: ButtonComponent.Background( style: .glass, @@ -302,7 +301,7 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { content: AnyComponentWithIdentity( id: AnyHashable("proceed"), component: AnyComponent(ButtonTextContentComponent( - text: "Proceed", + text: environment.strings.Checkout_ShippingMethod_Proceed, badge: 0, textColor: theme.list.itemCheckColors.foregroundColor, badgeBackground: theme.list.itemCheckColors.foregroundColor, @@ -323,16 +322,16 @@ private final class BotCheckoutShippingOptionScreenComponent: Component { animateOut: self.animateOut )), environment: { - environmentValue + environment ResizableSheetComponentEnvironment( theme: theme, - statusBarHeight: environmentValue.statusBarHeight, - safeInsets: environmentValue.safeInsets, + statusBarHeight: environment.statusBarHeight, + safeInsets: environment.safeInsets, inputHeight: 0.0, - metrics: environmentValue.metrics, - deviceMetrics: environmentValue.deviceMetrics, - isDisplaying: environmentValue.isVisible, - isCentered: environmentValue.metrics.widthClass == .regular, + metrics: environment.metrics, + deviceMetrics: environment.deviceMetrics, + isDisplaying: environment.isVisible, + isCentered: environment.metrics.widthClass == .regular, screenSize: availableSize, regularMetricsSize: nil, dismiss: { animated in diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 79b806e43d..d02ebd71aa 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -5900,8 +5900,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } actions.append(.init(title: self.presentationData.strings.Common_Cancel)) - //TODO:localize - let title: String = "Delete Chat" + let title: String = self.presentationData.strings.ChatList_DeleteChat var text: String if mainPeer.id == self.context.account.peerId { text = self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmation @@ -5948,56 +5947,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ], actions: actions ) - - -// let actionSheet = ActionSheetController(presentationData: self.presentationData) -// var items: [ActionSheetItem] = [] -// -// items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: mainPeer, chatPeer: chatPeer, action: .delete, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder)) -// -// if joined || mainPeer.isDeleted { -// items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in -// actionSheet?.dismissAnimated() -// self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { -// removed() -// }) -// completion(true) -// })) -// } else { -// items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForCurrentUser, color: .destructive, action: { [weak self, weak actionSheet] in -// actionSheet?.dismissAnimated() -// self?.schedulePeerChatRemoval(peer: peer, type: .forLocalPeer, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { -// removed() -// }) -// completion(true) -// })) -// items.append(ActionSheetButtonItem(title: self.presentationData.strings.ChatList_DeleteForEveryone(mainPeer.compactDisplayTitle).string, color: .destructive, action: { [weak self, weak actionSheet] in -// actionSheet?.dismissAnimated() -// guard let strongSelf = self else { -// return -// } -// strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationText, actions: [ -// TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { -// completion(false) -// }), -// TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteForEveryoneConfirmationAction, action: { -// self?.schedulePeerChatRemoval(peer: peer, type: .forEveryone, deleteGloballyIfPossible: deleteGloballyIfPossible, completion: { -// removed() -// }) -// completion(true) -// }) -// ], parseMarkdown: true), in: .window(.root)) -// })) -// } -// actionSheet.setItemGroups([ -// ActionSheetItemGroup(items: items), -// ActionSheetItemGroup(items: [ -// ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in -// actionSheet?.dismissAnimated() -// completion(false) -// }) -// ]) -// ]) self.present(alertScreen, in: .window(.root)) } else if peer.peerId == self.context.account.peerId { self.present(textAlertController(context: self.context, title: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: self.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ diff --git a/submodules/MtProtoKit/Sources/MTApiEnvironment.m b/submodules/MtProtoKit/Sources/MTApiEnvironment.m index 92b6ab69cf..36da88228b 100644 --- a/submodules/MtProtoKit/Sources/MTApiEnvironment.m +++ b/submodules/MtProtoKit/Sources/MTApiEnvironment.m @@ -737,6 +737,14 @@ NSString *suffix = @""; [platform isEqualToString:@"iPad16,6"]) return @"iPad Pro 12.9 inch (7th gen)"; + if ([platform isEqualToString:@"iPad16,8"] || + [platform isEqualToString:@"iPad16,9"]) + return @"iPad Air 11 inch (8th gen)"; + + if ([platform isEqualToString:@"iPad16,10"] || + [platform isEqualToString:@"iPad16,11"]) + return @"iPad Air 13 inch (8th gen)"; + if ([platform hasPrefix:@"iPhone"]) return @"Unknown iPhone"; if ([platform hasPrefix:@"iPod"]) diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift index 8253c1768c..7753274151 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/ComposePollScreen.swift @@ -37,6 +37,7 @@ import EdgeEffect import LocationUI import CountrySelectionUI import ShareWithPeersScreen +import ChatTextLinkEditUI public final class ComposedPoll { public struct Text { @@ -918,6 +919,11 @@ final class ComposePollScreenComponent: Component { self.deactivateInput() + if !replace, case .pollOption = subject, let webpage = self.attachedMedia(for: subject)?.media.media as? TelegramMediaWebpage, let link = webpage.content.url { + self.openAttachedLinkMedia(subject: subject, link: link) + return + } + guard replace || !self.openAttachMediaContextMenu(subject: subject) else { return } @@ -967,6 +973,38 @@ final class ComposePollScreenComponent: Component { }) } + private func openAttachedLinkMedia(subject: MediaAttachSubject, link: String) { + guard let component = self.component else { + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let controller = chatTextLinkEditController( + context: component.context, + updatedPresentationData: (presentationData, .never()), + text: presentationData.strings.CreatePoll_Link_Description, + link: link, + preview: true, + apply: { [weak self] link, webpage in + guard let self, let link else { + return + } + + if link.isEmpty { + self.setAttachedMedia(nil, for: subject) + self.state?.updated(transition: .easeInOut(duration: 0.2)) + return + } + + let attachedMedia = AttachedMedia(media: .standalone(media: webpage ?? makePollAttachmentLinkWebpage(link: link))) + self.setAttachedMedia(attachedMedia, for: subject) + self.uploadAttachedMediaIfNeeded(attachedMedia) + self.state?.updated(transition: .easeInOut(duration: 0.2)) + } + ) + (self.environment?.controller() as? ComposePollScreen)?.parentController()?.present(controller, in: .window(.root)) + } + private func uploadAttachedMediaIfNeeded(_ media: AttachedMedia) { guard let component = self.component, media.requiresUpload, media.uploadDisposable == nil else { return diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift index 9ad77ecf2c..3945de4fc1 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift @@ -23,6 +23,34 @@ public enum PollAttachmentSubject { case option } +func makePollAttachmentLinkWebpage(link: String) -> TelegramMediaWebpage { + let mediaId = Int64.random(in: Int64.min ... Int64.max) + return TelegramMediaWebpage( + webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: mediaId), + content: .Loaded(TelegramMediaWebpageLoadedContent( + url: link, + displayUrl: link, + hash: 0, + type: nil, + websiteName: nil, + title: nil, + text: nil, + embedUrl: nil, + embedType: nil, + embedSize: nil, + duration: nil, + author: nil, + isMediaLargeByDefault: nil, + imageIsVideoCover: false, + image: nil, + file: nil, + story: nil, + attributes: [], + instantPage: nil + )) + ) +} + public func presentPollAttachmentScreen( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, @@ -236,11 +264,11 @@ public func presentPollAttachmentScreen( case .link: attachmentController?.dismiss(animated: true) - //TODO:localize + let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } let controller = chatTextLinkEditController( context: context, updatedPresentationData: updatedPresentationData, - text: "Attach a link to this option.", + text: presentationData.strings.CreatePoll_Link_Description, link: nil, preview: true, apply: { link, webpage in @@ -251,31 +279,7 @@ public func presentPollAttachmentScreen( completion(.standalone(media: webpage)) return } - let mediaId = Int64.random(in: Int64.min ... Int64.max) - let webPage = TelegramMediaWebpage( - webpageId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: mediaId), - content: .Loaded(TelegramMediaWebpageLoadedContent( - url: link, - displayUrl: link, - hash: 0, - type: nil, - websiteName: nil, - title: nil, - text: nil, - embedUrl: nil, - embedType: nil, - embedSize: nil, - duration: nil, - author: nil, - isMediaLargeByDefault: nil, - imageIsVideoCover: false, - image: nil, - file: nil, - story: nil, - attributes: [], - instantPage: nil - ))) - completion(.standalone(media: webPage)) + completion(.standalone(media: makePollAttachmentLinkWebpage(link: link))) } ) present(controller, false) diff --git a/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift index d17edb70c6..51d69bcbb9 100644 --- a/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift +++ b/submodules/TelegramUI/Components/OpenUserGeneratedUrl/Sources/OpenUserGeneratedUrl.swift @@ -125,9 +125,8 @@ public func openUserGeneratedUrl( let disposable = MetaDisposable() let checkState = concealedAlertOption.map { _ in AlertCheckComponent.ExternalState() } - //TODO:localize var content: [AnyComponentWithIdentity] = [] - content.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(AlertTitleComponent(title: "Open link?")))) + content.append(AnyComponentWithIdentity(id: "title", component: AnyComponent(AlertTitleComponent(title: presentationData.strings.OpenLinkConfirmation_Title)))) content.append(AnyComponentWithIdentity(id: "text", component: AnyComponent(AlertTextComponent( content: .plain(displayUrl), alignment: .center, @@ -158,7 +157,7 @@ public func openUserGeneratedUrl( content: content, actions: [ .init(title: presentationData.strings.Common_Cancel), - .init(title: "Open", type: .default, action: { + .init(title: presentationData.strings.OpenLinkConfirmation_Open, type: .default, action: { if checkState?.value == true { concealedAlertOption?.action() } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift index 532a7b7719..63ae79c6ea 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenLinkContextMenu.swift @@ -250,8 +250,7 @@ extension ChatControllerImpl { })) if let openMode { - //TODO:localize - let reverseText = openMode.shouldOpenInApp ? "Open in Browser" : "Open In-App" + let reverseText = openMode.shouldOpenInApp ? self.presentationData.strings.Chat_ContextMenu_OpenInBrowser : self.presentationData.strings.Chat_ContextMenu_OpenInApp items.append(ActionSheetButtonItem(title: reverseText, color: .accent, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() guard let self else {