mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Refactor PasswordSetupUI, PassportUI, GalleryUI and related modules
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import Foundation
|
||||
|
||||
private final class CurrencyFormatterEntry {
|
||||
let symbol: String
|
||||
let thousandsSeparator: String
|
||||
let decimalSeparator: String
|
||||
let symbolOnLeft: Bool
|
||||
let spaceBetweenAmountAndSymbol: Bool
|
||||
let decimalDigits: Int
|
||||
|
||||
init(symbol: String, thousandsSeparator: String, decimalSeparator: String, symbolOnLeft: Bool, spaceBetweenAmountAndSymbol: Bool, decimalDigits: Int) {
|
||||
self.symbol = symbol
|
||||
self.thousandsSeparator = thousandsSeparator
|
||||
self.decimalSeparator = decimalSeparator
|
||||
self.symbolOnLeft = symbolOnLeft
|
||||
self.spaceBetweenAmountAndSymbol = spaceBetweenAmountAndSymbol
|
||||
self.decimalDigits = decimalDigits
|
||||
}
|
||||
}
|
||||
|
||||
private func loadCurrencyFormatterEntries() -> [String: CurrencyFormatterEntry] {
|
||||
guard let filePath = frameworkBundle.path(forResource: "currencies", ofType: "json") else {
|
||||
return [:]
|
||||
}
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
|
||||
return [:]
|
||||
}
|
||||
|
||||
guard let object = try? JSONSerialization.jsonObject(with: data, options: []), let dict = object as? [String: AnyObject] else {
|
||||
return [:]
|
||||
}
|
||||
|
||||
var result: [String: CurrencyFormatterEntry] = [:]
|
||||
|
||||
for (code, contents) in dict {
|
||||
if let contentsDict = contents as? [String: AnyObject] {
|
||||
let entry = CurrencyFormatterEntry(symbol: contentsDict["symbol"] as! String, thousandsSeparator: contentsDict["thousandsSeparator"] as! String, decimalSeparator: contentsDict["decimalSeparator"] as! String, symbolOnLeft: (contentsDict["symbolOnLeft"] as! NSNumber).boolValue, spaceBetweenAmountAndSymbol: (contentsDict["spaceBetweenAmountAndSymbol"] as! NSNumber).boolValue, decimalDigits: (contentsDict["decimalDigits"] as! NSNumber).intValue)
|
||||
result[code] = entry
|
||||
result[code.lowercased()] = entry
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private let currencyFormatterEntries = loadCurrencyFormatterEntries()
|
||||
|
||||
public func formatCurrencyAmount(_ amount: Int64, currency: String) -> String {
|
||||
if let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] {
|
||||
var result = ""
|
||||
if amount < 0 {
|
||||
result.append("-")
|
||||
}
|
||||
if entry.symbolOnLeft {
|
||||
result.append(entry.symbol)
|
||||
if entry.spaceBetweenAmountAndSymbol {
|
||||
result.append(" ")
|
||||
}
|
||||
}
|
||||
var integerPart = abs(amount)
|
||||
var fractional: [Character] = []
|
||||
for _ in 0 ..< entry.decimalDigits {
|
||||
let part = integerPart % 10
|
||||
integerPart /= 10
|
||||
if let scalar = UnicodeScalar(UInt32(part + 48)) {
|
||||
fractional.append(Character(scalar))
|
||||
}
|
||||
}
|
||||
result.append("\(integerPart)")
|
||||
result.append(entry.decimalSeparator)
|
||||
for i in 0 ..< fractional.count {
|
||||
result.append(fractional[fractional.count - i - 1])
|
||||
}
|
||||
if !entry.symbolOnLeft {
|
||||
if entry.spaceBetweenAmountAndSymbol {
|
||||
result.append(" ")
|
||||
}
|
||||
result.append(entry.symbol)
|
||||
}
|
||||
|
||||
return result
|
||||
} else {
|
||||
assertionFailure()
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .currency
|
||||
formatter.currencyCode = currency
|
||||
formatter.negativeFormat = "-¤#,##0.00"
|
||||
return formatter.string(from: (Float(amount) * 0.01) as NSNumber) ?? ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
private class FrameworkBundleClass: NSObject {
|
||||
}
|
||||
|
||||
let frameworkBundle: Bundle = Bundle(for: FrameworkBundleClass.self)
|
||||
@@ -0,0 +1,207 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
|
||||
public enum MessageContentKindKey {
|
||||
case text
|
||||
case image
|
||||
case video
|
||||
case videoMessage
|
||||
case audioMessage
|
||||
case sticker
|
||||
case animation
|
||||
case file
|
||||
case contact
|
||||
case game
|
||||
case location
|
||||
case liveLocation
|
||||
case expiredImage
|
||||
case expiredVideo
|
||||
case poll
|
||||
}
|
||||
|
||||
public enum MessageContentKind: Equatable {
|
||||
case text(String)
|
||||
case image
|
||||
case video
|
||||
case videoMessage
|
||||
case audioMessage
|
||||
case sticker(String)
|
||||
case animation
|
||||
case file(String)
|
||||
case contact
|
||||
case game(String)
|
||||
case location
|
||||
case liveLocation
|
||||
case expiredImage
|
||||
case expiredVideo
|
||||
case poll(String)
|
||||
|
||||
public var key: MessageContentKindKey {
|
||||
switch self {
|
||||
case .text:
|
||||
return .text
|
||||
case .image:
|
||||
return .image
|
||||
case .video:
|
||||
return .video
|
||||
case .videoMessage:
|
||||
return .videoMessage
|
||||
case .audioMessage:
|
||||
return .audioMessage
|
||||
case .sticker:
|
||||
return .sticker
|
||||
case .animation:
|
||||
return .animation
|
||||
case .file:
|
||||
return .file
|
||||
case .contact:
|
||||
return .contact
|
||||
case .game:
|
||||
return .game
|
||||
case .location:
|
||||
return .location
|
||||
case .liveLocation:
|
||||
return .liveLocation
|
||||
case .expiredImage:
|
||||
return .expiredImage
|
||||
case .expiredVideo:
|
||||
return .expiredVideo
|
||||
case .poll:
|
||||
return .poll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func messageContentKind(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> MessageContentKind {
|
||||
for media in message.media {
|
||||
if let kind = mediaContentKind(media, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
|
||||
return kind
|
||||
}
|
||||
}
|
||||
return .text(message.text)
|
||||
}
|
||||
|
||||
public func mediaContentKind(_ media: Media, message: Message? = nil, strings: PresentationStrings? = nil, nameDisplayOrder: PresentationPersonNameOrder? = nil, accountPeerId: PeerId? = nil) -> MessageContentKind? {
|
||||
switch media {
|
||||
case let expiredMedia as TelegramMediaExpiredContent:
|
||||
switch expiredMedia.data {
|
||||
case .image:
|
||||
return .expiredImage
|
||||
case .file:
|
||||
return .expiredVideo
|
||||
}
|
||||
case _ as TelegramMediaImage:
|
||||
return .image
|
||||
case let file as TelegramMediaFile:
|
||||
var fileName: String = ""
|
||||
for attribute in file.attributes {
|
||||
switch attribute {
|
||||
case let .Sticker(text, _, _):
|
||||
return .sticker(text)
|
||||
case let .FileName(name):
|
||||
fileName = name
|
||||
case let .Audio(isVoice, _, title, performer, _):
|
||||
if isVoice {
|
||||
return .audioMessage
|
||||
} else {
|
||||
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
|
||||
return .file(title + " — " + performer)
|
||||
} else if let title = title, !title.isEmpty {
|
||||
return .file(title)
|
||||
} else if let performer = performer, !performer.isEmpty {
|
||||
return .file(performer)
|
||||
}
|
||||
}
|
||||
case let .Video(_, _, flags):
|
||||
if file.isAnimated {
|
||||
return .animation
|
||||
} else {
|
||||
if flags.contains(.instantRoundVideo) {
|
||||
return .videoMessage
|
||||
} else {
|
||||
return .video
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if file.isAnimatedSticker {
|
||||
return .sticker("")
|
||||
}
|
||||
return .file(fileName)
|
||||
case _ as TelegramMediaContact:
|
||||
return .contact
|
||||
case let game as TelegramMediaGame:
|
||||
return .game(game.title)
|
||||
case let location as TelegramMediaMap:
|
||||
if location.liveBroadcastingTimeout != nil {
|
||||
return .liveLocation
|
||||
} else {
|
||||
return .location
|
||||
}
|
||||
case _ as TelegramMediaAction:
|
||||
if let message = message, let strings = strings, let nameDisplayOrder = nameDisplayOrder, let accountPeerId = accountPeerId {
|
||||
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let poll as TelegramMediaPoll:
|
||||
return .poll(poll.text)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func stringForMediaKind(_ kind: MessageContentKind, strings: PresentationStrings) -> (String, Bool) {
|
||||
switch kind {
|
||||
case let .text(text):
|
||||
return (text, false)
|
||||
case .image:
|
||||
return (strings.Message_Photo, true)
|
||||
case .video:
|
||||
return (strings.Message_Video, true)
|
||||
case .videoMessage:
|
||||
return (strings.Message_VideoMessage, true)
|
||||
case .audioMessage:
|
||||
return (strings.Message_Audio, true)
|
||||
case let .sticker(text):
|
||||
if text.isEmpty {
|
||||
return (strings.Message_Sticker, true)
|
||||
} else {
|
||||
return (strings.Message_StickerText(text).0, true)
|
||||
}
|
||||
case .animation:
|
||||
return (strings.Message_Animation, true)
|
||||
case let .file(text):
|
||||
if text.isEmpty {
|
||||
return (strings.Message_File, true)
|
||||
} else {
|
||||
return (text, true)
|
||||
}
|
||||
case .contact:
|
||||
return (strings.Message_Contact, true)
|
||||
case let .game(text):
|
||||
return (text, true)
|
||||
case .location:
|
||||
return (strings.Message_Location, true)
|
||||
case .liveLocation:
|
||||
return (strings.Message_LiveLocation, true)
|
||||
case .expiredImage:
|
||||
return (strings.Message_ImageExpired, true)
|
||||
case .expiredVideo:
|
||||
return (strings.Message_VideoExpired, true)
|
||||
case let .poll(text):
|
||||
return ("📊 \(text)", false)
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) {
|
||||
if !message.text.isEmpty {
|
||||
return (message.text, false)
|
||||
}
|
||||
return stringForMediaKind(messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId), strings: strings)
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TextFormat
|
||||
import LocalizedPeerData
|
||||
import Display
|
||||
|
||||
private let titleFont = Font.regular(13.0)
|
||||
private let titleBoldFont = Font.bold(13.0)
|
||||
|
||||
private func peerMentionAttributes(primaryTextColor: UIColor, peerId: PeerId) -> MarkdownAttributeSet {
|
||||
return MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [TelegramTextAttributes.PeerMention: TelegramPeerMention(peerId: peerId, mention: "")])
|
||||
}
|
||||
|
||||
private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, PeerId?)]) -> [Int: MarkdownAttributeSet] {
|
||||
var result: [Int: MarkdownAttributeSet] = [:]
|
||||
for (index, peerId) in peerIds {
|
||||
if let peerId = peerId {
|
||||
result[index] = peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: peerId)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> String? {
|
||||
return universalServiceMessageString(presentationData: nil, strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId)?.string
|
||||
}
|
||||
|
||||
public func universalServiceMessageString(presentationData: (PresentationTheme, TelegramWallpaper)?, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
|
||||
var attributedString: NSAttributedString?
|
||||
|
||||
let primaryTextColor: UIColor
|
||||
if let (theme, wallpaper) = presentationData {
|
||||
primaryTextColor = serviceMessageColorComponents(theme: theme, wallpaper: wallpaper).primaryText
|
||||
} else {
|
||||
primaryTextColor = .black
|
||||
}
|
||||
|
||||
let bodyAttributes = MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [:])
|
||||
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction {
|
||||
let authorName = message.author?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? ""
|
||||
|
||||
var isChannel = false
|
||||
if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
isChannel = true
|
||||
}
|
||||
|
||||
switch action.action {
|
||||
case let .groupCreated(title):
|
||||
if isChannel {
|
||||
attributedString = NSAttributedString(string: strings.Notification_CreatedChannel, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_CreatedChatWithTitle(authorName, title), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
}
|
||||
case let .addedMembers(peerIds):
|
||||
if let peerId = peerIds.first, peerId == message.author?.id {
|
||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedChannel(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId)]))
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedChat(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId)]))
|
||||
}
|
||||
} else {
|
||||
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
|
||||
if peerIds.count == 1 {
|
||||
attributePeerIds.append((1, peerIds.first))
|
||||
}
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_Invited(authorName, peerDebugDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
||||
}
|
||||
case let .removedMembers(peerIds):
|
||||
if peerIds.first == message.author?.id {
|
||||
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_LeftChannel(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_LeftChat(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
}
|
||||
} else {
|
||||
var attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
|
||||
if peerIds.count == 1 {
|
||||
attributePeerIds.append((1, peerIds.first))
|
||||
}
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_Kicked(authorName, peerDebugDisplayTitles(peerIds, message.peers)), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
||||
}
|
||||
case let .photoUpdated(image):
|
||||
if authorName.isEmpty || isChannel {
|
||||
if isChannel {
|
||||
if image != nil {
|
||||
attributedString = NSAttributedString(string: strings.Channel_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Channel_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
} else {
|
||||
if image != nil {
|
||||
attributedString = NSAttributedString(string: strings.Group_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Group_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if image != nil {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_ChangedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_RemovedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
}
|
||||
}
|
||||
case let .titleUpdated(title):
|
||||
if authorName.isEmpty || isChannel {
|
||||
attributedString = NSAttributedString(string: strings.Channel_MessageTitleUpdated(title).0, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_ChangedGroupName(authorName, title), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
}
|
||||
case .pinnedMessageUpdated:
|
||||
enum PinnnedMediaType {
|
||||
case text(String)
|
||||
case game
|
||||
case photo
|
||||
case video
|
||||
case round
|
||||
case audio
|
||||
case file
|
||||
case gif
|
||||
case sticker
|
||||
case location
|
||||
case contact
|
||||
case poll
|
||||
case deleted
|
||||
}
|
||||
|
||||
var pinnedMessage: Message?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
||||
pinnedMessage = message
|
||||
}
|
||||
}
|
||||
|
||||
var type: PinnnedMediaType
|
||||
if let pinnedMessage = pinnedMessage {
|
||||
type = .text(pinnedMessage.text)
|
||||
inner: for media in pinnedMessage.media {
|
||||
if media is TelegramMediaGame {
|
||||
type = .game
|
||||
break inner
|
||||
}
|
||||
if let _ = media as? TelegramMediaImage {
|
||||
type = .photo
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
type = .file
|
||||
if file.isAnimated {
|
||||
type = .gif
|
||||
} else {
|
||||
for attribute in file.attributes {
|
||||
switch attribute {
|
||||
case let .Video(_, _, flags):
|
||||
if flags.contains(.instantRoundVideo) {
|
||||
type = .round
|
||||
} else {
|
||||
type = .video
|
||||
}
|
||||
break inner
|
||||
case let .Audio(isVoice, _, _, _, _):
|
||||
if isVoice {
|
||||
type = .audio
|
||||
} else {
|
||||
type = .file
|
||||
}
|
||||
break inner
|
||||
case .Sticker:
|
||||
type = .sticker
|
||||
break inner
|
||||
case .Animated:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let _ = media as? TelegramMediaMap {
|
||||
type = .location
|
||||
} else if let _ = media as? TelegramMediaContact {
|
||||
type = .contact
|
||||
} else if let _ = media as? TelegramMediaPoll {
|
||||
type = .poll
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type = .deleted
|
||||
}
|
||||
|
||||
switch type {
|
||||
case let .text(text):
|
||||
var clippedText = text.replacingOccurrences(of: "\n", with: " ")
|
||||
if clippedText.count > 14 {
|
||||
clippedText = "\(clippedText[...clippedText.index(clippedText.startIndex, offsetBy: 14)])..."
|
||||
}
|
||||
let textWithRanges: (String, [(Int, NSRange)])
|
||||
if clippedText.isEmpty {
|
||||
textWithRanges = strings.PUSH_PINNED_NOTEXT(authorName)
|
||||
} else {
|
||||
textWithRanges = strings.Notification_PinnedTextMessage(authorName, clippedText)
|
||||
}
|
||||
attributedString = addAttributesToStringWithRanges(textWithRanges, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .game:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Message_AuthorPinnedGame(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .photo:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedPhotoMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .video:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedVideoMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .round:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedRoundMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .audio:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedAudioMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .file:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedDocumentMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .gif:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedAnimationMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .sticker:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedStickerMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .location:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedLocationMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .contact:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedContactMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .poll:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedPollMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .deleted:
|
||||
attributedString = addAttributesToStringWithRanges(strings.PUSH_PINNED_NOTEXT(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
}
|
||||
case .joinedByLink:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedGroupByLink(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .channelMigratedFromGroup, .groupMigratedToChannel:
|
||||
attributedString = NSAttributedString(string: "", font: titleFont, textColor: primaryTextColor)
|
||||
case let .messageAutoremoveTimeoutUpdated(timeout):
|
||||
if timeout > 0 {
|
||||
let timeValue = timeIntervalString(strings: strings, value: timeout)
|
||||
|
||||
let string: String
|
||||
if message.author?.id == accountPeerId {
|
||||
string = strings.Notification_MessageLifetimeChangedOutgoing(timeValue).0
|
||||
} else {
|
||||
let authorString: String
|
||||
if let author = messageMainPeer(message) {
|
||||
authorString = author.compactDisplayTitle
|
||||
} else {
|
||||
authorString = ""
|
||||
}
|
||||
string = strings.Notification_MessageLifetimeChanged(authorString, timeValue).0
|
||||
}
|
||||
attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
let string: String
|
||||
if message.author?.id == accountPeerId {
|
||||
string = strings.Notification_MessageLifetimeRemovedOutgoing
|
||||
} else {
|
||||
let authorString: String
|
||||
if let author = messageMainPeer(message) {
|
||||
authorString = author.compactDisplayTitle
|
||||
} else {
|
||||
authorString = ""
|
||||
}
|
||||
string = strings.Notification_MessageLifetimeRemoved(authorString).0
|
||||
}
|
||||
attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
case .historyCleared:
|
||||
break
|
||||
case .historyScreenshot:
|
||||
let text: String
|
||||
if message.effectivelyIncoming(accountPeerId) {
|
||||
text = strings.Notification_SecretChatMessageScreenshot(message.author?.compactDisplayTitle ?? "").0
|
||||
} else {
|
||||
text = strings.Notification_SecretChatMessageScreenshotSelf
|
||||
}
|
||||
attributedString = NSAttributedString(string: text, font: titleFont, textColor: primaryTextColor)
|
||||
case let .gameScore(gameId: _, score):
|
||||
var gameTitle: String?
|
||||
inner: for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
||||
for media in message.media {
|
||||
if let game = media as? TelegramMediaGame {
|
||||
gameTitle = game.title
|
||||
break inner
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var baseString: String
|
||||
if message.author?.id == accountPeerId {
|
||||
if let _ = gameTitle {
|
||||
baseString = strings.ServiceMessage_GameScoreSelfExtended(score)
|
||||
} else {
|
||||
baseString = strings.ServiceMessage_GameScoreSelfSimple(score)
|
||||
}
|
||||
} else {
|
||||
if let _ = gameTitle {
|
||||
baseString = strings.ServiceMessage_GameScoreExtended(score)
|
||||
} else {
|
||||
baseString = strings.ServiceMessage_GameScoreSimple(score)
|
||||
}
|
||||
}
|
||||
let baseStringValue = baseString as NSString
|
||||
var ranges: [(Int, NSRange)] = []
|
||||
if baseStringValue.range(of: "{name}").location != NSNotFound {
|
||||
ranges.append((0, baseStringValue.range(of: "{name}")))
|
||||
}
|
||||
if baseStringValue.range(of: "{game}").location != NSNotFound {
|
||||
ranges.append((1, baseStringValue.range(of: "{game}")))
|
||||
}
|
||||
ranges.sort(by: { $0.1.location < $1.1.location })
|
||||
|
||||
var argumentAttributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
|
||||
argumentAttributes[1] = MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [:])
|
||||
attributedString = addAttributesToStringWithRanges(formatWithArgumentRanges(baseString, ranges, [authorName, gameTitle ?? ""]), body: bodyAttributes, argumentAttributes: argumentAttributes)
|
||||
case let .paymentSent(currency, totalAmount):
|
||||
var invoiceMessage: Message?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute, let message = message.associatedMessages[attribute.messageId] {
|
||||
invoiceMessage = message
|
||||
}
|
||||
}
|
||||
|
||||
var invoiceTitle: String?
|
||||
if let invoiceMessage = invoiceMessage {
|
||||
for media in invoiceMessage.media {
|
||||
if let invoice = media as? TelegramMediaInvoice {
|
||||
invoiceTitle = invoice.title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let invoiceTitle = invoiceTitle {
|
||||
let botString: String
|
||||
if let peer = messageMainPeer(message) {
|
||||
botString = peer.compactDisplayTitle
|
||||
} else {
|
||||
botString = ""
|
||||
}
|
||||
let mutableString = NSMutableAttributedString()
|
||||
mutableString.append(NSAttributedString(string: strings.Notification_PaymentSent, font: titleFont, textColor: primaryTextColor))
|
||||
|
||||
var range = NSRange(location: NSNotFound, length: 0)
|
||||
|
||||
range = (mutableString.string as NSString).range(of: "{amount}")
|
||||
if range.location != NSNotFound {
|
||||
mutableString.replaceCharacters(in: range, with: NSAttributedString(string: formatCurrencyAmount(totalAmount, currency: currency), font: titleBoldFont, textColor: primaryTextColor))
|
||||
}
|
||||
range = (mutableString.string as NSString).range(of: "{name}")
|
||||
if range.location != NSNotFound {
|
||||
mutableString.replaceCharacters(in: range, with: NSAttributedString(string: botString, font: titleBoldFont, textColor: primaryTextColor))
|
||||
}
|
||||
range = (mutableString.string as NSString).range(of: "{title}")
|
||||
if range.location != NSNotFound {
|
||||
mutableString.replaceCharacters(in: range, with: NSAttributedString(string: invoiceTitle, font: titleFont, textColor: primaryTextColor))
|
||||
}
|
||||
attributedString = mutableString
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Message_PaymentSent(formatCurrencyAmount(totalAmount, currency: currency)).0, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
case let .phoneCall(_, discardReason, _):
|
||||
var titleString: String
|
||||
let incoming: Bool
|
||||
if message.flags.contains(.Incoming) {
|
||||
titleString = strings.Notification_CallIncoming
|
||||
incoming = true
|
||||
} else {
|
||||
titleString = strings.Notification_CallOutgoing
|
||||
incoming = false
|
||||
}
|
||||
if let discardReason = discardReason {
|
||||
switch discardReason {
|
||||
case .busy, .disconnect:
|
||||
titleString = strings.Notification_CallCanceled
|
||||
case .missed:
|
||||
titleString = incoming ? strings.Notification_CallMissed : strings.Notification_CallCanceled
|
||||
case .hangup:
|
||||
break
|
||||
}
|
||||
}
|
||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||
case let .customText(text, entities):
|
||||
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false)
|
||||
case let .botDomainAccessGranted(domain):
|
||||
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor)
|
||||
case let .botSentSecureValues(types):
|
||||
var typesString = ""
|
||||
var hasIdentity = false
|
||||
var hasAddress = false
|
||||
for type in types {
|
||||
if !typesString.isEmpty {
|
||||
typesString.append(", ")
|
||||
}
|
||||
switch type {
|
||||
case .personalDetails:
|
||||
typesString.append(strings.Notification_PassportValuePersonalDetails)
|
||||
case .passport, .internalPassport, .driversLicense, .idCard:
|
||||
if !hasIdentity {
|
||||
typesString.append(strings.Notification_PassportValueProofOfIdentity)
|
||||
hasIdentity = true
|
||||
}
|
||||
case .address:
|
||||
typesString.append(strings.Notification_PassportValueAddress)
|
||||
case .bankStatement, .utilityBill, .rentalAgreement, .passportRegistration, .temporaryRegistration:
|
||||
if !hasAddress {
|
||||
typesString.append(strings.Notification_PassportValueProofOfAddress)
|
||||
hasAddress = true
|
||||
}
|
||||
case .phone:
|
||||
typesString.append(strings.Notification_PassportValuePhone)
|
||||
case .email:
|
||||
typesString.append(strings.Notification_PassportValueEmail)
|
||||
}
|
||||
}
|
||||
attributedString = NSAttributedString(string: strings.Notification_PassportValuesSentMessage(message.peers[message.id.peerId]?.compactDisplayTitle ?? "", typesString).0, font: titleFont, textColor: primaryTextColor)
|
||||
case .peerJoined:
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_Joined(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
|
||||
case .phoneNumberRequest:
|
||||
attributedString = nil
|
||||
case .unknown:
|
||||
attributedString = nil
|
||||
}
|
||||
|
||||
break
|
||||
} else if let expiredMedia = media as? TelegramMediaExpiredContent {
|
||||
switch expiredMedia.data {
|
||||
case .image:
|
||||
attributedString = NSAttributedString(string: strings.Message_ImageExpired, font: titleFont, textColor: primaryTextColor)
|
||||
case .file:
|
||||
attributedString = NSAttributedString(string: strings.Message_VideoExpired, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributedString
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
public func stringForDuration(_ duration: Int32, position: Int32? = nil) -> String {
|
||||
var duration = duration
|
||||
if let position = position {
|
||||
duration = max(0, duration - position)
|
||||
}
|
||||
let hours = duration / 3600
|
||||
let minutes = duration / 60 % 60
|
||||
let seconds = duration % 60
|
||||
let durationString: String
|
||||
if hours > 0 {
|
||||
durationString = String(format: "%d:%02d:%02d", hours, minutes, seconds)
|
||||
} else {
|
||||
durationString = String(format: "%d:%02d", minutes, seconds)
|
||||
}
|
||||
return durationString
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
D0879ACA22F7063300C4D6B3 /* DateFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0879AC922F7063300C4D6B3 /* DateFormat.swift */; };
|
||||
D0879ACC22F7064000C4D6B3 /* TelegramUIPreferences.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0879ACB22F7064000C4D6B3 /* TelegramUIPreferences.framework */; };
|
||||
D0879ACE22F7069200C4D6B3 /* Locale.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0879ACD22F7069200C4D6B3 /* Locale.swift */; };
|
||||
D0C9C3542300AD7000FAB518 /* StringForDuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9C3532300AD7000FAB518 /* StringForDuration.swift */; };
|
||||
D0C9C3562300AD9900FAB518 /* MessageContentKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9C3552300AD9900FAB518 /* MessageContentKind.swift */; };
|
||||
D0C9C3582300ADCF00FAB518 /* ServiceMessageStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9C3572300ADCF00FAB518 /* ServiceMessageStrings.swift */; };
|
||||
D0C9C35A2300AE7200FAB518 /* TextFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C9C3592300AE7200FAB518 /* TextFormat.framework */; };
|
||||
D0C9C35C2300AEBA00FAB518 /* LocalizedPeerData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C9C35B2300AEBA00FAB518 /* LocalizedPeerData.framework */; };
|
||||
D0C9C35E2300AEF100FAB518 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C9C35D2300AEF100FAB518 /* Display.framework */; };
|
||||
D0C9C3622300AFCC00FAB518 /* CurrencyFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9C3612300AFCB00FAB518 /* CurrencyFormat.swift */; };
|
||||
D0C9C3642300AFE800FAB518 /* FrameworkBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C9C3632300AFE800FAB518 /* FrameworkBundle.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -30,6 +38,14 @@
|
||||
D0879AC922F7063300C4D6B3 /* DateFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormat.swift; sourceTree = "<group>"; };
|
||||
D0879ACB22F7064000C4D6B3 /* TelegramUIPreferences.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TelegramUIPreferences.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0879ACD22F7069200C4D6B3 /* Locale.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Locale.swift; sourceTree = "<group>"; };
|
||||
D0C9C3532300AD7000FAB518 /* StringForDuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringForDuration.swift; sourceTree = "<group>"; };
|
||||
D0C9C3552300AD9900FAB518 /* MessageContentKind.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageContentKind.swift; sourceTree = "<group>"; };
|
||||
D0C9C3572300ADCF00FAB518 /* ServiceMessageStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceMessageStrings.swift; sourceTree = "<group>"; };
|
||||
D0C9C3592300AE7200FAB518 /* TextFormat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TextFormat.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0C9C35B2300AEBA00FAB518 /* LocalizedPeerData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LocalizedPeerData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0C9C35D2300AEF100FAB518 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0C9C3612300AFCB00FAB518 /* CurrencyFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyFormat.swift; sourceTree = "<group>"; };
|
||||
D0C9C3632300AFE800FAB518 /* FrameworkBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameworkBundle.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -37,6 +53,9 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0C9C35E2300AEF100FAB518 /* Display.framework in Frameworks */,
|
||||
D0C9C35C2300AEBA00FAB518 /* LocalizedPeerData.framework in Frameworks */,
|
||||
D0C9C35A2300AE7200FAB518 /* TextFormat.framework in Frameworks */,
|
||||
D0879ACC22F7064000C4D6B3 /* TelegramUIPreferences.framework in Frameworks */,
|
||||
D0879AC622F705C300C4D6B3 /* TelegramPresentationData.framework in Frameworks */,
|
||||
D0879AC422F705BE00C4D6B3 /* TelegramCore.framework in Frameworks */,
|
||||
@@ -69,10 +88,15 @@
|
||||
D0879AAF22F7021000C4D6B3 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0C9C3612300AFCB00FAB518 /* CurrencyFormat.swift */,
|
||||
D0C9C3552300AD9900FAB518 /* MessageContentKind.swift */,
|
||||
D0C9C3572300ADCF00FAB518 /* ServiceMessageStrings.swift */,
|
||||
D0C9C3532300AD7000FAB518 /* StringForDuration.swift */,
|
||||
D0879ACD22F7069200C4D6B3 /* Locale.swift */,
|
||||
D0879AC922F7063300C4D6B3 /* DateFormat.swift */,
|
||||
D0879ABC22F705AE00C4D6B3 /* PresenceStrings.swift */,
|
||||
D0879AB022F7021000C4D6B3 /* TelegramStringFormatting.h */,
|
||||
D0C9C3632300AFE800FAB518 /* FrameworkBundle.swift */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
@@ -80,6 +104,9 @@
|
||||
D0879ABE22F705B700C4D6B3 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0C9C35D2300AEF100FAB518 /* Display.framework */,
|
||||
D0C9C35B2300AEBA00FAB518 /* LocalizedPeerData.framework */,
|
||||
D0C9C3592300AE7200FAB518 /* TextFormat.framework */,
|
||||
D0879ACB22F7064000C4D6B3 /* TelegramUIPreferences.framework */,
|
||||
D0879AC522F705C300C4D6B3 /* TelegramPresentationData.framework */,
|
||||
D0879AC322F705BE00C4D6B3 /* TelegramCore.framework */,
|
||||
@@ -169,8 +196,13 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0C9C3642300AFE800FAB518 /* FrameworkBundle.swift in Sources */,
|
||||
D0C9C3622300AFCC00FAB518 /* CurrencyFormat.swift in Sources */,
|
||||
D0C9C3562300AD9900FAB518 /* MessageContentKind.swift in Sources */,
|
||||
D0879ACA22F7063300C4D6B3 /* DateFormat.swift in Sources */,
|
||||
D0879ABD22F705AE00C4D6B3 /* PresenceStrings.swift in Sources */,
|
||||
D0C9C3542300AD7000FAB518 /* StringForDuration.swift in Sources */,
|
||||
D0C9C3582300ADCF00FAB518 /* ServiceMessageStrings.swift in Sources */,
|
||||
D0879ACE22F7069200C4D6B3 /* Locale.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
Reference in New Issue
Block a user