From bb596dba98bc9d83908ccb68f2825a72d02f6b90 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 21 Mar 2018 17:11:38 -0400 Subject: [PATCH] Add screen lock feature. --- .../PrivacySettingsTableViewController.m | 93 ++++++++---- Signal/src/util/OWSScreenLock.swift | 2 +- Signal/src/util/OWSScreenLockUI.m | 13 +- .../translations/en.lproj/Localizable.strings | 3 + SignalMessaging/SignalMessaging-Prefix.pch | 1 + .../DisappearingTimerConfigurationView.swift | 4 +- .../OWSDisappearingMessagesConfiguration.h | 2 - .../OWSDisappearingMessagesConfiguration.m | 117 +--------------- ...sappearingConfigurationUpdateInfoMessage.m | 5 +- SignalServiceKit/src/Util/NSString+SSK.h | 2 + SignalServiceKit/src/Util/NSString+SSK.m | 132 ++++++++++++++++++ 11 files changed, 221 insertions(+), 153 deletions(-) diff --git a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m index b0ec05742..15eee7b83 100644 --- a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m +++ b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m @@ -9,6 +9,7 @@ #import #import #import +#import #import #import @@ -53,19 +54,20 @@ NS_ASSUME_NONNULL_BEGIN __weak PrivacySettingsTableViewController *weakSelf = self; - [contents - addSection:[OWSTableSection - sectionWithTitle:nil - items:@[ - [OWSTableItem disclosureItemWithText: - NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", - @"Label for the block list section of the settings view") - actionBlock:^{ - [weakSelf showBlocklist]; - }], - ]]]; + OWSTableSection *blocklistSection = [OWSTableSection new]; + blocklistSection.headerTitle + = NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", @"Label for the block list section of the settings view"); + [blocklistSection + addItem:[OWSTableItem disclosureItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_TITLE", + @"Label for the block list section of the settings view") + actionBlock:^{ + [weakSelf showBlocklist]; + }]]; + [contents addSection:blocklistSection]; OWSTableSection *readReceiptsSection = [OWSTableSection new]; + readReceiptsSection.headerTitle + = NSLocalizedString(@"SETTINGS_READ_RECEIPT", @"Label for the 'read receipts' setting."); readReceiptsSection.footerTitle = NSLocalizedString( @"SETTINGS_READ_RECEIPTS_SECTION_FOOTER", @"An explanation of the 'read receipts' setting."); [readReceiptsSection @@ -76,6 +78,36 @@ NS_ASSUME_NONNULL_BEGIN selector:@selector(didToggleReadReceiptsSwitch:)]]; [contents addSection:readReceiptsSection]; + OWSTableSection *screenLockSection = [OWSTableSection new]; + screenLockSection.headerTitle = NSLocalizedString( + @"SETTINGS_SCREEN_LOCK_SECTION_TITLE", @"Title for the 'screen lock' section of the privacy settings."); + screenLockSection.footerTitle = NSLocalizedString( + @"SETTINGS_SCREEN_LOCK_SECTION_FOOTER", @"Footer for the 'screen lock' section of the privacy settings."); + [screenLockSection + addItem:[OWSTableItem + switchItemWithText:NSLocalizedString(@"SETTINGS_SCREEN_LOCK_SWITCH_LABEL", + @"Label for the 'enable screen lock' switch of the privacy settings.") + isOn:OWSScreenLock.sharedManager.isScreenLockEnabled + target:self + selector:@selector(isScreenLockEnabledDidChange:)]]; + [contents addSection:screenLockSection]; + + if (OWSScreenLock.sharedManager.isScreenLockEnabled) { + OWSTableSection *screenLockTimeoutSection = [OWSTableSection new]; + uint32_t screenLockTimeout = (uint32_t)round(OWSScreenLock.sharedManager.screenLockTimeout); + NSString *screenLockTimeoutString = [NSString formatDurationSeconds:screenLockTimeout useShortFormat:YES]; + [screenLockTimeoutSection + addItem:[OWSTableItem + disclosureItemWithText: + NSLocalizedString(@"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT", + @"Label for the 'screen lock activity timeout' setting of the privacy settings.") + detailText:screenLockTimeoutString + actionBlock:^{ + [weakSelf showScreenLockTimeoutUI]; + }]]; + [contents addSection:screenLockTimeoutSection]; + } + OWSTableSection *screenSecuritySection = [OWSTableSection new]; screenSecuritySection.headerTitle = NSLocalizedString(@"SETTINGS_SECURITY_TITLE", @"Section header"); screenSecuritySection.footerTitle = NSLocalizedString(@"SETTINGS_SCREEN_SECURITY_DETAIL", nil); @@ -167,20 +199,6 @@ NS_ASSUME_NONNULL_BEGIN }]]; [contents addSection:twoFactorAuthSection]; - OWSTableSection *screenLockSection = [OWSTableSection new]; - screenLockSection.headerTitle = NSLocalizedString( - @"SETTINGS_SCREEN_LOCK_SECTION_TITLE", @"Title for the 'screen lock' section of the privacy settings."); - screenLockSection.footerTitle = NSLocalizedString( - @"SETTINGS_SCREEN_LOCK_SECTION_FOOTER", @"Footer for the 'screen lock' section of the privacy settings."); - [screenLockSection - addItem:[OWSTableItem - switchItemWithText:NSLocalizedString(@"SETTINGS_SCREEN_LOCK_SWITCH_LABEL", - @"Label for the 'enable screen lock' switch of the privacy settings.") - isOn:OWSScreenLock.sharedManager.isScreenLockEnabled - target:self - selector:@selector(isScreenLockEnabledDidChange:)]]; - [contents addSection:screenLockSection]; - self.contents = contents; } @@ -328,6 +346,31 @@ NS_ASSUME_NONNULL_BEGIN [self updateTableContents]; } +- (void)showScreenLockTimeoutUI +{ + DDLogInfo(@"%@ %s", self.logTag, __PRETTY_FUNCTION__); + + UIAlertController *controller = [UIAlertController + alertControllerWithTitle:NSLocalizedString(@"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT", + @"Label for the 'screen lock activity timeout' setting of the privacy settings.") + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + for (NSNumber *timeoutValue in OWSScreenLock.sharedManager.screenLockTimeouts) { + uint32_t screenLockTimeout = (uint32_t)round(timeoutValue.doubleValue); + NSString *screenLockTimeoutString = [NSString formatDurationSeconds:screenLockTimeout useShortFormat:NO]; + + [controller addAction:[UIAlertAction actionWithTitle:screenLockTimeoutString + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [OWSScreenLock.sharedManager + setScreenLockTimeout:screenLockTimeout]; + }]]; + } + [controller addAction:[OWSAlerts cancelAction]]; + UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController]; + [fromViewController presentViewController:controller animated:YES completion:nil]; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/util/OWSScreenLock.swift b/Signal/src/util/OWSScreenLock.swift index 926d6f9eb..5b3818da6 100644 --- a/Signal/src/util/OWSScreenLock.swift +++ b/Signal/src/util/OWSScreenLock.swift @@ -84,7 +84,7 @@ import LocalAuthentication return self.dbConnection.double(forKey: OWSScreenLock_Key_ScreenLockTimeoutSeconds, inCollection: OWSScreenLock_Collection, defaultValue: defaultTimeout) } - private func setIsScreenLockEnabled(value: TimeInterval) { + @objc public func setScreenLockTimeout(_ value: TimeInterval) { AssertIsOnMainThread() assert(OWSStorage.isStorageReady()) diff --git a/Signal/src/util/OWSScreenLockUI.m b/Signal/src/util/OWSScreenLockUI.m index e6412bf08..0d0e6ec99 100644 --- a/Signal/src/util/OWSScreenLockUI.m +++ b/Signal/src/util/OWSScreenLockUI.m @@ -116,12 +116,6 @@ NS_ASSUME_NONNULL_BEGIN return; } - // Don't show 'Screen Protection' if: - // - // * App is active or... - // * 'Screen Protection' is not enabled. - BOOL shouldHaveScreenProtection = (self.appIsInactive && Environment.preferences.screenSecurityIsEnabled); - BOOL shouldHaveScreenLock = NO; if (self.appIsInactive) { // Don't show 'Screen Lock' if app is inactive. @@ -150,6 +144,13 @@ NS_ASSUME_NONNULL_BEGIN } } + // Show 'Screen Protection' if: + // + // * App is inactive and... + // * Either 'Screen Protection' or 'Screen Lock' is enabled. + BOOL shouldHaveScreenProtection = (self.appIsInactive + && (Environment.preferences.screenSecurityIsEnabled || OWSScreenLock.sharedManager.isScreenLockEnabled)); + BOOL shouldShowBlockWindow = shouldHaveScreenProtection || shouldHaveScreenLock; DDLogVerbose(@"%@, shouldHaveScreenProtection: %d, shouldHaveScreenLock: %d, shouldShowBlockWindow: %d", self.logTag, diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 7f32d028e..d20700716 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -1804,6 +1804,9 @@ /* Remove metadata section header */ "SETTINGS_REMOVE_METADATA_TITLE" = "Metadata"; +/* Label for the 'screen lock activity timeout' setting of the privacy settings. */ +"SETTINGS_SCREEN_LOCK_ACTIVITY_TIMEOUT" = "Screen Lock Timeout"; + /* Footer for the 'screen lock' section of the privacy settings. */ "SETTINGS_SCREEN_LOCK_SECTION_FOOTER" = "Lock Signal access with iOS Touch ID or Face ID."; diff --git a/SignalMessaging/SignalMessaging-Prefix.pch b/SignalMessaging/SignalMessaging-Prefix.pch index bf34561b0..31f15e44a 100644 --- a/SignalMessaging/SignalMessaging-Prefix.pch +++ b/SignalMessaging/SignalMessaging-Prefix.pch @@ -21,4 +21,5 @@ #import #import #import + #import #endif diff --git a/SignalMessaging/Views/DisappearingTimerConfigurationView.swift b/SignalMessaging/Views/DisappearingTimerConfigurationView.swift index f5cc7128d..3953c9638 100644 --- a/SignalMessaging/Views/DisappearingTimerConfigurationView.swift +++ b/SignalMessaging/Views/DisappearingTimerConfigurationView.swift @@ -57,7 +57,7 @@ public class DisappearingTimerConfigurationView: UIView { imageView.contentMode = .scaleAspectFit self.label = UILabel() - label.text = OWSDisappearingMessagesConfiguration.string(forDurationSeconds: durationSeconds, useShortFormat: true) + label.text = NSString.formatDurationSeconds(durationSeconds, useShortFormat: true) label.font = UIFont.systemFont(ofSize: 10) label.textColor = UIColor.white label.textAlignment = .center @@ -81,7 +81,7 @@ public class DisappearingTimerConfigurationView: UIView { // Accessability self.accessibilityLabel = NSLocalizedString("DISAPPEARING_MESSAGES_LABEL", comment: "Accessibility label for disappearing messages") let hintFormatString = NSLocalizedString("DISAPPEARING_MESSAGES_HINT", comment: "Accessibility hint that contains current timeout information") - let durationString = OWSDisappearingMessagesConfiguration.string(forDurationSeconds: durationSeconds, useShortFormat: false) + let durationString = NSString.formatDurationSeconds(durationSeconds, useShortFormat: false) self.accessibilityHint = String(format: hintFormatString, durationString) // Layout diff --git a/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.h b/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.h index 1f3add501..dc3bdbf35 100644 --- a/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.h +++ b/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.h @@ -25,8 +25,6 @@ NS_ASSUME_NONNULL_BEGIN + (NSArray *)validDurationsSeconds; -+ (NSString *)stringForDurationSeconds:(uint32_t)durationSeconds useShortFormat:(BOOL)useShortFormat; - @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m b/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m index ba3583497..0ca190426 100644 --- a/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m +++ b/SignalServiceKit/src/Contacts/OWSDisappearingMessagesConfiguration.m @@ -4,6 +4,7 @@ #import "OWSDisappearingMessagesConfiguration.h" #import "NSDate+OWS.h" +#import "NSString+SSK.h" NS_ASSUME_NONNULL_BEGIN @@ -59,120 +60,6 @@ NS_ASSUME_NONNULL_BEGIN } } -+ (NSString *)stringForDurationSeconds:(uint32_t)durationSeconds useShortFormat:(BOOL)useShortFormat -{ - NSString *amountFormat; - uint32_t duration; - - uint32_t secondsPerMinute = 60; - uint32_t secondsPerHour = secondsPerMinute * 60; - uint32_t secondsPerDay = secondsPerHour * 24; - uint32_t secondsPerWeek = secondsPerDay * 7; - - if (durationSeconds < secondsPerMinute) { // XX Seconds - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_SECONDS_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_SECONDS", - @"{{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds; - } else if (durationSeconds < secondsPerMinute * 1.5) { // 1 Minute - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_MINUTE", - @"{{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings"); - } - duration = durationSeconds / secondsPerMinute; - } else if (durationSeconds < secondsPerHour) { // Multiple Minutes - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES", - @"{{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds / secondsPerMinute; - } else if (durationSeconds < secondsPerHour * 1.5) { // 1 Hour - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_HOUR", - @"{{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds / secondsPerHour; - } else if (durationSeconds < secondsPerDay) { // Multiple Hours - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS", - @"{{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds / secondsPerHour; - } else if (durationSeconds < secondsPerDay * 1.5) { // 1 Day - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_DAY", - @"{{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{1 day}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds / secondsPerDay; - } else if (durationSeconds < secondsPerWeek) { // Multiple Days - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS", - @"{{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{5 days}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds / secondsPerDay; - } else if (durationSeconds < secondsPerWeek * 1.5) { // 1 Week - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_WEEK", - @"{{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{1 week}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds / secondsPerWeek; - } else { // Multiple weeks - if (useShortFormat) { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS_SHORT_FORMAT", - @"Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 characters, The space is intentionally ommitted between the text and the embedded duration so that we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings"); - } else { - amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS", - @"{{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages " - @"expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings"); - } - - duration = durationSeconds / secondsPerWeek; - } - - return [NSString stringWithFormat:amountFormat, [NSNumberFormatter localizedStringFromNumber:@(duration) - numberStyle:NSNumberFormatterNoStyle]]; -} - + (NSArray *)validDurationsSeconds { return @[ @(5), @@ -195,7 +82,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSString *)durationString { - return [self.class stringForDurationSeconds:self.durationSeconds useShortFormat:NO]; + return [NSString formatDurationSeconds:self.durationSeconds useShortFormat:NO]; } #pragma mark - Dirty Tracking diff --git a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m index eec9f356b..12b7006d2 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSDisappearingConfigurationUpdateInfoMessage.m @@ -3,6 +3,7 @@ // #import "OWSDisappearingConfigurationUpdateInfoMessage.h" +#import "NSString+SSK.h" #import "OWSDisappearingMessagesConfiguration.h" NS_ASSUME_NONNULL_BEGIN @@ -58,7 +59,7 @@ NS_ASSUME_NONNULL_BEGIN @"strings for context."); NSString *durationString = - [OWSDisappearingMessagesConfiguration stringForDurationSeconds:self.configurationDurationSeconds useShortFormat:NO]; + [NSString formatDurationSeconds:self.configurationDurationSeconds useShortFormat:NO]; return [NSString stringWithFormat:infoFormat, self.createdByRemoteName, durationString]; } else { NSString *infoFormat = NSLocalizedString(@"OTHER_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION", @@ -71,7 +72,7 @@ NS_ASSUME_NONNULL_BEGIN @"Info message embedding a {{time amount}}, see the *_TIME_AMOUNT strings for context."); NSString *durationString = - [OWSDisappearingMessagesConfiguration stringForDurationSeconds:self.configurationDurationSeconds useShortFormat:NO]; + [NSString formatDurationSeconds:self.configurationDurationSeconds useShortFormat:NO]; return [NSString stringWithFormat:infoFormat, durationString]; } else { return NSLocalizedString(@"YOU_DISABLED_DISAPPEARING_MESSAGES_CONFIGURATION", diff --git a/SignalServiceKit/src/Util/NSString+SSK.h b/SignalServiceKit/src/Util/NSString+SSK.h index 88ffba9df..f156c8992 100644 --- a/SignalServiceKit/src/Util/NSString+SSK.h +++ b/SignalServiceKit/src/Util/NSString+SSK.h @@ -14,6 +14,8 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)isValidE164; ++ (NSString *)formatDurationSeconds:(uint32_t)durationSeconds useShortFormat:(BOOL)useShortFormat; + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSString+SSK.m b/SignalServiceKit/src/Util/NSString+SSK.m index 7eb4e3ac0..f42c27299 100644 --- a/SignalServiceKit/src/Util/NSString+SSK.m +++ b/SignalServiceKit/src/Util/NSString+SSK.m @@ -234,6 +234,138 @@ NS_ASSUME_NONNULL_BEGIN return [regex rangeOfFirstMatchInString:self options:0 range:NSMakeRange(0, self.length)].location != NSNotFound; } ++ (NSString *)formatDurationSeconds:(uint32_t)durationSeconds useShortFormat:(BOOL)useShortFormat +{ + NSString *amountFormat; + uint32_t duration; + + uint32_t secondsPerMinute = 60; + uint32_t secondsPerHour = secondsPerMinute * 60; + uint32_t secondsPerDay = secondsPerHour * 24; + uint32_t secondsPerWeek = secondsPerDay * 7; + + if (durationSeconds < secondsPerMinute) { // XX Seconds + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SECONDS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of seconds}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5s' not '5 s'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SECONDS", + @"{{number of seconds}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 seconds}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds; + } else if (durationSeconds < secondsPerMinute * 1.5) { // 1 Minute + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_MINUTE", + @"{{1 minute}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 minute}}'. See other *_TIME_AMOUNT strings"); + } + duration = durationSeconds / secondsPerMinute; + } else if (durationSeconds < secondsPerHour) { // Multiple Minutes + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of minutes}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5m' not '5 m'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_MINUTES", + @"{{number of minutes}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 minutes}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerMinute; + } else if (durationSeconds < secondsPerHour * 1.5) { // 1 Hour + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_HOUR", + @"{{1 hour}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 hour}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerHour; + } else if (durationSeconds < secondsPerDay) { // Multiple Hours + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of hours}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5h' not '5 h'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_HOURS", + @"{{number of hours}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 hours}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerHour; + } else if (durationSeconds < secondsPerDay * 1.5) { // 1 Day + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_DAY", + @"{{1 day}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 day}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerDay; + } else if (durationSeconds < secondsPerWeek) { // Multiple Days + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of days}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5d' not '5 d'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_DAYS", + @"{{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 days}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerDay; + } else if (durationSeconds < secondsPerWeek * 1.5) { // 1 Week + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_SINGLE_WEEK", + @"{{1 week}} embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{1 week}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerWeek; + } else { // Multiple weeks + if (useShortFormat) { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS_SHORT_FORMAT", + @"Label text below navbar button, embeds {{number of weeks}}. Must be very short, like 1 or 2 " + @"characters, The space is intentionally ommitted between the text and the embedded duration so that " + @"we get, e.g. '5w' not '5 w'. See other *_TIME_AMOUNT strings"); + } else { + amountFormat = NSLocalizedString(@"TIME_AMOUNT_WEEKS", + @"{{number of weeks}}, embedded in strings, e.g. 'Alice updated disappearing messages " + @"expiration to {{5 weeks}}'. See other *_TIME_AMOUNT strings"); + } + + duration = durationSeconds / secondsPerWeek; + } + + return [NSString stringWithFormat:amountFormat, + [NSNumberFormatter localizedStringFromNumber:@(duration) numberStyle:NSNumberFormatterNoStyle]]; +} + @end NS_ASSUME_NONNULL_END