session-ios/SessionUtilitiesKit/General/String+Utilities.swift

244 lines
10 KiB
Swift
Raw Normal View History

// 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
}
Merge branch 'feature/session-id-blinding-part-2' into feature/database-refactor # Conflicts: # Podfile # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Closed Groups/EditClosedGroupVC.swift # Session/Closed Groups/NewClosedGroupVC.swift # Session/Conversations/Context Menu/ContextMenuVC+Action.swift # Session/Conversations/Context Menu/ContextMenuVC.swift # Session/Conversations/ConversationMessageMapping.swift # Session/Conversations/ConversationSearch.swift # Session/Conversations/ConversationVC+Interaction.swift # Session/Conversations/ConversationVC.swift # Session/Conversations/ConversationViewItem.h # Session/Conversations/ConversationViewItem.m # Session/Conversations/ConversationViewModel.m # Session/Conversations/Input View/InputView.swift # Session/Conversations/Input View/MentionSelectionView.swift # Session/Conversations/LongTextViewController.swift # Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift # Session/Conversations/Message Cells/MessageCell.swift # Session/Conversations/Message Cells/VisibleMessageCell.swift # Session/Conversations/Settings/OWSConversationSettingsViewController.m # Session/Conversations/Views & Modals/ConversationTitleView.swift # Session/Conversations/Views & Modals/DownloadAttachmentModal.swift # Session/Conversations/Views & Modals/JoinOpenGroupModal.swift # Session/Conversations/Views & Modals/LinkPreviewModal.swift # Session/Conversations/Views & Modals/MessagesTableView.swift # Session/Conversations/Views & Modals/URLModal.swift # Session/Home/GlobalSearch/GlobalSearchViewController.swift # Session/Home/HomeVC.swift # Session/Home/Message Requests/MessageRequestsViewController.swift # Session/Media Viewing & Editing/MediaDetailViewController.m # Session/Media Viewing & Editing/MediaPageViewController.swift # Session/Meta/AppDelegate.m # Session/Meta/AppDelegate.swift # Session/Meta/AppEnvironment.swift # Session/Meta/Signal-Bridging-Header.h # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Notifications/AppNotifications.swift # Session/Open Groups/JoinOpenGroupVC.swift # Session/Settings/NukeDataModal.swift # Session/Settings/SeedModal.swift # Session/Settings/SettingsVC.swift # Session/Settings/ShareLogsModal.swift # Session/Shared/ConversationCell.swift # Session/Shared/UserSelectionVC.swift # Session/Utilities/BackgroundPoller.swift # Session/Utilities/MentionUtilities.swift # Session/Utilities/MockDataGenerator.swift # SessionMessagingKit/Database/OWSPrimaryStorage.m # SessionMessagingKit/Database/SSKPreferences.swift # SessionMessagingKit/Database/Storage+Contacts.swift # SessionMessagingKit/Database/Storage+Jobs.swift # SessionMessagingKit/Database/Storage+Messaging.swift # SessionMessagingKit/Database/Storage+OpenGroups.swift # SessionMessagingKit/Database/TSDatabaseView.m # SessionMessagingKit/File Server/FileServerAPIV2.swift # SessionMessagingKit/Jobs/AttachmentDownloadJob.swift # SessionMessagingKit/Jobs/AttachmentUploadJob.swift # SessionMessagingKit/Jobs/JobQueue.swift # SessionMessagingKit/Jobs/MessageReceiveJob.swift # SessionMessagingKit/Jobs/MessageSendJob.swift # SessionMessagingKit/Jobs/NotifyPNServerJob.swift # SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift # SessionMessagingKit/Messages/Control Messages/ConfigurationMessage+Convenience.swift # SessionMessagingKit/Messages/Message+Destination.swift # SessionMessagingKit/Messages/Signal/TSIncomingMessage.h # SessionMessagingKit/Messages/Signal/TSIncomingMessage.m # SessionMessagingKit/Messages/Signal/TSInfoMessage.h # SessionMessagingKit/Messages/Signal/TSInfoMessage.m # SessionMessagingKit/Messages/Signal/TSInteraction.h # SessionMessagingKit/Messages/Signal/TSInteraction.m # SessionMessagingKit/Messages/Signal/TSMessage.h # SessionMessagingKit/Messages/Signal/TSMessage.m # SessionMessagingKit/Open Groups/OpenGroupAPIV2+ObjC.swift # SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift # SessionMessagingKit/Open Groups/OpenGroupManagerV2.swift # SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift # SessionMessagingKit/Sending & Receiving/Mentions/MentionsManager.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver.swift # SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift # SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Notifications/NotificationsProtocol.h # SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupPollerV2.swift # SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift # SessionMessagingKit/Storage.swift # SessionMessagingKit/Threads/Notification+Thread.swift # SessionMessagingKit/Threads/TSContactThread.h # SessionMessagingKit/Threads/TSContactThread.m # SessionMessagingKit/Threads/TSGroupModel.h # SessionMessagingKit/Threads/TSGroupModel.m # SessionMessagingKit/Threads/TSGroupThread.m # SessionMessagingKit/Utilities/General.swift # SessionNotificationServiceExtension/NSENotificationPresenter.swift # SessionNotificationServiceExtension/NotificationServiceExtension.swift # SessionSnodeKit/OnionRequestAPI+Encryption.swift # SessionSnodeKit/OnionRequestAPI.swift # SessionSnodeKit/SnodeAPI.swift # SessionSnodeKit/SnodeMessage.swift # SessionSnodeKit/Storage+SnodeAPI.swift # SessionSnodeKit/Storage.swift # SessionUtilitiesKit/General/Array+Utilities.swift # SessionUtilitiesKit/General/Dictionary+Utilities.swift # SessionUtilitiesKit/General/SNUserDefaults.swift # SessionUtilitiesKit/General/Set+Utilities.swift # SessionUtilitiesKit/Meta/SessionUtilitiesKit.h # SessionUtilitiesKit/Utilities/Optional+Utilities.swift # SessionUtilitiesKit/Utilities/Sodium+Conversion.swift # SignalUtilitiesKit/Configuration.swift # SignalUtilitiesKit/Database/Migrations/OpenGroupServerIdLookupMigration.swift # SignalUtilitiesKit/Messaging/FullTextSearcher.swift # SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift # SignalUtilitiesKit/Profile Pictures/Identicon+ObjC.swift # SignalUtilitiesKit/To Do/OWSProfileManager.m # SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift # SignalUtilitiesKit/Utilities/UIView+OWS.swift
2022-06-08 06:29:51 +02:00
func dataFromHex() -> Data? {
guard self.count > 0 && (self.count % 2) == 0 else { return nil }
let chars = self.map { $0 }
let bytes: [UInt8] = stride(from: 0, to: chars.count, by: 2)
.map { index -> String in String(chars[index]) + String(chars[index + 1]) }
.compactMap { (str: String) -> UInt8? in UInt8(str, radix: 16) }
guard bytes.count > 0 else { return nil }
guard (self.count / bytes.count) == 2 else { return nil }
return Data(bytes)
}
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
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
)
)
}
}
}
}