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.
244 lines
10 KiB
Swift
244 lines
10 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
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: - Convenience
|
|
|
|
public extension String {
|
|
/// Initialize with an optional pointer and a spoecific length
|
|
init?(pointer: UnsafeRawPointer?, length: Int, encoding: String.Encoding = .utf8) {
|
|
guard
|
|
let pointer: UnsafeRawPointer = pointer,
|
|
let result: String = String(data: Data(bytes: pointer, count: length), encoding: encoding)
|
|
else { return nil }
|
|
|
|
self = result
|
|
}
|
|
}
|
|
|
|
// MARK: - Formatting
|
|
|
|
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 .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
|
|
)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|