Fixed build errors and cleaned up Dependencies interface further

Fixed a build error
Cleaned up the Dependencies interface for UserDefaults and Storage settings
Refactored the AppVersion class to be in Swift
This commit is contained in:
Morgan Pretty 2023-09-19 18:13:37 +10:00
parent 7b04a4b888
commit 3abeeffd3d
53 changed files with 606 additions and 556 deletions

View File

@ -280,7 +280,7 @@
C328255225CA64470062D0A7 /* ContextMenuVC+ActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328255125CA64470062D0A7 /* ContextMenuVC+ActionView.swift */; };
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */; };
C32C599E256DB02B003C73A2 /* TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */; };
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */; };
C32C5A24256DB7DB003C73A2 /* UserDefaultsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB6B255A580F00E217F9 /* UserDefaultsInfo.swift */; };
C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */; };
C32C5A88256DBCF9003C73A2 /* MessageReceiver+LegacyClosedGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = C32C5A87256DBCF9003C73A2 /* MessageReceiver+LegacyClosedGroups.swift */; };
C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB75255A581000E217F9 /* AppReadiness.m */; };
@ -291,8 +291,6 @@
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDAFD255A580600E217F9 /* LRUCache.swift */; };
C32C5DDB256DD9FF003C73A2 /* ContentProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB68255A580F00E217F9 /* ContentProxy.swift */; };
C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA7A255A57FB00E217F9 /* NSRegularExpression+SSK.swift */; };
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */; };
C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33100282559000A00070591 /* UIView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33100272559000A00070591 /* UIView+Utilities.swift */; };
C331FF1F2558F9D300070591 /* SessionUIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C331FF1D2558F9D300070591 /* SessionUIKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
C331FF222558F9D300070591 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; };
@ -316,11 +314,9 @@
C33FD9C4255A54EF00E217F9 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; };
C33FD9C5255A54EF00E217F9 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; };
C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */; };
C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA8B255A57FD00E217F9 /* AppVersion.m */; };
C33FDC58255A582000E217F9 /* ReverseDispatchQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */; };
C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDADE255A580400E217F9 /* SwiftSingletons.swift */; };
C33FDD03255A582000E217F9 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB49255A580C00E217F9 /* WeakTimer.swift */; };
C33FDD06255A582000E217F9 /* AppVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB4C255A580D00E217F9 /* AppVersion.h */; settings = {ATTRIBUTES = (Public, ); }; };
C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB69255A580F00E217F9 /* FeatureFlags.swift */; };
C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB80255A581100E217F9 /* Notification+Loki.swift */; };
C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8F255A581200E217F9 /* ParamParser.swift */; };
@ -495,6 +491,7 @@
FD09C5EC282B8F18000CE219 /* AttachmentError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09C5EB282B8F17000CE219 /* AttachmentError.swift */; };
FD0B77B029B69A65009169BA /* TopBannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0B77AF29B69A65009169BA /* TopBannerController.swift */; };
FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */; };
FD0E353C2AB9880B006A81F7 /* AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0E353A2AB98773006A81F7 /* AppVersion.swift */; };
FD16AB5B2A1DD7CA0083D849 /* PlaceholderIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */; };
FD16AB5F2A1DD98F0083D849 /* ProfilePictureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */; };
FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */; };
@ -1502,7 +1499,6 @@
C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityManager.swift; sourceTree = "<group>"; };
C33FDA7A255A57FB00E217F9 /* NSRegularExpression+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSRegularExpression+SSK.swift"; sourceTree = "<group>"; };
C33FDA87255A57FC00E217F9 /* TypingIndicators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypingIndicators.swift; sourceTree = "<group>"; };
C33FDA8B255A57FD00E217F9 /* AppVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppVersion.m; sourceTree = "<group>"; };
C33FDA8E255A57FD00E217F9 /* OWSFileSystem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSFileSystem.m; sourceTree = "<group>"; };
C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseDispatchQueue.swift; sourceTree = "<group>"; };
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = "<group>"; };
@ -1523,14 +1519,11 @@
C33FDB3F255A580C00E217F9 /* String+SSK.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SSK.swift"; sourceTree = "<group>"; };
C33FDB41255A580C00E217F9 /* MIMETypeUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIMETypeUtil.m; sourceTree = "<group>"; };
C33FDB49255A580C00E217F9 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = "<group>"; };
C33FDB4C255A580D00E217F9 /* AppVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppVersion.h; sourceTree = "<group>"; };
C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSUserDefaults+OWS.h"; sourceTree = "<group>"; };
C33FDB54255A580D00E217F9 /* DataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataSource.h; sourceTree = "<group>"; };
C33FDB68255A580F00E217F9 /* ContentProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentProxy.swift; sourceTree = "<group>"; };
C33FDB69255A580F00E217F9 /* FeatureFlags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeatureFlags.swift; sourceTree = "<group>"; };
C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SNUserDefaults.swift; sourceTree = "<group>"; };
C33FDB6B255A580F00E217F9 /* UserDefaultsInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDefaultsInfo.swift; sourceTree = "<group>"; };
C33FDB75255A581000E217F9 /* AppReadiness.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppReadiness.m; sourceTree = "<group>"; };
C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSUserDefaults+OWS.m"; sourceTree = "<group>"; };
C33FDB80255A581100E217F9 /* Notification+Loki.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Notification+Loki.swift"; sourceTree = "<group>"; };
C33FDB81255A581100E217F9 /* UIImage+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+OWS.m"; sourceTree = "<group>"; };
C33FDB85255A581100E217F9 /* AppContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppContext.m; sourceTree = "<group>"; };
@ -1737,6 +1730,7 @@
FD09C5EB282B8F17000CE219 /* AttachmentError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentError.swift; sourceTree = "<group>"; };
FD0B77AF29B69A65009169BA /* TopBannerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopBannerController.swift; sourceTree = "<group>"; };
FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayUtilitiesSpec.swift; sourceTree = "<group>"; };
FD0E353A2AB98773006A81F7 /* AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersion.swift; sourceTree = "<group>"; };
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfilePictureView+Convenience.swift"; sourceTree = "<group>"; };
FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; };
FD17D79827F40AB800122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
@ -2776,12 +2770,9 @@
C352A3762557859C00338F3E /* NSTimer+Proxying.h */,
C352A36C2557858D00338F3E /* NSTimer+Proxying.m */,
7B1D74AF27C365960030B423 /* Timer+MainThread.swift */,
C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */,
C33FDB77255A581000E217F9 /* NSUserDefaults+OWS.m */,
C33FDB14255A580800E217F9 /* OWSMath.h */,
FD705A91278D051200F16121 /* ReusableView.swift */,
FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */,
C33FDB6B255A580F00E217F9 /* SNUserDefaults.swift */,
FD77289D284EF1C50018502F /* Sodium+Utilities.swift */,
C33FDB3F255A580C00E217F9 /* String+SSK.swift */,
C3C2AC2D2553CBEB00C340D1 /* String+Trimming.swift */,
@ -3543,8 +3534,6 @@
C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */,
C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */,
C33FDA6F255A57FA00E217F9 /* ReachabilityManager.swift */,
C33FDB4C255A580D00E217F9 /* AppVersion.h */,
C33FDA8B255A57FD00E217F9 /* AppVersion.m */,
C38EF3E4255B6DF4007E1867 /* CommonStrings.swift */,
C38EF304255B6DBE007E1867 /* ImageCache.swift */,
C38EF2F2255B6DBC007E1867 /* Searcher.swift */,
@ -3714,6 +3703,7 @@
FD09796527F6B0A800936362 /* Utilities */ = {
isa = PBXGroup;
children = (
FD0E353A2AB98773006A81F7 /* AppVersion.swift */,
FDFBB74A2A1EFF4900CA7350 /* Bencode.swift */,
FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */,
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */,
@ -4531,6 +4521,7 @@
FD23CE2F2A67B8820000B97C /* CacheInfo.swift */,
FDC6D75F2862B3F600B04575 /* Dependencies.swift */,
FDF01FAC2A9ECC4200CAF969 /* SingletonInfo.swift */,
C33FDB6B255A580F00E217F9 /* UserDefaultsInfo.swift */,
);
path = "Dependency Injection";
sourceTree = "<group>";
@ -4687,7 +4678,6 @@
files = (
C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */,
C33FD9AF255A548A00E217F9 /* SignalUtilitiesKit.h in Headers */,
C33FDD06255A582000E217F9 /* AppVersion.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4713,7 +4703,6 @@
C352A3772557864000338F3E /* NSTimer+Proxying.h in Headers */,
FD30036A2A3ADEC100B5A5FB /* CExceptionHelper.h in Headers */,
C3C2A67D255388CC00C340D1 /* SessionUtilitiesKit.h in Headers */,
C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */,
B8856D8D256F1502001CE70E /* UIView+OWS.h in Headers */,
FD52090128AF61BA006098F6 /* OWSBackgroundTask.h in Headers */,
);
@ -5828,7 +5817,6 @@
C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */,
C38EF407255B6DF7007E1867 /* Toast.swift in Sources */,
C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */,
C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */,
C38EF3C7255B6DE7007E1867 /* ImageEditorCanvasView.swift in Sources */,
C38EF400255B6DF7007E1867 /* GalleryRailView.swift in Sources */,
C38EF32E255B6DBF007E1867 /* ImageCache.swift in Sources */,
@ -5973,6 +5961,7 @@
FD7115FE28C8202D00B47552 /* ReplaySubject.swift in Sources */,
C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */,
C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */,
FD0E353C2AB9880B006A81F7 /* AppVersion.swift in Sources */,
C32C5DD2256DD9E5003C73A2 /* LRUCache.swift in Sources */,
FD71160228C8255900B47552 /* UIControl+Combine.swift in Sources */,
FDFBB74B2A1EFF4900CA7350 /* Bencode.swift in Sources */,
@ -5988,7 +5977,6 @@
FD30036E2A3AE26000B5A5FB /* CExceptionHelper.mm in Sources */,
FD9AECA72AAAF5B0009B3406 /* Crypto+SessionUtilitiesKit.swift in Sources */,
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
C32C600F256E07F5003C73A2 /* NSUserDefaults+OWS.m in Sources */,
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */,
FD5931AB2A8DCB0A0040147D /* ScopeAdapter+Utilities.swift in Sources */,
FD37E9FF28A5F2CD003AE748 /* Configuration.swift in Sources */,
@ -6029,7 +6017,7 @@
FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */,
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */,
FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */,
C32C5A24256DB7DB003C73A2 /* SNUserDefaults.swift in Sources */,
C32C5A24256DB7DB003C73A2 /* UserDefaultsInfo.swift in Sources */,
FD8ECF922938552800C0D1BB /* Threading.swift in Sources */,
B8856D7B256F14F4001CE70E /* UIView+OWS.m in Sources */,
FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */,

View File

@ -91,10 +91,13 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
)
}
public func reportOutgoingCall(_ call: SessionCall) {
public func reportOutgoingCall(
_ call: SessionCall,
using dependencies: Dependencies = Dependencies()
) {
AssertIsOnMainThread()
UserDefaults.sharedLokiProject?[.isCallOngoing] = true
UserDefaults.sharedLokiProject?[.lastCallPreOffer] = Date()
dependencies[defaults: .appGroup, key: .isCallOngoing] = true
dependencies[defaults: .appGroup, key: .lastCallPreOffer] = Date()
call.stateDidChange = {
if call.hasStartedConnecting {
@ -107,7 +110,12 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
}
}
public func reportIncomingCall(_ call: SessionCall, callerName: String, completion: @escaping (Error?) -> Void) {
public func reportIncomingCall(
_ call: SessionCall,
callerName: String,
using dependencies: Dependencies = Dependencies(),
completion: @escaping (Error?) -> Void
) {
let provider = provider ?? Self.sharedProvider(useSystemCallLog: false)
// Construct a CXCallUpdate describing the incoming call, including the caller.
let update = CXCallUpdate()
@ -124,8 +132,8 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
completion(error)
return
}
UserDefaults.sharedLokiProject?[.isCallOngoing] = true
UserDefaults.sharedLokiProject?[.lastCallPreOffer] = Date()
dependencies[defaults: .appGroup, key: .isCallOngoing] = true
dependencies[defaults: .appGroup, key: .lastCallPreOffer] = Date()
completion(nil)
}
}
@ -143,8 +151,8 @@ public final class SessionCallManager: NSObject, CallManagerProtocol {
func handleCallEnded() {
WebRTCSession.current = nil
UserDefaults.sharedLokiProject?[.isCallOngoing] = false
UserDefaults.sharedLokiProject?[.lastCallPreOffer] = nil
dependencies[defaults: .appGroup, key: .isCallOngoing] = false
dependencies[defaults: .appGroup, key: .lastCallPreOffer] = nil
if CurrentAppContext().isInBackground() {
(UIApplication.shared.delegate as? AppDelegate)?.stopPollers()

View File

@ -77,7 +77,7 @@ extension ConversationVC:
@objc func startCall(_ sender: Any?) {
guard SessionCall.isEnabled else { return }
guard viewModel.threadData.threadIsBlocked == false else { return }
guard Dependencies()[singleton: .storage][.areCallsEnabled] else {
guard Dependencies()[singleton: .storage, key: .areCallsEnabled] else {
let confirmationModal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "modal_call_permission_request_title".localized(),
@ -240,7 +240,7 @@ extension ConversationVC:
// MARK: - ExpandingAttachmentsButtonDelegate
func handleGIFButtonTapped() {
guard Dependencies()[singleton: .storage][.isGiphyEnabled] else {
guard Dependencies()[singleton: .storage, key: .isGiphyEnabled] else {
let modal: ConfirmationModal = ConfirmationModal(
info: ConfirmationModal.Info(
title: "GIPHY_PERMISSION_TITLE".localized(),
@ -624,7 +624,7 @@ extension ConversationVC:
}
func handleMessageSent() {
if Dependencies()[singleton: .storage][.playNotificationSoundInForeground] {
if Dependencies()[singleton: .storage, key: .playNotificationSoundInForeground] {
let soundID = Preferences.Sound.systemSoundId(for: .messageSent, quiet: true)
AudioServicesPlaySystemSound(soundID)
}

View File

@ -1044,7 +1044,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
.firstIndex(where: { $0.id == interactionId }),
currentIndex < (messageSection.elements.count - 1),
messageSection.elements[currentIndex + 1].cellType == .audio,
dependencies[singleton: .storage][.shouldAutoPlayConsecutiveAudioMessages] == true
dependencies[singleton: .storage, key: .shouldAutoPlayConsecutiveAudioMessages]
else { return }
let nextItem: MessageViewModel = messageSection.elements[currentIndex + 1]

View File

@ -291,15 +291,15 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M
// Suggest that the user enable link previews if they haven't already and we haven't
// told them about link previews yet
let text = inputTextView.text!
let areLinkPreviewsEnabled: Bool = dependencies[singleton: .storage][.areLinkPreviewsEnabled]
let areLinkPreviewsEnabled: Bool = dependencies[singleton: .storage, key: .areLinkPreviewsEnabled]
if
!LinkPreview.allPreviewUrls(forMessageBodyText: text).isEmpty &&
!areLinkPreviewsEnabled &&
!UserDefaults.standard[.hasSeenLinkPreviewSuggestion]
!dependencies[defaults: .standard, key: .hasSeenLinkPreviewSuggestion]
{
delegate?.showLinkPreviewSuggestionModal()
UserDefaults.standard[.hasSeenLinkPreviewSuggestion] = true
dependencies[defaults: .standard, key: .hasSeenLinkPreviewSuggestion] = true
return
}
// Check that link previews are enabled

View File

@ -141,7 +141,7 @@ final class CallMessageCell: MessageCell {
let shouldShowInfoIcon: Bool = (
messageInfo.state == .permissionDenied &&
!Dependencies()[singleton: .storage][.areCallsEnabled]
!Dependencies()[singleton: .storage, key: .areCallsEnabled]
)
infoImageViewWidthConstraint.constant = (shouldShowInfoIcon ? CallMessageCell.iconSize : 0)
infoImageViewHeightConstraint.constant = (shouldShowInfoIcon ? CallMessageCell.iconSize : 0)
@ -177,7 +177,7 @@ final class CallMessageCell: MessageCell {
else { return }
// Should only be tappable if the info icon is visible
guard messageInfo.state == .permissionDenied && !Dependencies()[singleton: .storage][.areCallsEnabled] else { return }
guard messageInfo.state == .permissionDenied && !Dependencies()[singleton: .storage, key: .areCallsEnabled] else { return }
self.delegate?.handleItemTapped(cellViewModel, gestureRecognizer: gestureRecognizer)
}

View File

@ -38,7 +38,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Called via the OS so create a default 'Dependencies' instance
let dependencies: Dependencies = Dependencies()
Cryptography.seedRandom()
AppVersion.sharedInstance()
AppVersion.configure(using: dependencies)
AppEnvironment.shared.pushRegistrationManager.createVoipRegistryIfNecessary()
// Prevent the device from sleeping during database view async registration
@ -99,8 +99,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
if Environment.shared?.callManager.wrappedValue?.currentCall == nil {
UserDefaults.sharedLokiProject?[.isCallOngoing] = false
UserDefaults.sharedLokiProject?[.lastCallPreOffer] = nil
dependencies[defaults: .appGroup, key: .isCallOngoing] = false
dependencies[defaults: .appGroup, key: .lastCallPreOffer] = nil
}
// No point continuing if we are running tests
@ -127,7 +127,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
NotificationCenter.default.addObserver(
self,
selector: #selector(showMissedCallTipsIfNeeded(_:)),
selector: #selector(showMissedCallTipsIfNeededNotification(_:)),
name: .missedCall,
object: nil
)
@ -236,7 +236,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// Called via the OS so create a default 'Dependencies' instance
let dependencies: Dependencies = Dependencies()
UserDefaults.sharedLokiProject?[.isMainAppActive] = true
dependencies[defaults: .appGroup, key: .isMainAppActive] = true
ensureRootViewController(calledFrom: .didBecomeActive, using: dependencies)
@ -259,9 +259,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
func applicationWillResignActive(_ application: UIApplication) {
clearAllNotificationsAndRestoreBadgeCount()
// Called via the OS so create a default 'Dependencies' instance
let dependencies: Dependencies = Dependencies()
UserDefaults.sharedLokiProject?[.isMainAppActive] = false
clearAllNotificationsAndRestoreBadgeCount(using: dependencies)
dependencies[defaults: .appGroup, key: .isMainAppActive] = false
DDLog.flushLog()
}
@ -374,7 +377,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
DeviceSleepManager.sharedInstance.removeBlock(blockObject: self)
/// App launch hasn't really completed until the main screen is loaded so wait until then to register it
AppVersion.sharedInstance().mainAppLaunchDidComplete()
AppVersion.shared.mainAppLaunchDidComplete(using: dependencies)
/// App won't be ready for extensions and no need to enqueue a config sync unless we successfully completed startup
dependencies[singleton: .storage].writeAsync { db in
@ -387,13 +390,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
db[.isReadyForAppExtensions] = true
if Identity.userCompletedRequiredOnboarding(db) {
let appVersion: AppVersion = AppVersion.sharedInstance()
// If the device needs to sync config or the user updated to a new version
if
needsConfigSync || (
(appVersion.lastAppVersion?.count ?? 0) > 0 &&
appVersion.lastAppVersion != appVersion.currentAppVersion
(AppVersion.shared.lastAppVersion?.count ?? 0) > 0 &&
AppVersion.shared.lastAppVersion != AppVersion.shared.currentAppVersion
)
{
ConfigurationSyncJob.enqueue(db, publicKey: getUserHexEncodedPublicKey(db))
@ -539,7 +540,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
/// called again - this can result in odd behaviours so hold off on running this logic until it's properly called again
guard
Identity.userExists(using: dependencies) &&
UserDefaults.sharedLokiProject?[.isMainAppActive] == true
dependencies[defaults: .appGroup, key: .isMainAppActive] == true
else { return }
enableBackgroundRefreshIfNecessary()
@ -589,7 +590,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let presentedViewController: UIViewController? = self?.window?.rootViewController?.presentedViewController
let targetRootViewController: UIViewController = TopBannerController(
child: StyledNavigationController(rootViewController: rootViewController),
cachedWarning: UserDefaults.sharedLokiProject?[.topBannerWarningToShow]
cachedWarning: dependencies[defaults: .appGroup, key: .topBannerWarningToShow]
.map { rawValue in TopBannerController.Warning(rawValue: rawValue) }
)
@ -687,7 +688,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
#endif
}
private func clearAllNotificationsAndRestoreBadgeCount(using dependencies: Dependencies = Dependencies()) {
private func clearAllNotificationsAndRestoreBadgeCount(using dependencies: Dependencies) {
AppReadiness.runNowOrWhenAppDidBecomeReady {
AppEnvironment.shared.notificationPresenter.clearAllNotifications()
@ -760,11 +761,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
handleActivation()
}
@objc public func showMissedCallTipsIfNeeded(_ notification: Notification) {
guard !UserDefaults.standard[.hasSeenCallMissedTips] else { return }
@objc public func showMissedCallTipsIfNeededNotification(_ notification: Notification) {
showMissedCallTipsIfNeeded(notification)
}
private func showMissedCallTipsIfNeeded(
_ notification: Notification,
using dependencies: Dependencies = Dependencies()
) {
guard !dependencies[defaults: .standard, key: .hasSeenCallMissedTips] else { return }
guard Thread.isMainThread else {
DispatchQueue.main.async {
self.showMissedCallTipsIfNeeded(notification)
self.showMissedCallTipsIfNeeded(notification, using: dependencies)
}
return
}
@ -778,7 +786,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
presentingVC.present(callMissedTipsModal, animated: true, completion: nil)
UserDefaults.standard[.hasSeenCallMissedTips] = true
dependencies[defaults: .standard, key: .hasSeenCallMissedTips] = true
}
// MARK: - Polling

View File

@ -239,10 +239,4 @@ final class MainAppContext: NSObject, AppContext {
return (targetPath ?? "")
}
func appUserDefaults() -> UserDefaults {
owsAssertDebug(UserDefaults.sharedLokiProject != nil)
return (UserDefaults.sharedLokiProject ?? UserDefaults.standard)
}
}

View File

@ -512,7 +512,7 @@ public class NotificationPresenter: NotificationsProtocol {
private func checkIfShouldPlaySound(applicationState: UIApplication.State) -> Bool {
guard applicationState == .active else { return true }
guard Dependencies()[singleton: .storage][.playNotificationSoundInForeground] else { return false }
guard Dependencies()[singleton: .storage, key: .playNotificationSoundInForeground] else { return false }
let nowMs: UInt64 = UInt64(floor(Date().timeIntervalSince1970 * 1000))
let recentThreshold = nowMs - UInt64(kAudioNotificationsThrottleInterval * Double(kSecondInMs))

View File

@ -23,7 +23,7 @@ public enum SyncPushTokensJob: JobExecutor {
using dependencies: Dependencies = Dependencies()
) {
// Don't run when inactive or not in main app or if the user doesn't exist yet
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
guard dependencies[defaults: .appGroup, key: .isMainAppActive] else {
return deferred(job, dependencies) // Don't need to do anything if it's not the main app
}
guard Identity.userCompletedRequiredOnboarding() else {
@ -32,12 +32,12 @@ public enum SyncPushTokensJob: JobExecutor {
}
// Determine if the device has 'Fast Mode' (APNS) enabled
let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs]
let isUsingFullAPNs: Bool = dependencies[defaults: .standard, key: .isUsingFullAPNs]
// If the job is running and 'Fast Mode' is disabled then we should try to unregister the existing
// token
guard isUsingFullAPNs else {
Just(dependencies[singleton: .storage][.lastRecordedPushToken])
Just(dependencies[singleton: .storage, key: .lastRecordedPushToken])
.setFailureType(to: Error.self)
.flatMap { lastRecordedPushToken -> AnyPublisher<Void, Error> in
// Tell the device to unregister for remote notifications (essentially try to invalidate
@ -92,7 +92,7 @@ public enum SyncPushTokensJob: JobExecutor {
/// We want to force an update
let timeSinceLastSubscription: TimeInterval = dependencies.dateNow
.timeIntervalSince(
dependencies.standardUserDefaults[.lastPushNotificationSync]
dependencies[defaults: .standard, key: .lastPushNotificationSync]
.defaulting(to: Date.distantPast)
)
let uploadOnlyIfStale: Bool? = {
@ -106,7 +106,7 @@ public enum SyncPushTokensJob: JobExecutor {
guard
timeSinceLastSubscription >= SyncPushTokensJob.maxFrequency ||
dependencies.storage[.lastRecordedPushToken] != pushToken ||
dependencies[singleton: .storage, key: .lastRecordedPushToken] != pushToken ||
uploadOnlyIfStale == false
else {
SNLog("[SyncPushTokensJob] OS subscription completed, skipping server subscription due to frequency")
@ -131,7 +131,7 @@ public enum SyncPushTokensJob: JobExecutor {
case .finished:
Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
SNLog("[SyncPushTokensJob] Completed")
dependencies[singleton: .standardUserDefaults][.lastPushNotificationSync] = dependencies.dateNow
dependencies[defaults: .standard, key: .lastPushNotificationSync] = dependencies.dateNow
dependencies[singleton: .storage].write(using: dependencies) { db in
db[.lastRecordedPushToken] = pushToken

View File

@ -102,7 +102,7 @@ enum Onboarding {
profileNameRetrievalIdentifier.mutate { $0 = nil }
profileNameRetrievalPublisher.mutate { $0 = nil }
dependencies[singleton: .standardUserDefaults][.hasSyncedInitialConfiguration] = false
dependencies[defaults: .standard, key: .hasSyncedInitialConfiguration] = false
}
func preregister(
@ -164,7 +164,7 @@ enum Onboarding {
// home screen a configuration sync is triggered (yes, the logic is a
// bit weird). This is needed so that if the user registers and
// immediately links a device, there'll be a configuration in their swarm.
dependencies[singleton: .standardUserDefaults][.hasSyncedInitialConfiguration] = (self == .register)
dependencies[defaults: .standard, key: .hasSyncedInitialConfiguration] = (self == .register)
// Only continue if this isn't a new account
guard self != .register else { return }

View File

@ -85,7 +85,7 @@ final class PNModeVC: BaseVC, OptionViewDelegate {
let registerButton = SessionButton(style: .filled, size: .large)
registerButton.accessibilityLabel = "Continue with settings"
registerButton.setTitle("continue_2".localized(), for: .normal)
registerButton.addTarget(self, action: #selector(register), for: UIControl.Event.touchUpInside)
registerButton.addTarget(self, action: #selector(registerTapped), for: UIControl.Event.touchUpInside)
// Set up register button container
let registerButtonContainer = UIView(wrapping: registerButton, withInsets: UIEdgeInsets(top: 0, leading: Values.massiveSpacing, bottom: 0, trailing: Values.massiveSpacing), shouldAdaptForIPadWithWidth: Values.iPadButtonWidth)
@ -128,7 +128,9 @@ final class PNModeVC: BaseVC, OptionViewDelegate {
optionViews.filter { $0 != optionView }.forEach { $0.isSelected = false }
}
@objc private func register() {
@objc private func registerTapped() { register() }
private func register(using dependencies: Dependencies = Dependencies()) {
guard selectedOptionView != nil else {
let modal: ConfirmationModal = ConfirmationModal(
targetView: self.view,
@ -141,7 +143,7 @@ final class PNModeVC: BaseVC, OptionViewDelegate {
self.present(modal, animated: true)
return
}
UserDefaults.standard[.isUsingFullAPNs] = (selectedOptionView == apnsOptionView)
dependencies[defaults: .standard, key: .isUsingFullAPNs] = (selectedOptionView == apnsOptionView)
// If we are registering then we can just continue on
guard flow != .register else {
@ -155,14 +157,13 @@ final class PNModeVC: BaseVC, OptionViewDelegate {
// Check if we already have a profile name (ie. profile retrieval completed while waiting on
// this screen)
let existingProfileName: String? = Dependencies()[singleton: .storage]
.read { db in
try Profile
.filter(id: getUserHexEncodedPublicKey(db))
.select(.name)
.asRequest(of: String.self)
.fetchOne(db)
}
let existingProfileName: String? = dependencies[singleton: .storage].read { db in
try Profile
.filter(id: getUserHexEncodedPublicKey(db))
.select(.name)
.asRequest(of: String.self)
.fetchOne(db)
}
guard existingProfileName?.isEmpty != false else {
// If we have one then we can go straight to the home screen

View File

@ -86,9 +86,9 @@ class NotificationSettingsViewModel: SessionTableViewModel<NoNav, NotificationSe
.handleEvents(didFail: { SNLog("[NotificationSettingsViewModel] Observation failed with error: \($0)") })
.publisher(in: dependencies[singleton: .storage], scheduling: dependencies[singleton: .scheduler])
.manualRefreshFrom(forcedRefresh)
.map { dbState -> State in
.map { [dependencies] dbState -> State in
State(
isUsingFullAPNs: UserDefaults.standard[.isUsingFullAPNs],
isUsingFullAPNs: dependencies[defaults: .standard, key: .isUsingFullAPNs],
notificationSound: dbState.notificationSound,
playNotificationSoundInForeground: dbState.playNotificationSoundInForeground,
previewType: dbState.previewType

View File

@ -262,8 +262,8 @@ final class NukeDataModal: Modal {
private func deleteAllLocalData(using dependencies: Dependencies = Dependencies()) {
// Unregister push notifications if needed
let isUsingFullAPNs: Bool = UserDefaults.standard[.isUsingFullAPNs]
let maybeDeviceToken: String? = UserDefaults.standard[.deviceToken]
let isUsingFullAPNs: Bool = dependencies[defaults: .standard, key: .isUsingFullAPNs]
let maybeDeviceToken: String? = dependencies[defaults: .standard, key: .deviceToken]
if isUsingFullAPNs, let deviceToken: String = maybeDeviceToken {
PushNotificationAPI
@ -298,11 +298,11 @@ final class NukeDataModal: Modal {
// Call through to the SessionApp's "resetAppData" which will wipe out logs, database and
// profile storage
let wasUnlinked: Bool = UserDefaults.standard[.wasUnlinked]
let wasUnlinked: Bool = dependencies[defaults: .standard, key: .wasUnlinked]
SessionApp.resetAppData {
// Resetting the data clears the old user defaults. We need to restore the unlink default.
UserDefaults.standard[.wasUnlinked] = wasUnlinked
dependencies[defaults: .standard, key: .wasUnlinked] = wasUnlinked
}
}
}

View File

@ -168,7 +168,7 @@ class ScreenLockUI {
// until the app is ready.
AppReadiness.runNowOrWhenAppWillBecomeReady { [weak self] in
DispatchQueue.global(qos: .background).async {
self?.isScreenLockLocked.mutate { $0 = Dependencies()[singleton: .storage][.isScreenLockEnabled] }
self?.isScreenLockLocked.mutate { $0 = Dependencies()[singleton: .storage, key: .isScreenLockEnabled] }
DispatchQueue.main.async {
self?.ensureUI()
@ -189,7 +189,7 @@ class ScreenLockUI {
Logger.verbose("tryToActivateScreenLockUponBecomingActive NO 0")
return
}
guard Dependencies()[singleton: .storage][.isScreenLockEnabled] else {
guard Dependencies()[singleton: .storage, key: .isScreenLockEnabled] else {
// Screen lock is not enabled.
Logger.verbose("tryToActivateScreenLockUponBecomingActive NO 1")
return;
@ -378,7 +378,7 @@ class ScreenLockUI {
}
DispatchQueue.global(qos: .background).async {
self.isScreenLockLocked.mutate { $0 = Dependencies()[singleton: .storage][.isScreenLockEnabled] }
self.isScreenLockLocked.mutate { $0 = Dependencies()[singleton: .storage, key: .isScreenLockEnabled] }
DispatchQueue.main.async {
// NOTE: this notifications fires _before_ applicationDidBecomeActive,

View File

@ -58,7 +58,7 @@ public struct ConfigDump: Codable, Equatable, Hashable, FetchableRecord, Persist
// MARK: - Convenience
public extension ConfigDump.Variant, CustomStringConvertible {
public extension ConfigDump.Variant {
static let userVariants: Set<ConfigDump.Variant> = [
.userProfile, .contacts, .convoInfoVolatile, .userGroups
]
@ -132,7 +132,9 @@ public extension ConfigDump.Variant, CustomStringConvertible {
default: return 1
}
}
}
extension ConfigDump.Variant: CustomStringConvertible {
public var description: String {
switch self {
case .userProfile: return "userProfile"

View File

@ -216,7 +216,7 @@ public extension LinkPreview {
selectedRange: NSRange? = nil,
using dependencies: Dependencies = Dependencies()
) -> String? {
guard dependencies[singleton: .storage][.areLinkPreviewsEnabled] else { return nil }
guard dependencies[singleton: .storage, key: .areLinkPreviewsEnabled] else { return nil }
guard let body: String = body else { return nil }
if let cachedUrl = previewUrlCache.wrappedValue.object(forKey: body as NSString) as String? {
@ -297,7 +297,7 @@ public extension LinkPreview {
// Exit early if link previews are not enabled in order to avoid
// tainting the cache.
guard dependencies[singleton: .storage][.areLinkPreviewsEnabled] else { return }
guard dependencies[singleton: .storage, key: .areLinkPreviewsEnabled] else { return }
serialQueue.sync {
linkPreviewDraftCache = linkPreviewDraft
@ -308,7 +308,7 @@ public extension LinkPreview {
previewUrl: String?,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<LinkPreviewDraft, Error> {
guard dependencies[singleton: .storage][.areLinkPreviewsEnabled] else {
guard dependencies[singleton: .storage, key: .areLinkPreviewsEnabled] else {
return Fail(error: LinkPreviewError.featureDisabled)
.eraseToAnyPublisher()
}

View File

@ -49,9 +49,12 @@ public enum DisappearingMessagesJob: JobExecutor {
// MARK: - Convenience
public extension DisappearingMessagesJob {
@discardableResult static func updateNextRunIfNeeded(_ db: Database) -> Job? {
@discardableResult static func updateNextRunIfNeeded(
_ db: Database,
using dependencies: Dependencies = Dependencies()
) -> Job? {
// Don't run when inactive or not in main app
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else { return nil }
guard dependencies[defaults: .appGroup, key: .isMainAppActive] else { return nil }
// If there is another expiring message then update the job to run 1 second after it's meant to expire
let nextExpirationTimestampMs: Double? = try? Interaction

View File

@ -40,7 +40,7 @@ public enum GarbageCollectionJob: JobExecutor {
/// app at about the same time every day will trigger the garbage collection) - since this runs when the app becomes active we
/// want to prevent it running to frequently (the app becomes active if a system alert, the notification center or the control panel
/// are shown)
let lastGarbageCollection: Date = dependencies[singleton: .standardUserDefaults][.lastGarbageCollection]
let lastGarbageCollection: Date = dependencies[defaults: .standard, key: .lastGarbageCollection]
.defaulting(to: Date.distantPast)
let finalTypesToCollect: Set<Types> = {
guard
@ -461,7 +461,7 @@ public enum GarbageCollectionJob: JobExecutor {
// If we did a full collection then update the 'lastGarbageCollection' date to
// prevent a full collection from running again in the next 23 hours
if job.behaviour == .recurringOnActive && dependencies.dateNow.timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60) {
dependencies[singleton: .standardUserDefaults][.lastGarbageCollection] = dependencies.dateNow
dependencies[defaults: .standard, key: .lastGarbageCollection] = dependencies.dateNow
}
success(job, false, dependencies)

View File

@ -18,7 +18,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
using dependencies: Dependencies
) {
// Don't run when inactive or not in main app
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
guard dependencies[defaults: .appGroup, key: .isMainAppActive] else {
deferred(job, dependencies) // Don't need to do anything if it's not the main app
return
}

View File

@ -18,13 +18,13 @@ public enum UpdateProfilePictureJob: JobExecutor {
using dependencies: Dependencies
) {
// Don't run when inactive or not in main app
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
guard dependencies[defaults: .appGroup, key: .isMainAppActive] else {
return deferred(job, dependencies) // Don't need to do anything if it's not the main app
}
// Only re-upload the profile picture if enough time has passed since the last upload
guard
let lastProfilePictureUpload: Date = dependencies[singleton: .standardUserDefaults][.lastProfilePictureUpload],
let lastProfilePictureUpload: Date = dependencies[defaults: .standard, key: .lastProfilePictureUpload],
dependencies.dateNow.timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60)
else {
// Reset the `nextRunTimestamp` value just in case the last run failed so we don't get stuck

View File

@ -1117,7 +1117,7 @@ public final class OpenGroupManager {
// don't double up on fetch requests by storing the existing request as a promise if
// there is one.
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
let lastOpenGroupImageUpdate: Date? = dependencies[singleton: .standardUserDefaults][.lastOpenGroupImageUpdate]
let lastOpenGroupImageUpdate: Date? = dependencies[defaults: .standard, key: .lastOpenGroupImageUpdate]
let now: Date = dependencies.dateNow
let timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
@ -1198,7 +1198,7 @@ public final class OpenGroupManager {
.filter(id: threadId)
.updateAll(db, OpenGroup.Columns.imageData.set(to: imageData))
}
dependencies[singleton: .standardUserDefaults][.lastOpenGroupImageUpdate] = now
dependencies[defaults: .standard, key: .lastOpenGroupImageUpdate] = now
}
resolver(Result.success(imageData))
@ -1244,7 +1244,7 @@ public extension OpenGroupManager {
return storedTimeSinceLastOpen
}
guard let lastOpen: Date = dependencies[singleton: .standardUserDefaults][.lastOpen] else {
guard let lastOpen: Date = dependencies[defaults: .standard, key: .lastOpen] else {
_timeSinceLastOpen = .greatestFiniteMagnitude
return .greatestFiniteMagnitude
}

View File

@ -18,7 +18,7 @@ extension MessageReceiver {
guard threadVariant == .contact else { return }
switch message.kind {
case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message)
case .preOffer: try MessageReceiver.handleNewCallMessage(db, message: message, using: dependencies)
case .offer: MessageReceiver.handleOfferCallMessage(db, message: message)
case .answer: MessageReceiver.handleAnswerCallMessage(db, message: message, using: dependencies)
case .provisionalAnswer: break // TODO: Implement
@ -44,12 +44,16 @@ extension MessageReceiver {
// MARK: - Specific Handling
private static func handleNewCallMessage(_ db: Database, message: CallMessage) throws {
private static func handleNewCallMessage(
_ db: Database,
message: CallMessage,
using dependencies: Dependencies
) throws {
SNLog("[Calls] Received pre-offer message.")
// Determine whether the app is active based on the prefs rather than the UIApplication state to avoid
// requiring main-thread execution
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false)
let isMainAppActive: Bool = dependencies[defaults: .appGroup, key: .isMainAppActive]
// It is enough just ignoring the pre offers, other call messages
// for this call would be dropped because of no Session call instance

View File

@ -21,7 +21,7 @@ extension MessageReceiver {
// Note: `message.sentTimestamp` is in ms (convert to TimeInterval before converting to
// seconds to maintain the accuracy)
let messageSentTimestamp: TimeInterval = (TimeInterval(message.sentTimestamp ?? 0) / 1000)
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false)
let isMainAppActive: Bool = dependencies[defaults: .appGroup, key: .isMainAppActive]
let expiresInSeconds: TimeInterval? = proto.hasExpirationTimer ? TimeInterval(proto.expirationTimer) : nil
// Update profile if needed (want to do this regardless of whether the message exists or
@ -400,7 +400,7 @@ extension MessageReceiver {
case .react:
// Determine whether the app is active based on the prefs rather than the UIApplication state to avoid
// requiring main-thread execution
let isMainAppActive: Bool = (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false)
let isMainAppActive: Bool = dependencies[defaults: .appGroup, key: .isMainAppActive]
let timestampMs: Int64 = Int64(messageSentTimestamp * 1000)
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
let reaction: Reaction = try Reaction(

View File

@ -303,7 +303,7 @@ public final class MessageSender {
guard
let job: Job = job,
shouldNotify &&
UserDefaults.sharedLokiProject?[.isMainAppActive] != true
!dependencies[defaults: .appGroup, key: .isMainAppActive]
else { return }
NotifyPushServerJob.run(

View File

@ -30,12 +30,6 @@ extension PushNotificationAPI {
/// The message's swarm expiry timestamp (unix epoch milliseconds)
public let expirationTimestampMs: Int64
/// The swarm timestamp when the message was created (unix epoch milliseconds)
public let createdTimestampMs: Int64
/// The message's swarm expiry timestamp (unix epoch milliseconds)
public let expirationTimestampMs: Int64
/// The length of the message data. This is always included, even if the message content
/// itself was too large to fit into the push notification.
public let dataLength: Int

View File

@ -30,9 +30,9 @@ public enum PushNotificationAPI {
HTTP.PreparedRequest<PushNotificationAPI.LegacyPushServerResponse>?
)
let hexEncodedToken: String = token.toHexString()
let oldToken: String? = dependencies[singleton: .standardUserDefaults][.deviceToken]
let lastUploadTime: Double = dependencies[singleton: .standardUserDefaults][.lastDeviceTokenUpload]
let now: TimeInterval = Date().timeIntervalSince1970
let oldToken: String? = dependencies[defaults: .standard, key: .deviceToken]
let lastUploadTime: Double = dependencies[defaults: .standard, key: .lastDeviceTokenUpload]
let now: TimeInterval = dependencies.dateNow.timeIntervalSince1970
guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else {
SNLog("Device token hasn't changed or expired; no need to re-upload.")
@ -54,9 +54,9 @@ public enum PushNotificationAPI {
receiveOutput: { _, response in
guard response.success == true else { return }
dependencies[singleton: .standardUserDefaults][.deviceToken] = hexEncodedToken
dependencies[singleton: .standardUserDefaults][.lastDeviceTokenUpload] = now
dependencies[singleton: .standardUserDefaults][.isUsingFullAPNs] = true
dependencies[defaults: .standard, key: .deviceToken] = hexEncodedToken
dependencies[defaults: .standard, key: .lastDeviceTokenUpload] = now
dependencies[defaults: .standard, key: .isUsingFullAPNs] = true
}
)
let preparedLegacyGroupRequest = try PushNotificationAPI
@ -131,7 +131,7 @@ public enum PushNotificationAPI {
receiveOutput: { _, response in
guard response.success == true else { return }
dependencies[singleton: .standardUserDefaults][.deviceToken] = nil
dependencies[defaults: .standard, key: .deviceToken] = nil
}
)
@ -175,8 +175,8 @@ public enum PushNotificationAPI {
using dependencies: Dependencies = Dependencies()
) throws -> HTTP.PreparedRequest<SubscribeResponse> {
guard
dependencies[singleton: .standardUserDefaults][.isUsingFullAPNs],
let token: String = dependencies[singleton: .standardUserDefaults][.deviceToken]
dependencies[defaults: .standard, key: .isUsingFullAPNs],
let token: String = dependencies[defaults: .standard, key: .deviceToken]
else { throw HTTPError.invalidRequest }
guard let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey(using: dependencies) else {
@ -312,13 +312,13 @@ public enum PushNotificationAPI {
legacyGroupIds: Set<String>,
using dependencies: Dependencies = Dependencies()
) throws -> HTTP.PreparedRequest<LegacyPushServerResponse>? {
let isUsingFullAPNs = dependencies[singleton: .standardUserDefaults][.isUsingFullAPNs]
let isUsingFullAPNs = dependencies[defaults: .standard, key: .isUsingFullAPNs]
// Only continue if PNs are enabled and we have a device token
guard
!legacyGroupIds.isEmpty,
(forced || isUsingFullAPNs),
let deviceToken: String = (token ?? dependencies[singleton: .standardUserDefaults][.deviceToken])
let deviceToken: String = (token ?? dependencies[defaults: .standard, key: .deviceToken])
else { return nil }
return try PushNotificationAPI

View File

@ -61,7 +61,7 @@ public final class CurrentUserPoller: Poller {
}
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
if UserDefaults.sharedLokiProject?[.isMainAppActive] != true {
if !dependencies[defaults: .appGroup, key: .isMainAppActive] {
// Do nothing when an error gets throws right after returning from the background (happens frequently)
}
else if

View File

@ -170,7 +170,7 @@ extension OpenGroupAPI {
dependencies.mutate(cache: .openGroupManager) { cache in
cache.hasPerformedInitialPoll[server] = true
cache.timeSinceLastPoll[server] = dependencies.dateNow.timeIntervalSince1970
dependencies[singleton: .standardUserDefaults][.lastOpen] = dependencies.dateNow
dependencies[defaults: .standard, key: .lastOpen] = dependencies.dateNow
}
SNLog("Open group polling finished for \(server).")

View File

@ -37,7 +37,7 @@ public class TypingIndicators {
//
// We also don't want to show/send typing indicators for message requests
guard
dependencies[singleton: .storage][.typingIndicatorsEnabled] &&
dependencies[singleton: .storage, key: .typingIndicatorsEnabled] &&
!threadIsBlocked &&
!threadIsMessageRequest
else { return nil }

View File

@ -374,7 +374,8 @@ public struct ProfileManager {
try success?(db)
}
},
failure: failure
failure: failure,
using: dependencies
)
}
}
@ -383,7 +384,8 @@ public struct ProfileManager {
queue: DispatchQueue,
imageData: Data,
success: @escaping ((downloadUrl: String, fileName: String, profileKey: Data)) -> (),
failure: ((ProfileManagerError) -> ())? = nil
failure: ((ProfileManagerError) -> ())? = nil,
using dependencies: Dependencies
) {
queue.async {
// If the profile avatar was updated or removed then encrypt with a new profile key
@ -504,7 +506,7 @@ public struct ProfileManager {
// Update the cached avatar image value
profileAvatarCache.mutate { $0[fileName] = avatarImageData }
UserDefaults.standard[.lastProfilePictureUpload] = Date()
dependencies[defaults: .standard, key: .lastProfilePictureUpload] = dependencies.dateNow
SNLog("Successfully uploaded avatar image.")
success((downloadUrl, fileName, newProfileKey))

View File

@ -63,7 +63,7 @@ class OpenGroupManagerSpec: QuickSpec {
dependencies[singleton: .storage] = mockStorage
dependencies[singleton: .network] = mockNetwork
dependencies[singleton: .crypto] = mockCrypto
dependencies[singleton: .standardUserDefaults] = mockUserDefaults
dependencies[defaults: .standard] = mockUserDefaults
dependencies[cache: .openGroupManager] = mockOGMCache
testInteraction1 = Interaction(
@ -252,7 +252,7 @@ class OpenGroupManagerSpec: QuickSpec {
it("defaults the time since last open to greatestFiniteMagnitude") {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(nil)
@ -264,7 +264,7 @@ class OpenGroupManagerSpec: QuickSpec {
it("returns the time since the last open") {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(Date(timeIntervalSince1970: 1234567880))
dependencies.dateNow = Date(timeIntervalSince1970: 1234567890)
@ -277,7 +277,7 @@ class OpenGroupManagerSpec: QuickSpec {
it("caches the time since the last open") {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(Date(timeIntervalSince1970: 1234567770))
dependencies.dateNow = Date(timeIntervalSince1970: 1234567780)
@ -287,7 +287,7 @@ class OpenGroupManagerSpec: QuickSpec {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(Date(timeIntervalSince1970: 1234567890))
@ -327,7 +327,7 @@ class OpenGroupManagerSpec: QuickSpec {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(Date(timeIntervalSince1970: 1234567890))
}
@ -783,7 +783,7 @@ class OpenGroupManagerSpec: QuickSpec {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(Date(timeIntervalSince1970: 1234567890))
}
@ -928,7 +928,7 @@ class OpenGroupManagerSpec: QuickSpec {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(Date(timeIntervalSince1970: 1234567890))
}
@ -1209,7 +1209,7 @@ class OpenGroupManagerSpec: QuickSpec {
mockUserDefaults
.when { (defaults: inout any UserDefaultsType) -> Any? in
defaults.object(forKey: SNUserDefaults.Date.lastOpen.rawValue)
defaults.object(forKey: UserDefaultsInfo.DateKey.lastOpen.rawValue)
}
.thenReturn(nil)
}
@ -3245,7 +3245,7 @@ class OpenGroupManagerSpec: QuickSpec {
.to(call(matchingParameters: .all) {
$0.set(
testDate,
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
forKey: UserDefaultsInfo.DateKey.lastOpenGroupImageUpdate.rawValue
)
})
expect(
@ -3354,7 +3354,7 @@ class OpenGroupManagerSpec: QuickSpec {
.toNot(call(matchingParameters: .all) {
$0.set(
dependencies.dateNow,
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
forKey: UserDefaultsInfo.DateKey.lastOpenGroupImageUpdate.rawValue
)
})
}
@ -3436,7 +3436,7 @@ class OpenGroupManagerSpec: QuickSpec {
.to(call(matchingParameters: .all) {
$0.set(
dependencies.dateNow,
forKey: SNUserDefaults.Date.lastOpenGroupImageUpdate.rawValue
forKey: UserDefaultsInfo.DateKey.lastOpenGroupImageUpdate.rawValue
)
})
}

View File

@ -37,7 +37,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
// Abort if the main app is running
guard !(UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
guard !dependencies[defaults: .appGroup, key: .isMainAppActive] else {
return self.completeSilenty(using: dependencies)
}
@ -46,9 +46,8 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
SetCurrentAppContext(NotificationServiceExtensionContext())
}
let isCallOngoing: Bool = (UserDefaults.sharedLokiProject?[.isCallOngoing])
.defaulting(to: false)
let lastCallPreOffer: Date? = UserDefaults.sharedLokiProject?[.lastCallPreOffer]
let isCallOngoing: Bool = dependencies[defaults: .appGroup, key: .isCallOngoing]
let lastCallPreOffer: Date? = dependencies[defaults: .appGroup, key: .lastCallPreOffer]
// Perform main setup
Storage.resumeDatabaseAccess(using: dependencies)
@ -240,8 +239,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
NSLog("[NotificationServiceExtension] Performing setup")
didPerformSetup = true
_ = AppVersion.sharedInstance()
AppVersion.configure(using: dependencies)
Cryptography.seedRandom()
AppSetup.setupEnvironment(
@ -264,7 +262,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
// this path should never occur. However, the service does have our push token
// so it is possible that could change in the future. If it does, do nothing
// and don't disturb the user. Messages will be processed when they open the app.
guard dependencies[singleton: .storage][.isReadyForAppExtensions] else {
guard dependencies[singleton: .storage, key: .isReadyForAppExtensions] else {
NSLog("[NotificationServiceExtension] Not ready for extensions")
self?.completeSilenty(using: dependencies)
return
@ -368,7 +366,7 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension
}
else {
NSLog("[NotificationServiceExtension] Successfully notified main app of call message.")
UserDefaults.sharedLokiProject?[.lastCallPreOffer] = Date()
dependencies[defaults: .appGroup, key: .lastCallPreOffer] = Date()
self.completeSilenty(using: dependencies)
}
}

View File

@ -46,13 +46,6 @@ final class NotificationServiceExtensionContext : NSObject, AppContext {
return groupContainerURL.path
}
func appUserDefaults() -> UserDefaults {
guard let userDefaults = UserDefaults.sharedLokiProject else {
preconditionFailure("Couldn't set up shared user defaults.")
}
return userDefaults
}
// MARK: - Currently Unused
let frame = CGRect.zero

View File

@ -9,5 +9,4 @@
#import <SessionUtilitiesKit/UIView+OWS.h>
#import <SessionUtilitiesKit/AppContext.h>
#import <SessionMessagingKit/AppReadiness.h>
#import <SignalUtilitiesKit/AppVersion.h>
#import <SessionUtilitiesKit/OWSMath.h>

View File

@ -164,12 +164,6 @@ final class ShareAppExtensionContext: NSObject, AppContext {
return (targetPath ?? "")
}
func appUserDefaults() -> UserDefaults {
owsAssertDebug(UserDefaults.sharedLokiProject != nil)
return (UserDefaults.sharedLokiProject ?? UserDefaults.standard)
}
func setStatusBarHidden(_ isHidden: Bool, animated isAnimated: Bool) {
OWSLogger.info("Ignoring request to show/hide status bar since we're in an app extension")
}

View File

@ -26,6 +26,9 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
override func loadView() {
super.loadView()
// Called via the OS so create a default 'Dependencies' instance
let dependencies: Dependencies = Dependencies()
view.themeBackgroundColor = .backgroundPrimary
// This should be the first thing we do (Note: If you leave the share context and return to it
@ -38,7 +41,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
Logger.info("")
_ = AppVersion.sharedInstance()
AppVersion.configure(using: dependencies)
Cryptography.seedRandom()
@ -50,9 +53,6 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
// TODO: Do we need to implement isRunningTests in the SAE context?
return
}
// Called via the OS so create a default 'Dependencies' instance
let dependencies: Dependencies = Dependencies()
AppSetup.setupEnvironment(
appSpecificBlock: {
@ -158,7 +158,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
// We don't need to use SyncPushTokensJob in the SAE.
// We don't need to use DeviceSleepManager in the SAE.
AppVersion.sharedInstance().saeLaunchDidComplete()
AppVersion.shared.saeLaunchDidComplete(using: dependencies)
showLockScreenOrMainContent(using: dependencies)
@ -188,7 +188,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
// Called via the OS so create a default 'Dependencies' instance
let dependencies: Dependencies = Dependencies()
if dependencies[singleton: .storage][.isScreenLockEnabled] {
if dependencies[singleton: .storage, key: .isScreenLockEnabled] {
self.dismiss(animated: false) { [weak self] in
AssertIsOnMainThread()
self?.extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
@ -210,7 +210,7 @@ final class ShareNavController: UINavigationController, ShareViewDelegate {
private func showLockScreenOrMainContent(
using dependencies: Dependencies
) {
if dependencies[singleton: .storage][.isScreenLockEnabled] {
if dependencies[singleton: .storage, key: .isScreenLockEnabled] {
showLockScreen()
}
else {

View File

@ -26,8 +26,8 @@ public final class SnodeAPI {
// MARK: - Hardfork version
public static var hardfork = UserDefaults.standard[.hardfork]
public static var softfork = UserDefaults.standard[.softfork]
public static var hardfork: Int = Dependencies()[defaults: .standard, key: .hardfork]
public static var softfork: Int = Dependencies()[defaults: .standard, key: .softfork]
// MARK: - Settings
@ -158,7 +158,7 @@ public final class SnodeAPI {
loadSnodePoolIfNeeded(using: dependencies)
let now: Date = Date()
let hasSnodePoolExpired: Bool = dependencies[singleton: .storage][.lastSnodePoolRefreshDate]
let hasSnodePoolExpired: Bool = dependencies[singleton: .storage, key: .lastSnodePoolRefreshDate]
.map { now.timeIntervalSince($0) > 2 * 60 * 60 }
.defaulting(to: true)
let snodePool: Set<Snode> = SnodeAPI.snodePool.wrappedValue
@ -1199,14 +1199,14 @@ public final class SnodeAPI {
if snodeResponse.hardFork[1] > softfork {
softfork = snodeResponse.hardFork[1]
UserDefaults.standard[.softfork] = softfork
dependencies[defaults: .standard, key: .softfork] = softfork
}
if snodeResponse.hardFork[0] > hardfork {
hardfork = snodeResponse.hardFork[0]
UserDefaults.standard[.hardfork] = hardfork
dependencies[defaults: .standard, key: .hardfork] = hardfork
softfork = snodeResponse.hardFork[1]
UserDefaults.standard[.softfork] = softfork
dependencies[defaults: .standard, key: .softfork] = softfork
}
}
)

View File

@ -64,7 +64,7 @@ public class TopBannerController: UIViewController {
)
result.contentMode = .center
result.themeTintColor = .black
result.addTarget(self, action: #selector(dismissBanner), for: .touchUpInside)
result.addTarget(self, action: #selector(dismissBannerTapped), for: .touchUpInside)
return result
}()
@ -136,9 +136,11 @@ public class TopBannerController: UIViewController {
// MARK: - Actions
@objc private func dismissBanner() {
@objc private func dismissBannerTapped() { dismissBanner() }
private func dismissBanner(using dependencies: Dependencies = Dependencies()) {
// Remove the cached warning
UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = nil
dependencies[defaults: .appGroup, key: .topBannerWarningToShow] = nil
UIView.animate(
withDuration: 0.3,
@ -155,10 +157,14 @@ public class TopBannerController: UIViewController {
// MARK: - Functions
public static func show(warning: Warning, inWindowFor view: UIView? = nil) {
public static func show(
warning: Warning,
inWindowFor view: UIView? = nil,
using dependencies: Dependencies = Dependencies()
) {
guard Thread.isMainThread else {
DispatchQueue.main.async {
TopBannerController.show(warning: warning, inWindowFor: view)
TopBannerController.show(warning: warning, inWindowFor: view, using: dependencies)
}
return
}
@ -169,7 +175,7 @@ public class TopBannerController: UIViewController {
}
// Cache the banner to show (so we can show it on re-launch)
UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = warning.rawValue
dependencies[defaults: .appGroup, key: .topBannerWarningToShow] = warning.rawValue
UIView.performWithoutAnimation {
instance.bannerLabel.text = warning.text

View File

@ -36,7 +36,7 @@ public enum ThemeManager {
internal static var dependencies: Dependencies = Dependencies()
public static var currentTheme: Theme = {
(_initialTheme ?? dependencies[singleton: .storage][.theme].defaulting(to: Theme.classicDark))
(_initialTheme ?? dependencies[singleton: .storage, key: .theme].defaulting(to: Theme.classicDark))
}() {
didSet {
// Only update if it was changed
@ -60,7 +60,7 @@ public enum ThemeManager {
}
public static var primaryColor: Theme.PrimaryColor = {
(_initialPrimaryColor ?? dependencies[singleton: .storage][.themePrimaryColor].defaulting(to: Theme.PrimaryColor.green))
(_initialPrimaryColor ?? dependencies[singleton: .storage, key: .themePrimaryColor].defaulting(to: Theme.PrimaryColor.green))
}() {
didSet {
// Only update if it was changed
@ -75,7 +75,7 @@ public enum ThemeManager {
}
public static var matchSystemNightModeSetting: Bool = {
(_initialMatchSystemNightModeSetting ?? dependencies[singleton: .storage][.themeMatchSystemDayNightCycle])
(_initialMatchSystemNightModeSetting ?? dependencies[singleton: .storage, key: .themeMatchSystemDayNightCycle])
}() {
didSet {
// Only update if it was changed
@ -134,8 +134,8 @@ public enum ThemeManager {
}
public static func applySavedTheme(using dependencies: Dependencies) {
ThemeManager.primaryColor = dependencies[singleton: .storage][.themePrimaryColor].defaulting(to: Theme.PrimaryColor.green)
ThemeManager.currentTheme = dependencies[singleton: .storage][.theme].defaulting(to: Theme.classicDark)
ThemeManager.primaryColor = dependencies[singleton: .storage, key: .themePrimaryColor].defaulting(to: Theme.PrimaryColor.green)
ThemeManager.currentTheme = dependencies[singleton: .storage, key: .theme].defaulting(to: Theme.classicDark)
}
public static func applyNavigationStyling() {

View File

@ -150,31 +150,7 @@ public protocol EnumStringSetting: RawRepresentable where RawValue == String {}
// MARK: - GRDB Interactions
public extension Storage {
subscript(key: Setting.BoolKey) -> Bool {
// Default to false if it doesn't exist
return (read { db in db[key] } ?? false)
}
subscript(key: Setting.DoubleKey) -> Double? { return read { db in db[key] } }
subscript(key: Setting.IntKey) -> Int? { return read { db in db[key] } }
subscript(key: Setting.StringKey) -> String? { return read { db in db[key] } }
subscript(key: Setting.DateKey) -> Date? { return read { db in db[key] } }
subscript<T: EnumIntSetting>(key: Setting.EnumKey) -> T? { return read { db in db[key] } }
subscript<T: EnumStringSetting>(key: Setting.EnumKey) -> T? { return read { db in db[key] } }
}
public extension Database {
@discardableResult func unsafeSet<T: Numeric>(key: String, value: T?) -> Setting? {
guard let value: T = value else {
_ = try? Setting.filter(id: key).deleteAll(self)
return nil
}
return try? Setting(key: key, value: value)?.saved(self)
}
private subscript(key: String) -> Setting? {
get { try? Setting.filter(id: key).fetchOne(self) }
set {

View File

@ -8,6 +8,7 @@ public class Dependencies {
private static var singletonInstances: Atomic<[Int: Any]> = Atomic([:])
private static var cacheInstances: Atomic<[Int: MutableCacheType]> = Atomic([:])
private static var userDefaultsInstances: Atomic<[Int: (any UserDefaultsType)]> = Atomic([:])
// MARK: - Subscript Access
@ -19,6 +20,9 @@ public class Dependencies {
getValueSettingIfNull(cache: cache, &Dependencies.cacheInstances)
}
public subscript<U: UserDefaultsType>(defaults defaults: UserDefaultsInfo.Config<U>) -> U {
getValueSettingIfNull(defaults: defaults, &Dependencies.userDefaultsInstances)
}
// MARK: - Timing and Async Handling
@ -82,4 +86,80 @@ public class Dependencies {
return cache.immutableInstance(value)
}
@discardableResult private func getValueSettingIfNull<U: UserDefaultsType>(
defaults: UserDefaultsInfo.Config<U>,
_ store: inout Atomic<[Int: UserDefaultsType]>
) -> U {
guard let value: U = (store.wrappedValue[defaults.key] as? U) else {
let value: U = defaults.createInstance(self)
store.mutate { $0[defaults.key] = value }
return value
}
return value
}
}
// MARK: - Storage Setting Convenience
public extension Dependencies {
subscript(singleton singleton: SingletonInfo.Config<Storage>, key key: Setting.BoolKey) -> Bool {
return self[singleton: singleton]
.read { db in db[key] }
.defaulting(to: false) // Default to false if it doesn't exist
}
subscript(singleton singleton: SingletonInfo.Config<Storage>, key key: Setting.DoubleKey) -> Double? {
return self[singleton: singleton].read { db in db[key] }
}
subscript(singleton singleton: SingletonInfo.Config<Storage>, key key: Setting.IntKey) -> Int? {
return self[singleton: singleton].read { db in db[key] }
}
subscript(singleton singleton: SingletonInfo.Config<Storage>, key key: Setting.StringKey) -> String? {
return self[singleton: singleton].read { db in db[key] }
}
subscript(singleton singleton: SingletonInfo.Config<Storage>, key key: Setting.DateKey) -> Date? {
return self[singleton: singleton].read { db in db[key] }
}
subscript<T: EnumIntSetting>(singleton singleton: SingletonInfo.Config<Storage>, key key: Setting.EnumKey) -> T? {
return self[singleton: singleton].read { db in db[key] }
}
subscript<T: EnumStringSetting>(singleton singleton: SingletonInfo.Config<Storage>, key key: Setting.EnumKey) -> T? {
return self[singleton: singleton].read { db in db[key] }
}
}
// MARK: - UserDefaults Convenience
public extension Dependencies {
subscript<U>(defaults defaults: UserDefaultsInfo.Config<U>, key key: UserDefaultsInfo.BoolKey) -> Bool {
get { return self[defaults: defaults].bool(forKey: key.rawValue) }
set { self[defaults: defaults].set(newValue, forKey: key.rawValue) }
}
subscript<U>(defaults defaults: UserDefaultsInfo.Config<U>, key key: UserDefaultsInfo.DateKey) -> Date? {
get { return self[defaults: defaults].object(forKey: key.rawValue) as? Date }
set { self[defaults: defaults].set(newValue, forKey: key.rawValue) }
}
subscript<U>(defaults defaults: UserDefaultsInfo.Config<U>, key key: UserDefaultsInfo.DoubleKey) -> Double {
get { return self[defaults: defaults].double(forKey: key.rawValue) }
set { self[defaults: defaults].set(newValue, forKey: key.rawValue) }
}
subscript<U>(defaults defaults: UserDefaultsInfo.Config<U>, key key: UserDefaultsInfo.IntKey) -> Int {
get { return self[defaults: defaults].integer(forKey: key.rawValue) }
set { self[defaults: defaults].set(newValue, forKey: key.rawValue) }
}
subscript<U>(defaults defaults: UserDefaultsInfo.Config<U>, key key: UserDefaultsInfo.StringKey) -> String? {
get { return self[defaults: defaults].string(forKey: key.rawValue) }
set { self[defaults: defaults].set(newValue, forKey: key.rawValue) }
}
}

View File

@ -0,0 +1,215 @@
import Foundation
// MARK: - UserDefaultsStorage
public class UserDefaultsStorage {}
public extension UserDefaultsStorage {
static var standard: UserDefaultsInfo.Config = UserDefaultsInfo.create(identifier: "standard") { _ in
UserDefaults.standard
}
static var appGroup: UserDefaultsInfo.Config = UserDefaultsInfo.create(identifier: UserDefaults.applicationGroup) { _ in
UserDefaults(suiteName: UserDefaults.applicationGroup)!
}
}
// MARK: - UserDefaultsInfo
public enum UserDefaultsInfo {
public class Config<U: UserDefaultsType>: UserDefaultsStorage {
public let key: Int
public let createInstance: (Dependencies) -> U
fileprivate init(
identifier: String,
createInstance: @escaping (Dependencies) -> U
) {
self.key = identifier.hashValue
self.createInstance = createInstance
}
}
}
public extension UserDefaultsInfo {
static func create<U: UserDefaultsType>(
identifier: String,
createInstance: @escaping (Dependencies) -> U
) -> UserDefaultsInfo.Config<U> {
return UserDefaultsInfo.Config(
identifier: identifier,
createInstance: createInstance
)
}
}
// MARK: - UserDefaultsType
public protocol UserDefaultsType: AnyObject {
func object(forKey defaultName: String) -> Any?
func string(forKey defaultName: String) -> String?
func array(forKey defaultName: String) -> [Any]?
func dictionary(forKey defaultName: String) -> [String : Any]?
func data(forKey defaultName: String) -> Data?
func stringArray(forKey defaultName: String) -> [String]?
func integer(forKey defaultName: String) -> Int
func float(forKey defaultName: String) -> Float
func double(forKey defaultName: String) -> Double
func bool(forKey defaultName: String) -> Bool
func url(forKey defaultName: String) -> URL?
func set(_ value: Any?, forKey defaultName: String)
func set(_ value: Int, forKey defaultName: String)
func set(_ value: Float, forKey defaultName: String)
func set(_ value: Double, forKey defaultName: String)
func set(_ value: Bool, forKey defaultName: String)
func set(_ url: URL?, forKey defaultName: String)
}
extension UserDefaults: UserDefaultsType {}
public extension UserDefaults {
static let applicationGroup: String = "group.com.loki-project.loki-messenger"
static func removeAll() {
UserDefaultsStorage.standard.createInstance(Dependencies()).removeAll()
UserDefaultsStorage.appGroup.createInstance(Dependencies()).removeAll()
}
private func removeAll() {
let data: [String: Any] = self.dictionaryRepresentation()
data.forEach { key, _ in self.removeObject(forKey: key) }
self.synchronize() // Shouldn't be needed but better safe than sorry
}
}
public extension UserDefaultsInfo {
struct BoolKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
public init?(rawValue: String) { self.rawValue = rawValue }
public init(stringLiteral value: String) { self.init(value) }
public init(unicodeScalarLiteral value: String) { self.init(value) }
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
}
struct DateKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
public init?(rawValue: String) { self.rawValue = rawValue }
public init(stringLiteral value: String) { self.init(value) }
public init(unicodeScalarLiteral value: String) { self.init(value) }
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
}
struct DoubleKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
public init?(rawValue: String) { self.rawValue = rawValue }
public init(stringLiteral value: String) { self.init(value) }
public init(unicodeScalarLiteral value: String) { self.init(value) }
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
}
struct IntKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
public init?(rawValue: String) { self.rawValue = rawValue }
public init(stringLiteral value: String) { self.init(value) }
public init(unicodeScalarLiteral value: String) { self.init(value) }
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
}
struct StringKey: RawRepresentable, ExpressibleByStringLiteral, Hashable {
public let rawValue: String
public init(_ rawValue: String) { self.rawValue = rawValue }
public init?(rawValue: String) { self.rawValue = rawValue }
public init(stringLiteral value: String) { self.init(value) }
public init(unicodeScalarLiteral value: String) { self.init(value) }
public init(extendedGraphemeClusterLiteral value: String) { self.init(value) }
}
}
// MARK: - UserDefault Values
public extension UserDefaultsInfo.BoolKey {
/// Indicates whether the user has synced an initial config message from this device
static let hasSyncedInitialConfiguration: UserDefaultsInfo.BoolKey = "hasSyncedConfiguration"
/// Indicates whether the user has seen the suggestion to enable link previews
static let hasSeenLinkPreviewSuggestion: UserDefaultsInfo.BoolKey = "hasSeenLinkPreviewSuggestion"
/// Indicates whether the user has seen the IP exposure warning when enabling calls
///
/// **Note:** This is currently not in use (it was decided that it's better to warn the user every time they enable calls instead
/// of just the first time)
static let hasSeenCallIPExposureWarning: UserDefaultsInfo.BoolKey = "hasSeenCallIPExposureWarning"
/// Indicates whether the user has seen the missed call tips modal
static let hasSeenCallMissedTips: UserDefaultsInfo.BoolKey = "hasSeenCallMissedTips"
/// Indicates whether the user is registered for APNS (ie. "Fast Mode" notifications)
static let isUsingFullAPNs: UserDefaultsInfo.BoolKey = "isUsingFullAPNs"
/// Indicates whether the device was unlinked from an account
///
/// **Note:** This doesn't seem to be properly used (we basically just maintain the existing value)
static let wasUnlinked: UserDefaultsInfo.BoolKey = "wasUnlinked"
/// Indicates whether the main app is active, this is set to `true` while the app is in the foreground and `false` when
/// the app is in the background
static let isMainAppActive: UserDefaultsInfo.BoolKey = "isMainAppActive"
/// Indicates whether there is an ongoing call
static let isCallOngoing: UserDefaultsInfo.BoolKey = "isCallOngoing"
}
public extension UserDefaultsInfo.DateKey {
/// The date/time when the users profile picture was last uploaded to the server (used to rate-limit re-uploading)
static let lastProfilePictureUpload: UserDefaultsInfo.DateKey = "lastProfilePictureUpload"
/// The date/time when the device last updated the default open group room images (used to rate-limit re-downloading)
static let lastOpenGroupImageUpdate: UserDefaultsInfo.DateKey = "lastOpenGroupImageUpdate"
/// The date/time when any open group last had a successful poll (used as a fallback date/time if the open group hasn't been polled
/// this session)
static let lastOpen: UserDefaultsInfo.DateKey = "lastOpen"
/// The date/time when the last garbage collection was performed (used to rate-limit garbage collection)
static let lastGarbageCollection: UserDefaultsInfo.DateKey = "lastGarbageCollection"
/// The date/time when we last subscribed for push notifications (used to rate-limit calling our subscription endpoint)
static let lastPushNotificationSync: UserDefaultsInfo.DateKey = "lastPushNotificationSync"
/// The date/time when we received a call pre-offer (used to suppress call notifications which are too old)
static let lastCallPreOffer: UserDefaultsInfo.DateKey = "lastCallPreOffer"
}
public extension UserDefaultsInfo.DoubleKey {
/// The timestamp when we last uploaded the users push token (used to rate-limit calling our subscription endpoint)
///
/// **Note:** Looks like this replicates the `lastPushNotificationSync` behaviour
static let lastDeviceTokenUpload: UserDefaultsInfo.DoubleKey = "lastDeviceTokenUploadTime"
}
public extension UserDefaultsInfo.IntKey {
/// The latest hardfork value returned when interacting with a service node
static let hardfork: UserDefaultsInfo.IntKey = "hardfork"
/// The latest softfork value returned when interacting with a service node
static let softfork: UserDefaultsInfo.IntKey = "softfork"
}
public extension UserDefaultsInfo.StringKey {
/// The most recently subscribed APNS token
static let deviceToken: UserDefaultsInfo.StringKey = "deviceToken"
/// The warning to show at the top of the app
static let topBannerWarningToShow: UserDefaultsInfo.StringKey = "topBannerWarningToShow"
}

View File

@ -94,8 +94,6 @@ NSString *NSStringForUIApplicationState(UIApplicationState value);
- (NSString *)appSharedDataDirectoryPath;
- (NSUserDefaults *)appUserDefaults;
@end
id<AppContext> CurrentAppContext(void);

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSUserDefaults (OWS)
+ (NSUserDefaults *)appUserDefaults;
+ (void)migrateToSharedUserDefaults;
+ (void)removeAll;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,45 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "NSUserDefaults+OWS.h"
#import "AppContext.h"
NS_ASSUME_NONNULL_BEGIN
@implementation NSUserDefaults (OWS)
+ (NSUserDefaults *)appUserDefaults
{
return CurrentAppContext().appUserDefaults;
}
+ (void)migrateToSharedUserDefaults
{
NSUserDefaults *appUserDefaults = self.appUserDefaults;
NSDictionary<NSString *, id> *dictionary = [NSUserDefaults standardUserDefaults].dictionaryRepresentation;
for (NSString *key in dictionary) {
id value = dictionary[key];
[appUserDefaults setObject:value forKey:key];
}
}
+ (void)removeAll
{
[NSUserDefaults.standardUserDefaults removeAll];
[self.appUserDefaults removeAll];
}
- (void)removeAll
{
NSDictionary<NSString *, id> *dictionary = self.dictionaryRepresentation;
for (NSString *key in dictionary) {
[self removeObjectForKey:key];
}
[self synchronize];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -1,106 +0,0 @@
import Foundation
// MARK: - Singleton
public extension Singleton {
static let standardUserDefaults: SingletonInfo.Config<UserDefaultsType> = SingletonInfo.create { _ in
UserDefaults.standard
}
}
// MARK: - UserDefaultsType
public protocol UserDefaultsType: AnyObject {
func object(forKey defaultName: String) -> Any?
func string(forKey defaultName: String) -> String?
func array(forKey defaultName: String) -> [Any]?
func dictionary(forKey defaultName: String) -> [String : Any]?
func data(forKey defaultName: String) -> Data?
func stringArray(forKey defaultName: String) -> [String]?
func integer(forKey defaultName: String) -> Int
func float(forKey defaultName: String) -> Float
func double(forKey defaultName: String) -> Double
func bool(forKey defaultName: String) -> Bool
func url(forKey defaultName: String) -> URL?
func set(_ value: Any?, forKey defaultName: String)
func set(_ value: Int, forKey defaultName: String)
func set(_ value: Float, forKey defaultName: String)
func set(_ value: Double, forKey defaultName: String)
func set(_ value: Bool, forKey defaultName: String)
func set(_ url: URL?, forKey defaultName: String)
}
extension UserDefaults: UserDefaultsType {}
public enum SNUserDefaults {
public enum Bool: Swift.String {
case hasSyncedInitialConfiguration = "hasSyncedConfiguration"
case hasSeenLinkPreviewSuggestion
case hasSeenCallIPExposureWarning
case hasSeenCallMissedTips
case isUsingFullAPNs
case wasUnlinked
case isMainAppActive
case isCallOngoing
}
public enum Date: Swift.String {
case lastProfilePictureUpload
case lastOpenGroupImageUpdate
case lastOpen
case lastGarbageCollection
case lastPushNotificationSync
case lastCallPreOffer
}
public enum Double: Swift.String {
case lastDeviceTokenUpload = "lastDeviceTokenUploadTime"
}
public enum Int: Swift.String {
case appMode
case hardfork
case softfork
}
public enum String : Swift.String {
case deviceToken
case topBannerWarningToShow
}
}
public extension UserDefaults {
static let applicationGroup: String = "group.com.loki-project.loki-messenger"
@objc static var sharedLokiProject: UserDefaults? {
UserDefaults(suiteName: UserDefaults.applicationGroup)
}
}
public extension UserDefaultsType {
subscript(bool: SNUserDefaults.Bool) -> Bool {
get { return self.bool(forKey: bool.rawValue) }
set { set(newValue, forKey: bool.rawValue) }
}
subscript(date: SNUserDefaults.Date) -> Date? {
get { return self.object(forKey: date.rawValue) as? Date }
set { set(newValue, forKey: date.rawValue) }
}
subscript(double: SNUserDefaults.Double) -> Double {
get { return self.double(forKey: double.rawValue) }
set { set(newValue, forKey: double.rawValue) }
}
subscript(int: SNUserDefaults.Int) -> Int {
get { return self.integer(forKey: int.rawValue) }
set { set(newValue, forKey: int.rawValue) }
}
subscript(string: SNUserDefaults.String) -> String? {
get { return self.string(forKey: string.rawValue) }
set { set(newValue, forKey: string.rawValue) }
}
}

View File

@ -8,7 +8,6 @@ FOUNDATION_EXPORT const unsigned char SessionUtilitiesKitVersionString[];
#import <SessionUtilitiesKit/MIMETypeUtil.h>
#import <SessionUtilitiesKit/NSData+Image.h>
#import <SessionUtilitiesKit/NSTimer+Proxying.h>
#import <SessionUtilitiesKit/NSUserDefaults+OWS.h>
#import <SessionUtilitiesKit/OWSFileSystem.h>
#import <SessionUtilitiesKit/OWSMath.h>
#import <SessionUtilitiesKit/UIImage+OWS.h>

View File

@ -0,0 +1,101 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
public class AppVersion {
private static var _shared: AppVersion?
public static var shared: AppVersion {
let result: AppVersion = (_shared ?? AppVersion())
_shared = result
return result
}
public var isFirstLaunch: Bool { self.firstAppVersion != nil }
public let isValid: Bool
public let currentAppVersion: String
/// The version of the app when it was first launched (`nil` if the app has never been launched before)
public var firstAppVersion: String?
/// The version of the app the last time it was launched (`nil` if the app has never been launched before)
public var lastAppVersion: String?
public var lastCompletedLaunchAppVersion: String?
public var lastCompletedLaunchMainAppVersion: String?
public var lastCompletedLaunchSAEAppVersion: String?
// MARK: - Initialization
private init(using dependencies: Dependencies = Dependencies()) {
guard let currentAppVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String else {
self.isValid = false
self.currentAppVersion = ""
self.firstAppVersion = nil
self.lastAppVersion = nil
self.lastCompletedLaunchAppVersion = nil
self.lastCompletedLaunchMainAppVersion = nil
self.lastCompletedLaunchSAEAppVersion = nil
return
}
let oldFirstAppVersion: String? = dependencies[defaults: .appGroup, key: .firstAppVersion]
self.isValid = true
self.currentAppVersion = currentAppVersion
self.firstAppVersion = dependencies[defaults: .appGroup, key: .firstAppVersion]
.defaulting(to: currentAppVersion)
self.lastAppVersion = dependencies[defaults: .appGroup, key: .lastAppVersion]
self.lastCompletedLaunchAppVersion = dependencies[defaults: .appGroup, key: .lastCompletedLaunchAppVersion]
self.lastCompletedLaunchMainAppVersion = dependencies[defaults: .appGroup, key: .lastCompletedLaunchMainAppVersion]
self.lastCompletedLaunchSAEAppVersion = dependencies[defaults: .appGroup, key: .lastCompletedLaunchSAEAppVersion]
// Ensure the value for the "first launched version".
if oldFirstAppVersion == nil {
dependencies[defaults: .appGroup, key: .firstAppVersion] = currentAppVersion
}
// Update the value for the "most recently launched version".
dependencies[defaults: .appGroup, key: .lastAppVersion] = currentAppVersion
}
// MARK: - Functions
public static func configure(using dependencies: Dependencies) {
_shared = AppVersion(using: dependencies)
}
private func appLaunchDidComplete(using dependencies: Dependencies) {
lastCompletedLaunchAppVersion = currentAppVersion
// Update the value for the "most recently launch-completed version".
dependencies[defaults: .appGroup, key: .lastCompletedLaunchAppVersion] = currentAppVersion
}
public func mainAppLaunchDidComplete(using dependencies: Dependencies) {
lastCompletedLaunchMainAppVersion = currentAppVersion
dependencies[defaults: .appGroup, key: .lastCompletedLaunchMainAppVersion] = currentAppVersion
appLaunchDidComplete(using: dependencies)
}
public func saeLaunchDidComplete(using dependencies: Dependencies) {
lastCompletedLaunchSAEAppVersion = currentAppVersion
dependencies[defaults: .appGroup, key: .lastCompletedLaunchSAEAppVersion] = currentAppVersion
appLaunchDidComplete(using: dependencies)
}
}
// MARK: - UserDefaults Keys
private extension UserDefaultsInfo.StringKey {
/// The version of the app when it was first launched
static let firstAppVersion: UserDefaultsInfo.StringKey = "kNSUserDefaults_FirstAppVersion"
/// The version of the app when it was last launched
static let lastAppVersion: UserDefaultsInfo.StringKey = "kNSUserDefaults_LastVersion"
static let lastCompletedLaunchAppVersion: UserDefaultsInfo.StringKey = "kNSUserDefaults_LastCompletedLaunchAppVersion"
static let lastCompletedLaunchMainAppVersion: UserDefaultsInfo.StringKey = "kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp"
static let lastCompletedLaunchSAEAppVersion: UserDefaultsInfo.StringKey = "kNSUserDefaults_LastCompletedLaunchAppVersion_SAE"
}

View File

@ -3,5 +3,4 @@
FOUNDATION_EXPORT double SignalUtilitiesKitVersionNumber;
FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[];
#import <SignalUtilitiesKit/AppVersion.h>
#import <SignalUtilitiesKit/OWSViewController.h>

View File

@ -1,32 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AppVersion : NSObject
// The properties are updated immediately after launch.
@property (atomic, readonly) NSString *firstAppVersion;
@property (atomic, nullable, readonly) NSString *lastAppVersion;
@property (atomic, readonly) NSString *currentAppVersion;
// There properties aren't updated until appLaunchDidComplete is called.
@property (atomic, nullable, readonly) NSString *lastCompletedLaunchAppVersion;
@property (atomic, nullable, readonly) NSString *lastCompletedLaunchMainAppVersion;
@property (atomic, nullable, readonly) NSString *lastCompletedLaunchSAEAppVersion;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)sharedInstance;
- (void)mainAppLaunchDidComplete;
- (void)saeLaunchDidComplete;
- (BOOL)isFirstLaunch;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,133 +0,0 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "AppVersion.h"
#import "NSUserDefaults+OWS.h"
#import <SignalCoreKit/OWSAsserts.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const kNSUserDefaults_FirstAppVersion = @"kNSUserDefaults_FirstAppVersion";
NSString *const kNSUserDefaults_LastAppVersion = @"kNSUserDefaults_LastVersion";
NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion = @"kNSUserDefaults_LastCompletedLaunchAppVersion";
NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp
= @"kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp";
NSString *const kNSUserDefaults_LastCompletedLaunchAppVersion_SAE
= @"kNSUserDefaults_LastCompletedLaunchAppVersion_SAE";
@interface AppVersion ()
@property (atomic) NSString *firstAppVersion;
@property (atomic, nullable) NSString *lastAppVersion;
@property (atomic) NSString *currentAppVersion;
@property (atomic, nullable) NSString *lastCompletedLaunchAppVersion;
@property (atomic, nullable) NSString *lastCompletedLaunchMainAppVersion;
@property (atomic, nullable) NSString *lastCompletedLaunchSAEAppVersion;
@end
#pragma mark -
@implementation AppVersion
+ (instancetype)sharedInstance
{
static AppVersion *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [AppVersion new];
[instance configure];
});
return instance;
}
- (void)configure {
OWSAssertIsOnMainThread();
self.currentAppVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
// The version of the app when it was first launched.
// nil if the app has never been launched before.
self.firstAppVersion = [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_FirstAppVersion];
// The version of the app the last time it was launched.
// nil if the app has never been launched before.
self.lastAppVersion = [[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastAppVersion];
self.lastCompletedLaunchAppVersion =
[[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion];
self.lastCompletedLaunchMainAppVersion =
[[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp];
self.lastCompletedLaunchSAEAppVersion =
[[NSUserDefaults appUserDefaults] objectForKey:kNSUserDefaults_LastCompletedLaunchAppVersion_SAE];
// Ensure the value for the "first launched version".
if (!self.firstAppVersion) {
self.firstAppVersion = self.currentAppVersion;
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion forKey:kNSUserDefaults_FirstAppVersion];
}
// Update the value for the "most recently launched version".
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion forKey:kNSUserDefaults_LastAppVersion];
[[NSUserDefaults appUserDefaults] synchronize];
// The long version string looks like an IPv4 address.
// To prevent the log scrubber from scrubbing it,
// we replace . with _.
NSString *longVersionString = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]
stringByReplacingOccurrencesOfString:@"."
withString:@"_"];
OWSLogInfo(@"firstAppVersion: %@", self.firstAppVersion);
OWSLogInfo(@"lastAppVersion: %@", self.lastAppVersion);
OWSLogInfo(@"currentAppVersion: %@ (%@)", self.currentAppVersion, longVersionString);
OWSLogInfo(@"lastCompletedLaunchAppVersion: %@", self.lastCompletedLaunchAppVersion);
OWSLogInfo(@"lastCompletedLaunchMainAppVersion: %@", self.lastCompletedLaunchMainAppVersion);
OWSLogInfo(@"lastCompletedLaunchSAEAppVersion: %@", self.lastCompletedLaunchSAEAppVersion);
}
- (void)appLaunchDidComplete
{
OWSAssertIsOnMainThread();
OWSLogInfo(@"appLaunchDidComplete");
self.lastCompletedLaunchAppVersion = self.currentAppVersion;
// Update the value for the "most recently launch-completed version".
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion
forKey:kNSUserDefaults_LastCompletedLaunchAppVersion];
[[NSUserDefaults appUserDefaults] synchronize];
}
- (void)mainAppLaunchDidComplete
{
OWSAssertIsOnMainThread();
self.lastCompletedLaunchMainAppVersion = self.currentAppVersion;
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion
forKey:kNSUserDefaults_LastCompletedLaunchAppVersion_MainApp];
[self appLaunchDidComplete];
}
- (void)saeLaunchDidComplete
{
OWSAssertIsOnMainThread();
self.lastCompletedLaunchSAEAppVersion = self.currentAppVersion;
[[NSUserDefaults appUserDefaults] setObject:self.currentAppVersion
forKey:kNSUserDefaults_LastCompletedLaunchAppVersion_SAE];
[self appLaunchDidComplete];
}
- (BOOL)isFirstLaunch
{
return self.firstAppVersion != nil;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -7,6 +7,7 @@ import Foundation
public class TestDependencies: Dependencies {
private var singletonInstances: [Int: Any] = [:]
private var cacheInstances: [Int: MutableCacheType] = [:]
private var defaultsInstances: [Int: (any UserDefaultsType)] = [:]
// MARK: - Subscript Access
@ -28,6 +29,15 @@ public class TestDependencies: Dependencies {
set { cacheInstances[cache.key] = newValue.map { cache.mutableInstance($0) } }
}
override public subscript<U>(defaults defaults: UserDefaultsInfo.Config<U>) -> U {
return getValueSettingIfNull(defaults: defaults, &defaultsInstances)
}
public subscript<U>(defaults defaults: UserDefaultsInfo.Config<U>) -> U? {
get { return (defaultsInstances[defaults.key] as? U) }
set { defaultsInstances[defaults.key] = newValue }
}
// MARK: - Timing and Async Handling
private var _dateNow: Atomic<Date?>
@ -120,4 +130,17 @@ public class TestDependencies: Dependencies {
return cache.immutableInstance(value)
}
@discardableResult private func getValueSettingIfNull<U>(
defaults: UserDefaultsInfo.Config<U>,
_ store: inout [Int: (any UserDefaultsType)]
) -> U {
guard let value: U = (store[defaults.key] as? U) else {
let value: U = defaults.createInstance(self)
store[defaults.key] = value
return value
}
return value
}
}