mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			257 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			257 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import Foundation
 | |
| import SignalCoreKit
 | |
| 
 | |
| public extension String {
 | |
|     var glyphCount: Int {
 | |
|         let richText = NSAttributedString(string: self)
 | |
|         let line = CTLineCreateWithAttributedString(richText)
 | |
|         
 | |
|         return CTLineGetGlyphCount(line)
 | |
|     }
 | |
|     
 | |
|     var isSingleAlphabet: Bool {
 | |
|         return (glyphCount == 1 && isAlphabetic)
 | |
|     }
 | |
|     
 | |
|     var isAlphabetic: Bool {
 | |
|         return !isEmpty && range(of: "[^a-zA-Z]", options: .regularExpression) == nil
 | |
|     }
 | |
| 
 | |
|     var isSingleEmoji: Bool {
 | |
|         return (glyphCount == 1 && containsEmoji)
 | |
|     }
 | |
| 
 | |
|     var containsEmoji: Bool {
 | |
|         return unicodeScalars.contains { $0.isEmoji }
 | |
|     }
 | |
| 
 | |
|     var containsOnlyEmoji: Bool {
 | |
|         return (
 | |
|             !isEmpty &&
 | |
|             !unicodeScalars.contains(where: {
 | |
|                 !$0.isEmoji &&
 | |
|                 !$0.isZeroWidthJoiner
 | |
|             })
 | |
|         )
 | |
|     }
 | |
|     
 | |
|     func localized() -> String {
 | |
|         // If the localized string matches the key provided then the localisation failed
 | |
|         let localizedString = NSLocalizedString(self, comment: "")
 | |
|         owsAssertDebug(localizedString != self, "Key \"\(self)\" is not set in Localizable.strings")
 | |
| 
 | |
|         return localizedString
 | |
|     }
 | |
|     
 | |
|     func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
 | |
|         var ranges: [Range<Index>] = []
 | |
|         
 | |
|         while
 | |
|             (ranges.last.map({ $0.upperBound < self.endIndex }) ?? true),
 | |
|             let range = self.range(
 | |
|                 of: substring,
 | |
|                 options: options,
 | |
|                 range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex,
 | |
|                 locale: locale
 | |
|             )
 | |
|         {
 | |
|             ranges.append(range)
 | |
|         }
 | |
|         
 | |
|         return ranges
 | |
|     }
 | |
|     
 | |
|     static func filterNotificationText(_ text: String?) -> String? {
 | |
|         guard let text = text?.filterStringForDisplay() else { return nil }
 | |
| 
 | |
|         // iOS strips anything that looks like a printf formatting character from
 | |
|         // the notification body, so if we want to dispay a literal "%" in a notification
 | |
|         // it must be escaped.
 | |
|         // see https://developer.apple.com/documentation/uikit/uilocalnotification/1616646-alertbody
 | |
|         // for more details.
 | |
|         return text.replacingOccurrences(of: "%", with: "%%")
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - Formatting
 | |
| 
 | |
| extension String.StringInterpolation {
 | |
|     mutating func appendInterpolation(_ value: Int, format: String) {
 | |
|         let result: String = String(format: "%\(format)d", value)
 | |
|         appendLiteral(result)
 | |
|     }
 | |
|     
 | |
|     mutating func appendInterpolation(_ value: Double, format: String, omitZeroDecimal: Bool = false) {
 | |
|         guard !omitZeroDecimal || Int(exactly: value) == nil else {
 | |
|             appendLiteral("\(Int(exactly: value)!)")
 | |
|             return
 | |
|         }
 | |
|         
 | |
|         let result: String = String(format: "%\(format)f", value)
 | |
|         appendLiteral(result)
 | |
|     }
 | |
| }
 | |
| 
 | |
| public extension String {
 | |
|     static func formattedDuration(_ duration: TimeInterval, format: TimeInterval.DurationFormat = .short) -> String {
 | |
|         let secondsPerMinute: TimeInterval = 60
 | |
|         let secondsPerHour: TimeInterval = (secondsPerMinute * 60)
 | |
|         let secondsPerDay: TimeInterval = (secondsPerHour * 24)
 | |
|         let secondsPerWeek: TimeInterval = (secondsPerDay * 7)
 | |
|         
 | |
|         switch format {
 | |
|             case .videoDuration:
 | |
|                 let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60))
 | |
|                 let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60))
 | |
|                 let hours: Int = Int(duration / 3600)
 | |
|                 
 | |
|                 guard hours > 0 else { return String(format: "%02ld:%02ld", minutes, seconds) }
 | |
|                 
 | |
|                 return String(format: "%ld:%02ld:%02ld", hours, minutes, seconds)
 | |
|             
 | |
|             case .hoursMinutesSeconds:
 | |
|                 let seconds: Int = Int(duration.truncatingRemainder(dividingBy: 60))
 | |
|                 let minutes: Int = Int((duration / 60).truncatingRemainder(dividingBy: 60))
 | |
|                 let hours: Int = Int(duration / 3600)
 | |
|                 
 | |
|                 guard hours > 0 else { return String(format: "%ld:%02ld", minutes, seconds) }
 | |
|                 
 | |
|                 return String(format: "%ld:%02ld:%02ld", hours, minutes, seconds)
 | |
|                 
 | |
|             case .short:
 | |
|                 switch duration {
 | |
|                     case 0..<secondsPerMinute:  // Seconds
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_SECONDS_SHORT_FORMAT".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                     
 | |
|                     case secondsPerMinute..<secondsPerHour:   // Minutes
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_MINUTES_SHORT_FORMAT".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case secondsPerHour..<secondsPerDay:   // Hours
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_HOURS_SHORT_FORMAT".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case secondsPerDay..<secondsPerWeek:   // Days
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_DAYS_SHORT_FORMAT".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     default:   // Weeks
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_WEEKS_SHORT_FORMAT".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                 }
 | |
|                 
 | |
|             case .long:
 | |
|                 switch duration {
 | |
|                     case 0..<secondsPerMinute:  // XX Seconds
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_SECONDS".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                     
 | |
|                     case secondsPerMinute..<(secondsPerMinute * 1.5):   // 1 Minute
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_SINGLE_MINUTE".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case (secondsPerMinute * 1.5)..<secondsPerHour:   // Multiple Minutes
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_MINUTES".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerMinute)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case secondsPerHour..<(secondsPerHour * 1.5):   // 1 Hour
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_SINGLE_HOUR".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case (secondsPerHour * 1.5)..<secondsPerDay:   // Multiple Hours
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_HOURS".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerHour)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case secondsPerDay..<(secondsPerDay * 1.5):   // 1 Day
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_SINGLE_DAY".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case (secondsPerDay * 1.5)..<secondsPerWeek:   // Multiple Days
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_DAYS".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerDay)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     case secondsPerWeek..<(secondsPerWeek * 1.5):   // 1 Week
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_SINGLE_WEEK".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                         
 | |
|                     default:   // Multiple Weeks
 | |
|                         return String(
 | |
|                             format: "TIME_AMOUNT_WEEKS".localized(),
 | |
|                             NumberFormatter.localizedString(
 | |
|                                 from: NSNumber(floatLiteral: floor(duration / secondsPerWeek)),
 | |
|                                 number: .none
 | |
|                             )
 | |
|                         )
 | |
|                 }
 | |
|         }
 | |
|     }
 | |
| }
 |