diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index e3cf59f815..1f3ab2992f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14932,3 +14932,44 @@ Sorry for the inconvenience."; "Notification.ChangedThemeGift" = "%1$@ changed chat theme to %2$@"; "Notification.YouChangedThemeGift" = "You changed chat theme to %@"; + +"Conversation.Theme.GiftTransfer.Text" = "This gift is already your theme in the chat with **%@**. Remove it there and use it here instead?"; +"Conversation.Theme.GiftTransfer.Proceed" = "Yes"; + +"PeerInfo.Tabs.SetMainTab" = "Set as Main Tab"; +"PeerInfo.Tabs.SetMainTab.Succeed" = "Tab order changed."; + +"MediaPlayer.SavedMusic.AddToProfile" = "Add to Profile"; +"MediaPlayer.SavedMusic.RemoveFromProfile" = "This audio is visible on your profile. [Remove >]()"; + +"MediaPlayer.SavedMusic.AddedToProfile.View" = "View"; +"MediaPlayer.SavedMusic.AddedToProfile" = "Audio added to your profile."; +"MediaPlayer.SavedMusic.RemovedFromProfile" = "Audio removed from your profile."; + +"MediaPlayer.ContextMenu.SaveToFiles" = "Save to Files"; +"MediaPlayer.ContextMenu.SaveTo" = "Save to..."; +"MediaPlayer.ContextMenu.SaveTo.Profile" = "Profile"; +"MediaPlayer.ContextMenu.SaveTo.SavedMessages" = "Saved Messages"; +"MediaPlayer.ContextMenu.SaveTo.Files" = "Files"; +"MediaPlayer.ContextMenu.SaveTo.Info" = "Save to Files"; +"MediaPlayer.ContextMenu.ShowInChat" = "Show in Chat"; +"MediaPlayer.ContextMenu.Forward" = "Forward"; +"MediaPlayer.ContextMenu.Delete" = "Delete"; +"MediaPlayer.ContextMenu.Remove" = "Remove"; + +"MediaPlayer.Playlist.ThisChat" = "AUDIO IN THIS CHAT"; +"MediaPlayer.Playlist.SavedMusic" = "%@'S PLAYLIST"; +"MediaPlayer.Playlist.SavedMusicYou" = "YOUR PLAYLIST"; + +"Notification.PremiumGift.Stars_1" = "%@ Star"; +"Notification.PremiumGift.Stars_any" = "%@ Stars"; + +"Ton.ProceedsOverview" = "PROCEEDS OVERVIEW"; +"Ton.AvailableBalance" = "Balance Available to Withdraw"; +"Ton.LifetimeProceeds" = "Total Lifetime Proceeds"; + +"Ton.WithdrawViaFragment" = "Withdraw via Fragment"; +"Ton.WithdrawViaFragment.Info" = "Collect your TON using Fragment. [Learn More >]()"; +"Ton.WithdrawViaFragment.Info_URL" = "https://telegram.org/tos/bot-developers#6-2-2-tpa-balance"; + +"MESSAGE_GIFT_THEME" = "%1$@ changed theme to %2$@"; diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index da4087c1c6..8b77355546 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -809,6 +809,27 @@ public enum StarGift: Equatable, Codable, PostboxCoding { themePeerId: self.themePeerId ) } + + public func withThemePeerId(_ themePeerId: EnginePeer.Id?) -> UniqueGift { + return UniqueGift( + id: self.id, + giftId: self.giftId, + title: self.title, + number: self.number, + slug: self.slug, + owner: self.owner, + attributes: self.attributes, + availability: self.availability, + giftAddress: self.giftAddress, + resellAmounts: self.resellAmounts, + resellForTonOnly: self.resellForTonOnly, + releasedBy: self.releasedBy, + valueAmount: self.valueAmount, + valueCurrency: self.valueCurrency, + flags: self.flags, + themePeerId: themePeerId + ) + } } public enum DecodingError: Error { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index cb71197c95..d880c1b20a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -148,6 +148,20 @@ public enum ChatTheme: PostboxCoding, Codable, Equatable { } } } + + public func withThemePeerId(_ themePeerId: EnginePeer.Id?) -> ChatTheme { + switch self { + case .emoticon: + return self + case let .gift(gift, themeSettings): + switch gift { + case let .unique(uniqueGift): + return .gift(.unique(uniqueGift.withThemePeerId(themePeerId)), themeSettings) + case .generic: + return self + } + } + } } extension ChatTheme { @@ -235,6 +249,22 @@ func _internal_setChatTheme(account: Account, peerId: PeerId, chatTheme: ChatThe return .complete() } return account.postbox.transaction { transaction -> Signal in + var chatTheme = chatTheme + if case let .gift(gift, _) = chatTheme, case let .unique(uniqueGift) = gift, let previousThemePeerId = uniqueGift.themePeerId { + transaction.updatePeerCachedData(peerIds: Set([previousThemePeerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedChatTheme(nil) + } else if let current = current as? CachedGroupData { + return current.withUpdatedChatTheme(nil) + } else if let current = current as? CachedChannelData { + return current.withUpdatedChatTheme(nil) + } else { + return current + } + }) + } + chatTheme = chatTheme?.withThemePeerId(peerId) + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in if let current = current as? CachedUserData { return current.withUpdatedChatTheme(chatTheme) @@ -246,6 +276,7 @@ func _internal_setChatTheme(account: Account, peerId: PeerId, chatTheme: ChatThe return current } }) + let inputTheme: Api.InputChatTheme if let chatTheme { inputTheme = chatTheme.apiChatTheme @@ -466,7 +497,7 @@ public final class UniqueGiftChatThemesContext { private let cacheDisposable = MetaDisposable() private var themes: [ChatTheme] = [] - private var nextOffset: Int32? + private var nextOffset: Int32 = 0 private var dataState: UniqueGiftChatThemesContext.State.DataState = .ready(canLoadMore: true) private let stateValue = Promise() @@ -487,7 +518,7 @@ public final class UniqueGiftChatThemesContext { public func reload() { self.themes = [] - self.nextOffset = nil + self.nextOffset = 0 self.dataState = .ready(canLoadMore: true) self.loadMore(reload: true) } @@ -521,7 +552,7 @@ public final class UniqueGiftChatThemesContext { self.pushState() } - let signal = network.request(Api.functions.account.getUniqueGiftChatThemes(offset: offset ?? 0, limit: 32, hash: 0)) + let signal = network.request(Api.functions.account.getUniqueGiftChatThemes(offset: offset, limit: 32, hash: 0)) |> map(Optional.init) |> `catch` { error in return .single(nil) @@ -557,7 +588,9 @@ public final class UniqueGiftChatThemesContext { } else { self.themes.append(contentsOf: themes) } - + if let nextOffset { + self.nextOffset = nextOffset + } self.dataState = .ready(canLoadMore: nextOffset != nil) self.pushState() })) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 2e3f36e6dc..ae1f5e25ca 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -830,8 +830,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { let price: String if currency == "XTR" { - //TODO:localize - price = "\(amount) Stars" + price = strings.Notification_PremiumGift_Stars(Int32(clamping: amount)) } else { price = formatCurrencyAmount(amount, currency: currency) } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 2f5be92e7b..3003487c92 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -485,6 +485,7 @@ swift_library( "//submodules/TelegramUI/Components/Stars/BalanceNeededScreen", "//submodules/TelegramUI/Components/FaceScanScreen", "//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist", + "//submodules/TelegramUI/Components/ChatThemeScreen", "//submodules/ContactsHelper", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/BUILD b/submodules/TelegramUI/Components/ChatThemeScreen/BUILD new file mode 100644 index 0000000000..783bf6048c --- /dev/null +++ b/submodules/TelegramUI/Components/ChatThemeScreen/BUILD @@ -0,0 +1,44 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatThemeScreen", + module_name = "ChatThemeScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/PresentationDataUtils", + "//submodules/TelegramNotices", + "//submodules/AnimationUI", + "//submodules/MergeLists", + "//submodules/MediaResources", + "//submodules/StickerResources", + "//submodules/WallpaperResources", + "//submodules/TooltipUI", + "//submodules/SolidRoundedButtonNode", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/ShimmerEffect", + "//submodules/AttachmentUI", + "//submodules/AvatarNode", + "//submodules/Markdown", + "//submodules/AppBundle", + "//submodules/ActivityIndicator", + "//submodules/TelegramUI/Components/Gifts/GiftItemComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift similarity index 98% rename from submodules/TelegramUI/Sources/ChatThemeScreen.swift rename to submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift index 7f7107b815..517c896dc8 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/ChatThemeScreen.swift @@ -21,12 +21,14 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import ShimmerEffect import AttachmentUI +import AvatarNode private struct ThemeSettingsThemeEntry: Comparable, Identifiable { let index: Int let chatTheme: ChatTheme? let emojiFile: TelegramMediaFile? let themeReference: PresentationThemeReference? + let peer: EnginePeer? let nightMode: Bool var selected: Bool let theme: PresentationTheme @@ -47,6 +49,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable { if lhs.themeReference?.index != rhs.themeReference?.index { return false } + if lhs.peer != rhs.peer { + return false + } if lhs.nightMode != rhs.nightMode { return false } @@ -70,16 +75,16 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable { } func item(context: AccountContext, action: @escaping (ChatTheme?) -> Void) -> ListViewItem { - return ThemeSettingsThemeIconItem(context: context, chatTheme: self.chatTheme, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action) + return ThemeSettingsThemeIconItem(context: context, chatTheme: self.chatTheme, emojiFile: self.emojiFile, themeReference: self.themeReference, peer: self.peer, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action) } } - private class ThemeSettingsThemeIconItem: ListViewItem { let context: AccountContext let chatTheme: ChatTheme? let emojiFile: TelegramMediaFile? let themeReference: PresentationThemeReference? + let peer: EnginePeer? let nightMode: Bool let selected: Bool let theme: PresentationTheme @@ -87,11 +92,24 @@ private class ThemeSettingsThemeIconItem: ListViewItem { let wallpaper: TelegramWallpaper? let action: (ChatTheme?) -> Void - public init(context: AccountContext, chatTheme: ChatTheme?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, nightMode: Bool, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (ChatTheme?) -> Void) { + public init( + context: AccountContext, + chatTheme: ChatTheme?, + emojiFile: TelegramMediaFile?, + themeReference: PresentationThemeReference?, + peer: EnginePeer?, + nightMode: Bool, + selected: Bool, + theme: PresentationTheme, + strings: PresentationStrings, + wallpaper: TelegramWallpaper?, + action: @escaping (ChatTheme?) -> Void + ) { self.context = context self.chatTheme = chatTheme self.emojiFile = emojiFile self.themeReference = themeReference + self.peer = peer self.nightMode = nightMode self.selected = selected self.theme = theme @@ -525,9 +543,9 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode { } } -final class ChatThemeScreen: ViewController { - static let themeCrossfadeDuration: Double = 0.3 - static let themeCrossfadeDelay: Double = 0.25 +public final class ChatThemeScreen: ViewController { + public static let themeCrossfadeDuration: Double = 0.3 + public static let themeCrossfadeDelay: Double = 0.25 private var controllerNode: ChatThemeScreenNode { return self.displayNode as! ChatThemeScreenNode @@ -539,7 +557,7 @@ final class ChatThemeScreen: ViewController { private let animatedEmojiStickers: [String: [StickerPackItem]] private let initiallySelectedTheme: ChatTheme? private let peerName: String - let canResetWallpaper: Bool + fileprivate let canResetWallpaper: Bool private let previewTheme: (ChatTheme?, Bool?) -> Void fileprivate let changeWallpaper: () -> Void fileprivate let resetWallpaper: () -> Void @@ -548,9 +566,9 @@ final class ChatThemeScreen: ViewController { private var presentationData: PresentationData private var presentationDataDisposable: Disposable? - var dismissed: (() -> Void)? + public var dismissed: (() -> Void)? - var passthroughHitTestImpl: ((CGPoint) -> UIView?)? { + public var passthroughHitTestImpl: ((CGPoint) -> UIView?)? { didSet { if self.isNodeLoaded { self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl @@ -558,7 +576,7 @@ final class ChatThemeScreen: ViewController { } } - init( + public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal), animatedEmojiStickers: [String: [StickerPackItem]], @@ -657,7 +675,7 @@ final class ChatThemeScreen: ViewController { } } - override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { self.forEachController({ controller in if let controller = controller as? TooltipScreen { controller.dismiss() @@ -683,7 +701,7 @@ final class ChatThemeScreen: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } - func dimTapped() { + public func dimTapped() { self.controllerNode.dimTapped() } } @@ -908,28 +926,13 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega chatTheme: nil, emojiFile: nil, themeReference: nil, + peer: nil, nightMode: false, selected: selectedTheme == nil, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil )) - for theme in themes { - guard let emoticon = theme.emoticon else { - continue - } - entries.append(ThemeSettingsThemeEntry( - index: entries.count, - chatTheme: .emoticon(emoticon), - emojiFile: animatedEmojiStickers[emoticon]?.first?.file._parse(), - themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), - nightMode: isDarkAppearance, - selected: selectedTheme?.id == ChatTheme.emoticon(emoticon).id, - theme: presentationData.theme, - strings: presentationData.strings, - wallpaper: nil - )) - } for theme in uniqueGiftChatThemesState.themes { guard case let .gift(gift, themeSettings) = theme else { continue @@ -954,6 +957,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega chatTheme: theme, emojiFile: emojiFile, themeReference: .builtin(.dayClassic), + peer: nil, nightMode: isDarkAppearance, selected: selectedTheme?.id == theme.id, theme: presentationData.theme, @@ -961,6 +965,24 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega wallpaper: wallpaper )) } + for theme in themes { + guard let emoticon = theme.emoticon else { + continue + } + entries.append(ThemeSettingsThemeEntry( + index: entries.count, + chatTheme: .emoticon(emoticon), + emojiFile: animatedEmojiStickers[emoticon]?.first?.file._parse(), + themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), + peer: nil, + nightMode: isDarkAppearance, + selected: selectedTheme?.id == ChatTheme.emoticon(emoticon).id, + theme: presentationData.theme, + strings: presentationData.strings, + wallpaper: nil + )) + } + let action: (ChatTheme?) -> Void = { [weak self] chatTheme in if let self, self.selectedTheme != chatTheme { diff --git a/submodules/TelegramUI/Components/ChatThemeScreen/Sources/GiftThemeTransferAlertController.swift b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/GiftThemeTransferAlertController.swift new file mode 100644 index 0000000000..a662a003d1 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatThemeScreen/Sources/GiftThemeTransferAlertController.swift @@ -0,0 +1,296 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import ComponentFlow +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import AppBundle +import AvatarNode +import Markdown +import GiftItemComponent +import ActivityIndicator + +private final class GiftThemeTransferAlertContentNode: AlertContentNode { + private let context: AccountContext + private let strings: PresentationStrings + private var presentationTheme: PresentationTheme + private let title: String + private let text: String + private let gift: StarGift.UniqueGift + + private let titleNode: ASTextNode + private let giftView = ComponentView() + private let textNode: ASTextNode + private let arrowNode: ASImageNode + private let avatarNode: AvatarNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private var activityIndicator: ActivityIndicator? + + private var validLayout: CGSize? + + var inProgress = false { + didSet { + if let size = self.validLayout { + let _ = self.updateLayout(size: size, transition: .immediate) + } + } + } + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init( + context: AccountContext, + theme: AlertControllerTheme, + ptheme: PresentationTheme, + strings: PresentationStrings, + gift: StarGift.UniqueGift, + peer: EnginePeer, + title: String, + text: String, + actions: [TextAlertAction] + ) { + self.context = context + self.strings = strings + self.presentationTheme = ptheme + self.title = title + self.text = text + self.gift = gift + + self.titleNode = ASTextNode() + self.titleNode.maximumNumberOfLines = 0 + + self.textNode = ASTextNode() + self.textNode.maximumNumberOfLines = 0 + + self.arrowNode = ASImageNode() + self.arrowNode.displaysAsynchronously = false + self.arrowNode.displayWithoutProcessing = true + + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + self.addSubnode(self.arrowNode) + self.addSubnode(self.avatarNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.updateTheme(theme) + + self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer) + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor) + self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor), + linkAttribute: { url in + return ("URL", url) + } + ), textAlignment: .center) + self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + + let avatarSize = CGSize(width: 60.0, height: 60.0) + self.avatarNode.updateSize(size: avatarSize) + + let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize) + + let _ = self.giftView.update( + transition: .immediate, + component: AnyComponent( + GiftItemComponent( + context: self.context, + theme: self.presentationTheme, + strings: self.strings, + peer: nil, + subject: .uniqueGift(gift: self.gift, price: nil), + mode: .thumbnail + ) + ), + environment: {}, + containerSize: avatarSize + ) + if let view = self.giftView.view { + if view.superview == nil { + self.view.addSubview(view) + } + view.frame = giftFrame + } + + if let arrowImage = self.arrowNode.image { + let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size) + transition.updateFrame(node: self.arrowNode, frame: arrowFrame) + } + + let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize) + transition.updateFrame(node: self.avatarNode, frame: avatarFrame) + + origin.y += avatarSize.height + 17.0 + + let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) + origin.y += titleSize.height + 5.0 + + let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) + origin.y += textSize.height + 10.0 + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + let contentWidth = max(size.width, minActionsWidth) + + let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + + let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 24.0 + insets.top + insets.bottom) + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + do { + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + do { + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + do { + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + if self.inProgress { + let activityIndicator: ActivityIndicator + if let current = self.activityIndicator { + activityIndicator = current + } else { + activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false)) + self.addSubnode(activityIndicator) + } + + if let actionNode = self.actionNodes.first { + actionNode.isHidden = true + + let indicatorSize = CGSize(width: 22.0, height: 22.0) + transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize)) + } + } + + return resultSize + } +} + +public func giftThemeTransferAlertController( + context: AccountContext, + gift: StarGift.UniqueGift, + previousPeer: EnginePeer, + commit: @escaping () -> Void +) -> AlertController { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let strings = presentationData.strings + + var contentNode: GiftThemeTransferAlertContentNode? + var dismissImpl: ((Bool) -> Void)? + let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: presentationData.strings.Conversation_Theme_GiftTransfer_Proceed, action: { [weak contentNode] in + contentNode?.inProgress = true + commit() + }), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + })] + + let text = strings.Conversation_Theme_GiftTransfer_Text(previousPeer.compactDisplayTitle).string + contentNode = GiftThemeTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: previousPeer, title: "", text: text, actions: actions) + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) + dismissImpl = { [weak controller] animated in + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 4a7afc830c..47d81e1bcd 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -1163,9 +1163,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat return } - //TODO:localize var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: "Set as Main Tab", icon: { theme in + items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Tabs_SetMainTab, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in guard let self else { @@ -1183,7 +1182,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat guard let self else { return } - let controller = UndoOverlayController(presentationData: params.presentationData, content: .actionSucceeded(title: nil, text: "Tab order changed.", cancel: nil, destructive: false), action: { _ in return true }) + let controller = UndoOverlayController(presentationData: params.presentationData, content: .actionSucceeded(title: nil, text: params.presentationData.strings.PeerInfo_Tabs_SetMainTab_Succeed, cancel: nil, destructive: false), action: { _ in return true }) self.parentController?.present(controller, in: .current) }) } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 6f928dde21..fa0006025b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -680,7 +680,7 @@ final class StarsTransactionsScreenComponent: Component { theme: environment.theme, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Proceeds Overview".uppercased(), + string: environment.strings.Ton_ProceedsOverview.uppercased(), font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -691,14 +691,14 @@ final class StarsTransactionsScreenComponent: Component { AnyComponentWithIdentity(id: 0, component: AnyComponent(StarsOverviewItemComponent( theme: environment.theme, dateTimeFormat: environment.dateTimeFormat, - title: "Balance Available to Withdraw", + title: environment.strings.Ton_AvailableBalance, value: self.revenueState?.balances.availableBalance ?? CurrencyAmount(amount: .zero, currency: .ton), rate: self.revenueState?.usdRate ?? 0.0 ))), AnyComponentWithIdentity(id: 1, component: AnyComponent(StarsOverviewItemComponent( theme: environment.theme, dateTimeFormat: environment.dateTimeFormat, - title: "Total Lifetime Proceeds", + title: environment.strings.Ton_LifetimeProceeds, value: self.revenueState?.balances.overallRevenue ?? CurrencyAmount(amount: .zero, currency: .ton), rate: self.revenueState?.usdRate ?? 0.0 ))) @@ -725,7 +725,7 @@ final class StarsTransactionsScreenComponent: Component { return (TelegramTextAttributes.URL, contents) }) - let balanceInfoRawString = "Collect your TON using Fragment. [Learn More >]()" + let balanceInfoRawString = environment.strings.Ton_WithdrawViaFragment_Info let balanceInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(balanceInfoRawString, attributes: termsMarkdownAttributes, textAlignment: .natural)) if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme { self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme) @@ -754,7 +754,7 @@ final class StarsTransactionsScreenComponent: Component { }, tapAction: { [weak self] attributes, _ in if let controller = self?.controller?() as? StarsTransactionsScreen, let navigationController = controller.navigationController as? NavigationController { - component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: environment.strings.Stars_BotRevenue_Withdraw_Info_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) + component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: component.starsContext.ton ? environment.strings.Ton_WithdrawViaFragment_Info_URL : environment.strings.Stars_BotRevenue_Withdraw_Info_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) } } )) : nil, @@ -766,7 +766,7 @@ final class StarsTransactionsScreenComponent: Component { count: self.starsState?.balance ?? StarsAmount.zero, currency: component.starsContext.ton ? .ton : .stars, rate: nil, - actionTitle: component.starsContext.ton ? "Withdraw via Fragment" : (withdrawAvailable ? environment.strings.Stars_Intro_BuyShort : environment.strings.Stars_Intro_Buy), + actionTitle: component.starsContext.ton ? environment.strings.Ton_WithdrawViaFragment : (withdrawAvailable ? environment.strings.Stars_Intro_BuyShort : environment.strings.Stars_Intro_Buy), actionAvailable: (!premiumConfiguration.areStarsDisabled && !premiumConfiguration.isPremiumDisabled), actionIsEnabled: true, actionIcon: component.starsContext.ton ? nil : PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme), diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift index 921c93180e..b51952d115 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift @@ -123,6 +123,7 @@ import ChatEmptyNode import ChatMediaInputStickerGridItem import AdsInfoScreen import Photos +import ChatThemeScreen extension ChatControllerImpl { public func presentThemeSelection() { @@ -186,11 +187,11 @@ extension ChatControllerImpl { } }, changeWallpaper: { [weak self] in - guard let strongSelf = self, let peerId else { + guard let self, let peerId else { return } - if let themeController = strongSelf.themeScreen { - strongSelf.themeScreen = nil + if let themeController = self.themeScreen { + self.themeScreen = nil themeController.dimTapped() } let dismissControllers = { [weak self] in @@ -206,60 +207,58 @@ extension ChatControllerImpl { } var openWallpaperPickerImpl: ((Bool) -> Void)? let openWallpaperPicker = { [weak self] animateAppearance in - guard let strongSelf = self else { + guard let self else { return } let controller = wallpaperMediaPickerController( - context: strongSelf.context, - updatedPresentationData: strongSelf.updatedPresentationData, + context: context, + updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), animateAppearance: animateAppearance, completion: { [weak self] _, result in - guard let strongSelf = self, let asset = result as? PHAsset else { + guard let self, let asset = result as? PHAsset else { return } - let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) + let controller = WallpaperGalleryController(context: context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) controller.navigationPresentation = .modal - controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness, forBoth in - if let strongSelf = self { - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peerId, forBoth: forBoth, completion: { - Queue.mainQueue().after(0.3, { - dismissControllers() - }) + controller.apply = { wallpaper, options, editedImage, cropRect, brightness, forBoth in + uploadCustomPeerWallpaper(context: context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peerId, forBoth: forBoth, completion: { + Queue.mainQueue().after(0.3, { + dismissControllers() }) - } + }) } - strongSelf.push(controller) + self.push(controller) }, openColors: { [weak self] in - guard let strongSelf = self else { + guard let self else { return } - let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in - if let strongSelf = self { - strongSelf.push(controller) + let controller = standaloneColorPickerController(context: context, peer: EnginePeer(peer), push: { [weak self] controller in + if let self { + self.push(controller) } }, openGallery: { openWallpaperPickerImpl?(false) }) controller.navigationPresentation = .flatModal - strongSelf.push(controller) + self.push(controller) } ) controller.navigationPresentation = .flatModal - strongSelf.push(controller) + self.push(controller) } openWallpaperPickerImpl = openWallpaperPicker openWallpaperPicker(true) }, resetWallpaper: { [weak self] in - guard let strongSelf = self, let peerId else { + guard let self, let peerId else { return } - let _ = strongSelf.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil, forBoth: false).startStandalone() + let _ = self.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil, forBoth: false).startStandalone() }, completion: { [weak self] chatTheme in - guard let strongSelf = self, let peerId else { + guard let self, let peerId else { return } if canResetWallpaper && chatTheme != nil { @@ -267,8 +266,8 @@ extension ChatControllerImpl { } strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((chatTheme ?? .emoticon(""), nil))) let _ = context.engine.themes.setChatTheme(peerId: peerId, chatTheme: chatTheme ?? .emoticon("")).startStandalone(completed: { [weak self] in - if let strongSelf = self { - strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((nil, nil))) + if let self { + self.chatThemeAndDarkAppearancePreviewPromise.set(.single((nil, nil))) } }) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e02763bd13..70d9760246 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -141,6 +141,7 @@ import SuggestedPostApproveAlert import AVFoundation import BalanceNeededScreen import FaceScanScreen +import ChatThemeScreen public final class ChatControllerOverlayPresentationData { public let expandData: (ASDisplayNode?, () -> Void) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 8852a916e9..4666344780 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -46,6 +46,7 @@ import ComponentFlow import ChatEmptyNode import SpaceWarpView import ChatSideTopicsPanel +import ChatThemeScreen final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 1ec0d69465..af6b7bb36d 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -570,19 +570,18 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu func addToSavedMusic(file: FileMediaReference) { self.dismissAllTooltips() - var actionText: String? = "View" + var actionText: String? = self.presentationData.strings.MediaPlayer_SavedMusic_AddedToProfile_View if let itemId = self.controlsNode.currentItemId as? PeerMessagesMediaPlaylistItemId, itemId.messageId.namespace == Namespaces.Message.Local && itemId.messageId.peerId == self.context.account.peerId { actionText = nil } - //TODO:localize let controller = UndoOverlayController( presentationData: self.presentationData, content: .universalImage( image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SavedMusic"), color: .white)!, size: nil, title: nil, - text: "Audio added to your profile.", + text: self.presentationData.strings.MediaPlayer_SavedMusic_AddedToProfile, customUndoText: actionText, timeout: 3.0 ), @@ -620,14 +619,13 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu func removeFromSavedMusic(file: FileMediaReference) { self.dismissAllTooltips() - //TODO:localize let controller = UndoOverlayController( presentationData: self.presentationData, content: .universalImage( image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SavedMusic"), color: .white)!, size: nil, title: nil, - text: "Audio removed from your profile.", + text: self.presentationData.strings.MediaPlayer_SavedMusic_RemovedFromProfile, customUndoText: nil, timeout: 3.0 ), @@ -1029,10 +1027,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } var items: [ContextMenuItem] = [] - //TODO:localize if canSaveToProfile || canSaveToSavedMessages { items.append( - .action(ContextMenuActionItem(text: "Save to...", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in + .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in if let self { var subActions: [ContextMenuItem] = [] subActions.append( @@ -1044,7 +1041,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu if canSaveToProfile { subActions.append( - .action(ContextMenuActionItem(text: "Profile", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Profile, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) if let self { @@ -1056,7 +1053,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu if canSaveToSavedMessages { subActions.append( - .action(ContextMenuActionItem(text: "Saved Messages", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_SavedMessages, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) if let self { @@ -1067,7 +1064,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } subActions.append( - .action(ContextMenuActionItem(text: "Files", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Files, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) if let self { @@ -1091,7 +1088,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil subActions.append( - .action(ContextMenuActionItem(text: "Choose where you want this audio to be saved.", textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: noAction)) + .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Info, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: noAction)) ) c?.pushItems(items: .single(ContextController.Items(content: .list(subActions)))) @@ -1099,7 +1096,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu })) ) } else { - items.append(.action(ContextMenuActionItem(text: "Save to Files", icon: { theme in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveToFiles, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in f(.default) @@ -1131,7 +1128,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu addedSeparator = true } items.append( - .action(ContextMenuActionItem(text: "Show in Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_ShowInChat, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.dismissWithoutContent) guard let self else { @@ -1144,7 +1141,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu } // items.append( - // .action(ContextMenuActionItem(text: "Forward", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in + // .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_Forward, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in // f(.default) // // if let _ = self { @@ -1181,9 +1178,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu items.append(.separator) addedSeparator = true } - var actionTitle = "Delete" + var actionTitle = presentationData.strings.MediaPlayer_ContextMenu_Delete if case .custom = self.source { - actionTitle = "Remove" + actionTitle = presentationData.strings.MediaPlayer_ContextMenu_Remove } items.append( .action(ContextMenuActionItem(text: actionTitle, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in diff --git a/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift b/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift index 3132575d1e..6f8141ddb2 100644 --- a/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift +++ b/submodules/TelegramUI/Sources/OverlayPlayerControlsNode.swift @@ -1026,13 +1026,12 @@ final class OverlayPlayerControlsNode: ASDisplayNode { self.separatorNode.isHidden = hasSectionHeader if hasSectionHeader { - //TODO:localize let sideInset: CGFloat = 16.0 - var sectionTitle = "AUDIO IN THIS CHAT" + var sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_ThisChat if let peerName = self.peerName { - sectionTitle = "\(peerName.uppercased())'S PLAYLIST" + sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_SavedMusic(peerName.uppercased()).string } else if case .custom = self.source { - sectionTitle = "YOUR PLAYLIST" + sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_SavedMusicYou } let sectionTitleSize = self.sectionTitle.update( transition: .immediate, @@ -1096,7 +1095,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { return (TelegramTextAttributes.URL, contents) }) - let attributedString = parseMarkdownIntoAttributedString("This audio is visible on your profile. [Remove >]()", attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString + let attributedString = parseMarkdownIntoAttributedString(self.presentationData.strings.MediaPlayer_SavedMusic_RemoveFromProfile, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString if let range = attributedString.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 { attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string)) attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string)) @@ -1125,7 +1124,6 @@ final class OverlayPlayerControlsNode: ASDisplayNode { )) profileAudioOffset = 18.0 } else { - //TODO:localize profileAudioComponent = AnyComponent(ButtonComponent( background: ButtonComponent.Background( color: self.presentationData.theme.list.itemCheckColors.fillColor, @@ -1139,7 +1137,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode { BundleIconComponent(name: "Peer Info/SaveMusic", tintColor: self.presentationData.theme.list.itemCheckColors.foregroundColor) )), AnyComponentWithIdentity(id: "label", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "Add to Profile", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: self.presentationData.strings.MediaPlayer_SavedMusic_AddToProfile, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor))) )) ], spacing: 8.0) )),