mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +00:00
Merge commit '4c962e6a6db9f554cfc0907e1cbab8848eb1bfe4'
# Conflicts: # Telegram/Telegram-iOS/en.lproj/Localizable.strings # submodules/PremiumUI/Sources/PremiumIntroScreen.swift # submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift # submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift
This commit is contained in:
commit
73062c70e6
@ -14485,7 +14485,6 @@ Sorry for the inconvenience.";
|
|||||||
"Chat.Todo.Message.Title" = "Checklist";
|
"Chat.Todo.Message.Title" = "Checklist";
|
||||||
"Chat.Todo.Message.TitleGroup" = "Group Checklist";
|
"Chat.Todo.Message.TitleGroup" = "Group Checklist";
|
||||||
"Chat.Todo.Message.TitlePersonal" = "%@'s Checklist";
|
"Chat.Todo.Message.TitlePersonal" = "%@'s Checklist";
|
||||||
"Chat.Todo.Message.CompletedPersonal" = "%@'s Checklist";
|
|
||||||
|
|
||||||
"Chat.Todo.ContextMenu.AddTask" = "Add a Task";
|
"Chat.Todo.ContextMenu.AddTask" = "Add a Task";
|
||||||
"Chat.Todo.ContextMenu.EditTask" = "Edit Item";
|
"Chat.Todo.ContextMenu.EditTask" = "Edit Item";
|
||||||
@ -14655,3 +14654,20 @@ Sorry for the inconvenience.";
|
|||||||
"Chat.PostSuggestion.Suggest.UserMinAmountStars.Text" = "You cannot offer less than %@ Stars.";
|
"Chat.PostSuggestion.Suggest.UserMinAmountStars.Text" = "You cannot offer less than %@ Stars.";
|
||||||
|
|
||||||
"Stars.SellGiftMinAmountToast.Text" = "You cannot sell gift for less than %@";
|
"Stars.SellGiftMinAmountToast.Text" = "You cannot sell gift for less than %@";
|
||||||
|
|
||||||
|
"Premium.Week.SignUp" = "Sign up for %@";
|
||||||
|
"Premium.Week.SignUpInfo" = "Get Telegram Premium for 1 week";
|
||||||
|
|
||||||
|
"Settings.MyTon" = "My TON";
|
||||||
|
|
||||||
|
"Stars.Gift.Ton.Text" = "Use TON to submit post suggestions to channels on Telegram.";
|
||||||
|
"Stars.Ton.Title" = "TON";
|
||||||
|
"Stars.Ton.Description" = "Use TON to submit post suggestions to channels on Telegram.";
|
||||||
|
|
||||||
|
"Notification.Ton.Subtitle" = "Use TON to submit post suggestions to channels.";
|
||||||
|
"Notification.Ton.SubtitleYou" = "With TON, %@ will be able to submit post suggestions to channels.";
|
||||||
|
|
||||||
|
"Chat.Todo.Message.Completed_1" = "%@ of {count} completed";
|
||||||
|
"Chat.Todo.Message.Completed_any" = "%@ of {count} completed";
|
||||||
|
"Chat.Todo.Message.CompletedBy_1" = "%@ of {count} completed by {name}";
|
||||||
|
"Chat.Todo.Message.CompletedBy_any" = "%@ of {count} completed by {name}";
|
||||||
|
|||||||
@ -3702,8 +3702,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
let buttonTitle: String
|
let buttonTitle: String
|
||||||
var buttonSubtitle: String?
|
var buttonSubtitle: String?
|
||||||
if case let .auth(price) = context.component.source {
|
if case let .auth(price) = context.component.source {
|
||||||
buttonTitle = environment.strings.Auth_PremiumSignUp_ActionTitle("\(price)").string
|
buttonTitle = environment.strings.Premium_Week_SignUp(price).string
|
||||||
buttonSubtitle = environment.strings.Auth_PremiumSignUp_ActionSubtitle
|
buttonSubtitle = environment.strings.Premium_Week_SignUpInfo
|
||||||
} else if isUnusedGift {
|
} else if isUnusedGift {
|
||||||
buttonTitle = environment.strings.Premium_Gift_ApplyLink
|
buttonTitle = environment.strings.Premium_Gift_ApplyLink
|
||||||
} else if state.isPremium == true && state.canUpgrade {
|
} else if state.isPremium == true && state.canUpgrade {
|
||||||
|
|||||||
@ -433,7 +433,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
let cryptoAmount = cryptoAmount ?? 0
|
let cryptoAmount = cryptoAmount ?? 0
|
||||||
|
|
||||||
title = "$ \(formatTonAmountText(cryptoAmount, dateTimeFormat: item.presentationData.dateTimeFormat, maxDecimalPositions: 3))"
|
title = "$ \(formatTonAmountText(cryptoAmount, dateTimeFormat: item.presentationData.dateTimeFormat, maxDecimalPositions: 3))"
|
||||||
text = incoming ? "Use TON to submit post suggestions to channels." : "With TON, \(peerName) will be able to submit post suggestions to channels."
|
text = incoming ? item.presentationData.strings.Notification_Ton_Subtitle : item.presentationData.strings.Notification_Ton_SubtitleYou(peerName).string
|
||||||
buttonTitle = ""
|
buttonTitle = ""
|
||||||
case let .prizeStars(count, _, channelId, _, _):
|
case let .prizeStars(count, _, channelId, _, _):
|
||||||
if count <= 1000 {
|
if count <= 1000 {
|
||||||
|
|||||||
@ -633,6 +633,60 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func linkRectsAtPoint(_ point: CGPoint?) -> [CGRect]? {
|
||||||
|
guard let textNode = self.titleNode else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var rects: [CGRect]?
|
||||||
|
if let point = point {
|
||||||
|
let textNodeFrame = textNode.textNode.frame
|
||||||
|
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
|
let possibleNames: [String] = [
|
||||||
|
TelegramTextAttributes.URL,
|
||||||
|
TelegramTextAttributes.PeerMention,
|
||||||
|
TelegramTextAttributes.PeerTextMention,
|
||||||
|
TelegramTextAttributes.BotCommand,
|
||||||
|
TelegramTextAttributes.Hashtag,
|
||||||
|
TelegramTextAttributes.BankCard
|
||||||
|
]
|
||||||
|
for name in possibleNames {
|
||||||
|
if let _ = attributes[NSAttributedString.Key(rawValue: name)], let textRects = textNode.textNode.attributeRects(name: name, at: index) {
|
||||||
|
rects = textRects.map { $0.offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY) }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rects
|
||||||
|
}
|
||||||
|
|
||||||
|
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||||
|
guard let textNode = self.titleNode else {
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .none)
|
||||||
|
}
|
||||||
|
let textNodeFrame = textNode.textNode.frame
|
||||||
|
if let (index, attributes) = textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
|
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
|
var concealed = true
|
||||||
|
if let (attributeText, fullText) = textNode.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||||
|
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||||
|
}
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .url(ChatMessageBubbleContentTapAction.Url(url: url, concealed: concealed)))
|
||||||
|
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .peerMention(peerId: peerMention.peerId, mention: peerMention.mention, openProfile: false))
|
||||||
|
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .textMention(peerName))
|
||||||
|
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .botCommand(botCommand))
|
||||||
|
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .hashtag(hashtag.peerName, hashtag.hashtag))
|
||||||
|
} else {
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .none)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ChatMessageBubbleContentTapAction(content: .none)
|
||||||
|
}
|
||||||
|
|
||||||
static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) {
|
static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) {
|
||||||
let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode)
|
let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode)
|
||||||
let makeNameLayout = TextNode.asyncLayout(maybeNode?.nameNode)
|
let makeNameLayout = TextNode.asyncLayout(maybeNode?.nameNode)
|
||||||
@ -663,12 +717,16 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
|||||||
optionEntities.append(MessageTextEntity(range: 0 ..< (optionText as NSString).length, type: .Strikethrough))
|
optionEntities.append(MessageTextEntity(range: 0 ..< (optionText as NSString).length, type: .Strikethrough))
|
||||||
}
|
}
|
||||||
|
|
||||||
let optionTextColor: UIColor = messageTheme.primaryTextColor
|
var underlineLinks = true
|
||||||
|
if !messageTheme.primaryTextColor.isEqual(messageTheme.linkTextColor) {
|
||||||
|
underlineLinks = false
|
||||||
|
}
|
||||||
|
|
||||||
let optionAttributedText = stringWithAppliedEntities(
|
let optionAttributedText = stringWithAppliedEntities(
|
||||||
optionText,
|
optionText,
|
||||||
entities: optionEntities,
|
entities: optionEntities,
|
||||||
baseColor: optionTextColor,
|
baseColor: messageTheme.primaryTextColor,
|
||||||
linkColor: optionTextColor,
|
linkColor: messageTheme.linkTextColor,
|
||||||
baseFont: presentationData.messageFont,
|
baseFont: presentationData.messageFont,
|
||||||
linkFont: presentationData.messageFont,
|
linkFont: presentationData.messageFont,
|
||||||
boldFont: presentationData.messageFont,
|
boldFont: presentationData.messageFont,
|
||||||
@ -676,6 +734,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
|||||||
boldItalicFont: presentationData.messageFont,
|
boldItalicFont: presentationData.messageFont,
|
||||||
fixedFont: presentationData.messageFont,
|
fixedFont: presentationData.messageFont,
|
||||||
blockQuoteFont: presentationData.messageFont,
|
blockQuoteFont: presentationData.messageFont,
|
||||||
|
underlineLinks: underlineLinks,
|
||||||
message: message
|
message: message
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -921,6 +980,8 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
private var optionNodes: [ChatMessageTodoItemNode] = []
|
private var optionNodes: [ChatMessageTodoItemNode] = []
|
||||||
private var shimmeringNodes: [ShimmeringLinkNode] = []
|
private var shimmeringNodes: [ShimmeringLinkNode] = []
|
||||||
|
|
||||||
|
private var linkHighlightingNode: LinkHighlightingNode?
|
||||||
|
|
||||||
private var todo: TelegramMediaTodo?
|
private var todo: TelegramMediaTodo?
|
||||||
|
|
||||||
override public var visibility: ListViewItemNodeVisibility {
|
override public var visibility: ListViewItemNodeVisibility {
|
||||||
@ -1155,15 +1216,15 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
var bottomText: String = ""
|
var bottomText: String = ""
|
||||||
if let todo {
|
if let todo {
|
||||||
|
let format: String
|
||||||
if let author = item.message.author, author.id != item.context.account.peerId && !todo.flags.contains(.othersCanComplete) {
|
if let author = item.message.author, author.id != item.context.account.peerId && !todo.flags.contains(.othersCanComplete) {
|
||||||
bottomText = "\(todo.completions.count) of \(todo.items.count) completed by \(EnginePeer(author).compactDisplayTitle)"
|
format = item.presentationData.strings.Chat_Todo_Message_CompletedBy(Int32(todo.completions.count)).replacingOccurrences(of: "{name}", with: EnginePeer(author).compactDisplayTitle)
|
||||||
} else {
|
} else {
|
||||||
bottomText = "\(todo.completions.count) of \(todo.items.count) completed"
|
format = item.presentationData.strings.Chat_Todo_Message_Completed(Int32(todo.completions.count))
|
||||||
}
|
}
|
||||||
|
bottomText = format.replacingOccurrences(of: "{count}", with: "\(todo.items.count)")
|
||||||
}
|
}
|
||||||
|
|
||||||
let (buttonViewResultsTextLayout, buttonViewResultsTextApply) = makeViewResultsTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: bottomText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
let (buttonViewResultsTextLayout, buttonViewResultsTextApply) = makeViewResultsTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: bottomText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||||
@ -1440,10 +1501,19 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for optionNode in self.optionNodes {
|
for optionNode in self.optionNodes {
|
||||||
if optionNode.frame.contains(point), case .tap = gesture {
|
if optionNode.frame.contains(point) {
|
||||||
if optionNode.isUserInteractionEnabled {
|
let optionAction = optionNode.tapActionAtPoint(self.view.convert(point, to: optionNode.view), gesture: gesture, isEstimating: isEstimating)
|
||||||
|
if case .none = optionAction.content {
|
||||||
|
if optionNode.isUserInteractionEnabled, case .tap = gesture {
|
||||||
return ChatMessageBubbleContentTapAction(content: .ignore)
|
return ChatMessageBubbleContentTapAction(content: .ignore)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
var rects: [CGRect]?
|
||||||
|
if let optionRects = optionNode.linkRectsAtPoint(self.view.convert(point, to: optionNode.view)), let rect = optionRects.first {
|
||||||
|
rects = [rect.offsetBy(dx: optionNode.frame.minX - 11.0, dy: optionNode.frame.minY - 5.0)]
|
||||||
|
}
|
||||||
|
return ChatMessageBubbleContentTapAction(content: optionAction.content, rects: rects)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.buttonNode.isUserInteractionEnabled, !self.buttonNode.isHidden, self.buttonNode.frame.contains(point) {
|
if self.buttonNode.isUserInteractionEnabled, !self.buttonNode.isHidden, self.buttonNode.frame.contains(point) {
|
||||||
@ -1453,6 +1523,56 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||||
|
guard let item = self.item else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var rects: [CGRect]?
|
||||||
|
if let point = point {
|
||||||
|
let textNodeFrame = self.textNode.textNode.frame
|
||||||
|
if let (index, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
|
let possibleNames: [String] = [
|
||||||
|
TelegramTextAttributes.URL,
|
||||||
|
TelegramTextAttributes.PeerMention,
|
||||||
|
TelegramTextAttributes.PeerTextMention,
|
||||||
|
TelegramTextAttributes.BotCommand,
|
||||||
|
TelegramTextAttributes.Hashtag,
|
||||||
|
TelegramTextAttributes.BankCard
|
||||||
|
]
|
||||||
|
for name in possibleNames {
|
||||||
|
if let _ = attributes[NSAttributedString.Key(rawValue: name)], let textRects = self.textNode.textNode.attributeRects(name: name, at: index) {
|
||||||
|
rects = textRects.map { $0.offsetBy(dx: textNodeFrame.minX, dy: textNodeFrame.minY) }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for optionNode in self.optionNodes {
|
||||||
|
if optionNode.frame.contains(point), let optionRects = optionNode.linkRectsAtPoint(CGPoint(x: point.x - optionNode.frame.minX, y: point.y - optionNode.frame.minY)) {
|
||||||
|
rects = optionRects.map { $0.offsetBy(dx: optionNode.frame.minX - 11.0, dy: optionNode.frame.minY - 4.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let rects {
|
||||||
|
let linkHighlightingNode: LinkHighlightingNode
|
||||||
|
if let current = self.linkHighlightingNode {
|
||||||
|
linkHighlightingNode = current
|
||||||
|
} else {
|
||||||
|
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
|
||||||
|
self.linkHighlightingNode = linkHighlightingNode
|
||||||
|
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode.textNode)
|
||||||
|
}
|
||||||
|
linkHighlightingNode.frame = self.textNode.textNode.frame
|
||||||
|
linkHighlightingNode.updateRects(rects)
|
||||||
|
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
||||||
|
self.linkHighlightingNode = nil
|
||||||
|
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
||||||
|
linkHighlightingNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
override public func reactionTargetView(value: MessageReaction.Reaction) -> UIView? {
|
||||||
if !self.statusNode.isHidden {
|
if !self.statusNode.isHidden {
|
||||||
return self.statusNode.reactionView(value: value)
|
return self.statusNode.reactionView(value: value)
|
||||||
|
|||||||
@ -419,7 +419,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
count = transaction.count
|
count = transaction.count
|
||||||
|
|
||||||
if count.currency == .ton {
|
if count.currency == .ton {
|
||||||
descriptionText = strings.Settings_Ton_Description
|
descriptionText = strings.Stars_Gift_Ton_Text
|
||||||
} else {
|
} else {
|
||||||
descriptionText = strings.Stars_Gift_Received_Text
|
descriptionText = strings.Stars_Gift_Received_Text
|
||||||
}
|
}
|
||||||
|
|||||||
@ -536,8 +536,8 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
let titleString: String
|
let titleString: String
|
||||||
let descriptionString: String
|
let descriptionString: String
|
||||||
if component.starsContext.ton {
|
if component.starsContext.ton {
|
||||||
titleString = environment.strings.Settings_TransactionsTon_Title
|
titleString = environment.strings.Stars_Ton_Title
|
||||||
descriptionString = environment.strings.Settings_TransactionsTon_Subtitle
|
descriptionString = environment.strings.Stars_Ton_Description
|
||||||
} else {
|
} else {
|
||||||
titleString = environment.strings.Stars_Intro_Title
|
titleString = environment.strings.Stars_Intro_Title
|
||||||
descriptionString = environment.strings.Stars_Intro_Description
|
descriptionString = environment.strings.Stars_Intro_Description
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user