From f7fd15dae0965ed14e230f0770a036c56024f784 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 20 Sep 2022 16:06:01 +1000 Subject: [PATCH] Finished removing usages to non-theme colour variables Updated theming throughout Fixed a couple of bugs with the media gallery --- Session.xcodeproj/project.pbxproj | 94 +- Session/Calls/Views & Modals/RenderView.swift | 1 + .../ContextMenuVC+ActionView.swift | 12 +- .../Context Menu/ContextMenuVC.swift | 25 +- .../Conversations/ConversationSearch.swift | 24 +- .../ConversationVC+Interaction.swift | 4 +- Session/Conversations/ConversationVC.swift | 10 +- .../Emoji Picker/EmojiSkinTonePicker.swift | 17 +- .../VoiceMessageRecordingView.swift | 2 +- .../Content Views/LinkPreviewView.swift | 9 +- .../Message Cells/MessageCell.swift | 4 +- .../Message Cells/VisibleMessageCell.swift | 4 +- .../ConversationTitleView.swift | 2 +- .../Views & Modals/ReactionListSheet.swift | 24 +- Session/DMs/NewDMVC.swift | 20 +- .../MessageRequestsViewController.swift | 4 +- .../AllMediaViewController.swift | 39 +- .../AudioProgressView.swift | 99 - .../CropScaleImageViewController.swift | 110 +- .../DocumentTitleViewController.swift | 145 +- .../GIFs/GifPickerCell.swift | 11 +- .../GIFs/GifPickerViewController.swift | 236 ++- .../ImagePickerController.swift | 36 +- .../MediaDetailViewController.swift | 8 +- .../MediaGalleryNavigationController.swift | 16 +- .../MediaGalleryViewModel.swift | 5 + .../MediaPageViewController.swift | 64 +- .../MediaTileViewController.swift | 40 +- .../PhotoCapture.swift | 5 +- .../PhotoCaptureViewController.swift | 81 +- .../PhotoGridViewCell.swift | 22 +- .../SendMediaNavigationController.swift | 429 ++-- Session/Meta/AppDelegate.swift | 2 - Session/Meta/MainAppContext.m | 2 +- Session/Meta/Signal-Bridging-Header.h | 4 - Session/Onboarding/DisplayNameVC.swift | 9 +- Session/Onboarding/FakeChatView.swift | 2 +- Session/Onboarding/LinkDeviceVC.swift | 92 +- Session/Onboarding/PNModeVC.swift | 30 +- Session/Onboarding/PNOptionView.swift | 83 +- Session/Onboarding/RegisterVC.swift | 21 +- Session/Onboarding/RestoreVC.swift | 37 +- Session/Onboarding/SeedReminderView.swift | 17 +- Session/Onboarding/SeedVC.swift | 44 +- Session/Open Groups/JoinOpenGroupVC.swift | 5 +- .../BlockedContactsViewController.swift | 4 +- Session/Settings/QRCodeVC.swift | 13 +- .../SettingsTableViewController.swift | 2 +- .../Views/PrimaryColorSelectionView.swift | 2 +- .../Settings/Views/ThemeSelectionView.swift | 8 +- Session/Shared/CaptionView.swift | 9 +- Session/Shared/DismissableTextField.swift | 66 - Session/Shared/FullConversationCell.swift | 4 +- Session/Shared/LoadingViewController.swift | 12 +- Session/Shared/MarqueeLabel.swift | 1841 ----------------- Session/Shared/NeverClearView.swift | 18 - Session/Shared/OWSBezierPathView.m | 1 - Session/Shared/OWSProgressView.h | 18 - Session/Shared/OWSProgressView.m | 132 -- .../Shared/OWSQRCodeScanningViewController.h | 34 - .../Shared/OWSQRCodeScanningViewController.m | 162 -- .../Shared/QRCodeScanningViewController.swift | 150 ++ Session/Shared/ReminderView.swift | 121 -- Session/Shared/ScanQRCodeWrapperVC.swift | 40 +- .../Sheets & Modals/ConfirmationModal.swift | 6 +- Session/Sheets & Modals/Modal.swift | 7 + Session/Sheets & Modals/Sheet.swift | 65 - Session/Utilities/AvatarViewHelper.m | 2 - Session/Utilities/UIApplication+OWS.swift | 2 +- .../Utilities/UINavigationBar+Utilities.swift | 3 +- Session/Utilities/UIView+Glow.swift | 48 - .../Utilities/UIViewController+Permissions.m | 1 - .../Database/Models/RecipientState.swift | 2 +- .../Utilities/OWSWindowManager.h | 19 - .../Utilities/OWSWindowManager.m | 233 +-- .../SAEScreenLockViewController.swift | 16 +- .../ShareAppExtensionContext.swift | 2 +- .../NotificationContentViewModelSpec.swift | 2 +- SessionUIKit/Configuration.swift | 4 +- SessionUIKit/Style Guide/ThemeManager.swift | 7 +- .../Themes/Theme+ClassicDark.swift | 4 +- .../Themes/Theme+ClassicLight.swift | 2 + .../Style Guide/Themes/Theme+Colors.swift | 15 +- .../Style Guide/Themes/Theme+OceanDark.swift | 2 + .../Style Guide/Themes/Theme+OceanLight.swift | 80 +- SessionUIKit/Style Guide/Themes/Theme.swift | 39 + .../Style Guide/Themes/UIKit+Theme.swift | 395 ++++ SessionUIKit/Utilities/UIImage+Tinting.swift | 6 +- SessionUtilitiesKit/General/UIView+OWS.h | 10 - SessionUtilitiesKit/General/UIView+OWS.m | 30 - SignalUtilitiesKit/Configuration.swift | 2 +- ...AttachmentApprovalInputAccessoryView.swift | 17 +- .../AttachmentApprovalViewController.swift | 35 +- .../AttachmentCaptionToolbar.swift | 14 +- .../AttachmentCaptionViewController.swift | 316 --- .../AttachmentItemCollection.swift | 10 +- .../AttachmentPrepViewController.swift | 48 +- .../AttachmentTextToolbar.swift | 20 +- .../ImageEditorBrushViewController.swift | 28 +- .../Image Editing/ImageEditorCanvasView.swift | 34 +- .../ImageEditorCropViewController.swift | 61 +- .../ImageEditorPaletteView.swift | 32 +- .../ImageEditorTextViewController.swift | 59 +- .../Image Editing/ImageEditorView.swift | 22 +- .../MediaMessageView.swift | 44 +- .../OWSViewController+ImageEditor.swift | 18 +- .../VideoPlayerView.swift | 35 +- SignalUtilitiesKit/Meta/SignalUtilitiesKit.h | 3 +- .../Profile Pictures/PlaceholderIcon.swift | 25 +- .../Profile Pictures/ProfilePictureView.swift | 6 +- .../ScreenLockViewController.swift | 9 +- ...ModalActivityIndicatorViewController.swift | 17 +- .../OWSNavigationController.m | 8 - .../OWSViewController.m | 9 - .../SheetViewController.swift | 223 -- .../Shared Views/ApprovalRailCellView.swift | 15 +- .../Shared Views/GalleryRailView.swift | 15 +- .../Shared Views/OWSButton.swift | 29 +- .../Shared Views/OWSFlatButton.swift | 191 -- .../Shared Views/OWSNavigationBar.swift | 104 +- SignalUtilitiesKit/Shared Views/Toast.swift | 85 +- SignalUtilitiesKit/Utilities/AppSetup.swift | 2 +- .../Utilities/UIImage+OWS.swift | 16 - SignalUtilitiesKit/Utilities/UIUtil.h | 30 - SignalUtilitiesKit/Utilities/UIUtil.m | 46 - .../Utilities/UIViewController+OWS.h | 26 - .../Utilities/UIViewController+OWS.m | 122 -- .../Utilities/UIViewController+OWS.swift | 109 + 128 files changed, 2196 insertions(+), 5278 deletions(-) delete mode 100644 Session/Media Viewing & Editing/AudioProgressView.swift delete mode 100644 Session/Shared/DismissableTextField.swift delete mode 100644 Session/Shared/MarqueeLabel.swift delete mode 100644 Session/Shared/NeverClearView.swift delete mode 100644 Session/Shared/OWSProgressView.h delete mode 100644 Session/Shared/OWSProgressView.m delete mode 100644 Session/Shared/OWSQRCodeScanningViewController.h delete mode 100644 Session/Shared/OWSQRCodeScanningViewController.m create mode 100644 Session/Shared/QRCodeScanningViewController.swift delete mode 100644 Session/Shared/ReminderView.swift delete mode 100644 Session/Sheets & Modals/Sheet.swift delete mode 100644 Session/Utilities/UIView+Glow.swift delete mode 100644 SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionViewController.swift delete mode 100644 SignalUtilitiesKit/Shared View Controllers/SheetViewController.swift delete mode 100644 SignalUtilitiesKit/Shared Views/OWSFlatButton.swift delete mode 100644 SignalUtilitiesKit/Utilities/UIImage+OWS.swift delete mode 100644 SignalUtilitiesKit/Utilities/UIUtil.h delete mode 100644 SignalUtilitiesKit/Utilities/UIUtil.m delete mode 100644 SignalUtilitiesKit/Utilities/UIViewController+OWS.h delete mode 100644 SignalUtilitiesKit/Utilities/UIViewController+OWS.m create mode 100644 SignalUtilitiesKit/Utilities/UIViewController+OWS.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 03190e540..67288258b 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -9,11 +9,8 @@ /* Begin PBXBuildFile section */ 1FFD68A448D5A1439F2F02FD /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DBA125424EDD2417B515C63A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; }; 3289CA2E9E89DA9D4D52A90C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BF4561630A52BE96F164CF6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; }; - 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */; }; 3427C64320F500E000EEC730 /* OWSMessageTimerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */; }; 3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3430FE171F7751D4000EC51B /* GiphyAPI.swift */; }; - 34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34330AA21E79686200DF2FB9 /* OWSProgressView.m */; }; - 34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34386A53207D271C009F5D9C /* NeverClearView.swift */; }; 34661FB820C1C0D60056EDD6 /* message_sent.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 34661FB720C1C0D60056EDD6 /* message_sent.aiff */; }; 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; }; 3478504C1FD7496D007B8332 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; }; @@ -37,7 +34,6 @@ 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D1F0511F7E8EA30066283D /* GiphyDownloader.swift */; }; 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */; }; 34D99CE4217509C2000AFB39 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */; }; - 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */; }; 34F308A21ECB469700BB7697 /* OWSBezierPathView.m in Sources */ = {isa = PBXBuildFile; fileRef = 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */; }; 4503F1BE20470A5B00CEE724 /* classic-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */; }; 4503F1BF20470A5B00CEE724 /* classic.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 4503F1BC20470A5B00CEE724 /* classic.aifc */; }; @@ -54,7 +50,6 @@ 455A16DE1F1FEA0000F86704 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 455A16DC1F1FEA0000F86704 /* MetalKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 45847E871E4283C30080EAB3 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45847E861E4283C30080EAB3 /* Intents.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 45A2F005204473A3002E978A /* NewMessage.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45A2F004204473A3002E978A /* NewMessage.aifc */; }; - 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; }; 45B5360E206DD8BB00D61655 /* UIResponder+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B5360D206DD8BB00D61655 /* UIResponder+OWS.swift */; }; 45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45B74A5B2044AAB300CD42F8 /* aurora-quiet.aifc */; }; 45B74A752044AAB600CD42F8 /* synth-quiet.aifc in Resources */ = {isa = PBXBuildFile; fileRef = 45B74A5C2044AAB300CD42F8 /* synth-quiet.aifc */; }; @@ -85,13 +80,11 @@ 45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */; }; 45CB2FA81CB7146C00E1B343 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */; }; 45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */; }; - 45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */; }; 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F32C1D205718B000A300D5 /* MediaPageViewController.swift */; }; 4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; }; 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1885D1218F8E1C00B67051 /* PhotoGridViewCell.swift */; }; 4C21D5D8223AC60F00EF8A77 /* PhotoCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */; }; 4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */; }; - 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */; }; 4C586926224FAB83003FD070 /* AVAudioSession+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */; }; 4C63CC00210A620B003AE45C /* SignalTSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C63CBFF210A620B003AE45C /* SignalTSan.supp */; }; 4C6F527C20FFE8400097DEEE /* SignalUBSan.supp in Resources */ = {isa = PBXBuildFile; fileRef = 4C6F527B20FFE8400097DEEE /* SignalUBSan.supp */; }; @@ -154,8 +147,8 @@ 7BAF54D427ACCF01003D12F8 /* SAEScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D227ACCF01003D12F8 /* SAEScreenLockViewController.swift */; }; 7BAF54D827ACD0E3003D12F8 /* ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */; }; 7BAF54DC27ACD12B003D12F8 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BAF54DB27ACD12B003D12F8 /* UIColor+Extensions.swift */; }; - 7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */; }; 7BBBDC44286EAD2D00747E59 /* TappableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */; }; + 7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; }; @@ -279,7 +272,6 @@ C300A5F22554B09800555489 /* MessageSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = C300A5F12554B09800555489 /* MessageSender.swift */; }; C300A60D2554B31900555489 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5CE2553860700C340D1 /* Logging.swift */; }; C302093E25DCBF08001F572D /* MentionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C302093D25DCBF07001F572D /* MentionSelectionView.swift */; }; - C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31A6C59247F214E001123EF /* UIView+Glow.swift */; }; C31A6C5C247F2CF3001123EF /* CGRect+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */; }; C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */; }; C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; @@ -309,7 +301,6 @@ C32C6018256E07F9003C73A2 /* NSUserDefaults+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB51255A580D00E217F9 /* NSUserDefaults+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */; }; C33100092558FF6D00070591 /* UserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DDC25217014005D4DA8 /* UserCell.swift */; }; - C33100142558FFC200070591 /* UIImage+Tinting.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33100132558FFC200070591 /* UIImage+Tinting.swift */; }; C33100282559000A00070591 /* UIView+Rendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33100272559000A00070591 /* UIView+Rendering.swift */; }; C3310033255900A400070591 /* Notification+AppMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3310032255900A400070591 /* Notification+AppMode.swift */; }; C331FF1F2558F9D300070591 /* SessionUIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C331FF1D2558F9D300070591 /* SessionUIKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -389,11 +380,9 @@ C38EF00C255B61CC007E1867 /* SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C33FD9AB255A548A00E217F9 /* SignalUtilitiesKit.framework */; }; C38EF22B255B6D5D007E1867 /* ShareViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF226255B6D5D007E1867 /* ShareViewDelegate.swift */; }; C38EF22C255B6D5D007E1867 /* OWSVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF227255B6D5D007E1867 /* OWSVideoPlayer.swift */; }; - C38EF243255B6D67007E1867 /* UIViewController+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF236255B6D65007E1867 /* UIViewController+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF245255B6D67007E1867 /* UIFont+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF238255B6D66007E1867 /* UIFont+OWS.m */; }; C38EF246255B6D67007E1867 /* UIFont+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF239255B6D66007E1867 /* UIFont+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF247255B6D67007E1867 /* NSAttributedString+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF23A255B6D66007E1867 /* NSAttributedString+OWS.m */; }; - C38EF248255B6D67007E1867 /* UIViewController+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF23B255B6D66007E1867 /* UIViewController+OWS.m */; }; C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23C255B6D66007E1867 /* UIColor+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF23F255B6D67007E1867 /* NSAttributedString+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF24D255B6D67007E1867 /* UIView+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF240255B6D67007E1867 /* UIView+OWS.swift */; }; @@ -406,15 +395,11 @@ C38EF2B4255B6D9C007E1867 /* UIView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2B2255B6D9C007E1867 /* UIView+Utilities.swift */; }; C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2E2255B6DB9007E1867 /* ScreenLock.swift */; }; C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F2255B6DBC007E1867 /* Searcher.swift */; }; - C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */; }; C38EF324255B6DBF007E1867 /* Bench.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF2FA255B6DBD007E1867 /* Bench.swift */; }; - C38EF32A255B6DBF007E1867 /* UIUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF300255B6DBD007E1867 /* UIUtil.m */; }; C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF301255B6DBD007E1867 /* OWSFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF32E255B6DBF007E1867 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF304255B6DBE007E1867 /* ImageCache.swift */; }; C38EF32F255B6DBF007E1867 /* OWSFormat.m in Sources */ = {isa = PBXBuildFile; fileRef = C38EF305255B6DBE007E1867 /* OWSFormat.m */; }; C38EF331255B6DBF007E1867 /* UIGestureRecognizer+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */; }; - C38EF334255B6DBF007E1867 /* UIUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF30A255B6DBE007E1867 /* UIUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C38EF359255B6DCC007E1867 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF33F255B6DC5007E1867 /* SheetViewController.swift */; }; C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF343255B6DC5007E1867 /* OWSNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF35E255B6DCC007E1867 /* OWSViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF344255B6DC5007E1867 /* OWSViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */; }; @@ -429,7 +414,6 @@ C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF381255B6DD1007E1867 /* AttachmentCaptionToolbar.swift */; }; C38EF38B255B6DD2007E1867 /* AttachmentPrepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF382255B6DD1007E1867 /* AttachmentPrepViewController.swift */; }; C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF383255B6DD1007E1867 /* ApprovalRailCellView.swift */; }; - C38EF38D255B6DD2007E1867 /* AttachmentCaptionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF384255B6DD2007E1867 /* AttachmentCaptionViewController.swift */; }; C38EF3B8255B6DE7007E1867 /* ImageEditorTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3A8255B6DE4007E1867 /* ImageEditorTextViewController.swift */; }; C38EF3B9255B6DE7007E1867 /* ImageEditorPinchGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3A9255B6DE4007E1867 /* ImageEditorPinchGestureRecognizer.swift */; }; C38EF3BA255B6DE7007E1867 /* ImageEditorItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3AA255B6DE4007E1867 /* ImageEditorItem.swift */; }; @@ -460,7 +444,6 @@ C38EF402255B6DF7007E1867 /* CommonStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3E4255B6DF4007E1867 /* CommonStrings.swift */; }; C38EF405255B6DF7007E1867 /* OWSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3E7255B6DF5007E1867 /* OWSButton.swift */; }; C38EF407255B6DF7007E1867 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3E9255B6DF6007E1867 /* Toast.swift */; }; - C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3EC255B6DF6007E1867 /* OWSFlatButton.swift */; }; C38EF40B255B6DF7007E1867 /* TappableStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */; }; C38EF40C255B6DF7007E1867 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38EF3EE255B6DF6007E1867 /* GradientView.swift */; }; C38EF48A255B7E3F007E1867 /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; }; @@ -533,7 +516,6 @@ C3D9E50E25677A510040E4F3 /* DataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB54255A580D00E217F9 /* DataSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */; }; - C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; CEE449BA3596483519120D91 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A8A44E3F8AC9282AC5E6E5A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit.framework */; }; @@ -721,6 +703,9 @@ FD71161728D00DA400B47552 /* ThreadSettingsViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161628D00DA400B47552 /* ThreadSettingsViewModelSpec.swift */; }; FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161928D00E1100B47552 /* NotificationContentViewModelSpec.swift */; }; FD71161C28D194FB00B47552 /* MentionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161B28D194FB00B47552 /* MentionInfo.swift */; }; + FD71161E28D9772700B47552 /* UIViewController+OWS.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161D28D9772700B47552 /* UIViewController+OWS.swift */; }; + FD71162028D97ABC00B47552 /* UIImage+Tinting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71161F28D97ABC00B47552 /* UIImage+Tinting.swift */; }; + FD71162228D983ED00B47552 /* QRCodeScanningViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD71162128D983ED00B47552 /* QRCodeScanningViewController.swift */; }; FD7162DB281B6C440060647B /* TypedTableAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7162DA281B6C440060647B /* TypedTableAlias.swift */; }; FD716E6428502DDD00C96BF4 /* CallManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD716E6328502DDD00C96BF4 /* CallManagerProtocol.swift */; }; FD716E6628502EE200C96BF4 /* CurrentCallProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD716E6528502EE200C96BF4 /* CurrentCallProtocol.swift */; }; @@ -1065,14 +1050,9 @@ 2581AFACDDDC1404866D7B8C /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; path = "Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.app store release.xcconfig"; sourceTree = ""; }; 2691123A7F231EDD8226C4B5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29CF8C79F41BF00B1C2E59A0 /* Pods-SessionUIKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUIKit.app store release.xcconfig"; path = "Target Support Files/Pods-SessionUIKit/Pods-SessionUIKit.app store release.xcconfig"; sourceTree = ""; }; - 340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSQRCodeScanningViewController.h; sourceTree = ""; }; - 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSQRCodeScanningViewController.m; sourceTree = ""; }; 3427C64120F500DE00EEC730 /* OWSMessageTimerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageTimerView.h; sourceTree = ""; }; 3427C64220F500DF00EEC730 /* OWSMessageTimerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageTimerView.m; sourceTree = ""; }; 3430FE171F7751D4000EC51B /* GiphyAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GiphyAPI.swift; sourceTree = ""; }; - 34330AA11E79686200DF2FB9 /* OWSProgressView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProgressView.h; sourceTree = ""; }; - 34330AA21E79686200DF2FB9 /* OWSProgressView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProgressView.m; sourceTree = ""; }; - 34386A53207D271C009F5D9C /* NeverClearView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NeverClearView.swift; sourceTree = ""; }; 34480B371FD092A900BC14EF /* SignalShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SignalShareExtension-Bridging-Header.h"; sourceTree = ""; }; 34480B381FD092E300BC14EF /* SessionShareExtension-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SessionShareExtension-Prefix.pch"; sourceTree = ""; }; 34661FB720C1C0D60056EDD6 /* message_sent.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; name = message_sent.aiff; path = Session/Meta/AudioFiles/message_sent.aiff; sourceTree = SOURCE_ROOT; }; @@ -1099,7 +1079,6 @@ 34D5CCA71EAE3D30005515DB /* AvatarViewHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvatarViewHelper.h; sourceTree = ""; }; 34D5CCA81EAE3D30005515DB /* AvatarViewHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AvatarViewHelper.m; sourceTree = ""; }; 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = ""; }; - 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioProgressView.swift; sourceTree = ""; }; 34F308A01ECB469700BB7697 /* OWSBezierPathView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSBezierPathView.h; sourceTree = ""; }; 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSBezierPathView.m; sourceTree = ""; }; 4503F1BB20470A5B00CEE724 /* classic-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "classic-quiet.aifc"; sourceTree = ""; }; @@ -1119,7 +1098,6 @@ 455A16DC1F1FEA0000F86704 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; 45847E861E4283C30080EAB3 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; 45A2F004204473A3002E978A /* NewMessage.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; name = NewMessage.aifc; path = Session/Meta/AudioFiles/NewMessage.aifc; sourceTree = SOURCE_ROOT; }; - 45A6DAD51EBBF85500893231 /* ReminderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReminderView.swift; sourceTree = ""; }; 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = ""; }; 45B5360D206DD8BB00D61655 /* UIResponder+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIResponder+OWS.swift"; sourceTree = ""; }; 45B74A5B2044AAB300CD42F8 /* aurora-quiet.aifc */ = {isa = PBXFileReference; lastKnownFileType = file; path = "aurora-quiet.aifc"; sourceTree = ""; }; @@ -1151,7 +1129,6 @@ 45C0DC1D1E69011F00E04C47 /* UIStoryboard+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard+OWS.swift"; sourceTree = ""; }; 45CB2FA71CB7146C00E1B343 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "Session/Meta/Launch Screen.storyboard"; sourceTree = SOURCE_ROOT; }; 45CD81EE1DC030E7004C9430 /* SyncPushTokensJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncPushTokensJob.swift; sourceTree = ""; }; - 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarqueeLabel.swift; sourceTree = ""; }; 45F32C1D205718B000A300D5 /* MediaPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MediaPageViewController.swift; path = "Session/Media Viewing & Editing/MediaPageViewController.swift"; sourceTree = SOURCE_ROOT; }; 48AD214D67ABED845101E795 /* Pods_GlobalDependencies_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = ""; }; @@ -1159,7 +1136,6 @@ 4C1D2337218B6BA000A0598F /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; 4C21D5D7223AC60F00EF8A77 /* PhotoCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCapture.swift; sourceTree = ""; }; 4C4AE69F224AF21900D4AF6F /* SendMediaNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMediaNavigationController.swift; sourceTree = ""; }; - 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissableTextField.swift; sourceTree = ""; }; 4C586924224FAB83003FD070 /* AVAudioSession+OWS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AVAudioSession+OWS.h"; sourceTree = ""; }; 4C586925224FAB83003FD070 /* AVAudioSession+OWS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "AVAudioSession+OWS.m"; sourceTree = ""; }; 4C63CBFF210A620B003AE45C /* SignalTSan.supp */ = {isa = PBXFileReference; lastKnownFileType = text; path = SignalTSan.supp; sourceTree = ""; }; @@ -1233,8 +1209,8 @@ 7BAF54D227ACCF01003D12F8 /* SAEScreenLockViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SAEScreenLockViewController.swift; sourceTree = ""; }; 7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableView.swift; sourceTree = ""; }; 7BAF54DB27ACD12B003D12F8 /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; - 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTitleViewController.swift; sourceTree = ""; }; 7BBBDC43286EAD2D00747E59 /* TappableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TappableLabel.swift; sourceTree = ""; }; + 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentTitleViewController.swift; sourceTree = ""; }; 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1377,7 +1353,6 @@ C300A5F12554B09800555489 /* MessageSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSender.swift; sourceTree = ""; }; C300A5FB2554B0A000555489 /* MessageReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReceiver.swift; sourceTree = ""; }; C302093D25DCBF07001F572D /* MentionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSelectionView.swift; sourceTree = ""; }; - C31A6C59247F214E001123EF /* UIView+Glow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Glow.swift"; sourceTree = ""; }; C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+Utilities.swift"; sourceTree = ""; }; C31D1DDC25217014005D4DA8 /* UserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = ""; }; C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionVC.swift; sourceTree = ""; }; @@ -1388,7 +1363,6 @@ C328254825CA60E60062D0A7 /* ContextMenuVC+Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+Action.swift"; sourceTree = ""; }; C328255125CA64470062D0A7 /* ContextMenuVC+ActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextMenuVC+ActionView.swift"; sourceTree = ""; }; C32C5A87256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ClosedGroups.swift"; sourceTree = ""; }; - C33100132558FFC200070591 /* UIImage+Tinting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Tinting.swift"; sourceTree = ""; }; C33100272559000A00070591 /* UIView+Rendering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Rendering.swift"; sourceTree = ""; }; C3310032255900A400070591 /* Notification+AppMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+AppMode.swift"; sourceTree = ""; }; C331FF1B2558F9D300070591 /* SessionUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SessionUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1491,12 +1465,10 @@ C38EF224255B6D5D007E1867 /* SignalAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SignalAttachment.swift; path = "SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift"; sourceTree = SOURCE_ROOT; }; C38EF226255B6D5D007E1867 /* ShareViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShareViewDelegate.swift; path = SignalUtilitiesKit/Utilities/ShareViewDelegate.swift; sourceTree = SOURCE_ROOT; }; C38EF227255B6D5D007E1867 /* OWSVideoPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSVideoPlayer.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/OWSVideoPlayer.swift"; sourceTree = SOURCE_ROOT; }; - C38EF236255B6D65007E1867 /* UIViewController+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIViewController+OWS.h"; path = "SignalUtilitiesKit/Utilities/UIViewController+OWS.h"; sourceTree = SOURCE_ROOT; }; C38EF237255B6D65007E1867 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIDevice+featureSupport.swift"; path = "SessionUtilitiesKit/General/UIDevice+featureSupport.swift"; sourceTree = SOURCE_ROOT; }; C38EF238255B6D66007E1867 /* UIFont+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIFont+OWS.m"; path = "SignalUtilitiesKit/Utilities/UIFont+OWS.m"; sourceTree = SOURCE_ROOT; }; C38EF239255B6D66007E1867 /* UIFont+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIFont+OWS.h"; path = "SignalUtilitiesKit/Utilities/UIFont+OWS.h"; sourceTree = SOURCE_ROOT; }; C38EF23A255B6D66007E1867 /* NSAttributedString+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+OWS.m"; path = "SignalUtilitiesKit/Utilities/NSAttributedString+OWS.m"; sourceTree = SOURCE_ROOT; }; - C38EF23B255B6D66007E1867 /* UIViewController+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIViewController+OWS.m"; path = "SignalUtilitiesKit/Utilities/UIViewController+OWS.m"; sourceTree = SOURCE_ROOT; }; C38EF23C255B6D66007E1867 /* UIColor+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+OWS.h"; path = "SignalUtilitiesKit/Utilities/UIColor+OWS.h"; sourceTree = SOURCE_ROOT; }; C38EF23D255B6D66007E1867 /* UIView+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+OWS.h"; path = "SessionUtilitiesKit/General/UIView+OWS.h"; sourceTree = SOURCE_ROOT; }; C38EF23E255B6D66007E1867 /* UIView+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+OWS.m"; path = "SessionUtilitiesKit/General/UIView+OWS.m"; sourceTree = SOURCE_ROOT; }; @@ -1514,20 +1486,16 @@ C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProximityMonitoringManager.swift; path = SessionMessagingKit/Utilities/ProximityMonitoringManager.swift; sourceTree = SOURCE_ROOT; }; C38EF2EF255B6DBB007E1867 /* Weak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Weak.swift; path = SessionUtilitiesKit/General/Weak.swift; sourceTree = SOURCE_ROOT; }; C38EF2F2255B6DBC007E1867 /* Searcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Searcher.swift; path = SignalUtilitiesKit/Utilities/Searcher.swift; sourceTree = SOURCE_ROOT; }; - C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIImage+OWS.swift"; path = "SignalUtilitiesKit/Utilities/UIImage+OWS.swift"; sourceTree = SOURCE_ROOT; }; C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSAudioPlayer.h; path = SessionMessagingKit/Utilities/OWSAudioPlayer.h; sourceTree = SOURCE_ROOT; }; C38EF2F7255B6DBC007E1867 /* OWSAudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSAudioPlayer.m; path = SessionMessagingKit/Utilities/OWSAudioPlayer.m; sourceTree = SOURCE_ROOT; }; C38EF2FA255B6DBD007E1867 /* Bench.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Bench.swift; path = SignalUtilitiesKit/Utilities/Bench.swift; sourceTree = SOURCE_ROOT; }; C38EF2FB255B6DBD007E1867 /* OWSWindowManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSWindowManager.h; path = SessionMessagingKit/Utilities/OWSWindowManager.h; sourceTree = SOURCE_ROOT; }; - C38EF300255B6DBD007E1867 /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = UIUtil.m; path = SignalUtilitiesKit/Utilities/UIUtil.m; sourceTree = SOURCE_ROOT; }; C38EF301255B6DBD007E1867 /* OWSFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSFormat.h; path = SignalUtilitiesKit/Utilities/OWSFormat.h; sourceTree = SOURCE_ROOT; }; C38EF304255B6DBE007E1867 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageCache.swift; path = SignalUtilitiesKit/Utilities/ImageCache.swift; sourceTree = SOURCE_ROOT; }; C38EF305255B6DBE007E1867 /* OWSFormat.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSFormat.m; path = SignalUtilitiesKit/Utilities/OWSFormat.m; sourceTree = SOURCE_ROOT; }; C38EF306255B6DBE007E1867 /* OWSWindowManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSWindowManager.m; path = SessionMessagingKit/Utilities/OWSWindowManager.m; sourceTree = SOURCE_ROOT; }; C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIGestureRecognizer+OWS.swift"; path = "SignalUtilitiesKit/Utilities/UIGestureRecognizer+OWS.swift"; sourceTree = SOURCE_ROOT; }; C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DeviceSleepManager.swift; path = SessionMessagingKit/Utilities/DeviceSleepManager.swift; sourceTree = SOURCE_ROOT; }; - C38EF30A255B6DBE007E1867 /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = UIUtil.h; path = SignalUtilitiesKit/Utilities/UIUtil.h; sourceTree = SOURCE_ROOT; }; - C38EF33F255B6DC5007E1867 /* SheetViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SheetViewController.swift; path = "SignalUtilitiesKit/Shared View Controllers/SheetViewController.swift"; sourceTree = SOURCE_ROOT; }; C38EF343255B6DC5007E1867 /* OWSNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSNavigationController.h; path = "SignalUtilitiesKit/Shared View Controllers/OWSNavigationController.h"; sourceTree = SOURCE_ROOT; }; C38EF344255B6DC5007E1867 /* OWSViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSViewController.h; path = "SignalUtilitiesKit/Shared View Controllers/OWSViewController.h"; sourceTree = SOURCE_ROOT; }; C38EF349255B6DC7007E1867 /* ModalActivityIndicatorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ModalActivityIndicatorViewController.swift; path = "SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift"; sourceTree = SOURCE_ROOT; }; @@ -1542,7 +1510,6 @@ C38EF381255B6DD1007E1867 /* AttachmentCaptionToolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AttachmentCaptionToolbar.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift"; sourceTree = SOURCE_ROOT; }; C38EF382255B6DD1007E1867 /* AttachmentPrepViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AttachmentPrepViewController.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift"; sourceTree = SOURCE_ROOT; }; C38EF383255B6DD1007E1867 /* ApprovalRailCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ApprovalRailCellView.swift; path = "SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift"; sourceTree = SOURCE_ROOT; }; - C38EF384255B6DD2007E1867 /* AttachmentCaptionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AttachmentCaptionViewController.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionViewController.swift"; sourceTree = SOURCE_ROOT; }; C38EF3A8255B6DE4007E1867 /* ImageEditorTextViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageEditorTextViewController.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift"; sourceTree = SOURCE_ROOT; }; C38EF3A9255B6DE4007E1867 /* ImageEditorPinchGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageEditorPinchGestureRecognizer.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPinchGestureRecognizer.swift"; sourceTree = SOURCE_ROOT; }; C38EF3AA255B6DE4007E1867 /* ImageEditorItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ImageEditorItem.swift; path = "SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorItem.swift"; sourceTree = SOURCE_ROOT; }; @@ -1573,7 +1540,6 @@ C38EF3E4255B6DF4007E1867 /* CommonStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CommonStrings.swift; path = SignalUtilitiesKit/Utilities/CommonStrings.swift; sourceTree = SOURCE_ROOT; }; C38EF3E7255B6DF5007E1867 /* OWSButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSButton.swift; path = "SignalUtilitiesKit/Shared Views/OWSButton.swift"; sourceTree = SOURCE_ROOT; }; C38EF3E9255B6DF6007E1867 /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Toast.swift; path = "SignalUtilitiesKit/Shared Views/Toast.swift"; sourceTree = SOURCE_ROOT; }; - C38EF3EC255B6DF6007E1867 /* OWSFlatButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OWSFlatButton.swift; path = "SignalUtilitiesKit/Shared Views/OWSFlatButton.swift"; sourceTree = SOURCE_ROOT; }; C38EF3ED255B6DF6007E1867 /* TappableStackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TappableStackView.swift; path = "SignalUtilitiesKit/Shared Views/TappableStackView.swift"; sourceTree = SOURCE_ROOT; }; C38EF3EE255B6DF6007E1867 /* GradientView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GradientView.swift; path = "SignalUtilitiesKit/Shared Views/GradientView.swift"; sourceTree = SOURCE_ROOT; }; C38EF458255B710A007E1867 /* SignalUtilitiesKit-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SignalUtilitiesKit-Prefix.pch"; sourceTree = ""; }; @@ -1649,7 +1615,6 @@ C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = ""; }; C3DB66AB260ACA42001EFC55 /* OpenGroupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManager.swift; sourceTree = ""; }; C3DB66C2260ACCE6001EFC55 /* OpenGroupPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupPoller.swift; sourceTree = ""; }; - C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; C3ECBF7A257056B700EA7FCE /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; @@ -1822,6 +1787,9 @@ FD71161628D00DA400B47552 /* ThreadSettingsViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadSettingsViewModelSpec.swift; sourceTree = ""; }; FD71161928D00E1100B47552 /* NotificationContentViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentViewModelSpec.swift; sourceTree = ""; }; FD71161B28D194FB00B47552 /* MentionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionInfo.swift; sourceTree = ""; }; + FD71161D28D9772700B47552 /* UIViewController+OWS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+OWS.swift"; sourceTree = ""; }; + FD71161F28D97ABC00B47552 /* UIImage+Tinting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Tinting.swift"; sourceTree = ""; }; + FD71162128D983ED00B47552 /* QRCodeScanningViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScanningViewController.swift; sourceTree = ""; }; FD7162DA281B6C440060647B /* TypedTableAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedTableAlias.swift; sourceTree = ""; }; FD716E6328502DDD00C96BF4 /* CallManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManagerProtocol.swift; sourceTree = ""; }; FD716E6528502EE200C96BF4 /* CurrentCallProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentCallProtocol.swift; sourceTree = ""; }; @@ -2247,7 +2215,6 @@ B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */, FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */, FD848B8E283EF2A8000E298B /* UIScrollView+Utilities.swift */, - C31A6C59247F214E001123EF /* UIView+Glow.swift */, C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */, 7BAADFCD27B215FE007BCF92 /* UIView+Draggable.swift */, 7BFD1A892745C4F000FB91B9 /* Permissions.swift */, @@ -2589,19 +2556,12 @@ isa = PBXGroup; children = ( 4CA46F4B219CCC630038ABDE /* CaptionView.swift */, - 4C4AEC4420EC343B0020E72B /* DismissableTextField.swift */, - 45E5A6981F61E6DD001E4A8A /* MarqueeLabel.swift */, - 34386A53207D271C009F5D9C /* NeverClearView.swift */, 34F308A01ECB469700BB7697 /* OWSBezierPathView.h */, 34F308A11ECB469700BB7697 /* OWSBezierPathView.m */, - 34330AA11E79686200DF2FB9 /* OWSProgressView.h */, - 34330AA21E79686200DF2FB9 /* OWSProgressView.m */, - 45A6DAD51EBBF85500893231 /* ReminderView.swift */, C354E75923FE2A7600CE22E3 /* BaseVC.swift */, B8BB82AA238F669C00BA5194 /* FullConversationCell.swift */, 4542DF53208D40AC007B4E76 /* LoadingViewController.swift */, - 340FC888204DAC8C007AEB0F /* OWSQRCodeScanningViewController.h */, - 340FC896204DAC8C007AEB0F /* OWSQRCodeScanningViewController.m */, + FD71162128D983ED00B47552 /* QRCodeScanningViewController.swift */, B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */, C31D1DDC25217014005D4DA8 /* UserCell.swift */, C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */, @@ -2841,9 +2801,9 @@ children = ( B8544E3023D16CA500299F14 /* DeviceUtilities.swift */, FD37E9D628A20B5D003AE748 /* UIColor+Utilities.swift */, - C33100132558FFC200070591 /* UIImage+Tinting.swift */, B885D5F52334A32100EE0D8E /* UIView+Constraints.swift */, C33100272559000A00070591 /* UIView+Rendering.swift */, + FD71161F28D97ABC00B47552 /* UIImage+Tinting.swift */, ); path = Utilities; sourceTree = ""; @@ -2990,7 +2950,6 @@ isa = PBXGroup; children = ( B86BD08323399ACF000F5AE3 /* Modal.swift */, - C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */, FD52090628B49738006098F6 /* ConfirmationModal.swift */, ); path = "Sheets & Modals"; @@ -3019,7 +2978,6 @@ 45F32C1D205718B000A300D5 /* MediaPageViewController.swift */, 454A84032059C787008B8C75 /* MediaTileViewController.swift */, 7BBBDC452875600700747E59 /* DocumentTitleViewController.swift */, - 34E3E5671EC4B19400495BAC /* AudioProgressView.swift */, 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */, 34969559219B605E00DCFE74 /* ImagePickerController.swift */, 34A6C27F21E503E600B5B12E /* OWSImagePickerController.swift */, @@ -3085,7 +3043,6 @@ C38EF356255B6DCB007E1867 /* OWSNavigationController.m */, C38EF344255B6DC5007E1867 /* OWSViewController.h */, C38EF355255B6DCB007E1867 /* OWSViewController.m */, - C38EF33F255B6DC5007E1867 /* SheetViewController.swift */, ); path = "Shared View Controllers"; sourceTree = ""; @@ -3127,7 +3084,6 @@ C38EF37D255B6DCF007E1867 /* AttachmentApprovalInputAccessoryView.swift */, C38EF37F255B6DD0007E1867 /* AttachmentApprovalViewController.swift */, C38EF381255B6DD1007E1867 /* AttachmentCaptionToolbar.swift */, - C38EF384255B6DD2007E1867 /* AttachmentCaptionViewController.swift */, C38EF37E255B6DD0007E1867 /* AttachmentItemCollection.swift */, C38EF382255B6DD1007E1867 /* AttachmentPrepViewController.swift */, C38EF37C255B6DCF007E1867 /* AttachmentTextToolbar.swift */, @@ -3142,7 +3098,6 @@ B8C2B2C72563685C00551B4D /* CircleView.swift */, C38EF383255B6DD1007E1867 /* ApprovalRailCellView.swift */, C38EF3E7255B6DF5007E1867 /* OWSButton.swift */, - C38EF3EC255B6DF6007E1867 /* OWSFlatButton.swift */, C38EF3DB255B6DF1007E1867 /* OWSLayerView.swift */, C38EF3D9255B6DF1007E1867 /* OWSNavigationBar.swift */, C38EF3D7255B6DF0007E1867 /* OWSTextField.h */, @@ -3358,16 +3313,12 @@ 7BAF54DB27ACD12B003D12F8 /* UIColor+Extensions.swift */, C38EF3DC255B6DF1007E1867 /* DirectionalPanGestureRecognizer.swift */, C38EF240255B6D67007E1867 /* UIView+OWS.swift */, - C38EF236255B6D65007E1867 /* UIViewController+OWS.h */, - C38EF23B255B6D66007E1867 /* UIViewController+OWS.m */, + FD71161D28D9772700B47552 /* UIViewController+OWS.swift */, C38EF23C255B6D66007E1867 /* UIColor+OWS.h */, C38EF242255B6D67007E1867 /* UIColor+OWS.m */, C38EF2B2255B6D9C007E1867 /* UIView+Utilities.swift */, C38EF2B1255B6D9C007E1867 /* UIViewController+Utilities.swift */, C38EF307255B6DBE007E1867 /* UIGestureRecognizer+OWS.swift */, - C38EF2F3255B6DBC007E1867 /* UIImage+OWS.swift */, - C38EF30A255B6DBE007E1867 /* UIUtil.h */, - C38EF300255B6DBD007E1867 /* UIUtil.m */, C38EF239255B6D66007E1867 /* UIFont+OWS.h */, C38EF238255B6D66007E1867 /* UIFont+OWS.m */, C33FDA96255A57FE00E217F9 /* OWSDispatch.h */, @@ -4213,7 +4164,6 @@ files = ( C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */, C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */, - C38EF334255B6DBF007E1867 /* UIUtil.h in Headers */, C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */, C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */, C38EF3F6255B6DF7007E1867 /* OWSTextView.h in Headers */, @@ -4221,7 +4171,6 @@ C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */, C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */, C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */, - C38EF243255B6D67007E1867 /* UIViewController+OWS.h in Headers */, C38EF35D255B6DCC007E1867 /* OWSNavigationController.h in Headers */, C38EF249255B6D67007E1867 /* UIColor+OWS.h in Headers */, C38EF3F5255B6DF7007E1867 /* OWSTextField.h in Headers */, @@ -5192,13 +5141,13 @@ C331FF982558FA6B00070591 /* AppMode.swift in Sources */, FD52090328B4680F006098F6 /* RadioButton.swift in Sources */, C331FFE82558FB0000070591 /* TextView.swift in Sources */, + FD71162028D97ABC00B47552 /* UIImage+Tinting.swift in Sources */, FD37E9D728A20B5D003AE748 /* UIColor+Utilities.swift in Sources */, FD37E9F928A5F14A003AE748 /* _001_ThemePreferences.swift in Sources */, FD37E9C328A1C6F3003AE748 /* ThemeManager.swift in Sources */, C331FF9A2558FA6B00070591 /* Values.swift in Sources */, FD37E9C628A1D4EC003AE748 /* Theme+ClassicDark.swift in Sources */, C331FFE42558FB0000070591 /* OutlineButton.swift in Sources */, - C33100142558FFC200070591 /* UIImage+Tinting.swift in Sources */, C3310033255900A400070591 /* Notification+AppMode.swift in Sources */, C331FFE92558FB0000070591 /* Separator.swift in Sources */, C33100282559000A00070591 /* UIView+Rendering.swift in Sources */, @@ -5220,6 +5169,7 @@ C38EF2A5255B6D93007E1867 /* Identicon+ObjC.swift in Sources */, C38EF385255B6DD2007E1867 /* AttachmentTextToolbar.swift in Sources */, C33FDD23255A582000E217F9 /* FeatureFlags.swift in Sources */, + FD71161E28D9772700B47552 /* UIViewController+OWS.swift in Sources */, C38EF389255B6DD2007E1867 /* AttachmentTextView.swift in Sources */, C38EF3FF255B6DF7007E1867 /* TappableView.swift in Sources */, C38EF3C2255B6DE7007E1867 /* ImageEditorPaletteView.swift in Sources */, @@ -5230,7 +5180,6 @@ C38EF30C255B6DBF007E1867 /* ScreenLock.swift in Sources */, C38EF363255B6DCC007E1867 /* ModalActivityIndicatorViewController.swift in Sources */, C38EF38A255B6DD2007E1867 /* AttachmentCaptionToolbar.swift in Sources */, - C38EF40A255B6DF7007E1867 /* OWSFlatButton.swift in Sources */, C33FDCD1255A582000E217F9 /* FunctionalUtil.m in Sources */, C38EF402255B6DF7007E1867 /* CommonStrings.swift in Sources */, C38EF3C1255B6DE7007E1867 /* ImageEditorBrushViewController.swift in Sources */, @@ -5243,7 +5192,6 @@ C33FDC29255A581F00E217F9 /* ReachabilityManager.swift in Sources */, C38EF407255B6DF7007E1867 /* Toast.swift in Sources */, C38EF38C255B6DD2007E1867 /* ApprovalRailCellView.swift in Sources */, - C38EF32A255B6DBF007E1867 /* UIUtil.m in Sources */, C38EF2A6255B6D93007E1867 /* PlaceholderIcon.swift in Sources */, C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */, C33FDC45255A581F00E217F9 /* AppVersion.m in Sources */, @@ -5254,7 +5202,6 @@ FD87DD0428B8727D00AF0F98 /* Configuration.swift in Sources */, C38EF3BA255B6DE7007E1867 /* ImageEditorItem.swift in Sources */, C38EF3F7255B6DF7007E1867 /* OWSNavigationBar.swift in Sources */, - C38EF248255B6D67007E1867 /* UIViewController+OWS.m in Sources */, C33FDD3A255A582000E217F9 /* Notification+Loki.swift in Sources */, 7BAF54DC27ACD12B003D12F8 /* UIColor+Extensions.swift in Sources */, C38EF370255B6DCC007E1867 /* OWSNavigationController.m in Sources */, @@ -5291,14 +5238,11 @@ C33FDC98255A582000E217F9 /* SwiftSingletons.swift in Sources */, C38EF3B8255B6DE7007E1867 /* ImageEditorTextViewController.swift in Sources */, C38EF40B255B6DF7007E1867 /* TappableStackView.swift in Sources */, - C38EF31D255B6DBF007E1867 /* UIImage+OWS.swift in Sources */, - C38EF359255B6DCC007E1867 /* SheetViewController.swift in Sources */, C38EF386255B6DD2007E1867 /* AttachmentApprovalInputAccessoryView.swift in Sources */, B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */, C38EF331255B6DBF007E1867 /* UIGestureRecognizer+OWS.swift in Sources */, C33FDDC5255A582000E217F9 /* OWSError.m in Sources */, FD848B9C284435D7000E298B /* AppSetup.swift in Sources */, - C38EF38D255B6DD2007E1867 /* AttachmentCaptionViewController.swift in Sources */, C38EF31C255B6DBF007E1867 /* Searcher.swift in Sources */, C38EF2B3255B6D9C007E1867 /* UIViewController+Utilities.swift in Sources */, C38EF3BE255B6DE7007E1867 /* OrderedDictionary.swift in Sources */, @@ -5668,7 +5612,6 @@ B83524A525C3BA4B0089A44F /* InfoMessageCell.swift in Sources */, 7B9F71D82853100A006DFE7B /* EmojiWithSkinTones.swift in Sources */, B84A89BC25DE328A0040017D /* ProfilePictureVC.swift in Sources */, - 34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */, FDCDB8E02811007F00352A0C /* HomeViewModel.swift in Sources */, 7B0EFDF62755CC5400FFAAE7 /* CallMissedTipsModal.swift in Sources */, C374EEF425DB31D40073A857 /* VoiceMessageRecordingView.swift in Sources */, @@ -5682,7 +5625,6 @@ 34B0796D1FCF46B100E248C2 /* MainAppContext.m in Sources */, 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */, FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */, - 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */, B849789625D4A2F500D0D0B3 /* LinkPreviewView.swift in Sources */, @@ -5693,7 +5635,6 @@ 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */, B835247925C38D880089A44F /* MessageCell.swift in Sources */, B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */, - 34E3E5681EC4B19400495BAC /* AudioProgressView.swift in Sources */, B8D0A26925E4A2C200C1835E /* Onboarding.swift in Sources */, 34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */, 4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */, @@ -5709,17 +5650,14 @@ 7BA6890F27325CE300EFC32F /* SessionCallManager+CXProvider.swift in Sources */, 34BECE301F7ABCF800D7438D /* GifPickerLayout.swift in Sources */, 7B46AAAF28766DF4001AF2DC /* AllMediaViewController.swift in Sources */, + FD71162228D983ED00B47552 /* QRCodeScanningViewController.swift in Sources */, C331FFFE2558FF3B00070591 /* FullConversationCell.swift in Sources */, FD52090728B49738006098F6 /* ConfirmationModal.swift in Sources */, - C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */, B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */, 45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */, - 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */, B82B408E239DC00D00A248E7 /* DisplayNameVC.swift in Sources */, B835246E25C38ABF0089A44F /* ConversationVC.swift in Sources */, 7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */, - B8AF4BB426A5204600583500 /* SendSeedModal.swift in Sources */, - B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */, 7BBBDC462875600700747E59 /* DocumentTitleViewController.swift in Sources */, B877E24226CA12910007970A /* CallVC.swift in Sources */, 7BA6890D27325CCC00EFC32F /* SessionCallManager+CXCallController.swift in Sources */, @@ -5730,7 +5668,6 @@ FD37EA1928AC5CCA003AE748 /* NotificationSoundViewModel.swift in Sources */, FD7115EE28C5D79B00B47552 /* SettingsAvatarCell.swift in Sources */, 4CA485BB2232339F004B9E7D /* PhotoCaptureViewController.swift in Sources */, - 34330AA31E79686200DF2FB9 /* OWSProgressView.m in Sources */, C328254925CA60E60062D0A7 /* ContextMenuVC+Action.swift in Sources */, 4542DF54208D40AC007B4E76 /* LoadingViewController.swift in Sources */, 34D5CCA91EAE3D30005515DB /* AvatarViewHelper.m in Sources */, @@ -5794,14 +5731,12 @@ FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */, B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */, B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */, - 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, 7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */, FD37EA0B28AB12E2003AE748 /* SettingsCell.swift in Sources */, 7B93D06A27CF173D00811CB6 /* MessageRequestsViewController.swift in Sources */, C33100082558FF6D00070591 /* NewConversationButtonSet.swift in Sources */, C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */, B8BB82A5238F627000BA5194 /* HomeVC.swift in Sources */, - C31A6C5A247F214E001123EF /* UIView+Glow.swift in Sources */, 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */, FD37E9D128A1F2EB003AE748 /* ThemeSelectionView.swift in Sources */, 7B9F71D22852EEE2006DFE7B /* Emoji+SkinTones.swift in Sources */, @@ -5821,7 +5756,6 @@ FDD2506E283711D600198BDA /* DifferenceKit+Utilities.swift in Sources */, B897621C25D201F7004F83B2 /* ScrollToBottomButton.swift in Sources */, 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */, - 45E5A6991F61E6DE001E4A8A /* MarqueeLabel.swift in Sources */, FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */, C302093E25DCBF08001F572D /* MentionSelectionView.swift in Sources */, C328251F25CA3A900062D0A7 /* QuoteView.swift in Sources */, diff --git a/Session/Calls/Views & Modals/RenderView.swift b/Session/Calls/Views & Modals/RenderView.swift index da2bd4c56..3a6e162e5 100644 --- a/Session/Calls/Views & Modals/RenderView.swift +++ b/Session/Calls/Views & Modals/RenderView.swift @@ -1,6 +1,7 @@ // Copyright © 2021 Rangeproof Pty Ltd. All rights reserved. import UIKit +import AVFoundation import CoreMedia class RenderView: UIView { diff --git a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift index 2e8e37b2d..07ebbc5ca 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC+ActionView.swift @@ -111,8 +111,8 @@ extension ContextMenuVC { else { if didTouchDownInside { themeBackgroundColor = .clear - iconImageView.themeTintColor = .textPrimary - titleLabel.themeTextColor = .textPrimary + iconImageView.themeTintColor = .contextMenu_text + titleLabel.themeTextColor = .contextMenu_text } return } @@ -125,8 +125,8 @@ extension ContextMenuVC { override func touchesEnded(_ touches: Set, with event: UIEvent?) { if didTouchDownInside { themeBackgroundColor = .clear - iconImageView.themeTintColor = .textPrimary - titleLabel.themeTextColor = .textPrimary + iconImageView.themeTintColor = .contextMenu_text + titleLabel.themeTextColor = .contextMenu_text } didTouchDownInside = false @@ -135,8 +135,8 @@ extension ContextMenuVC { override func touchesCancelled(_ touches: Set, with event: UIEvent?) { if didTouchDownInside { themeBackgroundColor = .clear - iconImageView.themeTintColor = .textPrimary - titleLabel.themeTextColor = .textPrimary + iconImageView.themeTintColor = .contextMenu_text + titleLabel.themeTextColor = .contextMenu_text } didTouchDownInside = false diff --git a/Session/Conversations/Context Menu/ContextMenuVC.swift b/Session/Conversations/Context Menu/ContextMenuVC.swift index 9bb8c85a0..4f32de036 100644 --- a/Session/Conversations/Context Menu/ContextMenuVC.swift +++ b/Session/Conversations/Context Menu/ContextMenuVC.swift @@ -340,17 +340,35 @@ final class ContextMenuVC: UIViewController { func snDismiss() { let currentFrame: CGRect = self.snapshot.frame + let currentLabelFrame: CGRect = self.timestampLabel.frame let originalFrame: CGRect = self.frame + let frameDiff: CGRect = CGRect( + x: (currentFrame.minX - originalFrame.minX), + y: (currentFrame.minY - originalFrame.minY), + width: (currentFrame.width - originalFrame.width), + height: (currentFrame.height - originalFrame.height) + ) + let endLabelFrame: CGRect = CGRect( + x: (currentLabelFrame.minX - (frameDiff.origin.x + frameDiff.width)), + y: (currentLabelFrame.minY - (frameDiff.origin.y + frameDiff.height)), + width: currentLabelFrame.width, + height: currentLabelFrame.height + ) - // Remove the snapshot view from the view hierarchy to remove its constaints (and prevent - // them from causing animation bugs - also need to turn 'translatesAutoresizingMaskIntoConstraints' - // back on so autod layout doesn't mess with the frame manipulation) + // Remove the snapshot view and it's timestampLabel from the view hierarchy to remove its + // constaints (and prevent them from causing animation bugs - also need to turn + // 'translatesAutoresizingMaskIntoConstraints' back on so autod layout doesn't mess with + // the frame manipulation) let oldSuperview: UIView? = self.snapshot.superview self.snapshot.removeFromSuperview() + self.timestampLabel.removeFromSuperview() oldSuperview?.insertSubview(self.snapshot, aboveSubview: self.blurView) + oldSuperview?.insertSubview(self.timestampLabel, aboveSubview: self.blurView) self.snapshot.translatesAutoresizingMaskIntoConstraints = true + self.timestampLabel.translatesAutoresizingMaskIntoConstraints = true self.snapshot.frame = currentFrame + self.timestampLabel.frame = currentLabelFrame UIView.animate( withDuration: 0.15, @@ -358,6 +376,7 @@ final class ContextMenuVC: UIViewController { options: .curveEaseOut, animations: { [weak self] in self?.snapshot.frame = originalFrame + self?.timestampLabel.frame = endLabelFrame }, completion: nil ) diff --git a/Session/Conversations/ConversationSearch.swift b/Session/Conversations/ConversationSearch.swift index d957543d0..c45aba372 100644 --- a/Session/Conversations/ConversationSearch.swift +++ b/Session/Conversations/ConversationSearch.swift @@ -126,43 +126,49 @@ public final class SearchResultsBar: UIView { private lazy var label: UILabel = { let result = UILabel() result.font = .boldSystemFont(ofSize: Values.smallFontSize) - result.textColor = Colors.text + result.themeTextColor = .textPrimary + return result }() private lazy var upButton: UIButton = { let icon = #imageLiteral(resourceName: "ic_chevron_up").withRenderingMode(.alwaysTemplate) - let result = UIButton() + let result: UIButton = UIButton() result.setImage(icon, for: UIControl.State.normal) - result.tintColor = Colors.accent + result.themeTintColor = .primary result.addTarget(self, action: #selector(handleUpButtonTapped), for: UIControl.Event.touchUpInside) + return result }() private lazy var downButton: UIButton = { let icon = #imageLiteral(resourceName: "ic_chevron_down").withRenderingMode(.alwaysTemplate) - let result = UIButton() + let result: UIButton = UIButton() result.setImage(icon, for: UIControl.State.normal) - result.tintColor = Colors.accent + result.themeTintColor = .primary result.addTarget(self, action: #selector(handleDownButtonTapped), for: UIControl.Event.touchUpInside) + return result }() private lazy var loadingIndicator: UIActivityIndicatorView = { let result = UIActivityIndicatorView(style: .medium) - result.tintColor = Colors.text + result.themeTintColor = .textPrimary result.alpha = 0.5 result.hidesWhenStopped = true + return result }() override init(frame: CGRect) { super.init(frame: frame) + setUpViewHierarchy() } required init?(coder: NSCoder) { super.init(coder: coder) + setUpViewHierarchy() } @@ -171,7 +177,7 @@ public final class SearchResultsBar: UIView { // Background & blur let backgroundView = UIView() - backgroundView.backgroundColor = isLightMode ? .white : .black + backgroundView.themeBackgroundColor = .backgroundSecondary backgroundView.alpha = Values.lowOpacity addSubview(backgroundView) backgroundView.pin(to: self) @@ -189,8 +195,8 @@ public final class SearchResultsBar: UIView { // Separator let separator = UIView() - separator.backgroundColor = Colors.text.withAlphaComponent(0.2) - separator.set(.height, to: 1 / UIScreen.main.scale) + separator.themeBackgroundColor = .borderSeparator + separator.set(.height, to: Values.separatorThickness) addSubview(separator) separator.pin([ UIView.HorizontalEdge.leading, UIView.VerticalEdge.top, UIView.HorizontalEdge.trailing ], to: self) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 894df7557..4bb30d35f 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -148,7 +148,7 @@ extension ConversationVC: // MARK: - SendMediaNavDelegate - func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController) { + func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController?) { dismiss(animated: true, completion: nil) } @@ -795,7 +795,7 @@ extension ConversationVC: ) } - self.contextMenuWindow?.backgroundColor = .clear + self.contextMenuWindow?.themeBackgroundColor = .clear self.contextMenuWindow?.rootViewController = self.contextMenuVC self.contextMenuWindow?.overrideUserInterfaceStyle = (isDarkMode ? .dark : .light) self.contextMenuWindow?.makeKeyAndVisible() diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 68eb87870..64e458508 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -137,7 +137,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl lazy var tableView: InsetLockableTableView = { let result: InsetLockableTableView = InsetLockableTableView() result.separatorStyle = .none - result.backgroundColor = .clear + result.themeBackgroundColor = .clear result.showsVerticalScrollIndicator = false result.contentInsetAdjustmentBehavior = .never result.keyboardDismissMode = .interactive @@ -257,7 +257,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl result.clipsToBounds = true result.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) result.setTitle("TXT_BLOCK_USER_TITLE".localized(), for: .normal) - result.setTitleColor(Colors.destructive, for: .normal) + result.setThemeTitleColor(.danger, for: .normal) result.addTarget(self, action: #selector(block), for: .touchUpInside) return result @@ -1058,7 +1058,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl self.view.layoutIfNeeded() } } - + let keyboardTop = (UIScreen.main.bounds.height - keyboardRect.minY) let messageRequestsOffset: CGFloat = (messageRequestView.isHidden ? 0 : messageRequestView.bounds.height + 16) let oldContentInset: UIEdgeInsets = tableView.contentInset @@ -1210,7 +1210,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl switch section.model { case .loadOlder, .loadNewer: let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .medium) - loadingIndicator.tintColor = Colors.text + loadingIndicator.themeTintColor = .textPrimary loadingIndicator.alpha = 0.5 loadingIndicator.startAnimating() @@ -1386,7 +1386,7 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl let ipadCancelButton = UIButton() ipadCancelButton.setTitle("cancel".localized(), for: .normal) ipadCancelButton.addTarget(self, action: #selector(hideSearchUI), for: .touchUpInside) - ipadCancelButton.setTitleColor(Colors.text, for: .normal) + ipadCancelButton.setThemeTitleColor(.textPrimary, for: .normal) searchBarContainer.addSubview(ipadCancelButton) ipadCancelButton.pin(.trailing, to: .trailing, of: searchBarContainer) ipadCancelButton.autoVCenterInSuperview() diff --git a/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift b/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift index 37eb27e42..4889dffa9 100644 --- a/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift +++ b/Session/Conversations/Emoji Picker/EmojiSkinTonePicker.swift @@ -1,5 +1,7 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit +import SessionUIKit class EmojiSkinTonePicker: UIView { let emoji: Emoji @@ -116,12 +118,12 @@ class EmojiSkinTonePicker: UIView { layer.shadowOpacity = 0.25 layer.shadowRadius = 4 - referenceOverlay.backgroundColor = Colors.modalBackground + referenceOverlay.themeBackgroundColor = .backgroundSecondary referenceOverlay.layer.cornerRadius = 9 addSubview(referenceOverlay) containerView.layoutMargins = UIEdgeInsets(top: 9, leading: 16, bottom: 9, trailing: 16) - containerView.backgroundColor = Colors.modalBackground + containerView.themeBackgroundColor = .backgroundSecondary containerView.layer.cornerRadius = 11 addSubview(containerView) containerView.autoPinWidthToSuperview() @@ -129,7 +131,8 @@ class EmojiSkinTonePicker: UIView { if emoji.baseEmoji!.allowsMultipleSkinTones { prepareForMultipleSkinTones() - } else { + } + else { prepareForSingleSkinTone() } } @@ -159,7 +162,7 @@ class EmojiSkinTonePicker: UIView { let divider = UIView() divider.autoSetDimension(.width, toSize: 1) - divider.backgroundColor = isDarkMode ? .ows_gray75 : .ows_gray05 + divider.themeBackgroundColor = .borderSeparator hStack.addArrangedSubview(divider) hStack.addArrangedSubview(.spacer(withWidth: 2)) @@ -266,7 +269,7 @@ class EmojiSkinTonePicker: UIView { let divider = UIView() divider.autoSetDimension(.height, toSize: 1) - divider.backgroundColor = isDarkMode ? .ows_gray75 : .ows_gray05 + divider.themeBackgroundColor = .borderSeparator vStack.addArrangedSubview(divider) let leftSpacer = UIView.hStretchingSpacer() @@ -296,7 +299,7 @@ class EmojiSkinTonePicker: UIView { let button = OWSButton { handler(emoji) } button.titleLabel?.font = .boldSystemFont(ofSize: 32) button.setTitle(emoji.rawValue, for: .normal) - button.setBackgroundImage(UIImage(color: isDarkMode ? .ows_gray60 : .ows_gray25), for: .selected) + button.setThemeBackgroundColor(.backgroundPrimary, for: .selected) button.layer.cornerRadius = 6 button.clipsToBounds = true button.autoSetDimensions(to: CGSize(width: 38, height: 38)) diff --git a/Session/Conversations/Input View/VoiceMessageRecordingView.swift b/Session/Conversations/Input View/VoiceMessageRecordingView.swift index 5c19a3d41..965f5486d 100644 --- a/Session/Conversations/Input View/VoiceMessageRecordingView.swift +++ b/Session/Conversations/Input View/VoiceMessageRecordingView.swift @@ -381,7 +381,7 @@ extension VoiceMessageRecordingView { private func setUpViewHierarchy() { // Background & blur let backgroundView: UIView = UIView() - backgroundView.themeBackgroundColor = .backgroundSecondary// .backgroundColor = isLightMode ? .white : .black + backgroundView.themeBackgroundColor = .backgroundSecondary backgroundView.alpha = Values.lowOpacity addSubview(backgroundView) backgroundView.pin(to: self) diff --git a/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift b/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift index 6c0d22802..85f821dff 100644 --- a/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift +++ b/Session/Conversations/Message Cells/Content Views/LinkPreviewView.swift @@ -65,10 +65,13 @@ final class LinkPreviewView: UIView { private lazy var hStackView: UIStackView = UIStackView() private lazy var cancelButton: UIButton = { - // FIXME: This will have issues with theme transitions let result: UIButton = UIButton(type: .custom) - result.setImage(UIImage(named: "X")?.withRenderingMode(.alwaysTemplate), for: UIControl.State.normal) - result.tintColor = (isLightMode ? .black : .white) + result.setImage( + UIImage(named: "X")? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) + result.themeTintColor = .textPrimary let cancelButtonSize = LinkPreviewView.cancelButtonSize result.set(.width, to: cancelButtonSize) diff --git a/Session/Conversations/Message Cells/MessageCell.swift b/Session/Conversations/Message Cells/MessageCell.swift index baf2d3340..bffbac5bd 100644 --- a/Session/Conversations/Message Cells/MessageCell.swift +++ b/Session/Conversations/Message Cells/MessageCell.swift @@ -30,10 +30,10 @@ public class MessageCell: UITableViewCell { } func setUpViewHierarchy() { - backgroundColor = .clear + themeBackgroundColor = .clear let selectedBackgroundView = UIView() - selectedBackgroundView.backgroundColor = .clear + selectedBackgroundView.themeBackgroundColor = .clear self.selectedBackgroundView = selectedBackgroundView } diff --git a/Session/Conversations/Message Cells/VisibleMessageCell.swift b/Session/Conversations/Message Cells/VisibleMessageCell.swift index 02aa81f04..7e81764f6 100644 --- a/Session/Conversations/Message Cells/VisibleMessageCell.swift +++ b/Session/Conversations/Message Cells/VisibleMessageCell.swift @@ -1118,8 +1118,8 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate { ) .forEach { range in let legacyRange: NSRange = NSRange(range, in: normalizedBody) - attributedText.addAttribute(.backgroundColor, value: backgroundPrimaryColor, range: legacyRange) - attributedText.addAttribute(.foregroundColor, value: textPrimaryColor, range: legacyRange) + attributedText.addThemeAttribute(.background(backgroundPrimaryColor), range: legacyRange) + attributedText.addThemeAttribute(.foreground(textPrimaryColor), range: legacyRange) } } } diff --git a/Session/Conversations/Views & Modals/ConversationTitleView.swift b/Session/Conversations/Views & Modals/ConversationTitleView.swift index c72d805a4..15c5696ae 100644 --- a/Session/Conversations/Views & Modals/ConversationTitleView.swift +++ b/Session/Conversations/Views & Modals/ConversationTitleView.swift @@ -125,7 +125,7 @@ final class ConversationTitleView: UIView { } guard !onlyNotifyForMentions else { let imageAttachment = NSTextAttachment() - imageAttachment.image = UIImage(named: "NotifyMentions.png")?.asTintedImage(color: textPrimary) + imageAttachment.image = UIImage(named: "NotifyMentions.png")?.withTint(textPrimary) imageAttachment.bounds = CGRect( x: 0, y: -2, diff --git a/Session/Conversations/Views & Modals/ReactionListSheet.swift b/Session/Conversations/Views & Modals/ReactionListSheet.swift index c91c95e05..1c2808e75 100644 --- a/Session/Conversations/Views & Modals/ReactionListSheet.swift +++ b/Session/Conversations/Views & Modals/ReactionListSheet.swift @@ -122,7 +122,7 @@ final class ReactionListSheet: BaseVC { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .clear + view.themeBackgroundColor = .clear let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(close)) swipeGestureRecognizer.direction = .down @@ -133,6 +133,7 @@ final class ReactionListSheet: BaseVC { override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() + reactionContainer.scrollToItem( at: IndexPath(item: lastSelectedReactionIndex, section: 0), at: .centeredHorizontally, @@ -150,7 +151,7 @@ final class ReactionListSheet: BaseVC { view.addSubview(contentView) contentView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.bottom ], to: view) // Emoji collectionView height + seleted emoji detail height + 5 × user cell height + footer cell height + bottom safe area inset - let contentViewHeight: CGFloat = 100 + 5 * 65 + 45 + UIApplication.shared.keyWindow!.safeAreaInsets.bottom + let contentViewHeight: CGFloat = 100 + 5 * 65 + 45 + (UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? 0) contentView.set(.height, to: contentViewHeight) populateContentView() } @@ -163,7 +164,7 @@ final class ReactionListSheet: BaseVC { // Seperator let seperator = UIView() - seperator.backgroundColor = Colors.border.withAlphaComponent(0.1) + seperator.themeBackgroundColor = .borderSeparator seperator.set(.height, to: 0.5) contentView.addSubview(seperator) seperator.pin(.leading, to: .leading, of: contentView, withInset: Values.smallSpacing) @@ -180,7 +181,7 @@ final class ReactionListSheet: BaseVC { // Line let line = UIView() line.set(.height, to: 0.5) - line.backgroundColor = Colors.border.withAlphaComponent(0.5) + line.themeBackgroundColor = .borderSeparator contentView.addSubview(line) line.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing ], to: contentView) line.pin(.top, to: .bottom, of: stackView, withInset: Values.smallSpacing) @@ -541,12 +542,12 @@ extension ReactionListSheet { } fileprivate final class FooterCell: UITableViewCell { - private lazy var label: UILabel = { - let result = UILabel() - result.textAlignment = .center + let result: UILabel = UILabel() result.font = .systemFont(ofSize: Values.smallFontSize) - result.textColor = Colors.grey.withAlphaComponent(0.8) + result.themeTextColor = .textSecondary + result.textAlignment = .center + return result }() @@ -554,17 +555,19 @@ extension ReactionListSheet { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) + setUpViewHierarchy() } required init?(coder: NSCoder) { super.init(coder: coder) + setUpViewHierarchy() } private func setUpViewHierarchy() { // Background color - backgroundColor = Colors.cellBackground + themeBackgroundColor = .backgroundSecondary contentView.addSubview(label) label.pin(to: contentView) @@ -572,9 +575,10 @@ extension ReactionListSheet { } func update(moreReactorCount: Int, emoji: String) { - label.text = (moreReactorCount == 1) ? + label.text = (moreReactorCount == 1 ? String(format: "EMOJI_REACTS_MORE_REACTORS_ONE".localized(), "\(emoji)") : String(format: "EMOJI_REACTS_MORE_REACTORS_MUTIPLE".localized(), "\(moreReactorCount)" ,"\(emoji)") + ) } } } diff --git a/Session/DMs/NewDMVC.swift b/Session/DMs/NewDMVC.swift index c1ace80c7..52ff7284c 100644 --- a/Session/DMs/NewDMVC.swift +++ b/Session/DMs/NewDMVC.swift @@ -1,13 +1,15 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import AVFoundation import GRDB import Curve25519Kit import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit +import SignalUtilitiesKit -final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { +final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, QRScannerDelegate { private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private var pages: [UIViewController] = [] private var targetVCIndex: Int? @@ -38,7 +40,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle private lazy var scanQRCodePlaceholderVC: ScanQRCodePlaceholderVC = { let result = ScanQRCodePlaceholderVC() - result.NewDMVC = self + result.newDMVC = self return result }() @@ -142,7 +144,7 @@ final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControlle dismiss(animated: true, completion: nil) } - func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { let hexEncodedPublicKey = string startNewDMIfPossible(with: hexEncodedPublicKey) } @@ -450,7 +452,7 @@ private final class EnterPublicKeyVC: UIViewController { // MARK: - ScanQRCodePlaceholderVC private final class ScanQRCodePlaceholderVC: UIViewController { - weak var NewDMVC: NewDMVC! + weak var newDMVC: NewDMVC! override func viewDidLoad() { // Remove background color @@ -493,12 +495,8 @@ private final class ScanQRCodePlaceholderVC: UIViewController { } @objc private func requestCameraAccess() { - ows_ask(forCameraPermissions: { [weak self] hasCameraAccess in - if hasCameraAccess { - self?.NewDMVC.handleCameraAccessGranted() - } else { - // Do nothing - } - }) + Permissions.requestLibraryPermissionIfNeeded { [weak self] in + self?.newDMVC.handleCameraAccessGranted() + } } } diff --git a/Session/Home/Message Requests/MessageRequestsViewController.swift b/Session/Home/Message Requests/MessageRequestsViewController.swift index 25b6ee571..bc79515af 100644 --- a/Session/Home/Message Requests/MessageRequestsViewController.swift +++ b/Session/Home/Message Requests/MessageRequestsViewController.swift @@ -38,7 +38,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat private lazy var tableView: UITableView = { let result: UITableView = UITableView() result.translatesAutoresizingMaskIntoConstraints = false - result.backgroundColor = .clear + result.themeBackgroundColor = .clear result.separatorStyle = .none result.register(view: FullConversationCell.self) result.dataSource = self @@ -277,7 +277,7 @@ class MessageRequestsViewController: BaseVC, UITableViewDelegate, UITableViewDat switch section.model { case .loadMore: let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .medium) - loadingIndicator.tintColor = Colors.text + loadingIndicator.themeTintColor = .textPrimary loadingIndicator.alpha = 0.5 loadingIndicator.startAnimating() diff --git a/Session/Media Viewing & Editing/AllMediaViewController.swift b/Session/Media Viewing & Editing/AllMediaViewController.swift index 2fbe07272..00303d1d9 100644 --- a/Session/Media Viewing & Editing/AllMediaViewController.swift +++ b/Session/Media Viewing & Editing/AllMediaViewController.swift @@ -12,22 +12,27 @@ public class AllMediaViewController: UIViewController, UIPageViewControllerDataS private var pages: [UIViewController] = [] private var targetVCIndex: Int? - // MARK: Components + // MARK: - Components + private lazy var tabBar: TabBar = { - let tabs = [ - TabBar.Tab(title: MediaStrings.media) { [weak self] in - guard let self = self else { return } - self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil) - self.updateSelectButton(updatedData: self.mediaTitleViewController.viewModel.galleryData, inBatchSelectMode: self.mediaTitleViewController.isInBatchSelectMode) - }, - TabBar.Tab(title: MediaStrings.document) { [weak self] in - guard let self = self else { return } - self.pageVC.setViewControllers([ self.pages[1] ], direction: .forward, animated: false, completion: nil) - self.endSelectMode() - self.navigationItem.rightBarButtonItem = nil - } - ] - return TabBar(tabs: tabs) + let result: TabBar = TabBar( + tabs: [ + TabBar.Tab(title: MediaStrings.media) { [weak self] in + guard let self = self else { return } + self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil) + self.updateSelectButton(updatedData: self.mediaTitleViewController.viewModel.galleryData, inBatchSelectMode: self.mediaTitleViewController.isInBatchSelectMode) + }, + TabBar.Tab(title: MediaStrings.document) { [weak self] in + guard let self = self else { return } + self.pageVC.setViewControllers([ self.pages[1] ], direction: .forward, animated: false, completion: nil) + self.endSelectMode() + self.navigationItem.rightBarButtonItem = nil + } + ] + ) + result.themeBackgroundColor = .backgroundPrimary + + return result }() private var mediaTitleViewController: MediaTileViewController @@ -54,11 +59,11 @@ public class AllMediaViewController: UIViewController, UIPageViewControllerDataS public override func viewDidLoad() { super.viewDidLoad() - view.themeBackgroundColor = .backgroundPrimary + view.themeBackgroundColor = .backgroundSecondary // Add a custom back button if this is the only view controller if self.navigationController?.viewControllers.first == self { - let backButton = OWSViewController.createOWSBackButton(withTarget: self, selector: #selector(didPressDismissButton)) + let backButton = UIViewController.createOWSBackButton(target: self, selector: #selector(didPressDismissButton)) self.navigationItem.leftBarButtonItem = backButton } diff --git a/Session/Media Viewing & Editing/AudioProgressView.swift b/Session/Media Viewing & Editing/AudioProgressView.swift deleted file mode 100644 index 7ff965500..000000000 --- a/Session/Media Viewing & Editing/AudioProgressView.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import UIKit -import SignalUtilitiesKit - -@objc public class AudioProgressView: UIView { - - @objc public override var bounds: CGRect { - didSet { - if oldValue != bounds { - updateSubviews() - } - } - } - - @objc public override var frame: CGRect { - didSet { - if oldValue != frame { - updateSubviews() - } - } - } - - @objc public var horizontalBarColor = UIColor.black { - didSet { - updateContent() - } - } - - @objc public var progressColor = UIColor.blue { - didSet { - updateContent() - } - } - - private let horizontalBarLayer: CAShapeLayer - private let progressLayer: CAShapeLayer - - @objc public var progress: CGFloat = 0 { - didSet { - if oldValue != progress { - updateContent() - } - } - } - - @available(*, unavailable, message:"use other constructor instead.") - @objc public required init?(coder aDecoder: NSCoder) { - notImplemented() - } - - public required init() { - self.horizontalBarLayer = CAShapeLayer() - self.progressLayer = CAShapeLayer() - - super.init(frame: CGRect.zero) - - self.layer.addSublayer(self.horizontalBarLayer) - self.layer.addSublayer(self.progressLayer) - } - - internal func updateSubviews() { - AssertIsOnMainThread() - - self.horizontalBarLayer.frame = self.bounds - self.progressLayer.frame = self.bounds - - updateContent() - } - - internal func updateContent() { - AssertIsOnMainThread() - - // Prevent the shape layer from animating changes. - CATransaction.begin() - CATransaction.setDisableActions(true) - - let horizontalBarPath = UIBezierPath() - let horizontalBarHeightFraction = CGFloat(0.25) - let horizontalBarHeight = bounds.size.height * horizontalBarHeightFraction - horizontalBarPath.append(UIBezierPath(rect: CGRect(x: 0, y: (bounds.size.height - horizontalBarHeight) * 0.5, width: bounds.size.width, height: horizontalBarHeight))) - horizontalBarLayer.path = horizontalBarPath.cgPath - horizontalBarLayer.fillColor = horizontalBarColor.cgColor - - let progressHeight = bounds.self.height - let progressWidth = progressHeight * 0.15 - let progressX = (bounds.self.width - progressWidth) * max(0.0, min(1.0, progress)) - let progressBounds = CGRect(x: progressX, y: 0, width: progressWidth, height: progressHeight) - let progressCornerRadius = progressWidth * 0.5 - let progressPath = UIBezierPath() - progressPath.append(UIBezierPath(roundedRect: progressBounds, cornerRadius: progressCornerRadius)) - progressLayer.path = progressPath.cgPath - progressLayer.fillColor = progressColor.cgColor - - CATransaction.commit() - } -} diff --git a/Session/Media Viewing & Editing/CropScaleImageViewController.swift b/Session/Media Viewing & Editing/CropScaleImageViewController.swift index 7703f137a..06f6275af 100644 --- a/Session/Media Viewing & Editing/CropScaleImageViewController.swift +++ b/Session/Media Viewing & Editing/CropScaleImageViewController.swift @@ -4,6 +4,7 @@ import Foundation import MediaPlayer +import SessionUIKit import SignalUtilitiesKit // This kind of view is tricky. I've tried to organize things in the @@ -145,13 +146,37 @@ import SignalUtilitiesKit // MARK: - Create Views private func createViews() { - - view.backgroundColor = .black + view.themeBackgroundColor = .backgroundPrimary let contentView = UIView() - contentView.backgroundColor = .black + contentView.themeBackgroundColor = .backgroundPrimary self.view.addSubview(contentView) contentView.autoPinEdgesToSuperviewEdges() + + let titleLabel: UILabel = UILabel() + titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) + titleLabel.text = "CROP_SCALE_IMAGE_VIEW_TITLE".localized() + titleLabel.themeTextColor = .textPrimary + titleLabel.textAlignment = .center + contentView.addSubview(titleLabel) + titleLabel.autoPinWidthToSuperview() + + let titleLabelMargin = ScaleFromIPhone5(16) + titleLabel.autoPinEdge(toSuperviewSafeArea: .top, withInset: titleLabelMargin) + + let buttonRow: UIView = createButtonRow() + contentView.addSubview(buttonRow) + buttonRow.pin(.leading, to: .leading, of: contentView) + buttonRow.pin(.trailing, to: .trailing, of: contentView) + buttonRow.pin(.bottom, to: .bottom, of: contentView) + buttonRow.set( + .height, + to: ( + ScaleFromIPhone5To7Plus(35, 45) + + Values.mediumSpacing + + (UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? Values.mediumSpacing) + ) + ) let imageView = OWSLayerView(frame: CGRect.zero, layoutCallback: { [weak self] _ in guard let strongSelf = self else { return } @@ -160,7 +185,10 @@ import SignalUtilitiesKit imageView.clipsToBounds = true self.imageView = imageView contentView.addSubview(imageView) - imageView.autoPinEdgesToSuperviewEdges() + imageView.pin(.top, to: .top, of: contentView, withInset: (Values.massiveSpacing + Values.smallSpacing)) + imageView.pin(.leading, to: .leading, of: contentView) + imageView.pin(.trailing, to: .trailing, of: contentView) + imageView.pin(.bottom, to: .top, of: buttonRow) let imageLayer = CALayer() self.imageLayer = imageLayer @@ -185,23 +213,13 @@ import SignalUtilitiesKit layer.path = path.cgPath layer.fillRule = .evenOdd - layer.fillColor = UIColor.black.cgColor + layer.themeFillColor = .black layer.opacity = 0.75 } - maskingView.autoPinEdgesToSuperviewEdges() - - let titleLabel = UILabel() - titleLabel.textColor = .white - titleLabel.textAlignment = .center - titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) - titleLabel.text = NSLocalizedString("CROP_SCALE_IMAGE_VIEW_TITLE", - comment: "Title for the 'crop/scale image' dialog.") - contentView.addSubview(titleLabel) - titleLabel.autoPinWidthToSuperview() - let titleLabelMargin = ScaleFromIPhone5(16) - titleLabel.autoPinEdge(toSuperviewSafeArea: .top, withInset: titleLabelMargin) - - createButtonRow(contentView: contentView) + maskingView.pin(.top, to: .top, of: contentView, withInset: (Values.massiveSpacing + Values.smallSpacing)) + maskingView.pin(.leading, to: .leading, of: contentView) + maskingView.pin(.trailing, to: .trailing, of: contentView) + maskingView.pin(.bottom, to: .top, of: buttonRow) contentView.isUserInteractionEnabled = true contentView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:)))) @@ -427,45 +445,35 @@ import SignalUtilitiesKit updateImageLayout() } - private func createButtonRow(contentView: UIView) { - let buttonTopMargin = ScaleFromIPhone5To7Plus(30, 40) - let buttonBottomMargin = ScaleFromIPhone5To7Plus(25, 40) + private func createButtonRow() -> UIView { + let result: UIStackView = UIStackView() + result.axis = .horizontal + result.distribution = .fillEqually + result.alignment = .fill - let buttonRow = UIView() - self.view.addSubview(buttonRow) - buttonRow.autoPinWidthToSuperview() - buttonRow.autoPinEdge(toSuperviewEdge: .bottom, withInset: buttonBottomMargin) - buttonRow.autoPinEdge(.top, to: .bottom, of: contentView, withOffset: buttonTopMargin) + let cancelButton = createButton(title: CommonStrings.cancelButton, action: #selector(cancelPressed)) + result.addArrangedSubview(cancelButton) - let cancelButton = createButton(title: CommonStrings.cancelButton, - action: #selector(cancelPressed)) - cancelButton.titleLabel!.font = .systemFont(ofSize: 18) // Match iOS UI - buttonRow.addSubview(cancelButton) - cancelButton.autoPinEdge(toSuperviewEdge: .top) - cancelButton.autoPinEdge(toSuperviewEdge: .bottom) - cancelButton.autoPinEdge(toSuperviewEdge: .left) - - let doneButton = createButton(title: CommonStrings.doneButton, - action: #selector(donePressed)) - doneButton.titleLabel!.font = .systemFont(ofSize: 18) // Match iOS UI - buttonRow.addSubview(doneButton) - doneButton.autoPinEdge(toSuperviewEdge: .top) - doneButton.autoPinEdge(toSuperviewEdge: .bottom) - doneButton.autoPinEdge(toSuperviewEdge: .right) + let doneButton = createButton(title: CommonStrings.doneButton, action: #selector(donePressed)) + result.addArrangedSubview(doneButton) + + return result } private func createButton(title: String, action: Selector) -> UIButton { - let buttonFont = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5To7Plus(18, 22)) - let buttonWidth = ScaleFromIPhone5To7Plus(110, 140) - let buttonHeight = ScaleFromIPhone5To7Plus(35, 45) - - let button = UIButton() + let button: UIButton = UIButton() + button.titleLabel?.font = .systemFont(ofSize: 18) button.setTitle(title, for: .normal) - button.setTitleColor(UIColor.white, for: .normal) - button.titleLabel!.font = buttonFont + button.setThemeTitleColor(.textPrimary, for: .normal) + button.setThemeBackgroundColor(.backgroundSecondary, for: .highlighted) + button.contentEdgeInsets = UIEdgeInsets( + top: Values.mediumSpacing, + leading: 0, + bottom: (UIApplication.shared.keyWindow?.safeAreaInsets.bottom ?? Values.mediumSpacing), + trailing: 0 + ) button.addTarget(self, action: action, for: .touchUpInside) - button.autoSetDimension(.width, toSize: buttonWidth) - button.autoSetDimension(.height, toSize: buttonHeight) + return button } diff --git a/Session/Media Viewing & Editing/DocumentTitleViewController.swift b/Session/Media Viewing & Editing/DocumentTitleViewController.swift index af790fb89..30d6fe159 100644 --- a/Session/Media Viewing & Editing/DocumentTitleViewController.swift +++ b/Session/Media Viewing & Editing/DocumentTitleViewController.swift @@ -49,8 +49,8 @@ public class DocumentTileViewController: UIViewController, UITableViewDelegate, } lazy var tableView: UITableView = { - let result = UITableView(frame: .zero, style: .grouped) - result.backgroundColor = Colors.navigationBarBackground + let result: UITableView = UITableView() + result.themeBackgroundColor = .backgroundSecondary result.separatorStyle = .none result.showsVerticalScrollIndicator = false result.register(view: DocumentCell.self) @@ -59,6 +59,10 @@ public class DocumentTileViewController: UIViewController, UITableViewDelegate, // Feels a bit weird to have content smashed all the way to the bottom edge. result.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0) + if #available(iOS 15.0, *) { + result.sectionHeaderTopPadding = 0 + } + return result }() @@ -69,7 +73,7 @@ public class DocumentTileViewController: UIViewController, UITableViewDelegate, // Add a custom back button if this is the only view controller if self.navigationController?.viewControllers.first == self { - let backButton = OWSViewController.createOWSBackButton(withTarget: self, selector: #selector(didPressDismissButton)) + let backButton = UIViewController.createOWSBackButton(target: self, selector: #selector(didPressDismissButton)) self.navigationItem.leftBarButtonItem = backButton } @@ -363,7 +367,8 @@ class DocumentCell: UITableViewCell { private let iconImageView: UIImageView = { let result: UIImageView = UIImageView(image: #imageLiteral(resourceName: "File").withRenderingMode(.alwaysTemplate)) result.translatesAutoresizingMaskIntoConstraints = false - result.tintColor = Colors.text + result.themeTintColor = .textPrimary + result.contentMode = .scaleAspectFit return result }() @@ -374,7 +379,20 @@ class DocumentCell: UITableViewCell { result.setContentHuggingPriority(.defaultHigh, for: .horizontal) result.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) result.font = .boldSystemFont(ofSize: Values.smallFontSize) - result.textColor = Colors.text + result.themeTextColor = .textPrimary + result.lineBreakMode = .byTruncatingTail + result.numberOfLines = 2 + + return result + }() + + private let timeLabel: UILabel = { + let result: UILabel = UILabel() + result.translatesAutoresizingMaskIntoConstraints = false + result.setContentHuggingPriority(.defaultHigh, for: .horizontal) + result.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + result.font = .systemFont(ofSize: Values.smallFontSize) + result.themeTextColor = .textSecondary result.lineBreakMode = .byTruncatingTail return result @@ -386,20 +404,26 @@ class DocumentCell: UITableViewCell { result.setContentHuggingPriority(.defaultHigh, for: .horizontal) result.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) result.font = .systemFont(ofSize: Values.smallFontSize) - result.textColor = Colors.text + result.themeTextColor = .textSecondary result.lineBreakMode = .byTruncatingTail return result }() private func setUpViewHierarchy() { - backgroundColor = Colors.cellBackground - selectedBackgroundView = UIView() - selectedBackgroundView?.backgroundColor = Colors.cellSelected + themeBackgroundColor = .clear + backgroundView = UIView() + backgroundView?.themeBackgroundColor = .conversationButton_background + backgroundView?.layer.cornerRadius = 5 + + selectedBackgroundView = UIView() + selectedBackgroundView?.themeBackgroundColor = .conversationButton_highlight + selectedBackgroundView?.layer.cornerRadius = 5 contentView.addSubview(iconImageView) contentView.addSubview(titleLabel) + contentView.addSubview(timeLabel) contentView.addSubview(detailLabel) } @@ -407,57 +431,110 @@ class DocumentCell: UITableViewCell { private func setupLayout() { NSLayoutConstraint.activate([ - contentView.heightAnchor.constraint(equalToConstant: 68), - - iconImageView.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: Values.mediumSpacing), iconImageView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - iconImageView.widthAnchor.constraint(equalToConstant: Self.iconImageViewSize.width), - iconImageView.heightAnchor.constraint(equalToConstant: Self.iconImageViewSize.height), + iconImageView.topAnchor.constraint( + greaterThanOrEqualTo: contentView.topAnchor, + constant: (Values.verySmallSpacing + Values.verySmallSpacing) + ), + iconImageView.leftAnchor.constraint( + equalTo: contentView.leftAnchor, + constant: (Values.largeSpacing + Values.mediumSpacing) + ), + iconImageView.bottomAnchor.constraint( + lessThanOrEqualTo: contentView.bottomAnchor, + constant: -(Values.verySmallSpacing + Values.verySmallSpacing) + ), + titleLabel.topAnchor.constraint( + equalTo: contentView.topAnchor, + constant: (Values.verySmallSpacing + Values.verySmallSpacing) + ), titleLabel.leftAnchor.constraint(equalTo: iconImageView.rightAnchor, constant: Values.mediumSpacing), - titleLabel.rightAnchor.constraint(lessThanOrEqualTo: contentView.rightAnchor, constant: -Values.mediumSpacing), - titleLabel.topAnchor.constraint(equalTo: iconImageView.topAnchor), + titleLabel.rightAnchor.constraint( + lessThanOrEqualTo: timeLabel.leftAnchor, + constant: -Values.mediumSpacing + ), + timeLabel.topAnchor.constraint(equalTo: iconImageView.topAnchor), + timeLabel.rightAnchor.constraint( + equalTo: contentView.rightAnchor, + constant: -(Values.mediumSpacing + Values.largeSpacing) + ), + + detailLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: Values.smallSpacing), detailLabel.leftAnchor.constraint(equalTo: iconImageView.rightAnchor, constant: Values.mediumSpacing), - detailLabel.rightAnchor.constraint(lessThanOrEqualTo: contentView.rightAnchor, constant: -Values.mediumSpacing), - detailLabel.bottomAnchor.constraint(equalTo: iconImageView.bottomAnchor), + detailLabel.rightAnchor.constraint( + lessThanOrEqualTo: contentView.rightAnchor, + constant: -(Values.verySmallSpacing + Values.largeSpacing) + ), + detailLabel.bottomAnchor.constraint( + lessThanOrEqualTo: contentView.bottomAnchor, + constant: -(Values.verySmallSpacing + Values.smallSpacing) + ), ]) } + override func layoutSubviews() { + super.layoutSubviews() + + backgroundView?.frame = CGRect( + x: Values.largeSpacing, + y: Values.verySmallSpacing, + width: (contentView.bounds.width - (Values.largeSpacing * 2)), + height: (contentView.bounds.height - (Values.verySmallSpacing * 2)) + ) + selectedBackgroundView?.frame = (backgroundView?.frame ?? .zero) + } + // MARK: - Content func update(with item: MediaGalleryViewModel.Item) { let attachment = item.attachment - titleLabel.text = attachment.sourceFilename ?? "File" + titleLabel.text = (attachment.sourceFilename ?? "File") detailLabel.text = "\(OWSFormat.formatFileSize(UInt(attachment.byteCount)))" + timeLabel.text = Date( + timeIntervalSince1970: TimeInterval(item.interactionTimestampMs / 1000) + ).formattedForDisplay } } class DocumentSectionHeaderView: UIView { + // HACK: scrollbar incorrectly appears *behind* section headers + // in collection view on iOS11 =( + private class AlwaysOnTopLayer: CALayer { + override var zPosition: CGFloat { + get { return 0 } + set {} + } + } let label: UILabel + + override class var layerClass: AnyClass { + get { + // HACK: scrollbar incorrectly appears *behind* section headers + // in collection view on iOS11 =( + return AlwaysOnTopLayer.self + } + } override init(frame: CGRect) { label = UILabel() - label.textColor = Colors.text - - let blurEffect = UIBlurEffect(style: .dark) - let blurEffectView = UIVisualEffectView(effect: blurEffect) - - blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + label.themeTextColor = .textPrimary super.init(frame: frame) - self.backgroundColor = isLightMode ? Colors.cellBackground : UIColor.ows_black.withAlphaComponent(OWSNavigationBar.backgroundBlurMutingFactor) + self.themeBackgroundColor = .clear + + let backgroundView: UIView = UIView() + backgroundView.themeBackgroundColor = .backgroundSecondary + addSubview(backgroundView) + backgroundView.pin(to: self) - self.addSubview(blurEffectView) self.addSubview(label) - - blurEffectView.autoPinEdgesToSuperviewEdges() - blurEffectView.isHidden = isLightMode - label.autoPinEdge(toSuperviewMargin: .trailing) - label.autoPinEdge(toSuperviewMargin: .leading) - label.autoVCenterInSuperview() + label.pin(.leading, to: .leading, of: self, withInset: Values.largeSpacing) + label.pin(.trailing, to: .trailing, of: self, withInset: -Values.largeSpacing) + label.center(.vertical, in: self) } @available(*, unavailable, message: "Unimplemented") @@ -479,7 +556,7 @@ class DocumentStaticHeaderView: UIView { addSubview(label) - label.textColor = Colors.text + label.themeTextColor = .textPrimary label.textAlignment = .center label.numberOfLines = 0 label.autoPinEdgesToSuperviewMargins(with: UIEdgeInsets(top: 0, leading: Values.largeSpacing, bottom: 0, trailing: Values.largeSpacing)) diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift index ccd544701..6bf633e81 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerCell.swift @@ -218,7 +218,7 @@ class GifPickerCell: UICollectionViewCell { return } imageView.image = image - self.backgroundColor = nil + self.themeBackgroundColor = nil if self.isCellSelected { let activityIndicator = UIActivityIndicatorView(style: .gray) @@ -229,11 +229,12 @@ class GifPickerCell: UICollectionViewCell { // Render activityIndicator on a white tile to ensure it's visible on // when overlayed on a variety of potential gifs. - activityIndicator.backgroundColor = UIColor.white.withAlphaComponent(0.3) + activityIndicator.themeBackgroundColor = .white + activityIndicator.alpha = 0.3 activityIndicator.autoSetDimension(.width, toSize: 30) activityIndicator.autoSetDimension(.height, toSize: 30) + activityIndicator.themeShadowColor = .black activityIndicator.layer.cornerRadius = 3 - activityIndicator.layer.shadowColor = UIColor.black.cgColor activityIndicator.layer.shadowOffset = CGSize(width: 1, height: 1) activityIndicator.layer.shadowOpacity = 0.7 activityIndicator.layer.shadowRadius = 1.0 @@ -270,9 +271,7 @@ class GifPickerCell: UICollectionViewCell { private func clearViewState() { imageView?.image = nil - self.backgroundColor = (isDarkMode - ? UIColor(white: 0.25, alpha: 1.0) - : UIColor(white: 0.95, alpha: 1.0)) + self.themeBackgroundColor = .backgroundSecondary } private func pickBestAsset() -> ProxiedContentAsset? { diff --git a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift index ce215af26..cefce14ec 100644 --- a/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift +++ b/Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift @@ -99,28 +99,35 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect override func viewDidLoad() { super.viewDidLoad() - self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, - target: self, - action: #selector(donePressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem( + barButtonSystemItem: .cancel, + target: self, + action: #selector(donePressed) + ) // Loki: Customize title - let titleLabel = UILabel() - titleLabel.text = "accessibility_gif_button".localized().uppercased() - titleLabel.textColor = Colors.text + let titleLabel: UILabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: Values.veryLargeFontSize) + titleLabel.text = "accessibility_gif_button".localized().uppercased() + titleLabel.themeTextColor = .textPrimary navigationItem.titleView = titleLabel createViews() reachability = Reachability.forInternetConnection() - NotificationCenter.default.addObserver(self, - selector: #selector(reachabilityChanged), - name: NSNotification.Name.reachabilityChanged, - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(didBecomeActive), - name: NSNotification.Name.OWSApplicationDidBecomeActive, - object: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(reachabilityChanged), + name: .reachabilityChanged, + object: nil + ) + NotificationCenter.default.addObserver( + self, + selector: #selector(didBecomeActive), + name: .OWSApplicationDidBecomeActive, + object: nil + ) + loadTrending() } @@ -140,14 +147,8 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect // MARK: Views private func createViews() { - - let backgroundColor = Colors.navigationBarBackground - self.view.backgroundColor = backgroundColor - - // Block UIKit from adjust insets of collection view which screws up - // min/max scroll positions. - self.automaticallyAdjustsScrollViewInsets = false - + self.view.themeBackgroundColor = .backgroundPrimary + // Search searchBar.delegate = self @@ -157,7 +158,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect self.collectionView.delegate = self self.collectionView.dataSource = self - self.collectionView.backgroundColor = backgroundColor + self.collectionView.themeBackgroundColor = .backgroundPrimary self.collectionView.register(GifPickerCell.self, forCellWithReuseIdentifier: kCellReuseIdentifier) // Inserted below searchbar because we later occlude the collectionview // by inserting a masking layer between the search bar and collectionview @@ -165,10 +166,14 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect self.collectionView.autoPinEdge(toSuperviewSafeArea: .leading) self.collectionView.autoPinEdge(toSuperviewSafeArea: .trailing) self.collectionView.autoPinEdge(.top, to: .bottom, of: searchBar) + + // Block UIKit from adjust insets of collection view which screws up + // min/max scroll positions + self.collectionView.contentInsetAdjustmentBehavior = .never // for iPhoneX devices, extends the black background to the bottom edge of the view. let bottomBannerContainer = UIView() - bottomBannerContainer.backgroundColor = isLightMode ? UIColor.black : Colors.navigationBarBackground + bottomBannerContainer.themeBackgroundColor = .backgroundPrimary self.view.addSubview(bottomBannerContainer) bottomBannerContainer.autoPinWidthToSuperview() bottomBannerContainer.autoPinEdge(.top, to: .bottom, of: self.collectionView) @@ -188,15 +193,13 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect logoImageView.autoPinHeightToSuperview(withMargin: 3) logoImageView.autoHCenterInSuperview() - let noResultsView = createErrorLabel(text: NSLocalizedString("GIF_VIEW_SEARCH_NO_RESULTS", - comment: "Indicates that the user's search had no results.")) + let noResultsView = createErrorLabel(text: "GIF_VIEW_SEARCH_NO_RESULTS".localized()) self.noResultsView = noResultsView self.view.addSubview(noResultsView) noResultsView.autoPinWidthToSuperview(withMargin: 20) noResultsView.autoAlignAxis(.horizontal, toSameAxisOf: self.collectionView) - let searchErrorView = createErrorLabel(text: NSLocalizedString("GIF_VIEW_SEARCH_ERROR", - comment: "Indicates that an error occurred while searching.")) + let searchErrorView = createErrorLabel(text: "GIF_VIEW_SEARCH_ERROR".localized()) self.searchErrorView = searchErrorView self.view.addSubview(searchErrorView) searchErrorView.autoPinWidthToSuperview(withMargin: 20) @@ -205,29 +208,24 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect searchErrorView.isUserInteractionEnabled = true searchErrorView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(retryTapped))) - let activityIndicator = UIActivityIndicatorView(style: .whiteLarge) + let activityIndicator = UIActivityIndicatorView(style: .large) self.activityIndicator = activityIndicator self.view.addSubview(activityIndicator) activityIndicator.autoHCenterInSuperview() activityIndicator.autoAlignAxis(.horizontal, toSameAxisOf: self.collectionView) - - let navigationBar = navigationController!.navigationBar - navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) - navigationBar.shadowImage = UIImage() - navigationBar.isTranslucent = false - navigationBar.barTintColor = Colors.navigationBarBackground self.updateContents() } private func createErrorLabel(text: String) -> UILabel { - let label = UILabel() + let label: UILabel = UILabel() + label.font = .ows_mediumFont(withSize: 20) label.text = text - label.textColor = Colors.text - label.font = UIFont.ows_mediumFont(withSize: 20) + label.themeTextColor = .textPrimary label.textAlignment = .center - label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping + label.numberOfLines = 0 + return label } @@ -246,39 +244,43 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect } switch viewMode { - case .idle: - self.collectionView.isHidden = true - noResultsView.isHidden = true - searchErrorView.isHidden = true - activityIndicator.isHidden = true - activityIndicator.stopAnimating() - case .searching: - self.collectionView.isHidden = true - noResultsView.isHidden = true - searchErrorView.isHidden = true - activityIndicator.isHidden = false - activityIndicator.startAnimating() - case .results: - self.collectionView.isHidden = false - noResultsView.isHidden = true - searchErrorView.isHidden = true - activityIndicator.isHidden = true - activityIndicator.stopAnimating() + case .idle: + self.collectionView.isHidden = true + noResultsView.isHidden = true + searchErrorView.isHidden = true + activityIndicator.isHidden = true + activityIndicator.stopAnimating() + + case .searching: + self.collectionView.isHidden = true + noResultsView.isHidden = true + searchErrorView.isHidden = true + activityIndicator.isHidden = false + activityIndicator.startAnimating() + + case .results: + self.collectionView.isHidden = false + noResultsView.isHidden = true + searchErrorView.isHidden = true + activityIndicator.isHidden = true + activityIndicator.stopAnimating() - self.collectionView.collectionViewLayout.invalidateLayout() - self.collectionView.reloadData() - case .noResults: - self.collectionView.isHidden = true - noResultsView.isHidden = false - searchErrorView.isHidden = true - activityIndicator.isHidden = true - activityIndicator.stopAnimating() - case .error: - self.collectionView.isHidden = true - noResultsView.isHidden = true - searchErrorView.isHidden = false - activityIndicator.isHidden = true - activityIndicator.stopAnimating() + self.collectionView.collectionViewLayout.invalidateLayout() + self.collectionView.reloadData() + + case .noResults: + self.collectionView.isHidden = true + noResultsView.isHidden = false + searchErrorView.isHidden = true + activityIndicator.isHidden = true + activityIndicator.stopAnimating() + + case .error: + self.collectionView.isHidden = true + noResultsView.isHidden = true + searchErrorView.isHidden = false + activityIndicator.isHidden = true + activityIndicator.stopAnimating() } } @@ -314,7 +316,6 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect // MARK: - UICollectionViewDelegate public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let cell = collectionView.cellForItem(at: indexPath) as? GifPickerCell else { owsFailDebug("unexpected cell.") return @@ -345,7 +346,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect layer.path = path.cgPath layer.fillRule = .evenOdd - layer.fillColor = UIColor.black.cgColor + layer.themeFillColor = .black layer.opacity = 0.7 } maskingView.autoPinEdgesToSuperviewEdges() @@ -389,9 +390,11 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect return } - let alert = UIAlertController(title: NSLocalizedString("GIF_PICKER_FAILURE_ALERT_TITLE", comment: "Shown when selected GIF couldn't be fetched"), - message: error.localizedDescription, - preferredStyle: .alert) + let alert = UIAlertController( + title: "GIF_PICKER_FAILURE_ALERT_TITLE".localized(), + message: error.localizedDescription, + preferredStyle: .alert + ) alert.addAction(UIAlertAction(title: CommonStrings.retryButton, style: .default) { _ in strongSelf.getFileForCell(cell) }) @@ -439,11 +442,7 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect progressiveSearchTimer = nil let kProgressiveSearchDelaySeconds = 1.0 progressiveSearchTimer = WeakTimer.scheduledTimer(timeInterval: kProgressiveSearchDelaySeconds, target: self, userInfo: nil, repeats: true) { [weak self] _ in - guard let strongSelf = self else { - return - } - - strongSelf.tryToSearch() + self?.tryToSearch() } } @@ -457,19 +456,24 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect progressiveSearchTimer?.invalidate() progressiveSearchTimer = nil - guard let text = searchBar.text else { + guard let text: String = searchBar.text else { // Alert message shown when user tries to search for GIFs without entering any search terms OWSAlerts.showErrorAlert(message: "GIF_PICKER_VIEW_MISSING_QUERY".localized()) return } - - let query = (text as String).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + + let query: String = text.trimmingCharacters(in: .whitespacesAndNewlines) if (viewMode == .searching || viewMode == .results) && lastQuery == query { Logger.info("ignoring duplicate search: \(query)") return } + guard !query.isEmpty else { + loadTrending() + return + } + search(query: query) } @@ -477,20 +481,22 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect assert(progressiveSearchTimer == nil) assert(searchBar.text == nil || searchBar.text?.count == 0) - GiphyAPI.sharedInstance.trending().done { [weak self] imageInfos in - guard let self = self else { return } - - Logger.info("showing trending") - if imageInfos.count > 0 { - self.imageInfos = imageInfos - self.viewMode = .results - } else { - owsFailDebug("trending results was unexpectedly empty") + GiphyAPI.sharedInstance.trending() + .done { [weak self] imageInfos in + Logger.info("showing trending") + + if imageInfos.count > 0 { + self?.imageInfos = imageInfos + self?.viewMode = .results + } + else { + owsFailDebug("trending results was unexpectedly empty") + } + } + .catch { error in + // Don't both showing error UI feedback for default "trending" results. + Logger.error("error: \(error)") } - }.catch { error in - // Don't both showing error UI feedback for default "trending" results. - Logger.error("error: \(error)") - } } private func search(query: String) { @@ -503,22 +509,26 @@ class GifPickerViewController: OWSViewController, UISearchBarDelegate, UICollect lastQuery = query self.collectionView.contentOffset = CGPoint.zero - GiphyAPI.sharedInstance.search(query: query, success: { [weak self] imageInfos in - guard let strongSelf = self else { return } - Logger.info("search complete") - strongSelf.imageInfos = imageInfos - if imageInfos.count > 0 { - strongSelf.viewMode = .results - } else { - strongSelf.viewMode = .noResults - } - }, - failure: { [weak self] _ in - guard let strongSelf = self else { return } - Logger.info("search failed.") - // TODO: Present this error to the user. - strongSelf.viewMode = .error - }) + GiphyAPI.sharedInstance + .search( + query: query, + success: { [weak self] imageInfos in + Logger.info("search complete") + self?.imageInfos = imageInfos + + if imageInfos.count > 0 { + self?.viewMode = .results + } + else { + self?.viewMode = .noResults + } + }, + failure: { [weak self] _ in + Logger.info("search failed.") + // TODO: Present this error to the user. + self?.viewMode = .error + } + ) } // MARK: - GifPickerLayoutDelegate diff --git a/Session/Media Viewing & Editing/ImagePickerController.swift b/Session/Media Viewing & Editing/ImagePickerController.swift index ef332e946..cb53d16f2 100644 --- a/Session/Media Viewing & Editing/ImagePickerController.swift +++ b/Session/Media Viewing & Editing/ImagePickerController.swift @@ -6,6 +6,7 @@ import Foundation import Photos import PromiseKit import SessionUIKit +import SignalUtilitiesKit protocol ImagePickerGridControllerDelegate: AnyObject { func imagePickerDidCompleteSelection(_ imagePicker: ImagePickerGridController) @@ -48,7 +49,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = Colors.navigationBarBackground + self.view.themeBackgroundColor = .backgroundSecondary library.add(delegate: self) @@ -71,7 +72,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat let cancelImage = UIImage(imageLiteralResourceName: "X") let cancelButton = UIBarButtonItem(image: cancelImage, style: .plain, target: self, action: #selector(didPressCancel)) - cancelButton.tintColor = Colors.text + cancelButton.themeTintColor = .textPrimary navigationItem.leftBarButtonItem = cancelButton let titleView = TitleView() @@ -80,7 +81,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat navigationItem.titleView = titleView self.titleView = titleView - collectionView.backgroundColor = Colors.navigationBarBackground + collectionView.themeBackgroundColor = .backgroundSecondary let selectionPanGesture = DirectionalPanGestureRecognizer(direction: [.horizontal], target: self, action: #selector(didPanSelection)) selectionPanGesture.delegate = self @@ -105,7 +106,9 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat enum BatchSelectionGestureMode { case select, deselect } + var selectionPanGestureMode: BatchSelectionGestureMode = .select + var hasEverAppeared: Bool = false @objc func didPanSelection(_ selectionPanGesture: UIPanGestureRecognizer) { @@ -189,20 +192,9 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat super.viewWillLayoutSubviews() updateLayout() } - - var hasEverAppeared: Bool = false + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - - let backgroundImage: UIImage = UIImage(color: Colors.navigationBarBackground) - self.navigationItem.title = nil - self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) - self.navigationController?.navigationBar.shadowImage = UIImage() - self.navigationController?.navigationBar.isTranslucent = false - self.navigationController?.navigationBar.barTintColor = Colors.navigationBarBackground - (self.navigationController?.navigationBar as? OWSNavigationBar)?.respectsTheme = true - self.navigationController?.navigationBar.backgroundColor = Colors.navigationBarBackground - self.navigationController?.navigationBar.setBackgroundImage(backgroundImage, for: .default) // Determine the size of the thumbnails to request let scale = UIScreen.main.scale @@ -362,9 +354,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat // MARK: - Batch Selection func batchSelectModeDidChange() { - guard let delegate = delegate else { - return - } + guard let delegate = delegate else { return } guard let collectionView = collectionView else { owsFailDebug("collectionView was unexpectedly nil") @@ -397,7 +387,7 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat let toastText = String(format: toastFormat, NSNumber(value: SignalAttachment.maxAttachmentsAllowed)) - let toastController = ToastController(text: toastText) + let toastController = ToastController(text: toastText, background: .backgroundPrimary) let kToastInset: CGFloat = 10 let bottomInset = kToastInset + collectionView.contentInset.bottom + view.layoutMargins.bottom @@ -531,8 +521,6 @@ class ImagePickerGridController: UICollectionViewController, PhotoLibraryDelegat } let cell: PhotoGridViewCell = collectionView.dequeue(type: PhotoGridViewCell.self, for: indexPath) - cell.loadingColor = UIColor(white: 0.2, alpha: 1) - let assetItem = photoCollectionContents.assetItem(at: indexPath.item, photoMediaSize: photoMediaSize) cell.configure(item: assetItem) @@ -586,12 +574,12 @@ class TitleView: UIView { addSubview(stackView) stackView.autoPinEdgesToSuperviewEdges() - - label.textColor = Colors.text + label.font = .boldSystemFont(ofSize: Values.mediumFontSize) + label.themeTextColor = .textPrimary - iconView.tintColor = Colors.text iconView.image = UIImage(named: "navbar_disclosure_down")?.withRenderingMode(.alwaysTemplate) + iconView.themeTintColor = .textPrimary addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(titleTapped))) } diff --git a/Session/Media Viewing & Editing/MediaDetailViewController.swift b/Session/Media Viewing & Editing/MediaDetailViewController.swift index 2d89e625d..18c0d0a4d 100644 --- a/Session/Media Viewing & Editing/MediaDetailViewController.swift +++ b/Session/Media Viewing & Editing/MediaDetailViewController.swift @@ -93,7 +93,7 @@ class MediaDetailViewController: OWSViewController, UIScrollViewDelegate, OWSVid override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = Colors.navigationBarBackground + self.view.themeBackgroundColor = .backgroundSecondary self.view.addSubview(scrollView) scrollView.pin(to: self.view) @@ -185,13 +185,13 @@ class MediaDetailViewController: OWSViewController, UIScrollViewDelegate, OWSVid } else { self.mediaView = UIView() - self.mediaView.backgroundColor = Colors.unimportant + self.mediaView.themeBackgroundColor = .backgroundSecondary } } else if self.image == nil { // Still loading thumbnail. self.mediaView = UIView() - self.mediaView.backgroundColor = Colors.unimportant + self.mediaView.themeBackgroundColor = .backgroundSecondary } else if self.galleryItem.attachment.isVideo { if self.galleryItem.attachment.isValid { @@ -199,7 +199,7 @@ class MediaDetailViewController: OWSViewController, UIScrollViewDelegate, OWSVid } else { self.mediaView = UIView() - self.mediaView.backgroundColor = Colors.unimportant + self.mediaView.themeBackgroundColor = .backgroundSecondary } } else { diff --git a/Session/Media Viewing & Editing/MediaGalleryNavigationController.swift b/Session/Media Viewing & Editing/MediaGalleryNavigationController.swift index 478a17847..1673a1fd7 100644 --- a/Session/Media Viewing & Editing/MediaGalleryNavigationController.swift +++ b/Session/Media Viewing & Editing/MediaGalleryNavigationController.swift @@ -16,7 +16,7 @@ class MediaGalleryNavigationController: OWSNavigationController { private lazy var backgroundView: UIView = { let result: UIView = UIView() - result.backgroundColor = Colors.navigationBarBackground + result.themeBackgroundColor = .backgroundSecondary return result }() @@ -27,18 +27,8 @@ class MediaGalleryNavigationController: OWSNavigationController { override func viewDidLoad() { super.viewDidLoad() - - guard let navigationBar = self.navigationBar as? OWSNavigationBar else { - owsFailDebug("navigationBar had unexpected class: \(self.navigationBar)") - return - } - - view.backgroundColor = Colors.navigationBarBackground - - navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) - navigationBar.shadowImage = UIImage() - navigationBar.isTranslucent = false - navigationBar.barTintColor = Colors.navigationBarBackground + + view.themeBackgroundColor = .backgroundSecondary // Insert a view to ensure the nav bar colour goes to the top of the screen relayoutBackgroundView() diff --git a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift index a4d760280..a3f534d8c 100644 --- a/Session/Media Viewing & Editing/MediaGalleryViewModel.swift +++ b/Session/Media Viewing & Editing/MediaGalleryViewModel.swift @@ -402,6 +402,7 @@ public class MediaGalleryViewModel { .baseQuery( orderSQL: SQL(interactionAttachment[.albumIndex]), customFilters: SQL(""" + \(attachment[.isVisualMedia]) = true AND \(attachment[.isValid]) = true AND \(interaction[.id]) = \(interactionId) """) @@ -416,6 +417,8 @@ public class MediaGalleryViewModel { .baseQuery( orderSQL: Item.galleryReverseOrderSQL, customFilters: SQL(""" + \(attachment[.isVisualMedia]) = true AND + \(attachment[.isValid]) = true AND \(interaction[.timestampMs]) > \(albumTimestampMs) AND \(interaction[.threadId]) = \(threadId) """) @@ -425,6 +428,8 @@ public class MediaGalleryViewModel { .baseQuery( orderSQL: Item.galleryOrderSQL, customFilters: SQL(""" + \(attachment[.isVisualMedia]) = true AND + \(attachment[.isValid]) = true AND \(interaction[.timestampMs]) < \(albumTimestampMs) AND \(interaction[.threadId]) = \(threadId) """) diff --git a/Session/Media Viewing & Editing/MediaPageViewController.swift b/Session/Media Viewing & Editing/MediaPageViewController.swift index 061dd010d..614fbf0fc 100644 --- a/Session/Media Viewing & Editing/MediaPageViewController.swift +++ b/Session/Media Viewing & Editing/MediaPageViewController.swift @@ -32,6 +32,17 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou owsFailDebug("unexpectedly unable to build new gallery page") return } + + // Cache and retrieve the new album items + viewModel.loadAndCacheAlbumData( + for: item.interactionId, + in: self.viewModel.threadId + ) + + // Swap out the database observer + dataChangeObservable?.cancel() + viewModel.replaceAlbumObservation(toObservationFor: item.interactionId) + startObservingChanges() updateTitle(item: item) updateCaption(item: item) @@ -93,12 +104,12 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou var footerBar: UIToolbar = { let result: UIToolbar = UIToolbar() result.clipsToBounds = true // hide 1px top-border - result.tintColor = Colors.text - result.barTintColor = Colors.navigationBarBackground + result.themeTintColor = .textPrimary + result.themeBarTintColor = .backgroundPrimary + result.themeBackgroundColor = .backgroundPrimary result.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: UIBarMetrics.default) result.setShadowImage(UIImage(), forToolbarPosition: .any) result.isTranslucent = false - result.backgroundColor = Colors.navigationBarBackground return result }() @@ -115,7 +126,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou // Navigation - let backButton = OWSViewController.createOWSBackButton(withTarget: self, selector: #selector(didPressDismissButton)) + let backButton = UIViewController.createOWSBackButton(target: self, selector: #selector(didPressDismissButton)) self.navigationItem.leftBarButtonItem = backButton self.navigationItem.titleView = portraitHeaderView @@ -154,9 +165,9 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou } // Views - pagerScrollView.backgroundColor = Colors.navigationBarBackground + pagerScrollView.themeBackgroundColor = .backgroundSecondary - view.backgroundColor = Colors.navigationBarBackground + view.themeBackgroundColor = .backgroundSecondary captionContainerView.delegate = self updateCaptionContainerVisibility() @@ -169,7 +180,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou let bottomContainer: DynamicallySizedView = DynamicallySizedView() bottomContainer.clipsToBounds = true bottomContainer.autoresizingMask = .flexibleHeight - bottomContainer.backgroundColor = Colors.navigationBarBackground + bottomContainer.themeBackgroundColor = .backgroundPrimary self.bottomContainer = bottomContainer let bottomStack = UIStackView(arrangedSubviews: [captionContainerView, galleryRailView, footerBar]) @@ -179,7 +190,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou bottomStack.autoPinEdgesToSuperviewEdges() let galleryRailBlockingView: UIView = UIView() - galleryRailBlockingView.backgroundColor = Colors.navigationBarBackground + galleryRailBlockingView.themeBackgroundColor = .backgroundPrimary bottomStack.addSubview(galleryRailBlockingView) galleryRailBlockingView.pin(.top, to: .bottom, of: footerBar) galleryRailBlockingView.pin(.left, to: .left, of: bottomStack) @@ -196,12 +207,6 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou let verticalSwipe = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeView)) verticalSwipe.direction = [.up, .down] view.addGestureRecognizer(verticalSwipe) - - let navigationBar = navigationController!.navigationBar - navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) - navigationBar.shadowImage = UIImage() - navigationBar.isTranslucent = false - navigationBar.barTintColor = Colors.navigationBarBackground // Notifications NotificationCenter.default.addObserver( @@ -316,7 +321,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou target: self, action: #selector(didPressShare) ) - shareBarButton.tintColor = Colors.text + shareBarButton.themeTintColor = .textPrimary return shareBarButton }() @@ -327,7 +332,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou target: self, action: #selector(didPressDelete) ) - deleteBarButton.tintColor = Colors.text + deleteBarButton.themeTintColor = .textPrimary return deleteBarButton }() @@ -342,7 +347,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou target: self, action: #selector(didPressPlayBarButton) ) - videoPlayBarButton.tintColor = Colors.text + videoPlayBarButton.themeTintColor = .textPrimary return videoPlayBarButton }() @@ -353,7 +358,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou target: self, action: #selector(didPressPauseBarButton) ) - videoPauseBarButton.tintColor = Colors.text + videoPauseBarButton.themeTintColor = .textPrimary return videoPauseBarButton }() @@ -818,9 +823,9 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou }() lazy private var portraitHeaderNameLabel: UILabel = { - let label = UILabel() - label.textColor = Colors.text + let label: UILabel = UILabel() label.font = .systemFont(ofSize: Values.mediumFontSize) + label.themeTextColor = .textPrimary label.textAlignment = .center label.adjustsFontSizeToFitWidth = true label.minimumScaleFactor = 0.8 @@ -829,9 +834,9 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou }() lazy private var portraitHeaderDateLabel: UILabel = { - let label = UILabel() - label.textColor = Colors.text + let label: UILabel = UILabel() label.font = .systemFont(ofSize: Values.verySmallFontSize) + label.themeTextColor = .textPrimary label.textAlignment = .center label.adjustsFontSizeToFitWidth = true label.minimumScaleFactor = 0.8 @@ -840,7 +845,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou }() private lazy var portraitHeaderView: UIView = { - let stackView = UIStackView() + let stackView: UIStackView = UIStackView() stackView.axis = .vertical stackView.alignment = .center stackView.spacing = 0 @@ -915,12 +920,15 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou extension MediaGalleryViewModel.Item: GalleryRailItem { public func buildRailItemView() -> UIView { - let imageView = UIImageView() + let imageView: UIImageView = UIImageView() imageView.contentMode = .scaleAspectFill - getRailImage().map { [weak imageView] image in - guard let imageView = imageView else { return } - imageView.image = image - }.retainUntilComplete() + + getRailImage() + .map { [weak imageView] image in + guard let imageView = imageView else { return } + imageView.image = image + } + .retainUntilComplete() return imageView } diff --git a/Session/Media Viewing & Editing/MediaTileViewController.swift b/Session/Media Viewing & Editing/MediaTileViewController.swift index 44d1bb4e3..bba9c9c6b 100644 --- a/Session/Media Viewing & Editing/MediaTileViewController.swift +++ b/Session/Media Viewing & Editing/MediaTileViewController.swift @@ -71,7 +71,7 @@ public class MediaTileViewController: UIViewController, UICollectionViewDataSour lazy var collectionView: UICollectionView = { let result: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: mediaTileViewLayout) result.translatesAutoresizingMaskIntoConstraints = false - result.backgroundColor = Colors.navigationBarBackground + result.themeBackgroundColor = .backgroundSecondary result.delegate = self result.dataSource = self result.register(view: PhotoGridViewCell.self) @@ -95,8 +95,8 @@ public class MediaTileViewController: UIViewController, UICollectionViewDataSour animated: false ) - result.barTintColor = Colors.navigationBarBackground - result.tintColor = Colors.text + result.themeBarTintColor = .backgroundPrimary + result.themeTintColor = .textPrimary return result }() @@ -107,19 +107,21 @@ public class MediaTileViewController: UIViewController, UICollectionViewDataSour target: self, action: #selector(didPressDelete) ) - result.tintColor = Colors.text + result.themeTintColor = .textPrimary return result }() // MARK: - Lifecycle - + override public func viewDidLoad() { super.viewDidLoad() + + view.themeBackgroundColor = .backgroundSecondary // Add a custom back button if this is the only view controller if self.navigationController?.viewControllers.first == self { - let backButton = OWSViewController.createOWSBackButton(withTarget: self, selector: #selector(didPressDismissButton)) + let backButton = UIViewController.createOWSBackButton(target: self, selector: #selector(didPressDismissButton)) self.navigationItem.leftBarButtonItem = backButton } @@ -763,25 +765,21 @@ private class MediaGallerySectionHeader: UICollectionReusableView { override init(frame: CGRect) { label = UILabel() - label.textColor = Colors.text - - let blurEffect = UIBlurEffect(style: .dark) - let blurEffectView = UIVisualEffectView(effect: blurEffect) - - blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + label.themeTextColor = .textPrimary super.init(frame: frame) - self.backgroundColor = isLightMode ? Colors.cellBackground : UIColor.ows_black.withAlphaComponent(OWSNavigationBar.backgroundBlurMutingFactor) + self.themeBackgroundColor = .clear + + let backgroundView: UIView = UIView() + backgroundView.themeBackgroundColor = .backgroundSecondary + addSubview(backgroundView) + backgroundView.pin(to: self) - self.addSubview(blurEffectView) self.addSubview(label) - - blurEffectView.autoPinEdgesToSuperviewEdges() - blurEffectView.isHidden = isLightMode - label.autoPinEdge(toSuperviewMargin: .trailing) - label.autoPinEdge(toSuperviewMargin: .leading) - label.autoVCenterInSuperview() + label.pin(.leading, to: .leading, of: self, withInset: Values.largeSpacing) + label.pin(.trailing, to: .trailing, of: self, withInset: -Values.largeSpacing) + label.center(.vertical, in: self) } @available(*, unavailable, message: "Unimplemented") @@ -811,7 +809,7 @@ private class MediaGalleryStaticHeader: UICollectionViewCell { addSubview(label) - label.textColor = Colors.text + label.themeTextColor = .textPrimary label.textAlignment = .center label.numberOfLines = 0 label.autoPinEdgesToSuperviewMargins(with: UIEdgeInsets(top: 0, leading: Values.largeSpacing, bottom: 0, trailing: Values.largeSpacing)) diff --git a/Session/Media Viewing & Editing/PhotoCapture.swift b/Session/Media Viewing & Editing/PhotoCapture.swift index 74afdf5ea..41ec6bb30 100644 --- a/Session/Media Viewing & Editing/PhotoCapture.swift +++ b/Session/Media Viewing & Editing/PhotoCapture.swift @@ -3,6 +3,7 @@ // import Foundation +import AVFoundation import PromiseKit import CoreServices @@ -54,9 +55,9 @@ class PhotoCapture: NSObject { self.session.beginConfiguration() defer { self.session.commitConfiguration() } - let audioDevice = AVCaptureDevice.default(for: .audio) + guard let audioDevice: AVCaptureDevice = AVCaptureDevice.default(for: .audio) else { return } // verify works without audio permissions - let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice!) + let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice) if session.canAddInput(audioDeviceInput) { // self.session.addInputWithNoConnections(audioDeviceInput) session.addInput(audioDeviceInput) diff --git a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift index cfa944e3a..e36ee0f57 100644 --- a/Session/Media Viewing & Editing/PhotoCaptureViewController.swift +++ b/Session/Media Viewing & Editing/PhotoCaptureViewController.swift @@ -1,10 +1,10 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// -import Foundation +import UIKit import AVFoundation import PromiseKit +import SessionUIKit +import SignalUtilitiesKit protocol PhotoCaptureViewControllerDelegate: AnyObject { func photoCaptureViewController(_ photoCaptureViewController: PhotoCaptureViewController, didFinishProcessingAttachment attachment: SignalAttachment) @@ -49,7 +49,7 @@ class PhotoCaptureViewController: OWSViewController { override func loadView() { self.view = UIView() - self.view.backgroundColor = .black + self.view.themeBackgroundColor = .backgroundSecondary } override func viewDidLoad() { @@ -82,7 +82,8 @@ class PhotoCaptureViewController: OWSViewController { navigationItem.rightBarButtonItems = nil navigationItem.titleView = recordingTimerView recordingTimerView.sizeToFit() - } else { + } + else { navigationItem.titleView = nil navigationItem.leftBarButtonItem = dismissControl.barButtonItem let fixedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) @@ -114,8 +115,9 @@ class PhotoCaptureViewController: OWSViewController { let barButtonItem: UIBarButtonItem init(imageName: String, block: @escaping () -> Void) { - self.button = OWSButton(imageName: imageName, tintColor: .ows_white, block: block) + self.button = OWSButton(imageName: imageName, tintColor: .white, block: block) button.autoPinToSquareAspectRatio() + button.themeShadowColor = .black button.layer.shadowOffset = CGSize.zero button.layer.shadowOpacity = 0.35 button.layer.shadowRadius = 4 @@ -222,10 +224,12 @@ class PhotoCaptureViewController: OWSViewController { private func setupOrientationMonitoring() { UIDevice.current.beginGeneratingDeviceOrientationNotifications() - NotificationCenter.default.addObserver(self, - selector: #selector(didChangeDeviceOrientation), - name: UIDevice.orientationDidChangeNotification, - object: UIDevice.current) + NotificationCenter.default.addObserver( + self, + selector: #selector(didChangeDeviceOrientation), + name: UIDevice.orientationDidChangeNotification, + object: UIDevice.current + ) } var lastKnownCaptureOrientation: AVCaptureVideoOrientation = .portrait @@ -282,15 +286,14 @@ class PhotoCaptureViewController: OWSViewController { captureButton.delegate = photoCapture previewView = CapturePreviewView(session: photoCapture.session) - photoCapture.startCapture().done { [weak self] in - guard let self = self else { return } - - self.showCaptureUI() - }.catch { [weak self] error in - guard let self = self else { return } - - self.showFailureUI(error: error) - }.retainUntilComplete() + photoCapture.startCapture() + .done { [weak self] in + self?.showCaptureUI() + } + .catch { [weak self] error in + self?.showFailureUI(error: error) + } + .retainUntilComplete() } private func showCaptureUI() { @@ -309,11 +312,17 @@ class PhotoCaptureViewController: OWSViewController { private func showFailureUI(error: Error) { Logger.error("error: \(error)") - - OWSAlerts.showAlert(title: nil, - message: error.localizedDescription, - buttonTitle: CommonStrings.dismissButton, - buttonAction: { [weak self] _ in self?.dismiss(animated: true) }) + let modal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: CommonStrings.errorAlertTitle, + explanation: error.localizedDescription, + cancelTitle: CommonStrings.dismissButton, + cancelStyle: .textPrimary, + afterClosed: { [weak self] in self?.dismiss(animated: true) } + ) + ) + + present(modal, animated: true) } private func updateFlashModeControl() { @@ -421,16 +430,17 @@ class CaptureButton: UIView { addSubview(innerButton) innerButtonSizeConstraints = autoSetDimensions(to: CGSize(width: defaultDiameter, height: defaultDiameter)) - innerButton.backgroundColor = UIColor.ows_white.withAlphaComponent(0.33) + innerButton.themeBackgroundColor = .white innerButton.layer.shadowOffset = .zero innerButton.layer.shadowOpacity = 0.33 innerButton.layer.shadowRadius = 2 + innerButton.alpha = 0.33 innerButton.autoPinEdgesToSuperviewEdges() addSubview(zoomIndicator) zoomIndicatorSizeConstraints = zoomIndicator.autoSetDimensions(to: CGSize(width: defaultDiameter, height: defaultDiameter)) zoomIndicator.isUserInteractionEnabled = false - zoomIndicator.layer.borderColor = UIColor.ows_white.cgColor + zoomIndicator.themeBorderColor = .white zoomIndicator.layer.borderWidth = 1.5 zoomIndicator.autoAlignAxis(.horizontal, toSameAxisOf: innerButton) zoomIndicator.autoAlignAxis(.vertical, toSameAxisOf: innerButton) @@ -569,10 +579,10 @@ class RecordingTimerView: UIView { // MARK: - Subviews private lazy var label: UILabel = { - let label = UILabel() - label.font = UIFont.ows_monospacedDigitFont(withSize: 20) + let label: UILabel = UILabel() + label.font = .ows_monospacedDigitFont(withSize: 20) + label.themeTextColor = .textPrimary label.textAlignment = .center - label.textColor = UIColor.white label.layer.shadowOffset = CGSize.zero label.layer.shadowOpacity = 0.35 label.layer.shadowRadius = 4 @@ -587,8 +597,7 @@ class RecordingTimerView: UIView { icon.layer.shadowOffset = CGSize.zero icon.layer.shadowOpacity = 0.35 icon.layer.shadowRadius = 4 - - icon.backgroundColor = .red + icon.themeBackgroundColor = .danger icon.autoSetDimensions(to: CGSize(width: iconWidth, height: iconWidth)) icon.alpha = 0 @@ -601,10 +610,12 @@ class RecordingTimerView: UIView { func startCounting() { recordingStartTime = CACurrentMediaTime() timer = Timer.weakScheduledTimer(withTimeInterval: 0.1, target: self, selector: #selector(updateView), userInfo: nil, repeats: true) - UIView.animate(withDuration: 0.5, - delay: 0, - options: [.autoreverse, .repeat], - animations: { self.icon.alpha = 1 }) + UIView.animate( + withDuration: 0.5, + delay: 0, + options: [.autoreverse, .repeat], + animations: { self.icon.alpha = 1 } + ) } func stopCounting() { diff --git a/Session/Media Viewing & Editing/PhotoGridViewCell.swift b/Session/Media Viewing & Editing/PhotoGridViewCell.swift index aa79acea2..bfbf0268a 100644 --- a/Session/Media Viewing & Editing/PhotoGridViewCell.swift +++ b/Session/Media Viewing & Editing/PhotoGridViewCell.swift @@ -28,9 +28,9 @@ public class PhotoGridViewCell: UICollectionViewCell { private static let videoBadgeImage = #imageLiteral(resourceName: "ic_gallery_badge_video") private static let animatedBadgeImage = #imageLiteral(resourceName: "ic_gallery_badge_gif") - private static let selectedBadgeImage = #imageLiteral(resourceName: "selected_blue_circle") + private static let selectedBadgeImage = UIImage(systemName: "checkmark.circle.fill") - public var loadingColor = Colors.unimportant + public var loadingColor: ThemeValue = .textSecondary override public var isSelected: Bool { didSet { @@ -52,18 +52,23 @@ public class PhotoGridViewCell: UICollectionViewCell { self.contentTypeBadgeView = UIImageView() contentTypeBadgeView.isHidden = true + let kSelectedBadgeSize = CGSize(width: 32, height: 32) self.selectedBadgeView = UIImageView() - selectedBadgeView.image = PhotoGridViewCell.selectedBadgeImage + selectedBadgeView.image = PhotoGridViewCell.selectedBadgeImage?.withRenderingMode(.alwaysTemplate) + selectedBadgeView.themeTintColor = .primary + selectedBadgeView.themeBorderColor = .textPrimary + selectedBadgeView.themeBackgroundColor = .textPrimary selectedBadgeView.isHidden = true + selectedBadgeView.layer.cornerRadius = (kSelectedBadgeSize.width / 2) self.highlightedView = UIView() highlightedView.alpha = 0.2 - highlightedView.backgroundColor = Colors.cellSelected + highlightedView.themeBackgroundColor = .black highlightedView.isHidden = true self.selectedView = UIView() selectedView.alpha = 0.3 - selectedView.backgroundColor = Colors.cellSelected + selectedView.themeBackgroundColor = .black selectedView.isHidden = true super.init(frame: frame) @@ -87,9 +92,8 @@ public class PhotoGridViewCell: UICollectionViewCell { contentTypeBadgeView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 3) contentTypeBadgeView.autoSetDimensions(to: kContentTypeBadgeSize) - let kSelectedBadgeSize = CGSize(width: 31, height: 31) - selectedBadgeView.autoPinEdge(toSuperviewEdge: .trailing, withInset: 0) - selectedBadgeView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0) + selectedBadgeView.autoPinEdge(toSuperviewEdge: .trailing, withInset: Values.verySmallSpacing) + selectedBadgeView.autoPinEdge(toSuperviewEdge: .bottom, withInset: Values.verySmallSpacing) selectedBadgeView.autoSetDimensions(to: kSelectedBadgeSize) } @@ -102,7 +106,7 @@ public class PhotoGridViewCell: UICollectionViewCell { get { return imageView.image } set { imageView.image = newValue - imageView.backgroundColor = newValue == nil ? loadingColor : .clear + imageView.themeBackgroundColor = (newValue == nil ? loadingColor : .clear) } } diff --git a/Session/Media Viewing & Editing/SendMediaNavigationController.swift b/Session/Media Viewing & Editing/SendMediaNavigationController.swift index 6af3f3090..7f6adf29a 100644 --- a/Session/Media Viewing & Editing/SendMediaNavigationController.swift +++ b/Session/Media Viewing & Editing/SendMediaNavigationController.swift @@ -81,9 +81,9 @@ class SendMediaNavigationController: OWSNavigationController { didSet { if oldValue != isInBatchSelectMode { mediaLibraryViewController.batchSelectModeDidChange() - guard let topViewController = viewControllers.last else { - return - } + + guard let topViewController = viewControllers.last else { return } + updateButtons(topViewController: topViewController) } } @@ -91,23 +91,26 @@ class SendMediaNavigationController: OWSNavigationController { func updateButtons(topViewController: UIViewController) { switch topViewController { - case is AttachmentApprovalViewController: - batchModeButton.isHidden = true - doneButton.isHidden = true - cameraModeButton.isHidden = true - mediaLibraryModeButton.isHidden = true - case is ImagePickerGridController: - batchModeButton.isHidden = isInBatchSelectMode - doneButton.isHidden = !isInBatchSelectMode || (attachmentDraftCollection.count == 0 && mediaLibrarySelections.count == 0) - cameraModeButton.isHidden = false - mediaLibraryModeButton.isHidden = true - case is PhotoCaptureViewController: - batchModeButton.isHidden = isInBatchSelectMode - doneButton.isHidden = !isInBatchSelectMode || (attachmentDraftCollection.count == 0 && mediaLibrarySelections.count == 0) - cameraModeButton.isHidden = true - mediaLibraryModeButton.isHidden = false - default: - owsFailDebug("unexpected topViewController: \(topViewController)") + case is AttachmentApprovalViewController: + batchModeButton.isHidden = true + doneButton.isHidden = true + cameraModeButton.isHidden = true + mediaLibraryModeButton.isHidden = true + + case is ImagePickerGridController: + batchModeButton.isHidden = isInBatchSelectMode + doneButton.isHidden = !isInBatchSelectMode || (attachmentDraftCollection.count == 0 && mediaLibrarySelections.count == 0) + cameraModeButton.isHidden = false + mediaLibraryModeButton.isHidden = true + + case is PhotoCaptureViewController: + batchModeButton.isHidden = isInBatchSelectMode + doneButton.isHidden = !isInBatchSelectMode || (attachmentDraftCollection.count == 0 && mediaLibrarySelections.count == 0) + cameraModeButton.isHidden = true + mediaLibraryModeButton.isHidden = false + + default: + owsFailDebug("unexpected topViewController: \(topViewController)") } doneButton.updateCount() @@ -150,52 +153,60 @@ class SendMediaNavigationController: OWSNavigationController { private lazy var doneButton: DoneButton = { let button = DoneButton() button.delegate = self - button.setShadow() return button }() private lazy var batchModeButton: UIButton = { - let button = OWSButton(imageName: "media_send_batch_mode_disabled", - tintColor: .ows_gray60, - block: { [weak self] in self?.didTapBatchModeButton() }) - - let width: CGFloat = type(of: self).bottomButtonWidth - button.autoSetDimensions(to: CGSize(width: width, height: width)) - button.layer.cornerRadius = width / 2 + let button = OWSButton( + imageName: "media_send_batch_mode_disabled", + tintColor: .backgroundPrimary, + block: { [weak self] in self?.didTapBatchModeButton() } + ) + button.clipsToBounds = true + button.adjustsImageWhenHighlighted = false + button.setThemeBackgroundColor(.textPrimary, for: .normal) + button.setThemeBackgroundColor(.textSecondary, for: .highlighted) button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) - button.backgroundColor = .ows_white - button.setShadow() + button.layer.cornerRadius = (SendMediaNavigationController.bottomButtonWidth / 2) + button.set(.width, to: SendMediaNavigationController.bottomButtonWidth) + button.set(.height, to: SendMediaNavigationController.bottomButtonWidth) return button }() private lazy var cameraModeButton: UIButton = { - let button = OWSButton(imageName: "settings-avatar-camera-2", - tintColor: .ows_gray60, - block: { [weak self] in self?.didTapCameraModeButton() }) - - let width: CGFloat = type(of: self).bottomButtonWidth - button.autoSetDimensions(to: CGSize(width: width, height: width)) - button.layer.cornerRadius = width / 2 + let button = OWSButton( + imageName: "settings-avatar-camera-2", + tintColor: .backgroundPrimary, + block: { [weak self] in self?.didTapCameraModeButton() } + ) + button.clipsToBounds = true + button.adjustsImageWhenHighlighted = false + button.setThemeBackgroundColor(.textPrimary, for: .normal) + button.setThemeBackgroundColor(.textSecondary, for: .highlighted) button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) - button.backgroundColor = .ows_white - button.setShadow() + button.layer.cornerRadius = (SendMediaNavigationController.bottomButtonWidth / 2) + button.set(.width, to: SendMediaNavigationController.bottomButtonWidth) + button.set(.height, to: SendMediaNavigationController.bottomButtonWidth) return button }() private lazy var mediaLibraryModeButton: UIButton = { - let button = OWSButton(imageName: "actionsheet_camera_roll_black", - tintColor: .ows_gray60, - block: { [weak self] in self?.didTapMediaLibraryModeButton() }) - - let width: CGFloat = type(of: self).bottomButtonWidth - button.autoSetDimensions(to: CGSize(width: width, height: width)) - button.layer.cornerRadius = width / 2 + let button = OWSButton( + imageName: "actionsheet_camera_roll_black", + tintColor: .backgroundPrimary, + block: { [weak self] in self?.didTapMediaLibraryModeButton() } + ) + button.clipsToBounds = true + button.adjustsImageWhenHighlighted = false + button.setThemeBackgroundColor(.textPrimary, for: .normal) + button.setThemeBackgroundColor(.textSecondary, for: .highlighted) button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) - button.backgroundColor = .ows_white - button.setShadow() + button.layer.cornerRadius = (SendMediaNavigationController.bottomButtonWidth / 2) + button.set(.width, to: SendMediaNavigationController.bottomButtonWidth) + button.set(.height, to: SendMediaNavigationController.bottomButtonWidth) return button }() @@ -243,63 +254,45 @@ class SendMediaNavigationController: OWSNavigationController { pushViewController(approvalViewController, animated: true) } - private func didRequestExit(dontAbandonText: String) { - if attachmentDraftCollection.count == 0 { + private func didRequestExit() { + guard attachmentDraftCollection.count > 0 else { self.sendMediaNavDelegate?.sendMediaNavDidCancel(self) - } else { - let alertTitle = NSLocalizedString("SEND_MEDIA_ABANDON_TITLE", comment: "alert title when user attempts to leave the send media flow when they have an in-progress album") - - let alert = UIAlertController(title: alertTitle, message: nil, preferredStyle: .alert) - - let confirmAbandonText = NSLocalizedString("SEND_MEDIA_CONFIRM_ABANDON_ALBUM", comment: "alert action, confirming the user wants to exit the media flow and abandon any photos they've taken") - let confirmAbandonAction = UIAlertAction(title: confirmAbandonText, - style: .destructive, - handler: { [weak self] _ in - guard let self = self else { return } - self.sendMediaNavDelegate?.sendMediaNavDidCancel(self) - }) - alert.addAction(confirmAbandonAction) - let dontAbandonAction = UIAlertAction(title: dontAbandonText, - style: .default, - handler: { _ in }) - alert.addAction(dontAbandonAction) - - self.presentAlert(alert) + return } + + let modal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "SEND_MEDIA_ABANDON_TITLE".localized(), + confirmTitle: "SEND_MEDIA_CONFIRM_ABANDON_ALBUM".localized(), + confirmStyle: .danger, + cancelStyle: .textPrimary, + onConfirm: { [weak self] _ in + self?.sendMediaNavDelegate?.sendMediaNavDidCancel(self) + } + ) + ) + self.present(modal, animated: true) } } extension SendMediaNavigationController: UINavigationControllerDelegate { - - private func setNavBarBackgroundColor(to color: UIColor) { - guard let navBar = navigationBar as? OWSNavigationBar else { return } - navBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) - navBar.shadowImage = UIImage() - navBar.isTranslucent = false - navBar.barTintColor = color - } - func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) { - if viewController == captureViewController { - setNavBarBackgroundColor(to: .black) - } else { - setNavBarBackgroundColor(to: Colors.navigationBarBackground) - } - switch viewController { - case is PhotoCaptureViewController: - if attachmentDraftCollection.count == 1 && !isInBatchSelectMode { - // User is navigating "back" to the previous view, indicating - // they want to discard the previously captured item - discardDraft() - } - case is ImagePickerGridController: - if attachmentDraftCollection.count == 1 && !isInBatchSelectMode { - isInBatchSelectMode = true - self.mediaLibraryViewController.batchSelectModeDidChange() - } - default: - break + case is PhotoCaptureViewController: + if attachmentDraftCollection.count == 1 && !isInBatchSelectMode { + // User is navigating "back" to the previous view, indicating + // they want to discard the previously captured item + discardDraft() + } + + case is ImagePickerGridController: + if attachmentDraftCollection.count == 1 && !isInBatchSelectMode { + isInBatchSelectMode = true + self.mediaLibraryViewController.batchSelectModeDidChange() + } + + default: + break } self.updateButtons(topViewController: viewController) @@ -307,30 +300,8 @@ extension SendMediaNavigationController: UINavigationControllerDelegate { // In case back navigation was canceled, we re-apply whatever is showing. func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { - if viewController == captureViewController { - setNavBarBackgroundColor(to: .black) - } else { - setNavBarBackgroundColor(to: Colors.navigationBarBackground) - } - self.updateButtons(topViewController: viewController) } - - // MARK: - Helpers - - private func preferredNavbarTheme(viewController: UIViewController) -> OWSNavigationBar.NavigationBarThemeOverride? { - switch viewController { - case is AttachmentApprovalViewController: - return .clear - case is ImagePickerGridController: - return .alwaysDark - case is PhotoCaptureViewController: - return .clear - default: - owsFailDebug("unexpected viewController: \(viewController)") - return nil - } - } } extension SendMediaNavigationController: PhotoCaptureViewControllerDelegate { @@ -338,14 +309,14 @@ extension SendMediaNavigationController: PhotoCaptureViewControllerDelegate { attachmentDraftCollection.append(.camera(attachment: attachment)) if isInBatchSelectMode { updateButtons(topViewController: photoCaptureViewController) - } else { + } + else { pushApprovalViewController() } } func photoCaptureViewControllerDidCancel(_ photoCaptureViewController: PhotoCaptureViewController) { - let dontAbandonText = NSLocalizedString("SEND_MEDIA_RETURN_TO_CAMERA", comment: "alert action when the user decides not to cancel the media flow after all.") - didRequestExit(dontAbandonText: dontAbandonText) + didRequestExit() } func discardDraft() { @@ -364,8 +335,7 @@ extension SendMediaNavigationController: ImagePickerGridControllerDelegate { } func imagePickerDidCancel(_ imagePicker: ImagePickerGridController) { - let dontAbandonText = NSLocalizedString("SEND_MEDIA_RETURN_TO_MEDIA_LIBRARY", comment: "alert action when the user decides not to cancel the media flow after all.") - didRequestExit(dontAbandonText: dontAbandonText) + didRequestExit() } func showApprovalAfterProcessingAnyMediaLibrarySelections() { @@ -374,18 +344,21 @@ extension SendMediaNavigationController: ImagePickerGridControllerDelegate { let backgroundBlock: (ModalActivityIndicatorViewController) -> Void = { modal in let attachmentPromises: [Promise] = mediaLibrarySelections.map { $0.promise } - when(fulfilled: attachmentPromises).map { attachments in - Logger.debug("built all attachments") - modal.dismiss { - self.attachmentDraftCollection.selectedFromPicker(attachments: attachments) - self.pushApprovalViewController() + when(fulfilled: attachmentPromises) + .map { attachments in + Logger.debug("built all attachments") + modal.dismiss { + self.attachmentDraftCollection.selectedFromPicker(attachments: attachments) + self.pushApprovalViewController() + } } - }.catch { error in - Logger.error("failed to prepare attachments. error: \(error)") - modal.dismiss { - OWSAlerts.showAlert(title: NSLocalizedString("IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS", comment: "alert title")) + .catch { error in + Logger.error("failed to prepare attachments. error: \(error)") + modal.dismiss { + OWSAlerts.showAlert(title: NSLocalizedString("IMAGE_PICKER_FAILED_TO_PROCESS_ATTACHMENTS", comment: "alert title")) + } } - }.retainUntilComplete() + .retainUntilComplete() } ModalActivityIndicatorViewController.present(fromViewController: self, @@ -398,22 +371,17 @@ extension SendMediaNavigationController: ImagePickerGridControllerDelegate { } func imagePicker(_ imagePicker: ImagePickerGridController, didSelectAsset asset: PHAsset, attachmentPromise: Promise) { - guard !mediaLibrarySelections.hasValue(forKey: asset) else { - return - } + guard !mediaLibrarySelections.hasValue(forKey: asset) else { return } let libraryMedia = MediaLibrarySelection(asset: asset, signalAttachmentPromise: attachmentPromise) mediaLibrarySelections.append(key: asset, value: libraryMedia) - updateButtons(topViewController: imagePicker) } func imagePicker(_ imagePicker: ImagePickerGridController, didDeselectAsset asset: PHAsset) { - guard mediaLibrarySelections.hasValue(forKey: asset) else { - return - } + guard mediaLibrarySelections.hasValue(forKey: asset) else { return } + mediaLibrarySelections.remove(key: asset) - updateButtons(topViewController: imagePicker) } @@ -599,19 +567,70 @@ private protocol DoneButtonDelegate: AnyObject { private class DoneButton: UIView { weak var delegate: DoneButtonDelegate? + let numberFormatter: NumberFormatter = NumberFormatter() + + private var didTouchDownInside: Bool = false + + // MARK: - UI + + private let container: UIView = { + let result: UIView = UIView() + result.themeBackgroundColor = .textPrimary + result.layer.cornerRadius = 20 + + return result + }() + + private lazy var badge: CircleView = { + let result: CircleView = CircleView() + result.themeBackgroundColor = .primary + + return result + }() + + private lazy var badgeLabel: UILabel = { + let result: UILabel = UILabel() + result.font = .ows_dynamicTypeSubheadline.ows_monospaced() + result.themeTextColor = .black // Will render on the primary color so should always be black + result.textAlignment = .center + + return result + }() + + private lazy var chevron: UIView = { + let image: UIImage = { + guard CurrentAppContext().isRTL else { return #imageLiteral(resourceName: "small_chevron_right") } + + return #imageLiteral(resourceName: "small_chevron_left") + }() + let result: UIImageView = UIImageView(image: image.withRenderingMode(.alwaysTemplate)) + result.contentMode = .scaleAspectFit + result.themeTintColor = .backgroundPrimary + result.set(.width, to: 10) + result.set(.height, to: 18) + + return result + }() + + // MARK: - Lifecycle init() { super.init(frame: .zero) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(tapGesture:))) addGestureRecognizer(tapGesture) - - let container = UIView() - container.backgroundColor = .ows_white - container.layer.cornerRadius = 20 - container.layoutMargins = UIEdgeInsets(top: 7, leading: 8, bottom: 7, trailing: 8) - + addSubview(container) - container.autoPinEdgesToSuperviewMargins() + container.pin(to: self) + + badge.addSubview(badgeLabel) + badgeLabel.pin(to: badge, withInset: 4) + + // Constrain to be a pill that is at least a circle, and maybe wider. + badgeLabel.autoPin(toAspectRatio: 1.0, relation: .greaterThanOrEqual) + NSLayoutConstraint.autoSetPriority(.defaultLow) { + badgeLabel.autoPinToSquareAspectRatio() + } let stackView = UIStackView(arrangedSubviews: [badge, chevron]) stackView.axis = .horizontal @@ -619,74 +638,78 @@ private class DoneButton: UIView { stackView.spacing = 9 container.addSubview(stackView) - stackView.autoPinEdgesToSuperviewMargins() - } - - let numberFormatter: NumberFormatter = NumberFormatter() - - func updateCount() { - guard let delegate = delegate else { - return - } - - badgeLabel.text = numberFormatter.string(for: delegate.doneButtonCount) + stackView.pin(.top, to: .top, of: container, withInset: 7) + stackView.pin(.leading, to: .leading, of: container, withInset: 8) + stackView.pin(.trailing, to: .trailing, of: container, withInset: -8) + stackView.pin(.bottom, to: .bottom, of: container, withInset: -7) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + // MARK: - Functions + + func updateCount() { + guard let delegate = delegate else { return } - // MARK: - Subviews + badgeLabel.text = numberFormatter.string(for: delegate.doneButtonCount) + } + + // MARK: - Interaction - private lazy var badge: UIView = { - let badge = CircleView() - badge.layoutMargins = UIEdgeInsets(top: 4, leading: 4, bottom: 4, trailing: 4) - badge.backgroundColor = .ows_signalBlue - badge.addSubview(badgeLabel) - badgeLabel.autoPinEdgesToSuperviewMargins() - - // Constrain to be a pill that is at least a circle, and maybe wider. - badgeLabel.autoPin(toAspectRatio: 1.0, relation: .greaterThanOrEqual) - NSLayoutConstraint.autoSetPriority(.defaultLow) { - badgeLabel.autoPinToSquareAspectRatio() - } - - return badge - }() - - private lazy var badgeLabel: UILabel = { - let label = UILabel() - label.textColor = .ows_white - label.font = UIFont.ows_dynamicTypeSubheadline.ows_monospaced() - label.textAlignment = .center - return label - }() - - private lazy var chevron: UIView = { - let image: UIImage - if CurrentAppContext().isRTL { - image = #imageLiteral(resourceName: "small_chevron_left") - } else { - image = #imageLiteral(resourceName: "small_chevron_right") - } - let chevron = UIImageView(image: image.withRenderingMode(.alwaysTemplate)) - chevron.contentMode = .scaleAspectFit - chevron.tintColor = .ows_gray60 - chevron.autoSetDimensions(to: CGSize(width: 10, height: 18)) - - return chevron - }() - - @objc - func didTap(tapGesture: UITapGestureRecognizer) { + @objc func didTap(tapGesture: UITapGestureRecognizer) { delegate?.doneButtonWasTapped(self) } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + guard + isUserInteractionEnabled, + let location: CGPoint = touches.first?.location(in: self), + bounds.contains(location) + else { return } + + didTouchDownInside = true + container.themeBackgroundColor = .textSecondary + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + guard + isUserInteractionEnabled, + let location: CGPoint = touches.first?.location(in: self), + bounds.contains(location), + didTouchDownInside + else { + if didTouchDownInside { + container.themeBackgroundColor = .textPrimary + } + return + } + + container.themeBackgroundColor = .textSecondary + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + if didTouchDownInside { + container.themeBackgroundColor = .textPrimary + } + + didTouchDownInside = false + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + if didTouchDownInside { + container.themeBackgroundColor = .textPrimary + } + + didTouchDownInside = false + } } // MARK: - SendMediaNavDelegate protocol SendMediaNavDelegate: AnyObject { - func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController) + func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController?) func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) func sendMediaNavInitialMessageText(_ sendMediaNavigationController: SendMediaNavigationController) -> String? diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index d1d66c44b..dcbb2cf7d 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -79,8 +79,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } ) - SNAppearance.switchToSessionAppearance() - if Environment.shared?.callManager.wrappedValue?.currentCall == nil { UserDefaults.sharedLokiProject?.set(false, forKey: "isCallOngoing") } diff --git a/Session/Meta/MainAppContext.m b/Session/Meta/MainAppContext.m index 6b70781cb..d7116c5e5 100644 --- a/Session/Meta/MainAppContext.m +++ b/Session/Meta/MainAppContext.m @@ -236,7 +236,7 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic - (nullable UIAlertAction *)openSystemSettingsAction { return [UIAlertAction actionWithTitle:CommonStrings.openSettingsButton - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"system_settings") + accessibilityIdentifier:[NSString stringWithFormat:@"%@.%@", self.class, @"system_settings"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *_Nonnull action) { [UIApplication.sharedApplication openSystemSettings]; diff --git a/Session/Meta/Signal-Bridging-Header.h b/Session/Meta/Signal-Bridging-Header.h index 4e89dff43..a1cd25fa6 100644 --- a/Session/Meta/Signal-Bridging-Header.h +++ b/Session/Meta/Signal-Bridging-Header.h @@ -13,9 +13,7 @@ #import "OWSBezierPathView.h" #import "OWSMessageTimerView.h" #import "OWSNavigationController.h" -#import "OWSProgressView.h" #import "OWSWindowManager.h" -#import "OWSQRCodeScanningViewController.h" #import "MainAppContext.h" #import "UIViewController+Permissions.h" #import @@ -31,9 +29,7 @@ #import #import #import -#import #import -#import #import #import #import diff --git a/Session/Onboarding/DisplayNameVC.swift b/Session/Onboarding/DisplayNameVC.swift index 920ecf533..cc3f6494d 100644 --- a/Session/Onboarding/DisplayNameVC.swift +++ b/Session/Onboarding/DisplayNameVC.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SessionMessagingKit +import SignalUtilitiesKit final class DisplayNameVC: BaseVC { private var spacer1HeightConstraint: NSLayoutConstraint! @@ -13,8 +14,8 @@ final class DisplayNameVC: BaseVC { // MARK: - Components private lazy var displayNameTextField: TextField = { - let result = TextField(placeholder: NSLocalizedString("vc_display_name_text_field_hint", comment: "")) - result.layer.borderColor = Colors.text.cgColor + let result = TextField(placeholder: "vc_display_name_text_field_hint".localized()) + result.themeBorderColor = .textPrimary result.accessibilityLabel = "Display name text field" return result @@ -31,7 +32,7 @@ final class DisplayNameVC: BaseVC { let titleLabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) titleLabel.text = "vc_display_name_title_2".localized() - titleLabel.textColor = Colors.text + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 @@ -39,7 +40,7 @@ final class DisplayNameVC: BaseVC { let explanationLabel = UILabel() explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) explanationLabel.text = "vc_display_name_explanation".localized() - explanationLabel.textColor = Colors.text + explanationLabel.themeTextColor = .textPrimary explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.numberOfLines = 0 diff --git a/Session/Onboarding/FakeChatView.swift b/Session/Onboarding/FakeChatView.swift index b585ac15d..1eb1c42fa 100644 --- a/Session/Onboarding/FakeChatView.swift +++ b/Session/Onboarding/FakeChatView.swift @@ -66,8 +66,8 @@ final class FakeChatView: UIView { let result = UIView() let bubbleView = UIView() bubbleView.set(.width, to: FakeChatView.bubbleWidth) + bubbleView.themeShadowColor = .black bubbleView.layer.cornerRadius = FakeChatView.bubbleCornerRadius - bubbleView.layer.shadowColor = UIColor.black.cgColor bubbleView.layer.shadowRadius = isLightMode ? 4 : 8 bubbleView.layer.shadowOpacity = isLightMode ? 0.16 : 0.24 bubbleView.layer.shadowOffset = CGSize.zero diff --git a/Session/Onboarding/LinkDeviceVC.swift b/Session/Onboarding/LinkDeviceVC.swift index 7b53c9e45..4f86d9f8e 100644 --- a/Session/Onboarding/LinkDeviceVC.swift +++ b/Session/Onboarding/LinkDeviceVC.swift @@ -1,12 +1,14 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import AVFoundation import PromiseKit import SessionUIKit import SessionUtilitiesKit import SessionSnodeKit +import SignalUtilitiesKit -final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { +final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, QRScannerDelegate { private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private var pages: [UIViewController] = [] private var targetVCIndex: Int? @@ -42,29 +44,32 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont }() private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = { - let message = NSLocalizedString("vc_link_device_scan_qr_code_explanation", comment: "") + let message = "vc_link_device_scan_qr_code_explanation".localized() let result = ScanQRCodeWrapperVC(message: message) result.delegate = self return result }() - // MARK: Lifecycle + // MARK: - Lifecycle + override func viewDidLoad() { super.viewDidLoad() setNavBarTitle("vc_link_device_title".localized()) - let navigationBar = navigationController!.navigationBar + // Page VC let hasCameraAccess = (AVCaptureDevice.authorizationStatus(for: .video) == .authorized) pages = [ recoveryPhraseVC, (hasCameraAccess ? scanQRCodeWrapperVC : scanQRCodePlaceholderVC) ] pageVC.dataSource = self pageVC.delegate = self pageVC.setViewControllers([ recoveryPhraseVC ], direction: .forward, animated: false, completion: nil) + // Tab bar view.addSubview(tabBar) tabBar.pin(.leading, to: .leading, of: view) tabBarTopConstraint = tabBar.autoPinEdge(toSuperviewSafeArea: .top) view.pin(.trailing, to: .trailing, of: tabBar) + // Page VC constraints let pageVCView = pageVC.view! view.addSubview(pageVCView) @@ -72,10 +77,11 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont pageVCView.pin(.top, to: .bottom, of: tabBar) view.pin(.trailing, to: .trailing, of: pageVCView) view.pin(.bottom, to: .bottom, of: pageVCView) + let screen = UIScreen.main.bounds pageVCView.set(.width, to: screen.width) let statusBarHeight = UIApplication.shared.statusBarFrame.height - let height = navigationController!.view.bounds.height - navigationBar.height() - TabBar.snHeight - statusBarHeight + let height = (navigationController?.view.bounds.height ?? 0) - (navigationController?.navigationBar.bounds.height ?? 0) - TabBar.snHeight - statusBarHeight pageVCView.set(.height, to: height) recoveryPhraseVC.constrainHeight(to: height) scanQRCodePlaceholderVC.constrainHeight(to: height) @@ -90,7 +96,8 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont NotificationCenter.default.removeObserver(self) } - // MARK: General + // MARK: - General + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let index = pages.firstIndex(of: viewController), index != 0 else { return nil } return pages[index - 1] @@ -106,7 +113,8 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont pageVC.setViewControllers([ scanQRCodeWrapperVC ], direction: .forward, animated: false, completion: nil) } - // MARK: Updating + // MARK: - Updating + func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { guard let targetVC = pendingViewControllers.first, let index = pages.firstIndex(of: targetVC) else { return } targetVCIndex = index @@ -117,27 +125,31 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont tabBar.selectTab(at: index) } - // MARK: Interaction + // MARK: - Interaction + @objc private func close() { dismiss(animated: true, completion: nil) } - func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { let seed = Data(hex: string) continueWithSeed(seed) } func continueWithSeed(_ seed: Data) { if (seed.count != 16) { - let alert = UIAlertController( - title: "invalid_recovery_phrase".localized(), - message: "INVALID_RECOVERY_PHRASE_MESSAGE".localized(), - preferredStyle: .alert + let modal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: "invalid_recovery_phrase".localized(), + explanation: "INVALID_RECOVERY_PHRASE_MESSAGE".localized(), + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .textPrimary, + afterClosed: { [weak self] in + self?.scanQRCodeWrapperVC.startCapture() + } + ) ) - alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: { _ in - self.scanQRCodeWrapperVC.startCapture() - })) - presentAlert(alert) + present(modal, animated: true) return } let (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) @@ -164,7 +176,7 @@ final class LinkDeviceVC: BaseVC, UIPageViewControllerDataSource, UIPageViewCont } } -private final class RecoveryPhraseVC : UIViewController { +private final class RecoveryPhraseVC: UIViewController { weak var linkDeviceVC: LinkDeviceVC! private var spacer1HeightConstraint: NSLayoutConstraint! private var spacer2HeightConstraint: NSLayoutConstraint! @@ -172,21 +184,23 @@ private final class RecoveryPhraseVC : UIViewController { private var bottomConstraint: NSLayoutConstraint! private lazy var mnemonicTextView: TextView = { - let result = TextView(placeholder: NSLocalizedString("vc_restore_seed_text_field_hint", comment: "")) - result.layer.borderColor = Colors.text.cgColor + let result = TextView(placeholder: "vc_restore_seed_text_field_hint".localized()) + result.themeBorderColor = .textPrimary result.accessibilityLabel = "Recovery phrase text view" + return result }() - // MARK: Lifecycle + // MARK: - Lifecycle + override func viewDidLoad() { - view.backgroundColor = .clear + view.themeBackgroundColor = .clear // Title label let titleLabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) titleLabel.text = "vc_enter_recovery_phrase_title".localized() - titleLabel.textColor = Colors.text + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 @@ -194,7 +208,7 @@ private final class RecoveryPhraseVC : UIViewController { let explanationLabel = UILabel() explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) explanationLabel.text = "vc_enter_recovery_phrase_explanation".localized() - explanationLabel.textColor = Colors.text + explanationLabel.themeTextColor = .textPrimary explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.numberOfLines = 0 @@ -267,7 +281,8 @@ private final class RecoveryPhraseVC : UIViewController { mnemonicTextView.resignFirstResponder() } - // MARK: Updating + // MARK: - Updating + @objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) { guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return } bottomConstraint.constant = -newHeight // Negative due to how the constraint is set up @@ -289,11 +304,12 @@ private final class RecoveryPhraseVC : UIViewController { } } - // MARK: Interaction + // MARK: - Interaction + @objc private func handleContinueButtonTapped() { func showError(title: String, message: String = "") { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) + alert.addAction(UIAlertAction(title: "BUTTON_OK".localized(), style: .default, handler: nil)) presentAlert(alert) } let mnemonic = mnemonicTextView.text!.lowercased() @@ -309,31 +325,35 @@ private final class RecoveryPhraseVC : UIViewController { } } -private final class ScanQRCodePlaceholderVC : UIViewController { +private final class ScanQRCodePlaceholderVC: UIViewController { weak var linkDeviceVC: LinkDeviceVC! override func viewDidLoad() { // Remove background color - view.backgroundColor = .clear + view.themeBackgroundColor = .clear + // Set up explanation label let explanationLabel = UILabel() - explanationLabel.textColor = Colors.text explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) - explanationLabel.text = NSLocalizedString("vc_scan_qr_code_camera_access_explanation", comment: "") - explanationLabel.numberOfLines = 0 + explanationLabel.text = "vc_scan_qr_code_camera_access_explanation".localized() + explanationLabel.themeTextColor = .textPrimary explanationLabel.textAlignment = .center explanationLabel.lineBreakMode = .byWordWrapping + explanationLabel.numberOfLines = 0 + // Set up call to action button let callToActionButton = UIButton() - callToActionButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize) - callToActionButton.setTitleColor(Colors.accent, for: UIControl.State.normal) - callToActionButton.setTitle(NSLocalizedString("vc_scan_qr_code_grant_camera_access_button_title", comment: ""), for: UIControl.State.normal) - callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: UIControl.Event.touchUpInside) + callToActionButton.titleLabel?.font = .boldSystemFont(ofSize: Values.mediumFontSize) + callToActionButton.setTitle("vc_scan_qr_code_grant_camera_access_button_title".localized(), for: .normal) + callToActionButton.setThemeTitleColor(.primary, for: .normal) + callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: .touchUpInside) + // Set up stack view let stackView = UIStackView(arrangedSubviews: [ explanationLabel, callToActionButton ]) stackView.axis = .vertical stackView.spacing = Values.mediumSpacing stackView.alignment = .center + // Set up constraints view.set(.width, to: UIScreen.main.bounds.width) view.addSubview(stackView) diff --git a/Session/Onboarding/PNModeVC.swift b/Session/Onboarding/PNModeVC.swift index 72379ecaa..fbfd97699 100644 --- a/Session/Onboarding/PNModeVC.swift +++ b/Session/Onboarding/PNModeVC.swift @@ -5,8 +5,9 @@ import PromiseKit import SessionUIKit import SessionMessagingKit import SessionSnodeKit +import SignalUtilitiesKit -final class PNModeVC : BaseVC, OptionViewDelegate { +final class PNModeVC: BaseVC, OptionViewDelegate { private var optionViews: [OptionView] { [ apnsOptionView, backgroundPollingOptionView ] @@ -16,36 +17,47 @@ final class PNModeVC : BaseVC, OptionViewDelegate { return optionViews.first { $0.isSelected } } - // MARK: Components + // MARK: - Components + private lazy var apnsOptionView: OptionView = { - let explanation = NSLocalizedString("fast_mode_explanation", comment: "") - let result = OptionView(title: "Fast Mode", explanation: explanation, delegate: self, isRecommended: true) + let result: OptionView = OptionView( + title: "Fast Mode", + explanation: "fast_mode_explanation".localized(), + delegate: self, + isRecommended: true + ) result.accessibilityLabel = "Fast mode option" + return result }() private lazy var backgroundPollingOptionView: OptionView = { - let explanation = NSLocalizedString("slow_mode_explanation", comment: "") - let result = OptionView(title: "Slow Mode", explanation: explanation, delegate: self) + let result: OptionView = OptionView( + title: "Slow Mode", + explanation: "slow_mode_explanation".localized(), + delegate: self + ) result.accessibilityLabel = "Slow mode option" + return result }() - // MARK: Lifecycle + // MARK: - Lifecycle + override func viewDidLoad() { super.viewDidLoad() setUpNavBarSessionIcon() let learnMoreButton = UIBarButtonItem(image: #imageLiteral(resourceName: "ic_info"), style: .plain, target: self, action: #selector(learnMore)) - learnMoreButton.tintColor = Colors.text + learnMoreButton.themeTintColor = .textPrimary navigationItem.rightBarButtonItem = learnMoreButton // Set up title label let titleLabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) titleLabel.text = "vc_pn_mode_title".localized() - titleLabel.textColor = Colors.text + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 diff --git a/Session/Onboarding/PNOptionView.swift b/Session/Onboarding/PNOptionView.swift index aa0e85d29..3f4f567bc 100644 --- a/Session/Onboarding/PNOptionView.swift +++ b/Session/Onboarding/PNOptionView.swift @@ -1,10 +1,14 @@ -import UIKit +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -final class OptionView : UIView { +import UIKit +import SessionUIKit + +final class OptionView: UIView { private let title: String private let explanation: String private let delegate: OptionViewDelegate private let isRecommended: Bool + var isSelected = false { didSet { handleIsSelectedChanged() } } private static let cornerRadius: CGFloat = 8 @@ -14,7 +18,9 @@ final class OptionView : UIView { self.explanation = explanation self.delegate = delegate self.isRecommended = isRecommended + super.init(frame: CGRect.zero) + setUpViewHierarchy() } @@ -27,49 +33,68 @@ final class OptionView : UIView { } private func setUpViewHierarchy() { - backgroundColor = Colors.pnOptionBackground + themeBackgroundColor = .backgroundSecondary + // Round corners layer.cornerRadius = OptionView.cornerRadius + // Set up border + themeBorderColor = .borderSeparator layer.borderWidth = 1 - layer.borderColor = Colors.pnOptionBorder.cgColor + // Set up shadow - layer.shadowColor = UIColor.black.cgColor + themeShadowColor = .black layer.shadowOffset = CGSize(width: 0, height: 0.8) - layer.shadowOpacity = isLightMode ? 0.16 : 1 - layer.shadowRadius = isLightMode ? 4 : 6 + + ThemeManager.onThemeChange(observer: self) { [weak self] theme, _ in + switch theme.interfaceStyle { + case .light: + self?.layer.shadowOpacity = 0.16 + self?.layer.shadowRadius = 4 + + default: + self?.layer.shadowOpacity = 1 + self?.layer.shadowRadius = 6 + } + } + // Set up title label - let titleLabel = UILabel() - titleLabel.textColor = Colors.text + let titleLabel: UILabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize) titleLabel.text = title - titleLabel.numberOfLines = 0 + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byWordWrapping + titleLabel.numberOfLines = 0 + // Set up explanation label - let explanationLabel = UILabel() - explanationLabel.textColor = Colors.text + let explanationLabel: UILabel = UILabel() explanationLabel.font = .systemFont(ofSize: Values.verySmallFontSize) explanationLabel.text = explanation - explanationLabel.numberOfLines = 0 + explanationLabel.themeTextColor = .textPrimary explanationLabel.lineBreakMode = .byWordWrapping + explanationLabel.numberOfLines = 0 + // Set up stack view let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ]) stackView.axis = .vertical stackView.spacing = 4 stackView.alignment = .fill addSubview(stackView) + stackView.pin(.leading, to: .leading, of: self, withInset: 12) stackView.pin(.top, to: .top, of: self, withInset: 12) self.pin(.trailing, to: .trailing, of: stackView, withInset: 12) self.pin(.bottom, to: .bottom, of: stackView, withInset: 12) + // Set up recommended label if needed if isRecommended { - let recommendedLabel = UILabel() - recommendedLabel.textColor = Colors.accent + let recommendedLabel: UILabel = UILabel() recommendedLabel.font = .boldSystemFont(ofSize: Values.smallFontSize) - recommendedLabel.text = NSLocalizedString("vc_pn_mode_recommended_option_tag", comment: "") + recommendedLabel.text = "vc_pn_mode_recommended_option_tag".localized() + recommendedLabel.themeTextColor = .primary stackView.addArrangedSubview(recommendedLabel) } + // Set up tap gesture recognizer let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) addGestureRecognizer(tapGestureRecognizer) @@ -80,30 +105,18 @@ final class OptionView : UIView { } private func handleIsSelectedChanged() { - let animationDuration: TimeInterval = 0.25 - // Animate border color - let newBorderColor = isSelected ? Colors.accent.cgColor : Colors.pnOptionBorder.cgColor - let borderAnimation = CABasicAnimation(keyPath: "borderColor") - borderAnimation.fromValue = layer.shadowColor - borderAnimation.toValue = newBorderColor - borderAnimation.duration = animationDuration - layer.add(borderAnimation, forKey: borderAnimation.keyPath) - layer.borderColor = newBorderColor - // Animate shadow color - let newShadowColor = isSelected ? Colors.expandedButtonGlowColor.cgColor : UIColor.black.cgColor - let shadowAnimation = CABasicAnimation(keyPath: "shadowColor") - shadowAnimation.fromValue = layer.shadowColor - shadowAnimation.toValue = newShadowColor - shadowAnimation.duration = animationDuration - layer.add(shadowAnimation, forKey: shadowAnimation.keyPath) - layer.shadowColor = newShadowColor + UIView.animate(withDuration: 0.3) { [weak self] in + self?.themeBorderColor = (self?.isSelected == true ? .primary : .borderSeparator) + self?.themeShadowColor = (self?.isSelected == true ? .primary : .black) + } + // Notify delegate if isSelected { delegate.optionViewDidActivate(self) } } } -// MARK: Option View Delegate -protocol OptionViewDelegate { +// MARK: - Option View Delegate +protocol OptionViewDelegate { func optionViewDidActivate(_ optionView: OptionView) } diff --git a/Session/Onboarding/RegisterVC.swift b/Session/Onboarding/RegisterVC.swift index 024cb50aa..40fb666c6 100644 --- a/Session/Onboarding/RegisterVC.swift +++ b/Session/Onboarding/RegisterVC.swift @@ -4,6 +4,7 @@ import UIKit import Sodium import Curve25519Kit import SessionUIKit +import SignalUtilitiesKit final class RegisterVC : BaseVC { private var seed: Data! { didSet { updateKeyPair() } } @@ -14,11 +15,11 @@ final class RegisterVC : BaseVC { private lazy var publicKeyLabel: UILabel = { let result = UILabel() - result.textColor = Colors.text result.font = Fonts.spaceMono(ofSize: isIPhone5OrSmaller ? Values.mediumFontSize : 20) - result.numberOfLines = 0 - result.lineBreakMode = .byCharWrapping + result.themeTextColor = .textPrimary result.accessibilityLabel = "Session ID label" + result.lineBreakMode = .byCharWrapping + result.numberOfLines = 0 return result }() @@ -33,21 +34,23 @@ final class RegisterVC : BaseVC { private lazy var legalLabel: UILabel = { let result = UILabel() - result.textColor = Colors.text result.font = .systemFont(ofSize: Values.verySmallFontSize) + result.themeTextColor = .textPrimary let text = "By using this service, you agree to our Terms of Service, End User License Agreement (EULA) and Privacy Policy" let attributedText = NSMutableAttributedString(string: text, attributes: [ .font : UIFont.systemFont(ofSize: Values.verySmallFontSize) ]) attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize), range: (text as NSString).range(of: "Terms of Service")) attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize), range: (text as NSString).range(of: "End User License Agreement (EULA)")) attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize), range: (text as NSString).range(of: "Privacy Policy")) result.attributedText = attributedText - result.numberOfLines = 0 result.textAlignment = .center result.lineBreakMode = .byWordWrapping + result.numberOfLines = 0 + return result }() - // MARK: Lifecycle + // MARK: - Lifecycle + override func viewDidLoad() { super.viewDidLoad() @@ -57,7 +60,7 @@ final class RegisterVC : BaseVC { let titleLabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) titleLabel.text = "vc_register_title".localized() - titleLabel.textColor = Colors.text + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 @@ -65,7 +68,7 @@ final class RegisterVC : BaseVC { let explanationLabel = UILabel() explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) explanationLabel.text = "vc_register_explanation".localized() - explanationLabel.textColor = Colors.text + explanationLabel.themeTextColor = .textPrimary explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.numberOfLines = 0 @@ -75,7 +78,7 @@ final class RegisterVC : BaseVC { publicKeyLabel.pin(to: publicKeyLabelContainer, withInset: Values.mediumSpacing) publicKeyLabelContainer.layer.cornerRadius = TextField.cornerRadius publicKeyLabelContainer.layer.borderWidth = 1 - publicKeyLabelContainer.layer.borderColor = Colors.text.cgColor + publicKeyLabelContainer.themeBorderColor = .textPrimary // Set up spacers let topSpacer = UIView.vStretchingSpacer() diff --git a/Session/Onboarding/RestoreVC.swift b/Session/Onboarding/RestoreVC.swift index 144376784..3bffe8bad 100644 --- a/Session/Onboarding/RestoreVC.swift +++ b/Session/Onboarding/RestoreVC.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SessionUtilitiesKit +import SignalUtilitiesKit final class RestoreVC: BaseVC { private var spacer1HeightConstraint: NSLayoutConstraint! @@ -11,11 +12,12 @@ final class RestoreVC: BaseVC { private var restoreButtonBottomOffsetConstraint: NSLayoutConstraint! private var bottomConstraint: NSLayoutConstraint! - // MARK: Components + // MARK: - Components + private lazy var mnemonicTextView: TextView = { let result = TextView(placeholder: "vc_restore_seed_text_field_hint".localized()) result.autocapitalizationType = .none - result.layer.borderColor = Colors.text.cgColor + result.themeBorderColor = .textPrimary result.accessibilityLabel = "Recovery phrase text view" return result @@ -34,7 +36,7 @@ final class RestoreVC: BaseVC { attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize), range: (text as NSString).range(of: "Terms of Service")) attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize), range: (text as NSString).range(of: "End User License Agreement (EULA)")) attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: Values.verySmallFontSize), range: (text as NSString).range(of: "Privacy Policy")) - result.textColor = Colors.text + result.themeTextColor = .textPrimary result.attributedText = attributedText result.textAlignment = .center result.lineBreakMode = .byWordWrapping @@ -54,7 +56,7 @@ final class RestoreVC: BaseVC { let titleLabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) titleLabel.text = "vc_restore_title".localized() - titleLabel.textColor = Colors.text + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 @@ -62,7 +64,7 @@ final class RestoreVC: BaseVC { let explanationLabel = UILabel() explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) explanationLabel.text = "vc_restore_explanation".localized() - explanationLabel.textColor = Colors.text + explanationLabel.themeTextColor = .textPrimary explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.numberOfLines = 0 @@ -70,6 +72,7 @@ final class RestoreVC: BaseVC { legalLabel.isUserInteractionEnabled = true let legalLabelTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleLegalLabelTapped)) legalLabel.addGestureRecognizer(legalLabelTapGestureRecognizer) + // Set up spacers let topSpacer = UIView.vStretchingSpacer() let spacer1 = UIView() @@ -146,12 +149,15 @@ final class RestoreVC: BaseVC { // MARK: Updating @objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) { guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return } + bottomConstraint.constant = -newHeight // Negative due to how the constraint is set up restoreButtonBottomOffsetConstraint.constant = isIPhone6OrSmaller ? Values.smallSpacing : Values.largeSpacing spacer1HeightConstraint.constant = isIPhone6OrSmaller ? Values.smallSpacing : Values.mediumSpacing spacer2HeightConstraint.constant = isIPhone6OrSmaller ? Values.smallSpacing : Values.mediumSpacing spacer3HeightConstraint.constant = isIPhone6OrSmaller ? Values.smallSpacing : Values.mediumSpacing + if isIPhone5OrSmaller { legalLabel.isUserInteractionEnabled = false } + UIView.animate(withDuration: 0.25) { if isIPhone5OrSmaller { self.legalLabel.alpha = 0 } self.view.layoutIfNeeded() @@ -164,20 +170,30 @@ final class RestoreVC: BaseVC { spacer1HeightConstraint.constant = isIPhone5OrSmaller ? Values.smallSpacing : Values.veryLargeSpacing spacer2HeightConstraint.constant = isIPhone5OrSmaller ? Values.smallSpacing : Values.veryLargeSpacing spacer3HeightConstraint.constant = isIPhone5OrSmaller ? Values.smallSpacing : Values.veryLargeSpacing + if isIPhone5OrSmaller { legalLabel.isUserInteractionEnabled = true } + UIView.animate(withDuration: 0.25) { if isIPhone5OrSmaller { self.legalLabel.alpha = 1 } self.view.layoutIfNeeded() } } - // MARK: Interaction + // MARK: - Interaction + @objc private func restore() { func showError(title: String, message: String = "") { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("BUTTON_OK", comment: ""), style: .default, handler: nil)) - presentAlert(alert) + let modal: ConfirmationModal = ConfirmationModal( + info: ConfirmationModal.Info( + title: title, + explanation: message, + cancelTitle: "BUTTON_OK".localized(), + cancelStyle: .textPrimary + ) + ) + present(modal, animated: true) } + let mnemonic = mnemonicTextView.text!.lowercased() do { let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic) @@ -185,6 +201,7 @@ final class RestoreVC: BaseVC { let (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) Onboarding.Flow.recover.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) mnemonicTextView.resignFirstResponder() + Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in let displayNameVC = DisplayNameVC() self.navigationController!.pushViewController(displayNameVC, animated: true) @@ -202,6 +219,7 @@ final class RestoreVC: BaseVC { let ppRange = (legalLabel.text! as NSString).range(of: "Privacy Policy") let touchInLegalLabelCoordinates = tapGestureRecognizer.location(in: legalLabel) let characterIndex = legalLabel.characterIndex(for: touchInLegalLabelCoordinates) + if tosRange.contains(characterIndex) { urlAsString = "https://getsession.org/terms-of-service/" } else if eulaRange.contains(characterIndex) { @@ -211,6 +229,7 @@ final class RestoreVC: BaseVC { } else { urlAsString = nil } + if let urlAsString = urlAsString { let url = URL(string: urlAsString)! UIApplication.shared.open(url) diff --git a/Session/Onboarding/SeedReminderView.swift b/Session/Onboarding/SeedReminderView.swift index db8fcd195..56e72636b 100644 --- a/Session/Onboarding/SeedReminderView.swift +++ b/Session/Onboarding/SeedReminderView.swift @@ -38,8 +38,8 @@ final class SeedReminderView: UIView { let result = UILabel() result.font = .systemFont(ofSize: Values.verySmallFontSize) result.themeTextColor = .textSecondary - result.numberOfLines = 0 result.lineBreakMode = .byWordWrapping + result.numberOfLines = 2 return result }() @@ -66,8 +66,17 @@ final class SeedReminderView: UIView { // Set background color themeBackgroundColor = .conversationButton_background + // Note: We hard-code the height of the subtitle to 2 lines so changing it's content + // doesn't result in the view changing height (which looks buggy) + let subtitleContainerView: UIView = UIView() + subtitleContainerView.set(.height, to: (subtitleLabel.font.lineHeight * 2)) + subtitleContainerView.addSubview(subtitleLabel) + subtitleLabel.pin(.top, to: .top, of: subtitleContainerView) + subtitleLabel.pin(.leading, to: .leading, of: subtitleContainerView) + subtitleLabel.pin(.trailing, to: .trailing, of: subtitleContainerView) + // Set up label stack view - let labelStackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel ]) + let labelStackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleContainerView ]) labelStackView.axis = .vertical labelStackView.spacing = 4 @@ -103,12 +112,12 @@ final class SeedReminderView: UIView { stackView.pin(to: self) } - // MARK: Updating + // MARK: - Updating + func setProgress(_ progress: Float, animated isAnimated: Bool) { progressIndicatorView.setProgress(progress, animated: isAnimated) } - // MARK: Updating @objc private func handleContinueButtonTapped() { delegate?.handleContinueButtonTapped(from: self) } diff --git a/Session/Onboarding/SeedVC.swift b/Session/Onboarding/SeedVC.swift index 0cb392eb4..a18ba7a0f 100644 --- a/Session/Onboarding/SeedVC.swift +++ b/Session/Onboarding/SeedVC.swift @@ -3,6 +3,7 @@ import UIKit import SessionUIKit import SessionUtilitiesKit +import SignalUtilitiesKit final class SeedVC: BaseVC { private let mnemonic: String = { @@ -26,20 +27,27 @@ final class SeedVC: BaseVC { private lazy var seedReminderView: SeedReminderView = { let result = SeedReminderView(hasContinueButton: false) - let title = "You're almost finished! 90%" - let attributedTitle = NSMutableAttributedString(string: title) - attributedTitle.addAttribute(.foregroundColor, value: Colors.accent, range: (title as NSString).range(of: "90%")) - result.title = attributedTitle result.subtitle = "view_seed_reminder_subtitle_2".localized() result.setProgress(0.9, animated: false) + ThemeManager.onThemeChange(observer: result) { [weak result] _, primaryColor in + let title = "You're almost finished! 90%" + let attributedTitle = NSMutableAttributedString(string: title) + attributedTitle.addAttribute( + .foregroundColor, + value: primaryColor.color, + range: (title as NSString).range(of: "90%") + ) + result?.title = attributedTitle + } + return result }() private lazy var mnemonicLabel: UILabel = { let result = UILabel() result.font = Fonts.spaceMono(ofSize: Values.mediumFontSize) - result.textColor = Colors.accent + result.themeTextColor = .primary result.textAlignment = .center result.lineBreakMode = .byWordWrapping result.numberOfLines = 0 @@ -64,14 +72,14 @@ final class SeedVC: BaseVC { // Set up navigation bar buttons let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close)) - closeButton.tintColor = Colors.text + closeButton.themeTintColor = .textPrimary navigationItem.leftBarButtonItem = closeButton // Set up title label let titleLabel = UILabel() titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? Values.largeFontSize : Values.veryLargeFontSize) titleLabel.text = "vc_seed_title_2".localized() - titleLabel.textColor = Colors.text + titleLabel.themeTextColor = .textPrimary titleLabel.lineBreakMode = .byWordWrapping titleLabel.numberOfLines = 0 @@ -79,7 +87,7 @@ final class SeedVC: BaseVC { let explanationLabel = UILabel() explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) explanationLabel.text = "vc_seed_explanation".localized() - explanationLabel.textColor = Colors.text + explanationLabel.themeTextColor = .textPrimary explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.numberOfLines = 0 @@ -94,15 +102,15 @@ final class SeedVC: BaseVC { let mnemonicLabelContainer = UIView() mnemonicLabelContainer.addSubview(mnemonicLabel) mnemonicLabel.pin(to: mnemonicLabelContainer, withInset: isIPhone6OrSmaller ? Values.smallSpacing : Values.mediumSpacing) + mnemonicLabelContainer.themeBorderColor = .textPrimary mnemonicLabelContainer.layer.cornerRadius = TextField.cornerRadius mnemonicLabelContainer.layer.borderWidth = 1 - mnemonicLabelContainer.layer.borderColor = Colors.text.cgColor // Set up call to action label let callToActionLabel = UILabel() callToActionLabel.font = .systemFont(ofSize: isIPhone5OrSmaller ? Values.smallFontSize : Values.mediumFontSize) callToActionLabel.text = "vc_seed_reveal_button_title".localized() - callToActionLabel.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) + callToActionLabel.themeTextColor = .textSecondary callToActionLabel.textAlignment = .center let callToActionLabelGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(revealMnemonic)) @@ -179,14 +187,20 @@ final class SeedVC: BaseVC { @objc private func revealMnemonic() { UIView.transition(with: mnemonicLabel, duration: 0.25, options: .transitionCrossDissolve, animations: { self.mnemonicLabel.text = self.mnemonic - self.mnemonicLabel.textColor = Colors.text + self.mnemonicLabel.themeTextColor = .textPrimary }, completion: nil) UIView.transition(with: seedReminderView.titleLabel, duration: 0.25, options: .transitionCrossDissolve, animations: { - let title = "Account Secured! 100%" - let attributedTitle = NSMutableAttributedString(string: title) - attributedTitle.addAttribute(.foregroundColor, value: Colors.accent, range: (title as NSString).range(of: "100%")) - self.seedReminderView.title = attributedTitle + ThemeManager.onThemeChange(observer: self.seedReminderView) { [weak self] _, primaryColor in + let title = "Account Secured! 100%" + let attributedTitle = NSMutableAttributedString(string: title) + attributedTitle.addAttribute( + .foregroundColor, + value: primaryColor.color, + range: (title as NSString).range(of: "100%") + ) + self?.seedReminderView.title = attributedTitle + } }, completion: nil) UIView.transition(with: seedReminderView.subtitleLabel, duration: 1, options: .transitionCrossDissolve, animations: { diff --git a/Session/Open Groups/JoinOpenGroupVC.swift b/Session/Open Groups/JoinOpenGroupVC.swift index 0257c7c63..9262857ee 100644 --- a/Session/Open Groups/JoinOpenGroupVC.swift +++ b/Session/Open Groups/JoinOpenGroupVC.swift @@ -1,12 +1,13 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import AVFoundation import GRDB import SessionUIKit import SessionMessagingKit import SessionUtilitiesKit -final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { +final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, QRScannerDelegate { private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private var pages: [UIViewController] = [] private var isJoining = false @@ -136,7 +137,7 @@ final class JoinOpenGroupVC: BaseVC, UIPageViewControllerDataSource, UIPageViewC dismiss(animated: true, completion: nil) } - func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { joinOpenGroup(with: string) } diff --git a/Session/Settings/BlockedContactsViewController.swift b/Session/Settings/BlockedContactsViewController.swift index 3496401c4..1732ad96f 100644 --- a/Session/Settings/BlockedContactsViewController.swift +++ b/Session/Settings/BlockedContactsViewController.swift @@ -38,7 +38,7 @@ class BlockedContactsViewController: BaseVC, UITableViewDelegate, UITableViewDat private lazy var tableView: UITableView = { let result: UITableView = UITableView() result.translatesAutoresizingMaskIntoConstraints = false - result.backgroundColor = .clear + result.themeBackgroundColor = .clear result.separatorStyle = .none result.register(view: BlockedContactCell.self) result.dataSource = self @@ -288,7 +288,7 @@ class BlockedContactsViewController: BaseVC, UITableViewDelegate, UITableViewDat switch section.model { case .loadMore: let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .medium) - loadingIndicator.tintColor = Colors.text + loadingIndicator.themeTintColor = .textPrimary loadingIndicator.alpha = 0.5 loadingIndicator.startAnimating() diff --git a/Session/Settings/QRCodeVC.swift b/Session/Settings/QRCodeVC.swift index 0561a1910..2a7b994de 100644 --- a/Session/Settings/QRCodeVC.swift +++ b/Session/Settings/QRCodeVC.swift @@ -1,11 +1,12 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import AVFoundation import Curve25519Kit import SessionUIKit import SessionUtilitiesKit -final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { +final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, QRScannerDelegate { private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) private var pages: [UIViewController] = [] private var targetVCIndex: Int? @@ -127,7 +128,7 @@ final class QRCodeVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControl dismiss(animated: true, completion: nil) } - func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) { let hexEncodedPublicKey = string startNewPrivateChatIfPossible(with: hexEncodedPublicKey) } @@ -200,12 +201,12 @@ private final class ViewMyQRCodeVC : UIViewController { ThemeManager.onThemeChange(observer: qrCodeImageView) { theme, _ in switch theme.interfaceStyle { case .light: - qrCodeImageView.tintColor = theme.colors[.textPrimary] - qrCodeImageViewBackgroundView.backgroundColor = nil + qrCodeImageView.themeTintColorForced = .theme(theme, color: .textPrimary) + qrCodeImageViewBackgroundView.themeBackgroundColorForced = nil default: - qrCodeImageView.tintColor = theme.colors[.backgroundPrimary] - qrCodeImageViewBackgroundView.backgroundColor = .white + qrCodeImageView.themeTintColorForced = .theme(theme, color: .backgroundPrimary) + qrCodeImageViewBackgroundView.themeBackgroundColorForced = .color(.white) } } diff --git a/Session/Settings/SettingsTableViewController.swift b/Session/Settings/SettingsTableViewController.swift index 9d7f0912f..0b079b033 100644 --- a/Session/Settings/SettingsTableViewController.swift +++ b/Session/Settings/SettingsTableViewController.swift @@ -29,7 +29,7 @@ class SettingsTableViewController? - - /** - Specifies the animation curve used in the scrolling motion of the labels. - Allowable options: - - - `UIViewAnimationOptionCurveEaseInOut` - - `UIViewAnimationOptionCurveEaseIn` - - `UIViewAnimationOptionCurveEaseOut` - - `UIViewAnimationOptionCurveLinear` - - Defaults to `UIViewAnimationOptionCurveEaseInOut`. - */ - open var animationCurve: UIView.AnimationCurve = .linear - - /** - A boolean property that sets whether the `MarqueeLabel` should behave like a normal `UILabel`. - - When set to `true` the `MarqueeLabel` will behave and look like a normal `UILabel`, and will not begin any scrolling animations. - Changes to this property take effect immediately, removing any in-flight animation as well as any edge fade. Note that `MarqueeLabel` - will respect the current values of the `lineBreakMode` and `textAlignment`properties while labelized. - - To simply prevent automatic scrolling, use the `holdScrolling` property. - - Defaults to `false`. - - - SeeAlso: holdScrolling - - SeeAlso: lineBreakMode - - Note: The label will not automatically scroll when this property is set to `true`. - - Warning: The UILabel default setting for the `lineBreakMode` property is `NSLineBreakByTruncatingTail`, which truncates - the text adds an ellipsis glyph (...). Set the `lineBreakMode` property to `NSLineBreakByClipping` in order to avoid the - ellipsis, especially if using an edge transparency fade. - */ - @IBInspectable open var labelize: Bool = false { - didSet { - if labelize != oldValue { - updateAndScroll() - } - } - } - - /** - A boolean property that sets whether the `MarqueeLabel` should hold (prevent) automatic label scrolling. - - When set to `true`, `MarqueeLabel` will not automatically scroll even its text is larger than the specified frame, - although the specified edge fades will remain. - - To set `MarqueeLabel` to act like a normal UILabel, use the `labelize` property. - - Defaults to `false`. - - - Note: The label will not automatically scroll when this property is set to `true`. - - SeeAlso: labelize - */ - @IBInspectable open var holdScrolling: Bool = false { - didSet { - if holdScrolling != oldValue { - if oldValue == true && !(awayFromHome || labelize || tapToScroll ) && labelShouldScroll() { - updateAndScroll(true) - } - } - } - } - - /** - A boolean property that sets whether the `MarqueeLabel` should only begin a scroll when tapped. - - If this property is set to `true`, the `MarqueeLabel` will only begin a scroll animation cycle when tapped. The label will - not automatically being a scroll. This setting overrides the setting of the `holdScrolling` property. - - Defaults to `false`. - - - Note: The label will not automatically scroll when this property is set to `false`. - - SeeAlso: holdScrolling - */ - @IBInspectable open var tapToScroll: Bool = false { - didSet { - if tapToScroll != oldValue { - if tapToScroll { - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(MarqueeLabel.labelWasTapped(_:))) - self.addGestureRecognizer(tapRecognizer) - isUserInteractionEnabled = true - } else { - if let recognizer = self.gestureRecognizers!.first as UIGestureRecognizer? { - self.removeGestureRecognizer(recognizer) - } - isUserInteractionEnabled = false - } - } - } - } - - /** - A read-only boolean property that indicates if the label's scroll animation has been paused. - - - SeeAlso: pauseLabel - - SeeAlso: unpauseLabel - */ - open var isPaused: Bool { - return (sublabel.layer.speed == 0.0) - } - - /** - A boolean property that indicates if the label is currently away from the home location. - - The "home" location is the traditional location of `UILabel` text. This property essentially reflects if a scroll animation is underway. - */ - open var awayFromHome: Bool { - if let presentationLayer = sublabel.layer.presentation() { - return !(presentationLayer.position.x == homeLabelFrame.origin.x) - } - - return false - } - - /** - The `MarqueeLabel` scrolling speed may be defined by one of two ways: - - Rate(CGFloat): The speed is defined by a rate of motion, in units of points per second. - - Duration(CGFloat): The speed is defined by the time to complete a scrolling animation cycle, in units of seconds. - - Each case takes an associated `CGFloat` value, which is the rate/duration desired. - */ - public enum SpeedLimit { - case rate(CGFloat) - case duration(CGFloat) - - var value: CGFloat { - switch self { - case .rate(let rate): - return rate - case .duration(let duration): - return duration - } - } - } - - /** - Defines the speed of the `MarqueeLabel` scrolling animation. - - The speed is set by specifying a case of the `SpeedLimit` enum along with an associated value. - - - SeeAlso: SpeedLimit - */ - open var speed: SpeedLimit = .duration(7.0) { - didSet { - switch (speed, oldValue) { - case (.rate(let a), .rate(let b)) where a == b: - return - case (.duration(let a), .duration(let b)) where a == b: - return - default: - updateAndScroll() - } - } - } - - @available(*, deprecated : 2.6, message : "Use speed property instead") - @IBInspectable open var scrollDuration: CGFloat { - get { - switch speed { - case .duration(let duration): return duration - case .rate: return 0.0 - } - } - set { - speed = .duration(newValue) - } - } - - @available(*, deprecated : 2.6, message : "Use speed property instead") - @IBInspectable open var scrollRate: CGFloat { - get { - switch speed { - case .duration: return 0.0 - case .rate(let rate): return rate - } - } - set { - speed = .rate(newValue) - } - } - - /** - A buffer (offset) between the leading edge of the label text and the label frame. - - This property adds additional space between the leading edge of the label text and the label frame. The - leading edge is the edge of the label text facing the direction of scroll (i.e. the edge that animates - offscreen first during scrolling). - - Defaults to `0`. - - - Note: The value set to this property affects label positioning at all times (including when `labelize` is set to `true`), - including when the text string length is short enough that the label does not need to scroll. - - Note: For Continuous-type labels, the smallest value of `leadingBuffer`, `trailingBuffer`, and `fadeLength` - is used as spacing between the two label instances. Zero is an allowable value for all three properties. - - - SeeAlso: trailingBuffer - */ - @IBInspectable open var leadingBuffer: CGFloat = 0.0 { - didSet { - if leadingBuffer != oldValue { - updateAndScroll() - } - } - } - - /** - A buffer (offset) between the trailing edge of the label text and the label frame. - - This property adds additional space (buffer) between the trailing edge of the label text and the label frame. The - trailing edge is the edge of the label text facing away from the direction of scroll (i.e. the edge that animates - offscreen last during scrolling). - - Defaults to `0`. - - - Note: The value set to this property has no effect when the `labelize` property is set to `true`. - - - Note: For Continuous-type labels, the smallest value of `leadingBuffer`, `trailingBuffer`, and `fadeLength` - is used as spacing between the two label instances. Zero is an allowable value for all three properties. - - - SeeAlso: leadingBuffer - */ - @IBInspectable open var trailingBuffer: CGFloat = 0.0 { - didSet { - if trailingBuffer != oldValue { - updateAndScroll() - } - } - } - - /** - The length of transparency fade at the left and right edges of the frame. - - This propery sets the size (in points) of the view edge transparency fades on the left and right edges of a `MarqueeLabel`. The - transparency fades from an alpha of 1.0 (fully visible) to 0.0 (fully transparent) over this distance. Values set to this property - will be sanitized to prevent a fade length greater than 1/2 of the frame width. - - Defaults to `0`. - */ - @IBInspectable open var fadeLength: CGFloat = 0.0 { - didSet { - if fadeLength != oldValue { - applyGradientMask(fadeLength, animated: true) - updateAndScroll() - } - } - } - - /** - The length of delay in seconds that the label pauses at the completion of a scroll. - */ - @IBInspectable open var animationDelay: CGFloat = 1.0 - - /** The read-only duration of the scroll animation (not including delay). - - The value of this property is calculated from the value set to the `speed` property. If a .duration value is - used to set the label animation speed, this value will be equivalent. - */ - private(set) public var animationDuration: CGFloat = 0.0 - - // - // MARK: - Class Functions and Helpers - // - - /** - Convenience method to restart all `MarqueeLabel` instances that have the specified view controller in their next responder chain. - - - Parameter controller: The view controller for which to restart all `MarqueeLabel` instances. - - - Warning: View controllers that appear with animation (such as from underneath a modal-style controller) can cause some `MarqueeLabel` text - position "jumping" when this method is used in `viewDidAppear` if scroll animations are already underway. Use this method inside `viewWillAppear:` - instead to avoid this problem. - - - Warning: This method may not function properly if passed the parent view controller when using view controller containment. - - - SeeAlso: restartLabel - - SeeAlso: controllerViewDidAppear: - - SeeAlso: controllerViewWillAppear: - */ - open class func restartLabelsOfController(_ controller: UIViewController) { - MarqueeLabel.notifyController(controller, message: .Restart) - } - - /** - Convenience method to restart all `MarqueeLabel` instances that have the specified view controller in their next responder chain. - - Alternative to `restartLabelsOfController`. This method is retained for backwards compatibility and future enhancements. - - - Parameter controller: The view controller that will appear. - - SeeAlso: restartLabel - - SeeAlso: controllerViewDidAppear - */ - open class func controllerViewWillAppear(_ controller: UIViewController) { - MarqueeLabel.restartLabelsOfController(controller) - } - - /** - Convenience method to restart all `MarqueeLabel` instances that have the specified view controller in their next responder chain. - - Alternative to `restartLabelsOfController`. This method is retained for backwards compatibility and future enhancements. - - - Parameter controller: The view controller that did appear. - - SeeAlso: restartLabel - - SeeAlso: controllerViewWillAppear - */ - open class func controllerViewDidAppear(_ controller: UIViewController) { - MarqueeLabel.restartLabelsOfController(controller) - } - - /** - Labelizes all `MarqueeLabel` instances that have the specified view controller in their next responder chain. - - The `labelize` property of all recognized `MarqueeLabel` instances will be set to `true`. - - - Parameter controller: The view controller for which all `MarqueeLabel` instances should be labelized. - - SeeAlso: labelize - */ - open class func controllerLabelsLabelize(_ controller: UIViewController) { - MarqueeLabel.notifyController(controller, message: .Labelize) - } - - /** - De-labelizes all `MarqueeLabel` instances that have the specified view controller in their next responder chain. - - The `labelize` property of all recognized `MarqueeLabel` instances will be set to `false`. - - - Parameter controller: The view controller for which all `MarqueeLabel` instances should be de-labelized. - - SeeAlso: labelize - */ - open class func controllerLabelsAnimate(_ controller: UIViewController) { - MarqueeLabel.notifyController(controller, message: .Animate) - } - - // - // MARK: - Initialization - // - - /** - Returns a newly initialized `MarqueeLabel` instance with the specified scroll rate and edge transparency fade length. - - - Parameter frame: A rectangle specifying the initial location and size of the view in its superview's coordinates. Text (for the given font, font size, etc.) that does not fit in this frame will automatically scroll. - - Parameter pixelsPerSec: A rate of scroll for the label scroll animation. Must be non-zero. Note that this will be the peak (mid-transition) rate for ease-type animation. - - Parameter fadeLength: A length of transparency fade at the left and right edges of the `MarqueeLabel` instance's frame. - - Returns: An initialized `MarqueeLabel` object or nil if the object couldn't be created. - - SeeAlso: fadeLength - */ - public init(frame: CGRect, rate: CGFloat, fadeLength fade: CGFloat) { - speed = .rate(rate) - fadeLength = CGFloat(min(fade, frame.size.width/2.0)) - super.init(frame: frame) - setup() - } - - /** - Returns a newly initialized `MarqueeLabel` instance with the specified scroll rate and edge transparency fade length. - - - Parameter frame: A rectangle specifying the initial location and size of the view in its superview's coordinates. Text (for the given font, font size, etc.) that does not fit in this frame will automatically scroll. - - Parameter scrollDuration: A scroll duration the label scroll animation. Must be non-zero. This will be the duration that the animation takes for one-half of the scroll cycle in the case of left-right and right-left marquee types, and for one loop of a continuous marquee type. - - Parameter fadeLength: A length of transparency fade at the left and right edges of the `MarqueeLabel` instance's frame. - - Returns: An initialized `MarqueeLabel` object or nil if the object couldn't be created. - - SeeAlso: fadeLength - */ - public init(frame: CGRect, duration: CGFloat, fadeLength fade: CGFloat) { - speed = .duration(duration) - fadeLength = CGFloat(min(fade, frame.size.width/2.0)) - super.init(frame: frame) - setup() - } - - required public init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setup() - } - - /** - Returns a newly initialized `MarqueeLabel` instance. - - The default scroll duration of 7.0 seconds and fade length of 0.0 are used. - - - Parameter frame: A rectangle specifying the initial location and size of the view in its superview's coordinates. Text (for the given font, font size, etc.) that does not fit in this frame will automatically scroll. - - Returns: An initialized `MarqueeLabel` object or nil if the object couldn't be created. - */ - convenience public override init(frame: CGRect) { - self.init(frame: frame, duration: 7.0, fadeLength: 0.0) - } - - private func setup() { - // Create sublabel - sublabel = UILabel(frame: self.bounds) - sublabel.tag = 700 - sublabel.layer.anchorPoint = CGPoint.zero - - // Add sublabel - addSubview(sublabel) - - // Configure self - super.clipsToBounds = true - super.numberOfLines = 1 - - // Add notification observers - // Custom class notifications - NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.restartForViewController(_:)), name: NSNotification.Name(rawValue: MarqueeKeys.Restart.rawValue), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.labelizeForController(_:)), name: NSNotification.Name(rawValue: MarqueeKeys.Labelize.rawValue), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.animateForController(_:)), name: NSNotification.Name(rawValue: MarqueeKeys.Animate.rawValue), object: nil) - // UIApplication state notifications - NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.restartLabel), name: NSNotification.Name.OWSApplicationDidBecomeActive, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.shutdownLabel), name: NSNotification.Name.OWSApplicationDidEnterBackground, object: nil) - } - - override open func awakeFromNib() { - super.awakeFromNib() - forwardPropertiesToSublabel() - } - - override open func prepareForInterfaceBuilder() { - super.prepareForInterfaceBuilder() - forwardPropertiesToSublabel() - } - - private func forwardPropertiesToSublabel() { - /* - Note that this method is currently ONLY called from awakeFromNib, i.e. when - text properties are set via a Storyboard. As the Storyboard/IB doesn't currently - support attributed strings, there's no need to "forward" the super attributedString value. - */ - - // Since we're a UILabel, we actually do implement all of UILabel's properties. - // We don't care about these values, we just want to forward them on to our sublabel. - let properties = ["baselineAdjustment", "enabled", "highlighted", "highlightedTextColor", - "minimumFontSize", "shadowOffset", "textAlignment", - "userInteractionEnabled", "adjustsFontSizeToFitWidth", - "lineBreakMode", "numberOfLines", "contentMode"] - - // Iterate through properties - sublabel.text = super.text - sublabel.font = super.font - sublabel.textColor = super.textColor - sublabel.backgroundColor = super.backgroundColor ?? UIColor.clear - sublabel.shadowColor = super.shadowColor - sublabel.shadowOffset = super.shadowOffset - for prop in properties { - let value = super.value(forKey: prop) - sublabel.setValue(value, forKeyPath: prop) - } - } - - // - // MARK: - MarqueeLabel Heavy Lifting - // - - override open func layoutSubviews() { - super.layoutSubviews() - - updateAndScroll(true) - } - - override open func willMove(toWindow newWindow: UIWindow?) { - if newWindow == nil { - shutdownLabel() - } - } - - override open func didMoveToWindow() { - if self.window == nil { - shutdownLabel() - } else { - updateAndScroll() - } - } - - private func updateAndScroll() { - updateAndScroll(true) - } - - private func updateAndScroll(_ shouldBeginScroll: Bool) { - // Check if scrolling can occur - if !labelReadyForScroll() { - return - } - - // Calculate expected size - let expectedLabelSize = sublabelSize() - - // Invalidate intrinsic size - invalidateIntrinsicContentSize() - - // Move label to home - returnLabelToHome() - - // Check if label should scroll - // Note that the holdScrolling propery does not affect this - if !labelShouldScroll() { - // Set text alignment and break mode to act like a normal label - sublabel.textAlignment = super.textAlignment - sublabel.lineBreakMode = super.lineBreakMode - - let labelFrame: CGRect - switch type { - case .continuousReverse, .rightLeft: - labelFrame = bounds.divided(atDistance: leadingBuffer, from: CGRectEdge.maxXEdge).remainder.integral - default: - labelFrame = CGRect(x: leadingBuffer, y: 0.0, width: bounds.size.width - leadingBuffer, height: bounds.size.height).integral - } - - homeLabelFrame = labelFrame - awayOffset = 0.0 - - // Remove an additional sublabels (for continuous types) - repliLayer?.instanceCount = 1 - - // Set the sublabel frame to calculated labelFrame - sublabel.frame = labelFrame - - // Remove fade, as by definition none is needed in this case - removeGradientMask() - - return - } - - // Label DOES need to scroll - - // Recompute the animation duration - animationDuration = { - switch self.speed { - case .rate(let rate): - return CGFloat(abs(self.awayOffset) / rate) - case .duration(let duration): - return duration - } - }() - - // Spacing between primary and second sublabel must be at least equal to leadingBuffer, and at least equal to the fadeLength - let minTrailing = max(max(leadingBuffer, trailingBuffer), fadeLength) - - // Determine positions and generate scroll steps - let sequence: [MarqueeStep] - - switch type { - case .continuous, .continuousReverse: - if (type == .continuous) { - homeLabelFrame = CGRect(x: leadingBuffer, y: 0.0, width: expectedLabelSize.width, height: bounds.size.height).integral - awayOffset = -(homeLabelFrame.size.width + minTrailing) - } else { // .ContinuousReverse - homeLabelFrame = CGRect(x: bounds.size.width - (expectedLabelSize.width + leadingBuffer), y: 0.0, width: expectedLabelSize.width, height: bounds.size.height).integral - awayOffset = (homeLabelFrame.size.width + minTrailing) - } - - // Find when the lead label will be totally offscreen - let offsetDistance = awayOffset - let offscreenAmount = homeLabelFrame.size.width - let startFadeFraction = abs(offscreenAmount / offsetDistance) - // Find when the animation will hit that point - let startFadeTimeFraction = timingFunctionForAnimationCurve(animationCurve).durationPercentageForPositionPercentage(startFadeFraction, duration: (animationDelay + animationDuration)) - let startFadeTime = startFadeTimeFraction * animationDuration - - sequence = scrollSequence ?? [ - ScrollStep(timeStep: 0.0, position: .home, edgeFades: .trailing), // Starting point, at home, with trailing fade - ScrollStep(timeStep: animationDelay, position: .home, edgeFades: .trailing), // Delay at home, maintaining fade state - FadeStep(timeStep: 0.2, edgeFades: [.leading, .trailing]), // 0.2 sec after scroll start, fade leading edge in as well - FadeStep(timeStep: (startFadeTime - animationDuration), // Maintain fade state until just before reaching end of scroll animation - edgeFades: [.leading, .trailing]), - ScrollStep(timeStep: animationDuration, timingFunction: animationCurve, // Ending point (back at home), with animationCurve transition, with trailing fade - position: .away, edgeFades: .trailing) - ] - - // Set frame and text - sublabel.frame = homeLabelFrame - - // Configure replication - repliLayer?.instanceCount = 2 - repliLayer?.instanceTransform = CATransform3DMakeTranslation(-awayOffset, 0.0, 0.0) - - case .leftRight, .left, .rightLeft, .right: - if (type == .leftRight || type == .left) { - homeLabelFrame = CGRect(x: leadingBuffer, y: 0.0, width: expectedLabelSize.width, height: bounds.size.height).integral - awayOffset = bounds.size.width - (expectedLabelSize.width + leadingBuffer + trailingBuffer) - // Enforce text alignment for this type - sublabel.textAlignment = NSTextAlignment.left - } else { - homeLabelFrame = CGRect(x: bounds.size.width - (expectedLabelSize.width + leadingBuffer), y: 0.0, width: expectedLabelSize.width, height: bounds.size.height).integral - awayOffset = (expectedLabelSize.width + trailingBuffer + leadingBuffer) - bounds.size.width - // Enforce text alignment for this type - sublabel.textAlignment = NSTextAlignment.right - } - // Set frame and text - sublabel.frame = homeLabelFrame - - // Remove any replication - repliLayer?.instanceCount = 1 - - if (type == .leftRight || type == .rightLeft) { - sequence = scrollSequence ?? [ - ScrollStep(timeStep: 0.0, position: .home, edgeFades: .trailing), // Starting point, at home, with trailing fade - ScrollStep(timeStep: animationDelay, position: .home, edgeFades: .trailing), // Delay at home, maintaining fade state - FadeStep(timeStep: 0.2, edgeFades: [.leading, .trailing]), // 0.2 sec after delay ends, fade leading edge in as well - FadeStep(timeStep: -0.2, edgeFades: [.leading, .trailing]), // Maintain fade state until 0.2 sec before reaching away position - ScrollStep(timeStep: animationDuration, timingFunction: animationCurve, // Away position, using animationCurve transition, with only leading edge faded in - position: .away, edgeFades: .leading), - ScrollStep(timeStep: animationDelay, position: .away, edgeFades: .leading), // Delay at away, maintaining fade state (leading only) - FadeStep(timeStep: 0.2, edgeFades: [.leading, .trailing]), // 0.2 sec after delay ends, fade trailing edge back in as well - FadeStep(timeStep: -0.2, edgeFades: [.leading, .trailing]), // Maintain fade state until 0.2 sec before reaching home position - ScrollStep(timeStep: animationDuration, timingFunction: animationCurve, // Ending point, back at home, with only trailing fade - position: .home, edgeFades: .trailing) - ] - } else { // .left or .right - sequence = scrollSequence ?? [ - ScrollStep(timeStep: 0.0, position: .home, edgeFades: .trailing), // Starting point, at home, with trailing fade - ScrollStep(timeStep: animationDelay, position: .home, edgeFades: .trailing), // Delay at home, maintaining fade state - FadeStep(timeStep: 0.2, edgeFades: [.leading, .trailing]), // 0.2 sec after delay ends, fade leading edge in as well - FadeStep(timeStep: -0.2, edgeFades: [.leading, .trailing]), // Maintain fade state until 0.2 sec before reaching away position - ScrollStep(timeStep: animationDuration, timingFunction: animationCurve, // Away position, using animationCurve transition, with only leading edge faded in - position: .away, edgeFades: .leading), - ScrollStep(timeStep: 60*60*24*365.0, // "Delay" at away, for huge time to effectie stay at away permanently - position: .away, edgeFades: .leading) - ] - } - } - - // Configure gradient for current condition - applyGradientMask(fadeLength, animated: !self.labelize) - - if !tapToScroll && !holdScrolling && shouldBeginScroll { - beginScroll(sequence) - } - } - - private func sublabelSize() -> CGSize { - // Bound the expected size - let maximumLabelSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) - // Calculate the expected size - var expectedLabelSize = sublabel.sizeThatFits(maximumLabelSize) - - #if os(tvOS) - // Sanitize width to 16384.0 (largest width a UILabel will draw on tvOS) - expectedLabelSize.width = min(expectedLabelSize.width, 16384.0) - #else - // Sanitize width to 5461.0 (largest width a UILabel will draw on an iPhone 6S Plus) - expectedLabelSize.width = min(expectedLabelSize.width, 5461.0) - #endif - - // Adjust to own height (make text baseline match normal label) - expectedLabelSize.height = bounds.size.height - return expectedLabelSize - } - - override open func sizeThatFits(_ size: CGSize) -> CGSize { - var fitSize = sublabel.sizeThatFits(size) - fitSize.width += leadingBuffer - return fitSize - } - - // - // MARK: - Animation Handling - // - - open func labelShouldScroll() -> Bool { - // Check for nil string - if sublabel.text == nil { - return false - } - - // Check for empty string - if sublabel.text!.isEmpty { - return false - } - - // Check if the label string fits - let labelTooLarge = (sublabelSize().width + leadingBuffer) > self.bounds.size.width + CGFloat.ulpOfOne - let animationHasDuration = speed.value > 0.0 - return (!labelize && labelTooLarge && animationHasDuration) - } - - private func labelReadyForScroll() -> Bool { - // Check if we have a superview - if superview == nil { - return false - } - - // Check if we are attached to a window - if window == nil { - return false - } - - // Check if our view controller is ready - let viewController = firstAvailableViewController() - if viewController != nil { - if !viewController!.isViewLoaded { - return false - } - } - - return true - } - - private func returnLabelToHome() { - // Remove any gradient animation - maskLayer?.removeAllAnimations() - - // Remove all sublabel position animations - sublabel.layer.removeAllAnimations() - - // Remove completion block - scrollCompletionBlock = nil - } - - private func beginScroll(_ sequence: [MarqueeStep]) { - let scroller = generateScrollAnimation(sequence) - let fader = generateGradientAnimation(sequence, totalDuration: scroller.duration) - - scroll(scroller, fader: fader) - } - - private func scroll(_ scroller: MLAnimation, fader: MLAnimation?) { - // Check for conditions which would prevent scrolling - if !labelReadyForScroll() { - return - } - // Convert fader to var - var fader = fader - - // Call pre-animation hook - labelWillBeginScroll() - - // Start animation transactions - CATransaction.begin() - CATransaction.setAnimationDuration(TimeInterval(scroller.duration)) - - // Create gradient animation, if needed - let gradientAnimation: CAKeyframeAnimation? - // Check for IBDesignable - #if !TARGET_INTERFACE_BUILDER - if fadeLength > 0.0 { - // Remove any setup animation, but apply final values - if let setupAnim = maskLayer?.animation(forKey: "setupFade") as? CABasicAnimation, let finalColors = setupAnim.toValue as? [CGColor] { - maskLayer?.colors = finalColors - } - maskLayer?.removeAnimation(forKey: "setupFade") - - // Generate animation if needed - if let previousAnimation = fader?.anim { - gradientAnimation = previousAnimation - } else { - gradientAnimation = nil - } - - // Apply fade animation - maskLayer?.add(gradientAnimation!, forKey: "gradient") - } else { - // No animation needed - fader = nil - } - #else - fader = nil - #endif - - scrollCompletionBlock = { [weak self] (finished: Bool) -> Void in - guard finished else { - // Do not continue into the next loop - return - } - - guard (self != nil) else { - return - } - - // Call returned home function - self!.labelReturnedToHome(true) - - // Check to ensure that: - // 1) We don't double fire if an animation already exists - // 2) The instance is still attached to a window - this completion block is called for - // many reasons, including if the animation is removed due to the view being removed - // from the UIWindow (typically when the view controller is no longer the "top" view) - guard self!.window != nil else { - return - } - - guard self!.sublabel.layer.animation(forKey: "position") == nil else { - return - } - - // Begin again, if conditions met - if (self!.labelShouldScroll() && !self!.tapToScroll && !self!.holdScrolling) { - // Perform completion callback - self!.scroll(scroller, fader: fader) - } - } - - // Perform scroll animation - scroller.anim.setValue(true, forKey: MarqueeKeys.CompletionClosure.rawValue) - scroller.anim.delegate = self - sublabel.layer.add(scroller.anim, forKey: "position") - - CATransaction.commit() - } - - private func generateScrollAnimation(_ sequence: [MarqueeStep]) -> MLAnimation { - // Create scroller, which defines the animation to perform - let homeOrigin = homeLabelFrame.origin - let awayOrigin = offsetCGPoint(homeLabelFrame.origin, offset: awayOffset) - - let scrollSteps = sequence.filter({ $0 is ScrollStep }) as! [ScrollStep] - let totalDuration = scrollSteps.reduce(0.0) { $0 + $1.timeStep } - - // Build scroll data - var totalTime: CGFloat = 0.0 - var scrollKeyTimes = [NSNumber]() - var scrollKeyValues = [NSValue]() - var scrollTimingFunctions = [CAMediaTimingFunction]() - - for (offset, step) in scrollSteps.enumerated() { - // Scroll Times - totalTime += step.timeStep - scrollKeyTimes.append(NSNumber(value: Float(totalTime/totalDuration))) - - // Scroll Values - let scrollPosition: CGPoint - switch step.position { - case .home: - scrollPosition = homeOrigin - case .away: - scrollPosition = awayOrigin - case .partial(let frac): - scrollPosition = offsetCGPoint(homeOrigin, offset: awayOffset*frac) - } - scrollKeyValues.append(NSValue(cgPoint: scrollPosition)) - - // Scroll Timing Functions - // Only need n-1 timing functions, so discard the first value as it's unused - if offset == 0 { continue } - scrollTimingFunctions.append(timingFunctionForAnimationCurve(step.timingFunction)) - } - - // Create animation - let animation = CAKeyframeAnimation(keyPath: "position") - // Set values - animation.keyTimes = scrollKeyTimes - animation.values = scrollKeyValues - animation.timingFunctions = scrollTimingFunctions - - return (anim: animation, duration: totalDuration) - } - - private func generateGradientAnimation(_ sequence: [MarqueeStep], totalDuration: CGFloat) -> MLAnimation { - // Setup - var totalTime: CGFloat = 0.0 - var stepTime: CGFloat = 0.0 - var fadeKeyValues = [[CGColor]]() - var fadeKeyTimes = [NSNumber]() - var fadeTimingFunctions = [CAMediaTimingFunction]() - let transp = UIColor.clear.cgColor - let opaque = UIColor.black.cgColor - - // Filter to get only scroll steps and valid precedent/subsequent fade steps - let fadeSteps = sequence.enumerated().filter { (arg: (offset: Int, element: MarqueeStep)) -> Bool in - let (offset, element) = arg - - // Include all Scroll Steps - if element is ScrollStep { return true } - - // Include all Fade Steps that have a directly preceding or subsequent Scroll Step - // Exception: Fade Step cannot be first step - if offset == 0 { return false } - - // Subsequent step if 1) positive/zero time step and 2) follows a Scroll Step - let subsequent = element.timeStep >= 0 && (sequence[max(0, offset - 1)] is ScrollStep) - // Precedent step if 1) negative time step and 2) precedes a Scroll Step - let precedent = element.timeStep < 0 && (sequence[min(sequence.count - 1, offset + 1)] is ScrollStep) - - return (precedent || subsequent) - } - - for (offset, step) in fadeSteps { - // Fade times - if (step is ScrollStep) { - totalTime += step.timeStep - stepTime = totalTime - } else { - if step.timeStep >= 0 { - // Is a Subsequent - stepTime = totalTime + step.timeStep - } else { - // Is a Precedent, grab next step - stepTime = totalTime + fadeSteps[offset + 1].element.timeStep + step.timeStep - } - } - fadeKeyTimes.append(NSNumber(value: Float(stepTime/totalDuration))) - - // Fade Values - let values: [CGColor] - let leading = step.edgeFades.contains(.leading) ? transp : opaque - let trailing = step.edgeFades.contains(.trailing) ? transp : opaque - switch type { - case .leftRight, .left, .continuous: - values = [leading, opaque, opaque, trailing] - case .rightLeft, .right, .continuousReverse: - values = [trailing, opaque, opaque, leading] - } - fadeKeyValues.append(values) - - // Fade Timing Function - // Only need n-1 timing functions, so discard the first value as it's unused - if offset == 0 { continue } - fadeTimingFunctions.append(timingFunctionForAnimationCurve(step.timingFunction)) - } - - // Create new animation - let animation = CAKeyframeAnimation(keyPath: "colors") - - animation.values = fadeKeyValues - animation.keyTimes = fadeKeyTimes - animation.timingFunctions = fadeTimingFunctions - - return (anim: animation, duration: max(totalTime, totalDuration)) - } - - private func applyGradientMask(_ fadeLength: CGFloat, animated: Bool, firstStep: MarqueeStep? = nil) { - // Remove any in-flight animations - maskLayer?.removeAllAnimations() - - // Check for zero-length fade - if (fadeLength <= 0.0) { - removeGradientMask() - return - } - - // Configure gradient mask without implicit animations - CATransaction.begin() - CATransaction.setDisableActions(true) - - // Determine if gradient mask needs to be created - let gradientMask: CAGradientLayer - if let currentMask = self.maskLayer { - // Mask layer already configured - gradientMask = currentMask - } else { - // No mask exists, create new mask - gradientMask = CAGradientLayer() - gradientMask.shouldRasterize = true - gradientMask.rasterizationScale = UIScreen.main.scale - gradientMask.startPoint = CGPoint(x: 0.0, y: 0.5) - gradientMask.endPoint = CGPoint(x: 1.0, y: 0.5) - } - - // Check if there is a mask to layer size mismatch - if gradientMask.bounds != self.layer.bounds { - // Adjust stops based on fade length - let leftFadeStop = fadeLength/self.bounds.size.width - let rightFadeStop = 1.0 - fadeLength/self.bounds.size.width - gradientMask.locations = [0.0, leftFadeStop, rightFadeStop, 1.0].map { NSNumber(value: Float($0)) } - } - - gradientMask.bounds = self.layer.bounds - gradientMask.position = CGPoint(x: self.bounds.midX, y: self.bounds.midY) - - // Set up colors - let transparent = UIColor.clear.cgColor - let opaque = UIColor.black.cgColor - - // Set mask - self.layer.mask = gradientMask - - // Determine colors for non-scrolling label (i.e. at home) - let adjustedColors: [CGColor] - let trailingFadeNeeded = self.labelShouldScroll() - - switch type { - case .continuousReverse, .rightLeft: - adjustedColors = [(trailingFadeNeeded ? transparent : opaque), opaque, opaque, opaque] - - // .Continuous, .LeftRight - default: - adjustedColors = [opaque, opaque, opaque, (trailingFadeNeeded ? transparent : opaque)] - } - - // Check for IBDesignable - #if TARGET_INTERFACE_BUILDER - gradientMask.colors = adjustedColors - CATransaction.commit() - return - #endif - - if (animated) { - // Finish transaction - CATransaction.commit() - - // Create animation for color change - let colorAnimation = GradientSetupAnimation(keyPath: "colors") - colorAnimation.fromValue = gradientMask.colors - colorAnimation.toValue = adjustedColors - colorAnimation.fillMode = CAMediaTimingFillMode.forwards - colorAnimation.isRemovedOnCompletion = false - colorAnimation.delegate = self - gradientMask.add(colorAnimation, forKey: "setupFade") - } else { - gradientMask.colors = adjustedColors - CATransaction.commit() - } - } - - private func removeGradientMask() { - self.layer.mask = nil - } - - private func timingFunctionForAnimationCurve(_ curve: UIView.AnimationCurve) -> CAMediaTimingFunction { - let timingFunction: CAMediaTimingFunctionName - switch curve { - case .easeIn: - timingFunction = .easeIn - case .easeInOut: - timingFunction = .easeInEaseOut - case .easeOut: - timingFunction = .easeOut - default: - timingFunction = .linear - } - - return CAMediaTimingFunction(name: timingFunction) - } - - private func transactionDurationType(_ labelType: MarqueeType, interval: CGFloat, delay: CGFloat) -> TimeInterval { - switch labelType { - case .leftRight, .rightLeft: - return TimeInterval(2.0 * (delay + interval)) - default: - return TimeInterval(delay + interval) - } - } - - public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { - if let setupAnim = anim as? GradientSetupAnimation { - if let finalColors = setupAnim.toValue as? [CGColor] { - maskLayer?.colors = finalColors - } - // Remove regardless, since we set removeOnCompletion = false - maskLayer?.removeAnimation(forKey: "setupFade") - } else { - scrollCompletionBlock?(flag) - } - } - - // - // MARK: - Private details - // - - private var sublabel = UILabel() - - fileprivate var homeLabelFrame = CGRect.zero - fileprivate var awayOffset: CGFloat = 0.0 - - override open class var layerClass: AnyClass { - return CAReplicatorLayer.self - } - - fileprivate var repliLayer: CAReplicatorLayer? { - return self.layer as? CAReplicatorLayer - } - - fileprivate var maskLayer: CAGradientLayer? { - return self.layer.mask as! CAGradientLayer? - } - - fileprivate var scrollCompletionBlock: MLAnimationCompletionBlock? - - override open func draw(_ layer: CALayer, in ctx: CGContext) { - // Do NOT call super, to prevent UILabel superclass from drawing into context - // Label drawing is handled by sublabel and CAReplicatorLayer layer class - - // Draw only background color - if let bgColor = backgroundColor { - ctx.setFillColor(bgColor.cgColor) - ctx.fill(layer.bounds) - } - } - - fileprivate enum MarqueeKeys: String { - case Restart = "MLViewControllerRestart" - case Labelize = "MLShouldLabelize" - case Animate = "MLShouldAnimate" - case CompletionClosure = "MLAnimationCompletion" - } - - class fileprivate func notifyController(_ controller: UIViewController, message: MarqueeKeys) { - NotificationCenter.default.post(name: Notification.Name(rawValue: message.rawValue), object: nil, userInfo: ["controller": controller]) - } - - @objc public func restartForViewController(_ notification: Notification) { - if let controller = (notification as NSNotification).userInfo?["controller"] as? UIViewController { - if controller === self.firstAvailableViewController() { - self.restartLabel() - } - } - } - - @objc public func labelizeForController(_ notification: Notification) { - if let controller = (notification as NSNotification).userInfo?["controller"] as? UIViewController { - if controller === self.firstAvailableViewController() { - self.labelize = true - } - } - } - - @objc public func animateForController(_ notification: Notification) { - if let controller = (notification as NSNotification).userInfo?["controller"] as? UIViewController { - if controller === self.firstAvailableViewController() { - self.labelize = false - } - } - } - - // - // MARK: - Label Control - // - - /** - Overrides any non-size condition which is preventing the receiver from automatically scrolling, and begins a scroll animation. - - Currently the only non-size conditions which can prevent a label from scrolling are the `tapToScroll` and `holdScrolling` properties. This - method will not force a label with a string that fits inside the label bounds (i.e. that would not automatically scroll) to begin a scroll - animation. - - Upon the completion of the first forced scroll animation, the receiver will not automatically continue to scroll unless the conditions - preventing scrolling have been removed. - - - Note: This method has no effect if called during an already in-flight scroll animation. - - - SeeAlso: restartLabel - */ - public func triggerScrollStart() { - if labelShouldScroll() && !awayFromHome { - updateAndScroll() - } - } - - /** - Immediately resets the label to the home position, cancelling any in-flight scroll animation, and restarts the scroll animation if the appropriate conditions are met. - - - SeeAlso: resetLabel - - SeeAlso: triggerScrollStart - */ - @objc public func restartLabel() { - // Shutdown the label - shutdownLabel() - // Restart scrolling if appropriate - if labelShouldScroll() && !tapToScroll && !holdScrolling { - updateAndScroll() - } - } - - /** - Resets the label text, recalculating the scroll animation. - - The text is immediately returned to the home position, and the scroll animation positions are cleared. Scrolling will not resume automatically after - a call to this method. To re-initiate scrolling, use either a call to `restartLabel` or make a change to a UILabel property such as text, bounds/frame, - font, font size, etc. - - - SeeAlso: restartLabel - */ - public func resetLabel() { - returnLabelToHome() - homeLabelFrame = CGRect.null - awayOffset = 0.0 - } - - /** - Immediately resets the label to the home position, cancelling any in-flight scroll animation. - - The text is immediately returned to the home position. Scrolling will not resume automatically after a call to this method. - To re-initiate scrolling use a call to `restartLabel` or `triggerScrollStart`, or make a change to a UILabel property such as text, bounds/frame, - font, font size, etc. - - - SeeAlso: restartLabel - - SeeAlso: triggerScrollStart - */ - @objc public func shutdownLabel() { - // Bring label to home location - returnLabelToHome() - // Apply gradient mask for home location - applyGradientMask(fadeLength, animated: false) - } - - /** - Pauses the text scrolling animation, at any point during an in-progress animation. - - - Note: This method has no effect if a scroll animation is NOT already in progress. To prevent automatic scrolling on a newly-initialized label prior to its presentation onscreen, see the `holdScrolling` property. - - - SeeAlso: holdScrolling - - SeeAlso: unpauseLabel - */ - public func pauseLabel() { - // Prevent pausing label while not in scrolling animation, or when already paused - guard (!isPaused && awayFromHome) else { - return - } - - // Pause sublabel position animations - let labelPauseTime = sublabel.layer.convertTime(CACurrentMediaTime(), from: nil) - sublabel.layer.speed = 0.0 - sublabel.layer.timeOffset = labelPauseTime - - // Pause gradient fade animation - let gradientPauseTime = maskLayer?.convertTime(CACurrentMediaTime(), from: nil) - maskLayer?.speed = 0.0 - maskLayer?.timeOffset = gradientPauseTime! - } - - /** - Un-pauses a previously paused text scrolling animation. This method has no effect if the label was not previously paused using `pauseLabel`. - - - SeeAlso: pauseLabel - */ - public func unpauseLabel() { - // Only unpause if label was previously paused - guard (isPaused) else { - return - } - - // Unpause sublabel position animations - let labelPausedTime = sublabel.layer.timeOffset - sublabel.layer.speed = 1.0 - sublabel.layer.timeOffset = 0.0 - sublabel.layer.beginTime = 0.0 - sublabel.layer.beginTime = sublabel.layer.convertTime(CACurrentMediaTime(), from: nil) - labelPausedTime - - // Unpause gradient fade animation - let gradientPauseTime = maskLayer?.timeOffset - maskLayer?.speed = 1.0 - maskLayer?.timeOffset = 0.0 - maskLayer?.beginTime = 0.0 - maskLayer?.beginTime = maskLayer!.convertTime(CACurrentMediaTime(), from: nil) - gradientPauseTime! - } - - @objc public func labelWasTapped(_ recognizer: UIGestureRecognizer) { - if labelShouldScroll() && !awayFromHome { - updateAndScroll() - } - } - - /** - Called when the label animation is about to begin. - - The default implementation of this method does nothing. Subclasses may override this method in order to perform any custom actions just as - the label animation begins. This is only called in the event that the conditions for scrolling to begin are met. - */ - open func labelWillBeginScroll() { - // Default implementation does nothing - override to customize - return - } - - /** - Called when the label animation has finished, and the label is at the home position. - - The default implementation of this method does nothing. Subclasses may override this method in order to perform any custom actions jas as - the label animation completes, and before the next animation would begin (assuming the scroll conditions are met). - - - Parameter finished: A Boolean that indicates whether or not the scroll animation actually finished before the completion handler was called. - - - Warning: This method will be called, and the `finished` parameter will be `NO`, when any property changes are made that would cause the label - scrolling to be automatically reset. This includes changes to label text and font/font size changes. - */ - open func labelReturnedToHome(_ finished: Bool) { - // Default implementation does nothing - override to customize - return - } - - // - // MARK: - Modified UILabel Functions/Getters/Setters - // - - #if os(iOS) - override open func forBaselineLayout() -> UIView { - // Use subLabel view for handling baseline layouts - return sublabel - } - - override open var forLastBaselineLayout: UIView { - // Use subLabel view for handling baseline layouts - return sublabel - } - #endif - - override open var text: String? { - get { - return sublabel.text - } - - set { - if sublabel.text == newValue { - return - } - sublabel.text = newValue - updateAndScroll() - super.text = text - } - } - - override open var attributedText: NSAttributedString? { - get { - return sublabel.attributedText - } - - set { - if sublabel.attributedText == newValue { - return - } - sublabel.attributedText = newValue - updateAndScroll() - super.attributedText = attributedText - } - } - - override open var font: UIFont! { - get { - return sublabel.font - } - - set { - if sublabel.font == newValue { - return - } - sublabel.font = newValue - super.font = newValue - - updateAndScroll() - } - } - - override open var textColor: UIColor! { - get { - return sublabel.textColor - } - - set { - sublabel.textColor = newValue - super.textColor = newValue - } - } - - override open var backgroundColor: UIColor? { - get { - return sublabel.backgroundColor - } - - set { - sublabel.backgroundColor = newValue - super.backgroundColor = newValue - } - } - - override open var shadowColor: UIColor? { - get { - return sublabel.shadowColor - } - - set { - sublabel.shadowColor = newValue - super.shadowColor = newValue - } - } - - override open var shadowOffset: CGSize { - get { - return sublabel.shadowOffset - } - - set { - sublabel.shadowOffset = newValue - super.shadowOffset = newValue - } - } - - override open var highlightedTextColor: UIColor? { - get { - return sublabel.highlightedTextColor - } - - set { - sublabel.highlightedTextColor = newValue - super.highlightedTextColor = newValue - } - } - - override open var isHighlighted: Bool { - get { - return sublabel.isHighlighted - } - - set { - sublabel.isHighlighted = newValue - super.isHighlighted = newValue - } - } - - override open var isEnabled: Bool { - get { - return sublabel.isEnabled - } - - set { - sublabel.isEnabled = newValue - super.isEnabled = newValue - } - } - - override open var numberOfLines: Int { - get { - return super.numberOfLines - } - - set { - // By the nature of MarqueeLabel, this is 1 - super.numberOfLines = 1 - } - } - - override open var adjustsFontSizeToFitWidth: Bool { - get { - return super.adjustsFontSizeToFitWidth - } - - set { - // By the nature of MarqueeLabel, this is false - super.adjustsFontSizeToFitWidth = false - } - } - - override open var minimumScaleFactor: CGFloat { - get { - return super.minimumScaleFactor - } - - set { - super.minimumScaleFactor = 0.0 - } - } - - override open var baselineAdjustment: UIBaselineAdjustment { - get { - return sublabel.baselineAdjustment - } - - set { - sublabel.baselineAdjustment = newValue - super.baselineAdjustment = newValue - } - } - - override open var intrinsicContentSize: CGSize { - var content = sublabel.intrinsicContentSize - content.width += leadingBuffer - return content - } - - override open var tintColor: UIColor! { - get { - return sublabel.tintColor - } - - set { - sublabel.tintColor = newValue - super.tintColor = newValue - } - } - - override open func tintColorDidChange() { - super.tintColorDidChange() - sublabel.tintColorDidChange() - } - - override open var contentMode: UIView.ContentMode { - get { - return sublabel.contentMode - } - - set { - super.contentMode = contentMode - sublabel.contentMode = newValue - } - } - - // - // MARK: - Support - // - - fileprivate func offsetCGPoint(_ point: CGPoint, offset: CGFloat) -> CGPoint { - return CGPoint(x: point.x + offset, y: point.y) - } - - // - // MARK: - Deinit - // - - deinit { - NotificationCenter.default.removeObserver(self) - } - -} - -// -// MARK: - Support -// -public protocol MarqueeStep { - var timeStep: CGFloat { get } - var timingFunction: UIView.AnimationCurve { get } - var edgeFades: EdgeFade { get } -} - -/** - `ScrollStep` types define the label position at a specified time delta since the last `ScrollStep` step, as well as - the animation curve to that position and edge fade state at the position - */ -public struct ScrollStep: MarqueeStep { - /** - An enum that provides the possible positions defined by a ScrollStep - - `home`: The starting, default position of the label - - `away`: The calculated position that results in the entirety of the label scrolling past. - - `partial(CGFloat)`: A fractional value, specified by the associated CGFloat value, between the `home` and `away` positions (must be between 0.0 and 1.0). - - The `away` position depends on the MarqueeLabel `type` value. - - For `left`, `leftRight`, `right`, and `rightLeft` types, the `away` position means the trailing edge of the label - is visible. For `leftRight` and `rightLeft` default types, the scroll animation reverses direction after reaching - this point and returns to the `home` position. - - For `continuous` and `continuousReverse` types, the `away` position is the location such that if the scroll is completed - at this point (i.e. the animation is removed), there will be no visible change in the label appearance. - */ - public enum Position { - case home - case away - case partial(CGFloat) - } - - /** - The desired time between this step and the previous `ScrollStep` in a sequence. - */ - public let timeStep: CGFloat - - /** - The animation curve to utilize between the previous `ScrollStep` in a sequence and this step. - - - Note: The animation curve value for the first `ScrollStep` in a sequence has no effect. - */ - public let timingFunction: UIView.AnimationCurve - - /** - The position of the label for this scroll step. - - SeeAlso: Position - */ - public let position: Position - - /** - The option set defining the edge fade state for this scroll step. - - Possible options include `.leading` and `.trailing`, corresponding to the leading edge of the label scrolling (i.e. - the direction of scroll) and trailing edge of the label. - */ - public let edgeFades: EdgeFade - - public init(timeStep: CGFloat, timingFunction: UIView.AnimationCurve = .linear, position: Position, edgeFades: EdgeFade) { - self.timeStep = timeStep - self.position = position - self.edgeFades = edgeFades - self.timingFunction = timingFunction - } -} - -/** - `FadeStep` types allow additional edge fade state definitions, around the states defined by the `ScrollStep` steps of - a sequence. `FadeStep` steps are defined by the time delta to the preceding or subsequent `ScrollStep` step and the timing - function to their edge fade state. - - - Note: A `FadeStep` cannot be the first step in a sequence. A `FadeStep` defined as such will be ignored. - */ -public struct FadeStep: MarqueeStep { - /** - The desired time between this `FadeStep` and the preceding or subsequent `ScrollStep` in a sequence. - - `FadeSteps` with a negative `timeStep` value will be associated _only_ with an immediately-subsequent `ScrollStep` step - in the sequence. - - `FadeSteps` with a positive `timeStep` value will be associated _only_ with an immediately-prior `ScrollStep` step in the - sequence. - - - Note: A `FadeStep` with a `timeStep` value of 0.0 will have no effect, and is the same as defining the fade state with - a `ScrollStep`. - */ - public let timeStep: CGFloat - - /** - The animation curve to utilize between the previous fade state in a sequence and this step. - */ - public let timingFunction: UIView.AnimationCurve - - /** - The option set defining the edge fade state for this fade step. - - Possible options include `.leading` and `.trailing`, corresponding to the leading edge of the label scrolling (i.e. - the direction of scroll) and trailing edge of the label. - - As an Option Set type, both edge fade states may be defined using an array literal: `[.leading, .trailing]`. - */ - public let edgeFades: EdgeFade - - public init(timeStep: CGFloat, timingFunction: UIView.AnimationCurve = .linear, edgeFades: EdgeFade) { - self.timeStep = timeStep - self.timingFunction = timingFunction - self.edgeFades = edgeFades - } -} - -public struct EdgeFade: OptionSet { - public let rawValue: Int - public static let leading = EdgeFade(rawValue: 1 << 0) - public static let trailing = EdgeFade(rawValue: 1 << 1) - - public init(rawValue: Int) { - self.rawValue = rawValue - } -} - -// Define helpful typealiases -private typealias MLAnimationCompletionBlock = (_ finished: Bool) -> Void -private typealias MLAnimation = (anim: CAKeyframeAnimation, duration: CGFloat) - -private class GradientSetupAnimation: CABasicAnimation { -} - -fileprivate extension UIResponder { - // Thanks to Phil M - // http://stackoverflow.com/questions/1340434/get-to-uiviewcontroller-from-uiview-on-iphone - - func firstAvailableViewController() -> UIViewController? { - // convenience function for casting and to "mask" the recursive function - return self.traverseResponderChainForFirstViewController() - } - - func traverseResponderChainForFirstViewController() -> UIViewController? { - if let nextResponder = self.next { - if nextResponder is UIViewController { - return nextResponder as? UIViewController - } else if nextResponder is UIView { - return nextResponder.traverseResponderChainForFirstViewController() - } else { - return nil - } - } - return nil - } -} - -fileprivate extension CAMediaTimingFunction { - - func durationPercentageForPositionPercentage(_ positionPercentage: CGFloat, duration: CGFloat) -> CGFloat { - // Finds the animation duration percentage that corresponds with the given animation "position" percentage. - // Utilizes Newton's Method to solve for the parametric Bezier curve that is used by CAMediaAnimation. - - let controlPoints = self.controlPoints() - let epsilon: CGFloat = 1.0 / (100.0 * CGFloat(duration)) - - // Find the t value that gives the position percentage we want - let t_found = solveTforY(positionPercentage, epsilon: epsilon, controlPoints: controlPoints) - - // With that t, find the corresponding animation percentage - let durationPercentage = XforCurveAt(t_found, controlPoints: controlPoints) - - return durationPercentage - } - - func solveTforY(_ y_0: CGFloat, epsilon: CGFloat, controlPoints: [CGPoint]) -> CGFloat { - // Use Newton's Method: http://en.wikipedia.org/wiki/Newton's_method - // For first guess, use t = y (i.e. if curve were linear) - var t0 = y_0 - var t1 = y_0 - var f0, df0: CGFloat - - for _ in 0..<15 { - // Base this iteration of t1 calculated from last iteration - t0 = t1 - // Calculate f(t0) - f0 = YforCurveAt(t0, controlPoints: controlPoints) - y_0 - // Check if this is close (enough) - if (abs(f0) < epsilon) { - // Done! - return t0 - } - // Else continue Newton's Method - df0 = derivativeCurveYValueAt(t0, controlPoints: controlPoints) - // Check if derivative is small or zero ( http://en.wikipedia.org/wiki/Newton's_method#Failure_analysis ) - if (abs(df0) < 1e-6) { - break - } - // Else recalculate t1 - t1 = t0 - f0/df0 - } - - // Give up - shouldn't ever get here...I hope - print("MarqueeLabel: Failed to find t for Y input!") - return t0 - } - - func YforCurveAt(_ t: CGFloat, controlPoints: [CGPoint]) -> CGFloat { - let P0 = controlPoints[0] - let P1 = controlPoints[1] - let P2 = controlPoints[2] - let P3 = controlPoints[3] - - // Per http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves - let y0 = (pow((1.0 - t), 3.0) * P0.y) - let y1 = (3.0 * pow(1.0 - t, 2.0) * t * P1.y) - let y2 = (3.0 * (1.0 - t) * pow(t, 2.0) * P2.y) - let y3 = (pow(t, 3.0) * P3.y) - - return y0 + y1 + y2 + y3 - } - - func XforCurveAt(_ t: CGFloat, controlPoints: [CGPoint]) -> CGFloat { - let P0 = controlPoints[0] - let P1 = controlPoints[1] - let P2 = controlPoints[2] - let P3 = controlPoints[3] - - // Per http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves - - let x0 = (pow((1.0 - t), 3.0) * P0.x) - let x1 = (3.0 * pow(1.0 - t, 2.0) * t * P1.x) - let x2 = (3.0 * (1.0 - t) * pow(t, 2.0) * P2.x) - let x3 = (pow(t, 3.0) * P3.x) - - return x0 + x1 + x2 + x3 - } - - func derivativeCurveYValueAt(_ t: CGFloat, controlPoints: [CGPoint]) -> CGFloat { - let P0 = controlPoints[0] - let P1 = controlPoints[1] - let P2 = controlPoints[2] - let P3 = controlPoints[3] - - let dy0 = (P0.y + 3.0 * P1.y + 3.0 * P2.y - P3.y) * -3.0 - let dy1 = t * (6.0 * P0.y + 6.0 * P2.y) - let dy2 = (-3.0 * P0.y + 3.0 * P1.y) - - return dy0 * pow(t, 2.0) + dy1 + dy2 - } - - func controlPoints() -> [CGPoint] { - // Create point array to point to - var point: [Float] = [0.0, 0.0] - var pointArray = [CGPoint]() - for i in 0...3 { - self.getControlPoint(at: i, values: &point) - pointArray.append(CGPoint(x: CGFloat(point[0]), y: CGFloat(point[1]))) - } - - return pointArray - } -} diff --git a/Session/Shared/NeverClearView.swift b/Session/Shared/NeverClearView.swift deleted file mode 100644 index 1d3ddc498..000000000 --- a/Session/Shared/NeverClearView.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -// This view can be used to safely fill a region of a table -// or collection view cell. These cells change the background -// colors of their subviews when selected. This can inadvertently -// change the color of filled subviews. This view will -// reject a new background once its background has been set. -@objc class NeverClearView: UIView { - override var backgroundColor: UIColor? { - didSet { - if backgroundColor?.cgColor.alpha == 0 { - backgroundColor = oldValue - } - } - } -} diff --git a/Session/Shared/OWSBezierPathView.m b/Session/Shared/OWSBezierPathView.m index 1426a19b6..eaac80fc0 100644 --- a/Session/Shared/OWSBezierPathView.m +++ b/Session/Shared/OWSBezierPathView.m @@ -33,7 +33,6 @@ NS_ASSUME_NONNULL_BEGIN { self.opaque = NO; self.userInteractionEnabled = NO; - self.backgroundColor = [UIColor clearColor]; } - (void)setFrame:(CGRect)frame diff --git a/Session/Shared/OWSProgressView.h b/Session/Shared/OWSProgressView.h deleted file mode 100644 index 3206f13bf..000000000 --- a/Session/Shared/OWSProgressView.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSProgressView : UIView - -@property (nonatomic) UIColor *color; -@property (nonatomic) CGFloat progress; - -+ (CGSize)defaultSize; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Shared/OWSProgressView.m b/Session/Shared/OWSProgressView.m deleted file mode 100644 index 0aaf688d0..000000000 --- a/Session/Shared/OWSProgressView.m +++ /dev/null @@ -1,132 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSProgressView.h" -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSProgressView () - -@property (nonatomic) CAShapeLayer *borderLayer; -@property (nonatomic) CAShapeLayer *progressLayer; - -@end - -#pragma mark - - -@implementation OWSProgressView - -- (id)init -{ - self = [super init]; - if (self) { - [self initCommon]; - } - - return self; -} - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - [self initCommon]; - } - return self; -} - -- (void)initCommon -{ - self.opaque = NO; - self.userInteractionEnabled = NO; - self.color = [UIColor whiteColor]; - - // Prevent the shape layer from animating changes. - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - - self.borderLayer = [CAShapeLayer new]; - [self.layer addSublayer:self.borderLayer]; - - self.progressLayer = [CAShapeLayer new]; - [self.layer addSublayer:self.progressLayer]; - - [CATransaction commit]; - - [self setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; - [self setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; -} - -- (void)layoutSubviews -{ - [super layoutSubviews]; - [self update]; -} - -- (void)setProgress:(CGFloat)progress -{ - if (_progress != progress) { - _progress = progress; - [self update]; - } -} - -- (void)setColor:(UIColor *)color -{ - if (![_color isEqual:color]) { - _color = color; - [self update]; - } -} - -- (void)update -{ - // Prevent the shape layer from animating changes. - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - - CGFloat borderThickness = MAX(CGHairlineWidth(), self.bounds.size.height * 0.1f); - CGFloat cornerRadius = MIN(self.bounds.size.width, self.bounds.size.height) * 0.5f; - - // Add the outer border. - UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:cornerRadius]; - self.borderLayer.path = borderPath.CGPath; - self.borderLayer.strokeColor = self.color.CGColor; - self.borderLayer.lineWidth = borderThickness; - self.borderLayer.fillColor = [UIColor clearColor].CGColor; - - // Add the inner progress. - CGRect progressRect = self.bounds; - progressRect.size.width = cornerRadius * 2; - CGFloat baseProgress = borderThickness * 2; - CGFloat minProgress = baseProgress; - CGFloat maxProgress = MAX(0, self.bounds.size.width - baseProgress); - progressRect.size.width = CGFloatLerp(minProgress, maxProgress, CGFloatClamp01(self.progress)); - UIBezierPath *progressPath = [UIBezierPath bezierPathWithRoundedRect:progressRect cornerRadius:cornerRadius]; - self.progressLayer.path = progressPath.CGPath; - self.progressLayer.fillColor = self.color.CGColor; - - [CATransaction commit]; -} - -+ (CGSize)defaultSize -{ - return CGSizeMake(150, 16); -} - -- (CGSize)sizeThatFits:(CGSize)size -{ - return OWSProgressView.defaultSize; -} - -- (CGSize)intrinsicContentSize -{ - return CGSizeMake(UIViewNoIntrinsicMetric, 16); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Shared/OWSQRCodeScanningViewController.h b/Session/Shared/OWSQRCodeScanningViewController.h deleted file mode 100644 index 465c4e646..000000000 --- a/Session/Shared/OWSQRCodeScanningViewController.h +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@class OWSQRCodeScanningViewController; - -@protocol OWSQRScannerDelegate - -@optional - -- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithString:(NSString *)string; -- (void)controller:(OWSQRCodeScanningViewController *)controller didDetectQRCodeWithData:(NSData *)data; - -@end - -#pragma mark - - -@interface OWSQRCodeScanningViewController - : OWSViewController - -@property (nonatomic, weak) UIViewController *scanDelegate; - -- (void)startCapture; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Shared/OWSQRCodeScanningViewController.m b/Session/Shared/OWSQRCodeScanningViewController.m deleted file mode 100644 index bc4778ead..000000000 --- a/Session/Shared/OWSQRCodeScanningViewController.m +++ /dev/null @@ -1,162 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import "OWSQRCodeScanningViewController.h" -#import "OWSBezierPathView.h" -#import "UIColor+OWS.h" -#import "UIView+OWS.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface OWSQRCodeScanningViewController () - -@property (atomic) ZXCapture *capture; -@property (nonatomic) BOOL captureEnabled; -@property (nonatomic) UIView *maskingView; -@property (nonatomic) dispatch_queue_t captureQueue; - -@end - -#pragma mark - - -@implementation OWSQRCodeScanningViewController - -- (void)dealloc -{ - [self.capture.layer removeFromSuperlayer]; -} - -- (instancetype)init -{ - self = [super init]; - if (!self) { - return self; - } - - _captureEnabled = NO; - _captureQueue = dispatch_get_main_queue(); - - return self; -} - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder -{ - self = [super initWithCoder:aDecoder]; - if (!self) { - return self; - } - - _captureEnabled = NO; - _captureQueue = dispatch_get_main_queue(); - - return self; -} - -- (void)loadView -{ - [super loadView]; - - OWSBezierPathView *maskingView = [OWSBezierPathView new]; - self.maskingView = maskingView; - [maskingView setConfigureShapeLayerBlock:^(CAShapeLayer *layer, CGRect bounds) { - // Add a circular mask - UIBezierPath *path = [UIBezierPath bezierPathWithRect:bounds]; - CGFloat margin = ScaleFromIPhone5To7Plus(24.f, 48.f); - CGFloat radius = MIN(bounds.size.width, bounds.size.height) * 0.5f - margin; - - // Center the circle's bounding rectangle - CGRect circleRect = CGRectMake( - bounds.size.width * 0.5f - radius, bounds.size.height * 0.5f - radius, radius * 2.f, radius * 2.f); - UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:16.f]; - [path appendPath:circlePath]; - [path setUsesEvenOddFillRule:YES]; - - layer.path = path.CGPath; - layer.fillRule = kCAFillRuleEvenOdd; - layer.fillColor = UIColor.lokiDarkestGray.CGColor; - layer.opacity = 0.32f; - }]; - [self.view addSubview:maskingView]; - [maskingView ows_autoPinToSuperviewEdges]; -} - -- (void)viewWillAppear:(BOOL)animated -{ - [super viewWillAppear:animated]; - - if (self.captureEnabled) { - [self startCapture]; - } -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - [self stopCapture]; -} - -- (void)viewWillLayoutSubviews -{ - self.capture.layer.frame = self.view.bounds; -} - -- (void)startCapture -{ - self.captureEnabled = YES; - if (!self.capture) { - dispatch_async(self.captureQueue, ^{ - self.capture = [[ZXCapture alloc] init]; - self.capture.camera = self.capture.back; - self.capture.focusMode = AVCaptureFocusModeContinuousAutoFocus; - self.capture.delegate = self; - - dispatch_async(dispatch_get_main_queue(), ^{ - self.capture.layer.frame = self.view.bounds; - [self.view.layer addSublayer:self.capture.layer]; - [self.view bringSubviewToFront:self.maskingView]; - [self.capture start]; - }); - }); - } else { - [self.capture start]; - } -} - -- (void)stopCapture -{ - self.captureEnabled = NO; - dispatch_async(self.captureQueue, ^{ - [self.capture stop]; - }); -} - -- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result -{ - if (!self.captureEnabled) { - return; - } - [self stopCapture]; - - // Vibrate - AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); - - if (self.scanDelegate) { - if ([self.scanDelegate respondsToSelector:@selector(controller:didDetectQRCodeWithData:)]) { - OWSLogInfo(@"Scanned Data Code."); - ZXByteArray *byteArray = result.resultMetadata[@(kResultMetadataTypeByteSegments)][0]; - NSData *decodedData = [NSData dataWithBytes:byteArray.array length:byteArray.length]; - - [self.scanDelegate controller:self didDetectQRCodeWithData:decodedData]; - } - - if ([self.scanDelegate respondsToSelector:@selector(controller:didDetectQRCodeWithString:)]) { - OWSLogInfo(@"Scanned String Code."); - [self.scanDelegate controller:self didDetectQRCodeWithString:result.text]; - } - } -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Session/Shared/QRCodeScanningViewController.swift b/Session/Shared/QRCodeScanningViewController.swift new file mode 100644 index 000000000..011823fa1 --- /dev/null +++ b/Session/Shared/QRCodeScanningViewController.swift @@ -0,0 +1,150 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import AVFoundation +import ZXingObjC +import SessionUIKit + +protocol QRScannerDelegate: AnyObject { + func controller(_ controller: QRCodeScanningViewController, didDetectQRCodeWith string: String) +} + +class QRCodeScanningViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ZXCaptureDelegate { + public weak var scanDelegate: QRScannerDelegate? + + private let captureQueue: DispatchQueue = DispatchQueue.global(qos: .default) + private var capture: ZXCapture? + private var captureEnabled: Bool = false + + // MARK: - Initialization + + deinit { + self.capture?.layer.removeFromSuperlayer() + } + + // MARK: - Components + + private let maskingView: UIView = { + let result: OWSBezierPathView = OWSBezierPathView() + result.configureShapeLayerBlock = { layer, bounds in + // Add a circular mask + let path: UIBezierPath = UIBezierPath(rect: bounds) + let margin: CGFloat = ScaleFromIPhone5To7Plus(24, 48) + let radius: CGFloat = ((min(bounds.size.width, bounds.size.height) * 0.5) - margin) + + // Center the circle's bounding rectangle + let circleRect: CGRect = CGRect( + x: ((bounds.size.width * 0.5) - radius), + y: ((bounds.size.height * 0.5) - radius), + width: (radius * 2), + height: (radius * 2) + ) + let circlePath: UIBezierPath = UIBezierPath.init( + roundedRect: circleRect, + cornerRadius: 16 + ) + path.append(circlePath) + path.usesEvenOddFillRule = true + + layer.path = path.cgPath + layer.fillRule = .evenOdd + layer.themeFillColor = .black + layer.opacity = 0.32 + } + + return result + }() + + // MARK: - Lifecycle + + override func loadView() { + super.loadView() + + self.view.addSubview(maskingView) + maskingView.pin(to: self.view) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if captureEnabled { + self.startCapture() + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + self.stopCapture() + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + + // Note: When accessing 'capture.layer' if the setup hasn't been completed it + // will result in a layout being triggered which creates an infinite loop, this + // check prevents that case + if let capture: ZXCapture = self.capture { + capture.layer.frame = self.view.bounds + } + } + + // MARK: - Functions + + public func startCapture() { + self.captureEnabled = true + + // Note: The simulator doesn't support video but if we do try to start an + // AVCaptureSession it seems to hang on that particular thread indefinitely + // this will prevent us from trying to start a session on the simulator + #if targetEnvironment(simulator) + #else + if self.capture == nil { + self.captureQueue.async { [weak self] in + let capture: ZXCapture = ZXCapture() + capture.camera = capture.back() + capture.focusMode = .autoFocus + capture.delegate = self + capture.start() + + // Note: When accessing the 'layer' for the first time it will create + // an instance of 'AVCaptureVideoPreviewLayer', this can hang a little + // so we do this on the background thread first + if capture.layer != nil {} + + DispatchQueue.main.async { + capture.layer.frame = (self?.view.bounds ?? .zero) + self?.view.layer.addSublayer(capture.layer) + + if let maskingView: UIView = self?.maskingView { + self?.view.bringSubviewToFront(maskingView) + } + + self?.capture = capture + } + } + } + else { + self.capture?.start() + } + #endif + } + + private func stopCapture() { + self.captureEnabled = false + self.captureQueue.async { [weak self] in + self?.capture?.stop() + } + } + + internal func captureResult(_ capture: ZXCapture, result: ZXResult) { + guard self.captureEnabled else { return } + + self.stopCapture() + + // Vibrate + AudioServicesPlaySystemSound(kSystemSoundID_Vibrate) + + self.scanDelegate?.controller(self, didDetectQRCodeWith: result.text) + } +} diff --git a/Session/Shared/ReminderView.swift b/Session/Shared/ReminderView.swift deleted file mode 100644 index 4ae32143e..000000000 --- a/Session/Shared/ReminderView.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation -import SessionUIKit - -class ReminderView: UIView { - - let label = UILabel() - - typealias Action = () -> Void - - var tapAction: Action? - - var text: String? { - get { - return label.text - } - - set(newText) { - label.text = newText - } - } - - enum ReminderViewMode { - // Nags are urgent interactive prompts, bidding for the user's attention. - case nag - // Explanations are not interactive or urgent. - case explanation - } - let mode: ReminderViewMode - - @available(*, unavailable, message:"use other constructor instead.") - required init?(coder aDecoder: NSCoder) { - notImplemented() - } - - @available(*, unavailable, message:"use other constructor instead.") - override init(frame: CGRect) { - notImplemented() - } - - private init(mode: ReminderViewMode, - text: String, tapAction: Action?) { - self.mode = mode - self.tapAction = tapAction - - super.init(frame: .zero) - - self.text = text - - setupSubviews() - } - - @objc public class func nag(text: String, tapAction: Action?) -> ReminderView { - return ReminderView(mode: .nag, text: text, tapAction: tapAction) - } - - @objc public class func explanation(text: String) -> ReminderView { - return ReminderView(mode: .explanation, text: text, tapAction: nil) - } - - func setupSubviews() { - let textColor: UIColor - let iconColor: UIColor - switch mode { - case .nag: - self.backgroundColor = UIColor.ows_reminderYellow - textColor = UIColor.ows_gray90 - iconColor = UIColor.ows_gray60 - case .explanation: - // TODO: Theme, review with design. - self.backgroundColor = Colors.unimportant - textColor = Colors.text - iconColor = Colors.separator - } - self.clipsToBounds = true - - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(gestureRecognizer:))) - self.addGestureRecognizer(tapGesture) - - let container = UIStackView() - container.axis = .horizontal - container.alignment = .center - container.isLayoutMarginsRelativeArrangement = true - - self.addSubview(container) - container.layoutMargins = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16) - container.ows_autoPinToSuperviewEdges() - - // Label - label.font = UIFont.ows_dynamicTypeSubheadline - container.addArrangedSubview(label) - label.textColor = textColor - label.numberOfLines = 0 - label.lineBreakMode = .byWordWrapping - - // Show the disclosure indicator if this reminder has a tap action. - if tapAction != nil { - // Icon - let iconName = (CurrentAppContext().isRTL ? "system_disclosure_indicator_rtl" : "system_disclosure_indicator") - guard let iconImage = UIImage(named: iconName) else { - owsFailDebug("missing icon.") - return - } - let iconView = UIImageView(image: iconImage.withRenderingMode(.alwaysTemplate)) - iconView.contentMode = .scaleAspectFit - iconView.tintColor = iconColor - iconView.autoSetDimension(.width, toSize: 13) - container.addArrangedSubview(iconView) - } - } - - @objc func handleTap(gestureRecognizer: UIGestureRecognizer) { - guard gestureRecognizer.state == .recognized else { - return - } - tapAction?() - } -} diff --git a/Session/Shared/ScanQRCodeWrapperVC.swift b/Session/Shared/ScanQRCodeWrapperVC.swift index d6a345195..1e492d358 100644 --- a/Session/Shared/ScanQRCodeWrapperVC.swift +++ b/Session/Shared/ScanQRCodeWrapperVC.swift @@ -1,14 +1,18 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -final class ScanQRCodeWrapperVC : BaseVC { - var delegate: (UIViewController & OWSQRScannerDelegate)? = nil +import UIKit +import SessionUIKit + +final class ScanQRCodeWrapperVC: BaseVC { + var delegate: (UIViewController & QRScannerDelegate)? = nil var isPresentedModally = false private let message: String - private let scanQRCodeVC = OWSQRCodeScanningViewController() + private let scanQRCodeVC = QRCodeScanningViewController() - // MARK: Settings override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait } - // MARK: Lifecycle + // MARK: - Lifecycle + init(message: String) { self.message = message super.init(nibName: nil, bundle: nil) @@ -24,6 +28,9 @@ final class ScanQRCodeWrapperVC : BaseVC { override func viewDidLoad() { super.viewDidLoad() + + title = "Scan QR Code" + // Set up navigation bar if needed if isPresentedModally { navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(close)) @@ -37,6 +44,7 @@ final class ScanQRCodeWrapperVC : BaseVC { scanQRCodeVCView.pin(.trailing, to: .trailing, of: view) scanQRCodeVCView.autoPinEdge(.top, to: .top, of: view) scanQRCodeVCView.autoPinToSquareAspectRatio() + // Set up bottom view let bottomView = UIView() view.addSubview(bottomView) @@ -44,37 +52,35 @@ final class ScanQRCodeWrapperVC : BaseVC { bottomView.pin(.leading, to: .leading, of: view) bottomView.pin(.trailing, to: .trailing, of: view) bottomView.pin(.bottom, to: .bottom, of: view) + // Set up explanation label let explanationLabel = UILabel() explanationLabel.text = message - explanationLabel.textColor = Colors.text + explanationLabel.themeTextColor = .textPrimary explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) - explanationLabel.numberOfLines = 0 - explanationLabel.lineBreakMode = .byWordWrapping explanationLabel.textAlignment = .center + explanationLabel.lineBreakMode = .byWordWrapping + explanationLabel.numberOfLines = 0 bottomView.addSubview(explanationLabel) explanationLabel.autoPinWidthToSuperview(withMargin: 32) explanationLabel.autoPinHeightToSuperview(withMargin: 32) - // Title - title = "Scan QR Code" } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + UIDevice.current.ows_setOrientation(.portrait) - DispatchQueue.main.async { [weak self] in - self?.scanQRCodeVC.startCapture() - } + + self.scanQRCodeVC.startCapture() } - // MARK: Interaction + // MARK: - Interaction + @objc private func close() { presentingViewController?.dismiss(animated: true, completion: nil) } public func startCapture() { - DispatchQueue.main.async { [weak self] in - self?.scanQRCodeVC.startCapture() - } + self.scanQRCodeVC.startCapture() } } diff --git a/Session/Sheets & Modals/ConfirmationModal.swift b/Session/Sheets & Modals/ConfirmationModal.swift index 44261e518..24c65f818 100644 --- a/Session/Sheets & Modals/ConfirmationModal.swift +++ b/Session/Sheets & Modals/ConfirmationModal.swift @@ -39,7 +39,7 @@ public class ConfirmationModal: Modal { attributedExplanation: NSAttributedString? = nil, stateToShow: State = .always, confirmTitle: String? = nil, - confirmStyle: ThemeValue = .textPrimary, + confirmStyle: ThemeValue = .alert_text, cancelTitle: String = "TXT_CANCEL_TITLE".localized(), cancelStyle: ThemeValue = .danger, dismissOnConfirm: Bool = true, @@ -115,7 +115,7 @@ public class ConfirmationModal: Modal { private lazy var titleLabel: UILabel = { let result: UILabel = UILabel() result.font = .boldSystemFont(ofSize: Values.mediumFontSize) - result.themeTextColor = .textPrimary + result.themeTextColor = .alert_text result.textAlignment = .center result.lineBreakMode = .byWordWrapping result.numberOfLines = 0 @@ -126,7 +126,7 @@ public class ConfirmationModal: Modal { private lazy var explanationLabel: UILabel = { let result: UILabel = UILabel() result.font = .systemFont(ofSize: Values.smallFontSize) - result.themeTextColor = .textPrimary + result.themeTextColor = .alert_text result.textAlignment = .center result.lineBreakMode = .byWordWrapping result.numberOfLines = 0 diff --git a/Session/Sheets & Modals/Modal.swift b/Session/Sheets & Modals/Modal.swift index d4e670b77..13b8add01 100644 --- a/Session/Sheets & Modals/Modal.swift +++ b/Session/Sheets & Modals/Modal.swift @@ -58,6 +58,13 @@ public class Modal: BaseVC, UIGestureRecognizerDelegate { self.afterClosed = afterClosed super.init(nibName: nil, bundle: nil) + + // Ensure the modal doesn't crash on iPad when being presented + if UIDevice.current.isIPad { + self.popoverPresentationController?.permittedArrowDirections = [] + self.popoverPresentationController?.sourceView = self.view + self.popoverPresentationController?.sourceRect = self.view.bounds + } } required init?(coder: NSCoder) { diff --git a/Session/Sheets & Modals/Sheet.swift b/Session/Sheets & Modals/Sheet.swift deleted file mode 100644 index 9b938b962..000000000 --- a/Session/Sheets & Modals/Sheet.swift +++ /dev/null @@ -1,65 +0,0 @@ - -class Sheet : BaseVC { - private(set) var bottomConstraint: NSLayoutConstraint! - - // MARK: Settings - let overshoot: CGFloat = 40 - class var isDismissable: Bool { true } - - // MARK: Components - lazy var contentView: UIView = { - let result = UIView() - result.backgroundColor = Colors.modalBackground - result.layer.cornerRadius = 24 - result.layer.masksToBounds = false - result.layer.borderColor = isLightMode ? UIColor.white.cgColor : Colors.modalBorder.cgColor - result.layer.borderWidth = 1 - result.layer.shadowColor = UIColor.black.cgColor - result.layer.shadowRadius = isLightMode ? 2 : 8 - result.layer.shadowOpacity = isLightMode ? 0.1 : 0.64 - return result - }() - - // MARK: Lifecycle - override func viewDidLoad() { - super.viewDidLoad() - let alpha = isLightMode ? CGFloat(0.1) : Values.highOpacity - view.backgroundColor = UIColor(hex: 0x000000).withAlphaComponent(alpha) - if type(of: self).isDismissable { - let swipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(close)) - swipeGestureRecognizer.direction = .down - view.addGestureRecognizer(swipeGestureRecognizer) - } - setUpViewHierarchy() - } - - private func setUpViewHierarchy() { - view.addSubview(contentView) - contentView.pin(.leading, to: .leading, of: view, withInset: -1) - contentView.pin(.trailing, to: .trailing, of: view, withInset: 1) - bottomConstraint = contentView.pin(.bottom, to: .bottom, of: view, withInset: overshoot) - populateContentView() - } - - /// To be overridden by subclasses. - func populateContentView() { - preconditionFailure("populateContentView() is abstract and must be overridden.") - } - - // MARK: Interaction - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - let touch = touches.first! - let location = touch.location(in: view) - if contentView.frame.contains(location) { - super.touchesBegan(touches, with: event) - } else { - if type(of: self).isDismissable { - close() - } - } - } - - @objc func close() { - dismiss(animated: true, completion: nil) - } -} diff --git a/Session/Utilities/AvatarViewHelper.m b/Session/Utilities/AvatarViewHelper.m index 5783b7510..75088343a 100644 --- a/Session/Utilities/AvatarViewHelper.m +++ b/Session/Utilities/AvatarViewHelper.m @@ -7,8 +7,6 @@ #import "Session-Swift.h" #import -#import - #import NS_ASSUME_NONNULL_BEGIN diff --git a/Session/Utilities/UIApplication+OWS.swift b/Session/Utilities/UIApplication+OWS.swift index 6e33cb009..3980dbe57 100644 --- a/Session/Utilities/UIApplication+OWS.swift +++ b/Session/Utilities/UIApplication+OWS.swift @@ -21,7 +21,7 @@ import UIKit owsFailDebug("Missing root view controller.") return nil } - return viewController.findFrontmostViewController(ignoringAlerts) + return viewController.findFrontmostViewController(ignoringAlerts: ignoringAlerts) } func openSystemSettings() { diff --git a/Session/Utilities/UINavigationBar+Utilities.swift b/Session/Utilities/UINavigationBar+Utilities.swift index 09ba0a8bf..c715f7fb1 100644 --- a/Session/Utilities/UINavigationBar+Utilities.swift +++ b/Session/Utilities/UINavigationBar+Utilities.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import SessionUIKit extension UINavigationBar { func generateSnapshot(in coordinateSpace: UICoordinateSpace) -> (UIView, CGRect)? { @@ -31,7 +32,7 @@ extension UINavigationBar { height: frame.maxY ) ) - snapshotView.backgroundColor = backgroundColor + snapshotView.themeBackgroundColorForced = self.themeBackgroundColorForced let imageView: UIImageView = UIImageView(image: image) imageView.frame = frame diff --git a/Session/Utilities/UIView+Glow.swift b/Session/Utilities/UIView+Glow.swift deleted file mode 100644 index a50952fd7..000000000 --- a/Session/Utilities/UIView+Glow.swift +++ /dev/null @@ -1,48 +0,0 @@ - -extension UIView { - - struct CircularGlowConfiguration { - let size: CGFloat - let color: UIColor - let isAnimated: Bool - let animationDuration: TimeInterval - let offset: CGSize - let opacity: Float - let radius: CGFloat - - init(size: CGFloat, color: UIColor, isAnimated: Bool = false, animationDuration: TimeInterval = 0.25, offset: CGSize = CGSize(width: 0, height: 0.8), opacity: Float = isLightMode ? 0.4 : 1, radius: CGFloat) { - self.size = size - self.color = color - self.isAnimated = isAnimated - self.animationDuration = animationDuration - self.offset = offset - self.opacity = opacity - self.radius = radius - } - } - - func setCircularGlow(with configuration: CircularGlowConfiguration) { - let newSize = configuration.size - let newPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint.zero, size: CGSize(width: newSize, height: newSize))).cgPath - if configuration.isAnimated { - let pathAnimation = CABasicAnimation(keyPath: "shadowPath") - pathAnimation.fromValue = layer.shadowPath - pathAnimation.toValue = newPath - pathAnimation.duration = configuration.animationDuration - layer.add(pathAnimation, forKey: pathAnimation.keyPath) - } - layer.shadowPath = newPath - let newColor = configuration.color.cgColor - if configuration.isAnimated { - let colorAnimation = CABasicAnimation(keyPath: "shadowColor") - colorAnimation.fromValue = layer.shadowColor - colorAnimation.toValue = newColor - colorAnimation.duration = configuration.animationDuration - layer.add(colorAnimation, forKey: colorAnimation.keyPath) - } - layer.shadowColor = newColor - layer.shadowOffset = configuration.offset - layer.shadowOpacity = configuration.opacity - layer.shadowRadius = configuration.radius - } -} diff --git a/Session/Utilities/UIViewController+Permissions.m b/Session/Utilities/UIViewController+Permissions.m index bed8a8800..9dce25003 100644 --- a/Session/Utilities/UIViewController+Permissions.m +++ b/Session/Utilities/UIViewController+Permissions.m @@ -7,7 +7,6 @@ #import #import #import -#import NS_ASSUME_NONNULL_BEGIN diff --git a/SessionMessagingKit/Database/Models/RecipientState.swift b/SessionMessagingKit/Database/Models/RecipientState.swift index baad3bea6..bed9a84f2 100644 --- a/SessionMessagingKit/Database/Models/RecipientState.swift +++ b/SessionMessagingKit/Database/Models/RecipientState.swift @@ -65,7 +65,7 @@ public struct RecipientState: Codable, Equatable, FetchableRecord, PersistableRe } } - public func statusIconInfo(variant: Interaction.Variant, hasAtLeastOneReadReceipt: Bool) -> (image: UIImage?, tintColor: ThemeValue) { + public func statusIconInfo(variant: Interaction.Variant, hasAtLeastOneReadReceipt: Bool) -> (image: UIImage?, themeTintColor: ThemeValue) { guard variant == .standardOutgoing else { return (nil, .textPrimary) } switch (self, hasAtLeastOneReadReceipt) { diff --git a/SessionMessagingKit/Utilities/OWSWindowManager.h b/SessionMessagingKit/Utilities/OWSWindowManager.h index a4aed979e..6bafabfe7 100644 --- a/SessionMessagingKit/Utilities/OWSWindowManager.h +++ b/SessionMessagingKit/Utilities/OWSWindowManager.h @@ -6,8 +6,6 @@ NS_ASSUME_NONNULL_BEGIN -extern NSString *const OWSWindowManagerCallDidChangeNotification; - extern NSString *const IsScreenBlockActiveDidChangeNotification; // This VC can become first responder @@ -32,27 +30,10 @@ extern const UIWindowLevel UIWindowLevel_Background; - (void)setupWithRootWindow:(UIWindow *)rootWindow screenBlockingWindow:(UIWindow *)screenBlockingWindow; @property (nonatomic, readonly) UIWindow *rootWindow; -@property (nonatomic, readonly) UIWindow *menuActionsWindow; @property (nonatomic) BOOL isScreenBlockActive; - (BOOL)isAppWindow:(UIWindow *)window; -#pragma mark - Message Actions - -@property (nonatomic, readonly) BOOL isPresentingMenuActions; - -- (void)showMenuActionsWindow:(UIViewController *)menuActionsViewController; -- (void)hideMenuActionsWindow; - -#pragma mark - Calls - -@property (nonatomic, readonly) BOOL shouldShowCallView; - -- (void)startCall:(UIViewController *)callViewController; -- (void)endCall:(UIViewController *)callViewController; -- (void)leaveCallView; -- (BOOL)hasCall; - @end NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Utilities/OWSWindowManager.m b/SessionMessagingKit/Utilities/OWSWindowManager.m index f257a09d3..c66daed0b 100644 --- a/SessionMessagingKit/Utilities/OWSWindowManager.m +++ b/SessionMessagingKit/Utilities/OWSWindowManager.m @@ -8,31 +8,11 @@ NS_ASSUME_NONNULL_BEGIN -NSString *const OWSWindowManagerCallDidChangeNotification = @"OWSWindowManagerCallDidChangeNotification"; - NSString *const IsScreenBlockActiveDidChangeNotification = @"IsScreenBlockActiveDidChangeNotification"; -const CGFloat OWSWindowManagerCallBannerHeight(void) -{ - return CurrentAppContext().statusBarHeight + 20; -} - // Behind everything, especially the root window. const UIWindowLevel UIWindowLevel_Background = -1.f; -const UIWindowLevel UIWindowLevel_ReturnToCall(void); -const UIWindowLevel UIWindowLevel_ReturnToCall(void) -{ - return UIWindowLevelStatusBar - 1; -} - -// In front of the root window, behind the screen blocking window. -const UIWindowLevel UIWindowLevel_CallView(void); -const UIWindowLevel UIWindowLevel_CallView(void) -{ - return UIWindowLevelNormal + 1.f; -} - // In front of the status bar and CallView const UIWindowLevel UIWindowLevel_ScreenBlocking(void); const UIWindowLevel UIWindowLevel_ScreenBlocking(void) @@ -40,36 +20,6 @@ const UIWindowLevel UIWindowLevel_ScreenBlocking(void) return UIWindowLevelStatusBar + 2.f; } -// In front of everything -const UIWindowLevel UIWindowLevel_MessageActions(void); -const UIWindowLevel UIWindowLevel_MessageActions(void) -{ - // Note: To cover the keyboard, this is higher than the ScreenBlocking level, - // but this window is hidden when screen protection is shown. - return CGFLOAT_MAX - 100; -} - -#pragma mark - - -@interface MessageActionsWindow : UIWindow - -@end - -#pragma mark - - -@implementation MessageActionsWindow - -- (UIWindowLevel)windowLevel -{ - // As of iOS11, setWindowLevel clamps the value below - // the height of the keyboard window. - // Because we want to display above the keyboard, we hardcode - // the `windowLevel` getter. - return UIWindowLevel_MessageActions(); -} - -@end - #pragma mark - @implementation OWSWindowRootViewController @@ -114,22 +64,10 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) // UIWindowLevelNormal @property (nonatomic) UIWindow *rootWindow; -// UIWindowLevel_CallView -@property (nonatomic) UIWindow *callViewWindow; -@property (nonatomic) UINavigationController *callNavigationController; - -// UIWindowLevel_MessageActions -@property (nonatomic) UIWindow *menuActionsWindow; -@property (nonatomic, nullable) UIViewController *menuActionsViewController; - // UIWindowLevel_Background if inactive, // UIWindowLevel_ScreenBlocking() if active. @property (nonatomic) UIWindow *screenBlockingWindow; -@property (nonatomic) BOOL shouldShowCallView; - -@property (nonatomic, nullable) UIViewController *callViewController; - @end #pragma mark - @@ -156,10 +94,7 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) { self.rootWindow = rootWindow; self.screenBlockingWindow = screenBlockingWindow; - - self.callViewWindow = [self createCallViewWindow:rootWindow]; - self.menuActionsWindow = [self createMenuActionsWindowWithRoowWindow:rootWindow]; - + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification @@ -175,47 +110,10 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) - (void)didChangeStatusBarFrame:(NSNotification *)notification { - } - (void)applicationWillResignActive:(NSNotification *)notification { - [self hideMenuActionsWindow]; -} - -- (UIWindow *)createMenuActionsWindowWithRoowWindow:(UIWindow *)rootWindow -{ - UIWindow *window = [[MessageActionsWindow alloc] initWithFrame:rootWindow.bounds]; - window.hidden = YES; - window.backgroundColor = UIColor.clearColor; - - return window; -} - -- (UIWindow *)createCallViewWindow:(UIWindow *)rootWindow -{ - UIWindow *window = [[UIWindow alloc] initWithFrame:rootWindow.bounds]; - window.hidden = YES; - window.windowLevel = UIWindowLevel_CallView(); - window.opaque = YES; - // TODO: What's the right color to use here? - window.backgroundColor = [UIColor blackColor]; - - UIViewController *viewController = [OWSWindowRootViewController new]; - viewController.view.backgroundColor = [UIColor blackColor]; - - // NOTE: Do not use OWSNavigationController for call window. - // It adjusts the size of the navigation bar to reflect the - // call window. We don't want those adjustments made within - // the call window itself. - OWSWindowRootNavigationViewController *navigationController = - [[OWSWindowRootNavigationViewController alloc] initWithRootViewController:viewController]; - navigationController.navigationBarHidden = YES; - self.callNavigationController = navigationController; - - window.rootViewController = navigationController; - - return window; } - (void)setIsScreenBlockActive:(BOOL)isScreenBlockActive @@ -231,92 +129,7 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) - (BOOL)isAppWindow:(UIWindow *)window { - return (window == self.rootWindow || window == self.callViewWindow - || window == self.menuActionsWindow || window == self.screenBlockingWindow); -} - -#pragma mark - Message Actions - -- (BOOL)isPresentingMenuActions -{ - return self.menuActionsViewController != nil; -} - -- (void)showMenuActionsWindow:(UIViewController *)menuActionsViewController -{ - self.menuActionsViewController = menuActionsViewController; - self.menuActionsWindow.rootViewController = menuActionsViewController; - - [self ensureWindowState]; -} - -- (void)hideMenuActionsWindow -{ - self.menuActionsWindow.rootViewController = nil; - self.menuActionsViewController = nil; - - [self ensureWindowState]; -} - -#pragma mark - Calls - -- (void)setCallViewController:(nullable UIViewController *)callViewController -{ - if (callViewController == _callViewController) { - return; - } - - _callViewController = callViewController; - - [NSNotificationCenter.defaultCenter postNotificationName:OWSWindowManagerCallDidChangeNotification object:nil]; -} - -- (void)startCall:(UIViewController *)callViewController -{ - self.callViewController = callViewController; - - // Attach callViewController to window. - [self.callNavigationController popToRootViewControllerAnimated:NO]; - [self.callNavigationController pushViewController:callViewController animated:NO]; - self.shouldShowCallView = YES; - // CallViewController only supports portrait, but if we're _already_ landscape it won't - // automatically switch. - [UIDevice.currentDevice ows_setOrientation:UIInterfaceOrientationPortrait]; - [self ensureWindowState]; -} - -- (void)endCall:(UIViewController *)callViewController -{ - if (self.callViewController != callViewController) { - return; - } - - // Dettach callViewController from window. - [self.callNavigationController popToRootViewControllerAnimated:NO]; - self.callViewController = nil; - - self.shouldShowCallView = NO; - - [self ensureWindowState]; -} - -- (void)leaveCallView -{ - self.shouldShowCallView = NO; - - [self ensureWindowState]; -} - -- (void)showCallView -{ - self.shouldShowCallView = YES; - - [self ensureWindowState]; -} - -- (BOOL)hasCall -{ - return self.callViewController != nil; + return (window == self.rootWindow || window == self.screenBlockingWindow); } #pragma mark - Window State @@ -332,30 +145,13 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) // Show Screen Block. [self ensureRootWindowHidden]; - [self ensureCallViewWindowHidden]; - [self ensureMessageActionsWindowHidden]; [self ensureScreenBlockWindowShown]; - } else if (self.callViewController && self.shouldShowCallView) { - // Show Call View. - - [self ensureRootWindowHidden]; - [self ensureCallViewWindowShown]; - [self ensureMessageActionsWindowHidden]; - [self ensureScreenBlockWindowHidden]; - } else { + } + else { // Show Root Window [self ensureRootWindowShown]; - [self ensureCallViewWindowHidden]; [self ensureScreenBlockWindowHidden]; - - if (self.menuActionsViewController) { - // Add "Message Actions" action sheet - - [self ensureMessageActionsWindowShown]; - } else { - [self ensureMessageActionsWindowHidden]; - } } } @@ -374,27 +170,6 @@ const UIWindowLevel UIWindowLevel_MessageActions(void) self.rootWindow.hidden = YES; } -- (void)ensureCallViewWindowShown -{ - [self.callViewWindow makeKeyAndVisible]; -} - -- (void)ensureCallViewWindowHidden -{ - self.callViewWindow.hidden = YES; -} - -- (void)ensureMessageActionsWindowShown -{ - // Do not make key, we want the keyboard to stay popped. - self.menuActionsWindow.hidden = NO; -} - -- (void)ensureMessageActionsWindowHidden -{ - self.menuActionsWindow.hidden = YES; -} - - (void)ensureScreenBlockWindowShown { self.screenBlockingWindow.windowLevel = UIWindowLevel_ScreenBlocking(); diff --git a/SessionShareExtension/SAEScreenLockViewController.swift b/SessionShareExtension/SAEScreenLockViewController.swift index b196ef1a4..68b0d71c1 100644 --- a/SessionShareExtension/SAEScreenLockViewController.swift +++ b/SessionShareExtension/SAEScreenLockViewController.swift @@ -62,20 +62,20 @@ final class SAEScreenLockViewController: ScreenLockViewController { ThemeManager.onThemeChange(observer: self.unlockButton) { [weak self] theme, _ in switch theme.interfaceStyle { case .light: - self?.unlockButton.setTitleColor(theme.colors[.textPrimary], for: .normal) - self?.unlockButton.setBackgroundImage( - theme.colors[.textPrimary]?.withAlphaComponent(0.3).toImage(), + self?.unlockButton.setThemeTitleColorForced(.theme(theme, color: .textPrimary), for: .normal) + self?.unlockButton.setThemeBackgroundColorForced( + .theme(theme, color: .textPrimary), for: .highlighted ) - self?.unlockButton.layer.borderColor = theme.colors[.textPrimary]?.cgColor + self?.unlockButton.themeBorderColorForced = .theme(theme, color: .textPrimary) default: - self?.unlockButton.setTitleColor(Theme.PrimaryColor.green.color, for: .normal) - self?.unlockButton.setBackgroundImage( - Theme.PrimaryColor.green.color.withAlphaComponent(0.3).toImage(), + self?.unlockButton.setThemeTitleColorForced(.primary(.green), for: .normal) + self?.unlockButton.setThemeBackgroundColorForced( + .primary(.green, alpha: 0.3), for: .highlighted ) - self?.unlockButton.layer.borderColor = Theme.PrimaryColor.green.color.cgColor + self?.unlockButton.themeBorderColorForced = .primary(.green) } } } diff --git a/SessionShareExtension/ShareAppExtensionContext.swift b/SessionShareExtension/ShareAppExtensionContext.swift index f5fbcc86a..5f1445a53 100644 --- a/SessionShareExtension/ShareAppExtensionContext.swift +++ b/SessionShareExtension/ShareAppExtensionContext.swift @@ -139,7 +139,7 @@ final class ShareAppExtensionContext: NSObject, AppContext { } func frontmostViewController() -> UIViewController? { - return rootViewController.findFrontmostViewController(true) + return rootViewController.findFrontmostViewController(ignoringAlerts: true) } func appDocumentDirectoryPath() -> String { diff --git a/SessionTests/Settings/NotificationContentViewModelSpec.swift b/SessionTests/Settings/NotificationContentViewModelSpec.swift index 8a15a6319..e0979189e 100644 --- a/SessionTests/Settings/NotificationContentViewModelSpec.swift +++ b/SessionTests/Settings/NotificationContentViewModelSpec.swift @@ -25,7 +25,7 @@ class NotificationContentViewModelSpec: QuickSpec { SNUtilitiesKit.migrations(), SNSnodeKit.migrations(), SNMessagingKit.migrations(), - SUIKit.migrations() + SNUIKit.migrations() ] ) viewModel = NotificationContentViewModel(storage: mockStorage) diff --git a/SessionUIKit/Configuration.swift b/SessionUIKit/Configuration.swift index cd9c22eae..f5b2a6186 100644 --- a/SessionUIKit/Configuration.swift +++ b/SessionUIKit/Configuration.swift @@ -3,13 +3,13 @@ import Foundation import SessionUtilitiesKit -public enum SUIKit { +public enum SNUIKit { public static func migrations() -> TargetMigrations { return TargetMigrations( identifier: .uiKit, migrations: [ // Want to ensure the initial DB stuff has been completed before doing any - // SUIKit migrations + // SNUIKit migrations [], // Initial DB Creation [], // YDB to GRDB Migration [], // YDB Removal diff --git a/SessionUIKit/Style Guide/ThemeManager.swift b/SessionUIKit/Style Guide/ThemeManager.swift index 5f53f66b8..dd19af53d 100644 --- a/SessionUIKit/Style Guide/ThemeManager.swift +++ b/SessionUIKit/Style Guide/ThemeManager.swift @@ -82,7 +82,10 @@ public enum ThemeManager { // Note: We have to trigger this directly or the 'TraitObservingWindow' won't actually // trigger the trait change if the app launched with this setting switched off - applyWindowStyling() + + // Note: We need to set this to 'unspecified' to force the UI to properly update as the + // 'TraitObservingWindow' won't actually trigger the trait change otherwise + mainWindow?.overrideUserInterfaceStyle = .unspecified } } @@ -203,8 +206,6 @@ public enum ThemeManager { public static func applyWindowStyling() { mainWindow?.overrideUserInterfaceStyle = { - guard !ThemeManager.matchSystemNightModeSetting else { return .unspecified } - switch ThemeManager.currentTheme.interfaceStyle { case .light: return .light case .dark, .unspecified: return .dark diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift index 6c275661c..3db082cc8 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicDark.swift @@ -74,6 +74,7 @@ internal enum Theme_ClassicDark: ThemeColors { .appearance_buttonHighlight: .classicDark3, // Alert + .alert_text: .classicDark6, .alert_background: .classicDark1, .alert_buttonBackground: .classicDark1, .alert_buttonHighlight: .classicDark3, @@ -96,6 +97,7 @@ internal enum Theme_ClassicDark: ThemeColors { // ContextMenu .contextMenu_background: .classicDark1, .contextMenu_highlight: .primary, + .contextMenu_text: .classicDark6, .contextMenu_textHighlight: .classicDark0, // Call @@ -104,6 +106,6 @@ internal enum Theme_ClassicDark: ThemeColors { // Reactions .reactions_contextBackground: .classicDark2, - .reactions_contextMoreBackground: .classicDark3 + .reactions_contextMoreBackground: .classicDark1 ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift index 729e38507..a1506dc39 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+ClassicLight.swift @@ -74,6 +74,7 @@ internal enum Theme_ClassicLight: ThemeColors { .appearance_buttonHighlight: .classicLight4, // Alert + .alert_text: .classicLight0, .alert_background: .classicLight6, .alert_buttonBackground: .classicLight6, .alert_buttonHighlight: .classicLight4, @@ -96,6 +97,7 @@ internal enum Theme_ClassicLight: ThemeColors { // ContextMenu .contextMenu_background: .classicLight6, .contextMenu_highlight: .primary, + .contextMenu_text: .classicLight0, .contextMenu_textHighlight: .classicLight0, // Call diff --git a/SessionUIKit/Style Guide/Themes/Theme+Colors.swift b/SessionUIKit/Style Guide/Themes/Theme+Colors.swift index 33b9ec3d1..56e24d1ec 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+Colors.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+Colors.swift @@ -75,13 +75,14 @@ internal extension UIColor { static let oceanDark5: UIColor = #colorLiteral(red: 0.6509803922, green: 0.662745098, blue: 0.8078431373, alpha: 1) // #A6A9CE static let oceanDark6: UIColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) // #FFFFFF - static let oceanLight0: UIColor = #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1) // #19345D - static let oceanLight1: UIColor = #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1) // #6A6E90 - static let oceanLight2: UIColor = #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1) // #5CAACC - static let oceanLight3: UIColor = #colorLiteral(red: 0.7019607843, green: 0.9294117647, blue: 0.9490196078, alpha: 1) // #B3EDF2 - static let oceanLight4: UIColor = #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1) // #E7F3F4 - static let oceanLight5: UIColor = #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1) // #ECFAFB - static let oceanLight6: UIColor = #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1) // #FCFFFF + static let oceanLight0: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) // #000000 + static let oceanLight1: UIColor = #colorLiteral(red: 0.09803921569, green: 0.2039215686, blue: 0.3647058824, alpha: 1) // #19345D + static let oceanLight2: UIColor = #colorLiteral(red: 0.4156862745, green: 0.431372549, blue: 0.5647058824, alpha: 1) // #6A6E90 + static let oceanLight3: UIColor = #colorLiteral(red: 0.3607843137, green: 0.6666666667, blue: 0.8, alpha: 1) // #5CAACC + static let oceanLight4: UIColor = #colorLiteral(red: 0.7019607843, green: 0.9294117647, blue: 0.9490196078, alpha: 1) // #B3EDF2 + static let oceanLight5: UIColor = #colorLiteral(red: 0.9058823529, green: 0.9529411765, blue: 0.9568627451, alpha: 1) // #E7F3F4 + static let oceanLight6: UIColor = #colorLiteral(red: 0.9254901961, green: 0.9803921569, blue: 0.9843137255, alpha: 1) // #ECFAFB + static let oceanLight7: UIColor = #colorLiteral(red: 0.9882352941, green: 1, blue: 1, alpha: 1) // #FCFFFF } public extension UIColor { diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift index d7581a662..891416cd2 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanDark.swift @@ -74,6 +74,7 @@ internal enum Theme_OceanDark: ThemeColors { .appearance_buttonHighlight: .oceanDark4, // Alert + .alert_text: .oceanDark6, .alert_background: .oceanDark3, .alert_buttonBackground: .oceanDark3, .alert_buttonHighlight: .oceanDark4, @@ -96,6 +97,7 @@ internal enum Theme_OceanDark: ThemeColors { // ContextMenu .contextMenu_background: .oceanDark2, .contextMenu_highlight: .primary, + .contextMenu_text: .oceanDark6, .contextMenu_textHighlight: .oceanDark0, // Call diff --git a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift index 0d2b640a6..91a6efd57 100644 --- a/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift +++ b/SessionUIKit/Style Guide/Themes/Theme+OceanLight.swift @@ -12,27 +12,27 @@ internal enum Theme_OceanLight: ThemeColors { .defaultPrimary: Theme.PrimaryColor.blue.color, .danger: .dangerLight, .disabled: .disabledLight, - .backgroundPrimary: .oceanLight6, - .backgroundSecondary: .oceanLight5, - .textPrimary: .oceanLight0, - .textSecondary: .oceanLight1, - .borderSeparator: .oceanLight2, + .backgroundPrimary: .oceanLight7, + .backgroundSecondary: .oceanLight6, + .textPrimary: .oceanLight1, + .textSecondary: .oceanLight2, + .borderSeparator: .oceanLight3, // Path .path_connected: .pathConnected, .path_connecting: .pathConnecting, .path_error: .pathError, - .path_unknown: .oceanLight4, + .path_unknown: .oceanLight5, // TextBox - .textBox_background: .oceanLight6, - .textBox_border: .oceanLight2, + .textBox_background: .oceanLight7, + .textBox_border: .oceanLight3, // MessageBubble .messageBubble_outgoingBackground: .primary, - .messageBubble_incomingBackground: .oceanLight3, - .messageBubble_outgoingText: .oceanLight0, - .messageBubble_incomingText: .oceanLight0, + .messageBubble_incomingBackground: .oceanLight4, + .messageBubble_outgoingText: .oceanLight1, + .messageBubble_incomingText: .oceanLight1, .messageBubble_overlay: .black_06, // MenuButton @@ -44,58 +44,60 @@ internal enum Theme_OceanLight: ThemeColors { // RadioButton .radioButton_selectedBackground: .primary, .radioButton_unselectedBackground: .clear, - .radioButton_selectedBorder: .oceanLight0, - .radioButton_unselectedBorder: .oceanLight2, + .radioButton_selectedBorder: .oceanLight1, + .radioButton_unselectedBorder: .oceanLight3, // OutlineButton - .outlineButton_text: .oceanLight0, + .outlineButton_text: .oceanLight1, .outlineButton_background: .clear, - .outlineButton_highlight: .oceanLight0.withAlphaComponent(0.1), - .outlineButton_border: .oceanLight0, - .outlineButton_filledText: .oceanLight6, - .outlineButton_filledBackground: .oceanLight0, - .outlineButton_filledHighlight: .oceanLight1, + .outlineButton_highlight: .oceanLight1.withAlphaComponent(0.1), + .outlineButton_border: .oceanLight1, + .outlineButton_filledText: .oceanLight7, + .outlineButton_filledBackground: .oceanLight1, + .outlineButton_filledHighlight: .oceanLight2, .outlineButton_destructiveText: .dangerLight, .outlineButton_destructiveBackground: .clear, .outlineButton_destructiveHighlight: .dangerLight.withAlphaComponent(0.3), .outlineButton_destructiveBorder: .dangerLight, // SolidButton - .solidButton_background: .oceanLight4, - .solidButton_highlight: .oceanLight5, + .solidButton_background: .oceanLight5, + .solidButton_highlight: .oceanLight6, // Settings - .settings_tabBackground: .oceanLight6, - .settings_tabHighlight: .oceanLight4, + .settings_tabBackground: .oceanLight7, + .settings_tabHighlight: .oceanLight5, // Appearance - .appearance_sectionBackground: .oceanLight6, - .appearance_buttonBackground: .oceanLight6, - .appearance_buttonHighlight: .oceanLight4, + .appearance_sectionBackground: .oceanLight7, + .appearance_buttonBackground: .oceanLight7, + .appearance_buttonHighlight: .oceanLight5, // Alert - .alert_background: .oceanLight6, - .alert_buttonBackground: .oceanLight6, - .alert_buttonHighlight: .oceanLight4, + .alert_text: .oceanLight0, + .alert_background: .oceanLight7, + .alert_buttonBackground: .oceanLight7, + .alert_buttonHighlight: .oceanLight5, // ConversationButton - .conversationButton_background: .oceanLight6, - .conversationButton_highlight: .oceanLight4, - .conversationButton_unreadBackground: .oceanLight5, - .conversationButton_unreadHighlight: .oceanLight4, + .conversationButton_background: .oceanLight7, + .conversationButton_highlight: .oceanLight5, + .conversationButton_unreadBackground: .oceanLight6, + .conversationButton_unreadHighlight: .oceanLight5, .conversationButton_unreadStripBackground: .primary, .conversationButton_unreadBubbleBackground: .primary, - .conversationButton_unreadBubbleText: .oceanLight0, + .conversationButton_unreadBubbleText: .oceanLight1, .conversationButton_swipeDestructive: .dangerLight, - .conversationButton_swipeSecondary: .oceanLight1, + .conversationButton_swipeSecondary: .oceanLight2, .conversationButton_swipeTertiary: Theme.PrimaryColor.orange.color, // InputButton - .inputButton_background: .oceanLight6, + .inputButton_background: .oceanLight7, // ContextMenu - .contextMenu_background: .oceanLight6, + .contextMenu_background: .oceanLight7, .contextMenu_highlight: .primary, + .contextMenu_text: .oceanLight0, .contextMenu_textHighlight: .oceanLight0, // Call @@ -103,7 +105,7 @@ internal enum Theme_OceanLight: ThemeColors { .callDecline_background: .dangerLight, // Reactions - .reactions_contextBackground: .oceanLight6, - .reactions_contextMoreBackground: .oceanLight5 + .reactions_contextBackground: .oceanLight7, + .reactions_contextMoreBackground: .oceanLight6 ] } diff --git a/SessionUIKit/Style Guide/Themes/Theme.swift b/SessionUIKit/Style Guide/Themes/Theme.swift index 71c17c416..bb5cab5c8 100644 --- a/SessionUIKit/Style Guide/Themes/Theme.swift +++ b/SessionUIKit/Style Guide/Themes/Theme.swift @@ -125,6 +125,7 @@ public enum ThemeValue { case appearance_buttonHighlight // Alert + case alert_text case alert_background case alert_buttonBackground case alert_buttonHighlight @@ -147,6 +148,7 @@ public enum ThemeValue { // ContextMenu case contextMenu_background case contextMenu_highlight + case contextMenu_text case contextMenu_textHighlight // Call @@ -157,3 +159,40 @@ public enum ThemeValue { case reactions_contextBackground case reactions_contextMoreBackground } + +// MARK: - ForcedThemeValue + +public enum ForcedThemeValue { + case color(UIColor) + case primary(Theme.PrimaryColor, alpha: CGFloat?) + case theme(Theme, color: ThemeValue, alpha: CGFloat?) + + public static func primary(_ primary: Theme.PrimaryColor) -> ForcedThemeValue { + return .primary(primary, alpha: nil) + } + + public static func theme(_ theme: Theme, color: ThemeValue) -> ForcedThemeValue { + return .theme(theme, color: color, alpha: nil) + } +} + +// MARK: - ForcedThemeAttribute + +public enum ForcedThemeAttribute { + case background(UIColor) + case foreground(UIColor) + + public var key: NSAttributedString.Key { + switch self { + case .background: return NSAttributedString.Key.backgroundColor + case .foreground: return NSAttributedString.Key.foregroundColor + } + } + + public var value: Any { + switch self { + case .background(let value): return value + case .foreground(let value): return value + } + } +} diff --git a/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift b/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift index b9e4a9038..a896c543a 100644 --- a/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift +++ b/SessionUIKit/Style Guide/Themes/UIKit+Theme.swift @@ -8,20 +8,124 @@ public extension UIView { get { return nil } } + var themeBackgroundColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): backgroundColor = color + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + backgroundColor = value.color + return + } + + backgroundColor = value.color.withAlphaComponent(alpha) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + backgroundColor = theme.colors[value] + return + } + + backgroundColor = theme.colors[value]?.withAlphaComponent(alpha) + + case .none: backgroundColor = nil + } + } + get { return self.backgroundColor.map { .color($0) } } + } + var themeTintColor: ThemeValue? { set { ThemeManager.set(self, keyPath: \.tintColor, to: newValue) } get { return nil } } + var themeTintColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): tintColor = color + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + tintColor = value.color + return + } + + tintColor = value.color.withAlphaComponent(alpha) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + tintColor = theme.colors[value] + return + } + + tintColor = theme.colors[value]?.withAlphaComponent(alpha) + + case .none: tintColor = nil + } + } + get { return self.tintColor.map { .color($0) } } + } + var themeBorderColor: ThemeValue? { set { ThemeManager.set(self, keyPath: \.layer.borderColor, to: newValue) } get { return nil } } + var themeBorderColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): layer.borderColor = color.cgColor + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + layer.borderColor = value.color.cgColor + return + } + + layer.borderColor = value.color.withAlphaComponent(alpha).cgColor + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + layer.borderColor = theme.colors[value]?.cgColor + return + } + + layer.borderColor = theme.colors[value]?.withAlphaComponent(alpha).cgColor + + case .none: layer.borderColor = nil + } + } + get { return nil } + } + var themeShadowColor: ThemeValue? { set { ThemeManager.set(self, keyPath: \.layer.shadowColor, to: newValue) } get { return nil } } + + var themeShadowColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): layer.shadowColor = color.cgColor + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + layer.shadowColor = value.color.cgColor + return + } + + layer.shadowColor = value.color.withAlphaComponent(alpha).cgColor + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + layer.shadowColor = theme.colors[value]?.cgColor + return + } + + layer.shadowColor = theme.colors[value]?.withAlphaComponent(alpha).cgColor + + case .none: layer.shadowColor = nil + } + } + get { return self.layer.shadowColor.map { .color(UIColor(cgColor: $0)) } } + } } public extension UILabel { @@ -29,6 +133,32 @@ public extension UILabel { set { ThemeManager.set(self, keyPath: \.textColor, to: newValue) } get { return nil } } + + var themeTextColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): textColor = color + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + textColor = value.color + return + } + + textColor = value.color.withAlphaComponent(alpha) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + textColor = theme.colors[value] + return + } + + textColor = theme.colors[value]?.withAlphaComponent(alpha) + + case .none: textColor = nil + } + } + get { return self.textColor.map { .color($0) } } + } } public extension UITextView { @@ -36,6 +166,32 @@ public extension UITextView { set { ThemeManager.set(self, keyPath: \.textColor, to: newValue) } get { return nil } } + + var themeTextColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): textColor = color + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + textColor = value.color + return + } + + textColor = value.color.withAlphaComponent(alpha) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + textColor = theme.colors[value] + return + } + + textColor = theme.colors[value]?.withAlphaComponent(alpha) + + case .none: textColor = nil + } + } + get { return self.textColor.map { .color($0) } } + } } public extension UITextField { @@ -43,6 +199,32 @@ public extension UITextField { set { ThemeManager.set(self, keyPath: \.textColor, to: newValue) } get { return nil } } + + var themeTextColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): textColor = color + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + textColor = value.color + return + } + + textColor = value.color.withAlphaComponent(alpha) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + textColor = theme.colors[value] + return + } + + textColor = theme.colors[value]?.withAlphaComponent(alpha) + + case .none: textColor = nil + } + } + get { return self.textColor.map { .color($0) } } + } } public extension UIButton { @@ -71,6 +253,29 @@ public extension UIButton { ) } + func setThemeBackgroundColorForced(_ newValue: ForcedThemeValue?, for state: UIControl.State) { + switch newValue { + case .color(let color): self.setBackgroundImage(color.toImage(), for: state) + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + self.setBackgroundImage(value.color.toImage(), for: state) + return + } + + self.setBackgroundImage(value.color.withAlphaComponent(alpha).toImage(), for: state) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + self.setBackgroundImage(theme.colors[value]?.toImage(), for: state) + return + } + + self.setBackgroundImage(theme.colors[value]?.withAlphaComponent(alpha).toImage(), for: state) + + case .none: self.setBackgroundImage(nil, for: state) + } + } + func setThemeTitleColor(_ value: ThemeValue?, for state: UIControl.State) { let keyPath: KeyPath = \.titleLabel?.textColor @@ -95,6 +300,29 @@ public extension UIButton { } ) } + + func setThemeTitleColorForced(_ newValue: ForcedThemeValue?, for state: UIControl.State) { + switch newValue { + case .color(let color): self.setTitleColor(color, for: state) + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + self.setTitleColor(value.color, for: state) + return + } + + self.setTitleColor(value.color.withAlphaComponent(alpha), for: state) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + self.setTitleColor(theme.colors[value], for: state) + return + } + + self.setTitleColor(theme.colors[value]?.withAlphaComponent(alpha), for: state) + + case .none: self.setTitleColor(nil, for: state) + } + } } public extension UISwitch { @@ -117,12 +345,57 @@ public extension UIProgressView { get { return nil } } + var themeProgressTintColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): progressTintColor = color + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + progressTintColor = value.color + return + } + + progressTintColor = value.color.withAlphaComponent(alpha) + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + progressTintColor = theme.colors[value] + return + } + + progressTintColor = theme.colors[value]?.withAlphaComponent(alpha) + + case .none: progressTintColor = nil + } + } + get { return self.progressTintColor.map { .color($0) } } + } + var themeTrackTintColor: ThemeValue? { set { ThemeManager.set(self, keyPath: \.trackTintColor, to: newValue) } get { return nil } } } +public extension UISlider { + var themeMinimumTrackTintColor: ThemeValue? { + set { ThemeManager.set(self, keyPath: \.minimumTrackTintColor, to: newValue) } + get { return nil } + } + + var themeMaximumTrackTintColor: ThemeValue? { + set { ThemeManager.set(self, keyPath: \.maximumTrackTintColor, to: newValue) } + get { return nil } + } +} + +public extension UIToolbar { + var themeBarTintColor: ThemeValue? { + set { ThemeManager.set(self, keyPath: \.barTintColor, to: newValue) } + get { return nil } + } +} + public extension UIContextualAction { var themeBackgroundColor: ThemeValue? { set { ThemeManager.set(self, keyPath: \.backgroundColor, to: newValue) } @@ -136,13 +409,96 @@ public extension CAShapeLayer { get { return nil } } + var themeStrokeColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): strokeColor = color.cgColor + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + strokeColor = value.color.cgColor + return + } + + strokeColor = value.color.withAlphaComponent(alpha).cgColor + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + strokeColor = theme.colors[value]?.cgColor + return + } + + strokeColor = theme.colors[value]?.withAlphaComponent(alpha).cgColor + + case .none: strokeColor = nil + } + } + get { return self.strokeColor.map { .color(UIColor(cgColor: $0)) } } + } + var themeFillColor: ThemeValue? { set { ThemeManager.set(self, keyPath: \.fillColor, to: newValue) } get { return nil } } + + var themeFillColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): fillColor = color.cgColor + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + fillColor = value.color.cgColor + return + } + + fillColor = value.color.withAlphaComponent(alpha).cgColor + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + fillColor = theme.colors[value]?.cgColor + return + } + + fillColor = theme.colors[value]?.withAlphaComponent(alpha).cgColor + + case .none: fillColor = nil + } + } + get { return self.fillColor.map { .color(UIColor(cgColor: $0)) } } + } } public extension CALayer { + var themeBackgroundColor: ThemeValue? { + set { ThemeManager.set(self, keyPath: \.backgroundColor, to: newValue) } + get { return nil } + } + + var themeBackgroundColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): backgroundColor = color.cgColor + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + backgroundColor = value.color.cgColor + return + } + + backgroundColor = value.color.withAlphaComponent(alpha).cgColor + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + backgroundColor = theme.colors[value]?.cgColor + return + } + + backgroundColor = theme.colors[value]?.withAlphaComponent(alpha).cgColor + + case .none: backgroundColor = nil + } + } + get { return self.backgroundColor.map { .color(UIColor(cgColor: $0)) } } + } + var themeBorderColor: ThemeValue? { set { ThemeManager.set(self, keyPath: \.borderColor, to: newValue) } get { return nil } @@ -153,3 +509,42 @@ public extension CALayer { get { return nil } } } + +public extension CATextLayer { + var themeForegroundColor: ThemeValue? { + set { ThemeManager.set(self, keyPath: \.foregroundColor, to: newValue) } + get { return nil } + } + + var themeForegroundColorForced: ForcedThemeValue? { + set { + switch newValue { + case .color(let color): foregroundColor = color.cgColor + case .primary(let value, let alpha): + guard let alpha: CGFloat = alpha else { + foregroundColor = value.color.cgColor + return + } + + foregroundColor = value.color.withAlphaComponent(alpha).cgColor + + case .theme(let theme, let value, let alpha): + guard let alpha: CGFloat = alpha else { + foregroundColor = theme.colors[value]?.cgColor + return + } + + foregroundColor = theme.colors[value]?.withAlphaComponent(alpha).cgColor + + case .none: foregroundColor = nil + } + } + get { return self.foregroundColor.map { .color(UIColor(cgColor: $0)) } } + } +} + +public extension NSMutableAttributedString { + func addThemeAttribute(_ attribute: ForcedThemeAttribute, range: NSRange) { + self.addAttribute(attribute.key, value: attribute.value, range: range) + } +} diff --git a/SessionUIKit/Utilities/UIImage+Tinting.swift b/SessionUIKit/Utilities/UIImage+Tinting.swift index ddabf8b4b..e6f83b909 100644 --- a/SessionUIKit/Utilities/UIImage+Tinting.swift +++ b/SessionUIKit/Utilities/UIImage+Tinting.swift @@ -1,11 +1,13 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + import UIKit public extension UIImage { - func withTint(_ color: UIColor) -> UIImage? { let template = self.withRenderingMode(.alwaysTemplate) let imageView = UIImageView(image: template) - imageView.tintColor = color + imageView.themeTintColorForced = .color(color) + return imageView.toImage(isOpaque: imageView.isOpaque, scale: UIScreen.main.scale) } } diff --git a/SessionUtilitiesKit/General/UIView+OWS.h b/SessionUtilitiesKit/General/UIView+OWS.h index e010b7d1c..70bb155b2 100644 --- a/SessionUtilitiesKit/General/UIView+OWS.h +++ b/SessionUtilitiesKit/General/UIView+OWS.h @@ -111,22 +111,12 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value); - (NSArray *)autoPinToEdgesOfView:(UIView *)view; -- (void)traverseViewHierarchyWithVisitor:(UIViewVisitorBlock)visitor; - #pragma mark - Containers + (UIView *)containerView; + (UIView *)verticalStackWithSubviews:(NSArray *)subviews spacing:(int)spacing; -#pragma mark - Debugging - -- (void)addBorderWithColor:(UIColor *)color; -- (void)addRedBorder; - -// Add red border to self, and all subviews recursively. -- (void)addRedBorderRecursively; - @end #pragma mark - diff --git a/SessionUtilitiesKit/General/UIView+OWS.m b/SessionUtilitiesKit/General/UIView+OWS.m index 63d50459c..490ab1efd 100644 --- a/SessionUtilitiesKit/General/UIView+OWS.m +++ b/SessionUtilitiesKit/General/UIView+OWS.m @@ -439,36 +439,6 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) return container; } -#pragma mark - Debugging - -- (void)addBorderWithColor:(UIColor *)color -{ - self.layer.borderColor = color.CGColor; - self.layer.borderWidth = 1; -} - -- (void)addRedBorder -{ - [self addBorderWithColor:[UIColor redColor]]; -} - -- (void)addRedBorderRecursively -{ - [self addRedBorder]; - for (UIView *subview in self.subviews) { - [subview addRedBorderRecursively]; - } -} - -- (void)traverseViewHierarchyWithVisitor:(UIViewVisitorBlock)visitor -{ - visitor(self); - - for (UIView *subview in self.subviews) { - [subview traverseViewHierarchyWithVisitor:visitor]; - } -} - @end #pragma mark - diff --git a/SignalUtilitiesKit/Configuration.swift b/SignalUtilitiesKit/Configuration.swift index ec275f1c8..2c0414db2 100644 --- a/SignalUtilitiesKit/Configuration.swift +++ b/SignalUtilitiesKit/Configuration.swift @@ -14,6 +14,6 @@ public enum Configuration { SNMessagingKit.configure() SNSnodeKit.configure() - SUIKit.configure() + SNUIKit.configure() } } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift index 1a71f3808..983cf35f6 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift @@ -1,6 +1,4 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import UIKit @@ -55,17 +53,17 @@ class AttachmentApprovalInputAccessoryView: UIView { // sizing when used as an input accessory view. self.autoresizingMask = .flexibleHeight self.translatesAutoresizingMaskIntoConstraints = false - self.backgroundColor = .clear + self.themeBackgroundColor = .clear preservesSuperviewLayoutMargins = true // Use a background view that extends below the keyboard to avoid animation glitches. let backgroundView = UIView() - backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.6) + backgroundView.themeBackgroundColor = .backgroundPrimary addSubview(backgroundView) backgroundView.autoPinEdgesToSuperviewEdges() - currentCaptionLabel.textColor = .white + currentCaptionLabel.themeTextColor = .white currentCaptionLabel.font = .systemFont(ofSize: Values.mediumFontSize) currentCaptionLabel.numberOfLines = 5 currentCaptionLabel.lineBreakMode = .byWordWrapping @@ -90,7 +88,7 @@ class AttachmentApprovalInputAccessoryView: UIView { stackView.autoPinEdge(toSuperviewMargin: .bottom) let galleryRailBlockingView: UIView = UIView() - galleryRailBlockingView.backgroundColor = backgroundView.backgroundColor + galleryRailBlockingView.themeBackgroundColor = .backgroundPrimary stackView.addSubview(galleryRailBlockingView) galleryRailBlockingView.pin(.top, to: .bottom, of: attachmentTextToolbar) galleryRailBlockingView.pin(.left, to: .left, of: stackView) @@ -101,9 +99,8 @@ class AttachmentApprovalInputAccessoryView: UIView { // MARK: - Events @objc func captionTapped(sender: UIGestureRecognizer) { - guard sender.state == .recognized else { - return - } + guard sender.state == .recognized else { return } + delegate?.attachmentApprovalInputStartEditingCaptions() } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift index 87593e7c3..0d2bafdec 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift @@ -213,18 +213,8 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC override public func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = Colors.navigationBarBackground + self.view.themeBackgroundColor = .backgroundSecondary - let backgroundImage: UIImage = UIImage(color: Colors.navigationBarBackground) - self.navigationItem.title = nil - self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) - self.navigationController?.navigationBar.shadowImage = UIImage() - self.navigationController?.navigationBar.isTranslucent = false - self.navigationController?.navigationBar.barTintColor = Colors.navigationBarBackground - (self.navigationController?.navigationBar as? OWSNavigationBar)?.respectsTheme = true - self.navigationController?.navigationBar.backgroundColor = Colors.navigationBarBackground - self.navigationController?.navigationBar.setBackgroundImage(backgroundImage, for: .default) - // Avoid an unpleasant "bounce" which doesn't make sense in the context of a single item. pagerScrollView?.isScrollEnabled = (attachmentItems.count > 1) @@ -369,12 +359,9 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC let cancelButton = OWSButton(title: CommonStrings.cancelButton) { [weak self] in self?.cancelPressed() } - cancelButton.setTitleColor(Colors.text, for: .normal) - if let titleLabel = cancelButton.titleLabel { - titleLabel.font = UIFont.systemFont(ofSize: 17.0) - } else { - owsFailDebug("Missing titleLabel.") - } + cancelButton.titleLabel?.font = .systemFont(ofSize: 17.0) + cancelButton.setThemeTitleColor(.textPrimary, for: .normal) + cancelButton.setThemeTitleColor(.textSecondary, for: .highlighted) cancelButton.sizeToFit() navigationItem.leftBarButtonItem = UIBarButtonItem(customView: cancelButton) } @@ -382,7 +369,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC // Mimic a conventional back button, but with a shadow. let isRTL = CurrentAppContext().isRTL let imageName = (isRTL ? "NavBarBackRTL" : "NavBarBack") - let backButton = OWSButton(imageName: imageName, tintColor: Colors.text) { [weak self] in + let backButton = OWSButton(imageName: imageName, tintColor: .textPrimary) { [weak self] in self?.navigationController?.popViewController(animated: true) } @@ -727,13 +714,9 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC // MARK: - extension AttachmentApprovalViewController: AttachmentTextToolbarDelegate { - func attachmentTextToolbarDidBeginEditing(_ attachmentTextToolbar: AttachmentTextToolbar) { - currentPageViewController?.setAttachmentViewScale(.compact, animated: true) - } + func attachmentTextToolbarDidBeginEditing(_ attachmentTextToolbar: AttachmentTextToolbar) {} - func attachmentTextToolbarDidEndEditing(_ attachmentTextToolbar: AttachmentTextToolbar) { - currentPageViewController?.setAttachmentViewScale(.fullsize, animated: true) - } + func attachmentTextToolbarDidEndEditing(_ attachmentTextToolbar: AttachmentTextToolbar) {} func attachmentTextToolbarDidTapSend(_ attachmentTextToolbar: AttachmentTextToolbar) { // Toolbar flickers in and out if there are errors @@ -768,9 +751,9 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate extension SignalAttachmentItem: GalleryRailItem { func buildRailItemView() -> UIView { let imageView = UIImageView() - imageView.contentMode = .scaleAspectFill - imageView.backgroundColor = UIColor.black.withAlphaComponent(0.33) imageView.image = getThumbnailImage() + imageView.themeBackgroundColor = .backgroundSecondary + imageView.contentMode = .scaleAspectFill return imageView } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift index f319ff2f1..cc348c6b7 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionToolbar.swift @@ -6,7 +6,7 @@ import Foundation import UIKit import SessionUIKit -protocol AttachmentCaptionToolbarDelegate: class { +protocol AttachmentCaptionToolbarDelegate: AnyObject { func attachmentCaptionToolbarDidEdit(_ attachmentCaptionToolbar: AttachmentCaptionToolbar) func attachmentCaptionToolbarDidComplete() } @@ -49,7 +49,7 @@ class AttachmentCaptionToolbar: UIView, UITextViewDelegate { // sizing when used as an input accessory view. self.autoresizingMask = .flexibleHeight self.translatesAutoresizingMaskIntoConstraints = false - self.backgroundColor = UIColor.clear + self.themeBackgroundColor = .clear textView.delegate = self @@ -92,12 +92,12 @@ class AttachmentCaptionToolbar: UIView, UITextViewDelegate { let lengthLimitLabel = UILabel() // Length Limit Label shown when the user inputs too long of a message - lengthLimitLabel.textColor = .white + lengthLimitLabel.themeTextColor = .textPrimary lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the media message field.") lengthLimitLabel.textAlignment = .center // Add shadow in case overlayed on white content - lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor + lengthLimitLabel.themeShadowColor = .black lengthLimitLabel.layer.shadowOffset = .zero lengthLimitLabel.layer.shadowOpacity = 0.8 lengthLimitLabel.layer.shadowRadius = 2.0 @@ -127,11 +127,11 @@ class AttachmentCaptionToolbar: UIView, UITextViewDelegate { let textView = AttachmentTextView() textView.keyboardAppearance = isLightMode ? .default : .dark - textView.backgroundColor = .clear - textView.tintColor = .white + textView.themeBackgroundColor = .clear + textView.themeTintColor = .textPrimary textView.font = UIFont.ows_dynamicTypeBody - textView.textColor = .white + textView.themeTextColor = .textPrimary textView.textContainerInset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7) return textView diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionViewController.swift deleted file mode 100644 index d792e433d..000000000 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentCaptionViewController.swift +++ /dev/null @@ -1,316 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import UIKit -import SessionUIKit - -protocol AttachmentCaptionDelegate: class { - func captionView(_ captionView: AttachmentCaptionViewController, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem) - func captionViewDidCancel() -} - -// MARK: - - -class AttachmentCaptionViewController: OWSViewController { - - weak var delegate: AttachmentCaptionDelegate? - - private let attachmentItem: SignalAttachmentItem - - private let originalCaptionText: String? - - private let textView = UITextView() - - private var textViewHeightConstraint: NSLayoutConstraint? - - private let kMaxCaptionCharacterCount = 240 - - init(delegate: AttachmentCaptionDelegate, - attachmentItem: SignalAttachmentItem) { - self.delegate = delegate - self.attachmentItem = attachmentItem - self.originalCaptionText = attachmentItem.captionText - - super.init(nibName: nil, bundle: nil) - - self.addObserver(textView, forKeyPath: "contentSize", options: .new, context: nil) - } - - @available(*, unavailable, message: "use other init() instead.") - required public init?(coder aDecoder: NSCoder) { - notImplemented() - } - - deinit { - self.removeObserver(textView, forKeyPath: "contentSize") - } - - open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) { - updateTextView() - } - - // MARK: - View Lifecycle - - public override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - textView.becomeFirstResponder() - - updateTextView() - } - - public override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - textView.becomeFirstResponder() - - updateTextView() - } - - public override func loadView() { - self.view = UIView() - self.view.backgroundColor = UIColor(white: 0, alpha: 0.25) - self.view.isOpaque = false - - self.view.isUserInteractionEnabled = true - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(backgroundTapped))) - - configureTextView() - - let doneIcon = UIImage(named: "image_editor_checkmark_full")?.withRenderingMode(.alwaysTemplate) - let doneButton = UIBarButtonItem(image: doneIcon, style: .plain, - target: self, - action: #selector(didTapDone)) - doneButton.tintColor = .white - navigationItem.rightBarButtonItem = doneButton - - self.view.layoutMargins = .zero - - lengthLimitLabel.setContentHuggingHigh() - lengthLimitLabel.setCompressionResistanceHigh() - - let stackView = UIStackView(arrangedSubviews: [lengthLimitLabel, textView]) - stackView.axis = .vertical - stackView.spacing = 20 - stackView.alignment = .fill - stackView.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) - stackView.isLayoutMarginsRelativeArrangement = true - self.view.addSubview(stackView) - stackView.autoPinEdge(toSuperviewEdge: .leading) - stackView.autoPinEdge(toSuperviewEdge: .trailing) - self.autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true) - - let backgroundView = UIView() - backgroundView.backgroundColor = UIColor(white: 0, alpha: 0.5) - view.addSubview(backgroundView) - view.sendSubviewToBack(backgroundView) - backgroundView.autoPinEdge(toSuperviewEdge: .leading) - backgroundView.autoPinEdge(toSuperviewEdge: .trailing) - backgroundView.autoPinEdge(toSuperviewEdge: .bottom) - backgroundView.autoPinEdge(.top, to: .top, of: stackView) - - let minTextHeight: CGFloat = textView.font?.lineHeight ?? 0 - textViewHeightConstraint = textView.autoSetDimension(.height, toSize: minTextHeight) - - view.addSubview(placeholderTextView) - placeholderTextView.autoAlignAxis(.horizontal, toSameAxisOf: textView) - placeholderTextView.autoPinEdge(.leading, to: .leading, of: textView) - placeholderTextView.autoPinEdge(.trailing, to: .trailing, of: textView) - } - - private func configureTextView() { - textView.delegate = self - - textView.text = attachmentItem.captionText - textView.font = UIFont.ows_dynamicTypeBody - textView.textColor = .white - - textView.isEditable = true - textView.backgroundColor = .clear - textView.isOpaque = false - // We use a white cursor since we use a dark background. - textView.tintColor = .white - textView.isScrollEnabled = true - textView.scrollsToTop = false - textView.isUserInteractionEnabled = true - textView.textAlignment = .left - textView.textContainerInset = .zero - textView.textContainer.lineFragmentPadding = 0 - textView.contentInset = .zero - } - - // MARK: - Events - - @objc func backgroundTapped(sender: UIGestureRecognizer) { - AssertIsOnMainThread() - - completeAndDismiss(didCancel: false) - } - - @objc public func didTapCancel() { - completeAndDismiss(didCancel: true) - } - - @objc public func didTapDone() { - completeAndDismiss(didCancel: false) - } - - private func completeAndDismiss(didCancel: Bool) { - if didCancel { - self.delegate?.captionViewDidCancel() - } else { - self.delegate?.captionView(self, didChangeCaptionText: self.textView.text, attachmentItem: attachmentItem) - } - - self.dismiss(animated: true) { - // Do nothing. - } - } - - // MARK: - Length Limit - - private lazy var lengthLimitLabel: UILabel = { - let lengthLimitLabel = UILabel() - - // Length Limit Label shown when the user inputs too long of a message - lengthLimitLabel.textColor = UIColor.ows_destructiveRed - lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the attachment caption.") - lengthLimitLabel.textAlignment = .center - - // Add shadow in case overlayed on white content - lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor - lengthLimitLabel.layer.shadowOffset = .zero - lengthLimitLabel.layer.shadowOpacity = 0.8 - lengthLimitLabel.isHidden = true - - return lengthLimitLabel - }() - - // MARK: - Text Height - - // TODO: We need to revisit this with Myles. - func updatePlaceholderTextViewVisibility() { - let isHidden: Bool = { - guard !self.textView.isFirstResponder else { - return true - } - - guard let captionText = self.textView.text else { - return false - } - - guard captionText.count > 0 else { - return false - } - - return true - }() - - placeholderTextView.isHidden = isHidden - } - - private lazy var placeholderTextView: UIView = { - let placeholderTextView = UITextView() - placeholderTextView.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER", comment: "placeholder text for an empty captioning field") - placeholderTextView.isEditable = false - - placeholderTextView.backgroundColor = .clear - placeholderTextView.font = UIFont.ows_dynamicTypeBody - - placeholderTextView.textColor = Colors.text - placeholderTextView.tintColor = Colors.text - placeholderTextView.returnKeyType = .done - - return placeholderTextView - }() - - // MARK: - Text Height - - private func updateTextView() { - guard let textViewHeightConstraint = textViewHeightConstraint else { - owsFailDebug("Missing textViewHeightConstraint.") - return - } - - let contentSize = textView.sizeThatFits(CGSize(width: textView.width(), height: CGFloat.greatestFiniteMagnitude)) - - // `textView.contentSize` isn't accurate when restoring a multiline draft, so we compute it here. - textView.contentSize = contentSize - - let minHeight: CGFloat = textView.font?.lineHeight ?? 0 - let maxHeight: CGFloat = 300 - let newHeight = contentSize.height.clamp(minHeight, maxHeight) - - textViewHeightConstraint.constant = newHeight - textView.invalidateIntrinsicContentSize() - textView.superview?.invalidateIntrinsicContentSize() - - textView.isScrollEnabled = contentSize.height > maxHeight - - updatePlaceholderTextViewVisibility() - } -} - -extension AttachmentCaptionViewController: UITextViewDelegate { - - public func textViewDidChange(_ textView: UITextView) { - updateTextView() - } - - public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { - let existingText: String = textView.text ?? "" - let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text) - - let kMaxCaptionByteCount = kOversizeTextMessageSizeThreshold / 4 - guard proposedText.utf8.count <= kMaxCaptionByteCount else { - Logger.debug("hit caption byte count limit") - self.lengthLimitLabel.isHidden = false - - // `range` represents the section of the existing text we will replace. We can re-use that space. - // Range is in units of NSStrings's standard UTF-16 characters. Since some of those chars could be - // represented as single bytes in utf-8, while others may be 8 or more, the only way to be sure is - // to just measure the utf8 encoded bytes of the replaced substring. - let bytesAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").utf8.count - - // Accept as much of the input as we can - let byteBudget: Int = Int(kOversizeTextMessageSizeThreshold) - bytesAfterDelete - if byteBudget >= 0, let acceptableNewText = text.truncated(toByteCount: UInt(byteBudget)) { - textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) - } - - return false - } - - // After verifying the byte-length is sufficiently small, verify the character count is within bounds. - // Normally this character count should entail *much* less byte count. - guard proposedText.count <= kMaxCaptionCharacterCount else { - Logger.debug("hit caption character count limit") - - self.lengthLimitLabel.isHidden = false - - // `range` represents the section of the existing text we will replace. We can re-use that space. - let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count - - // Accept as much of the input as we can - let charBudget: Int = Int(kMaxCaptionCharacterCount) - charsAfterDelete - if charBudget >= 0 { - let acceptableNewText = String(text.prefix(charBudget)) - textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) - } - - return false - } - - self.lengthLimitLabel.isHidden = true - return true - } - - public func textViewDidBeginEditing(_ textView: UITextView) { - updatePlaceholderTextViewVisibility() - } - - public func textViewDidEndEditing(_ textView: UITextView) { - updatePlaceholderTextViewVisibility() - } -} diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift index 503b28ad0..778c0bf13 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentItemCollection.swift @@ -9,10 +9,10 @@ import SessionMessagingKit class AddMoreRailItem: GalleryRailItem { func buildRailItemView() -> UIView { let view = UIView() - view.backgroundColor = UIColor.black.withAlphaComponent(0.33) + view.themeBackgroundColor = .backgroundSecondary let iconView = UIImageView(image: #imageLiteral(resourceName: "ic_plus_24").withRenderingMode(.alwaysTemplate)) - iconView.tintColor = .ows_white + iconView.themeTintColor = .textPrimary view.addSubview(iconView) iconView.setCompressionResistanceHigh() iconView.setContentHuggingHigh() @@ -66,9 +66,9 @@ class SignalAttachmentItem: Hashable { } // MARK: Hashable - - public var hashValue: Int { - return attachment.hashValue + + func hash(into hasher: inout Hasher) { + attachment.hash(into: &hasher) } // MARK: Equatable diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift index 0258dc0e3..a00824b74 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentPrepViewController.swift @@ -1,6 +1,4 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import UIKit @@ -49,7 +47,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD AttachmentTextToolbar.kMinTextViewHeight + (AttachmentTextToolbar.kToolbarMargin * 2) ) - private lazy var scrollView: UIScrollView = { + public lazy var scrollView: UIScrollView = { // Scroll View - used to zoom/pan on images and video let scrollView: UIScrollView = UIScrollView() scrollView.translatesAutoresizingMaskIntoConstraints = false @@ -124,9 +122,8 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD }() public var shouldHideControls: Bool { - guard let imageEditorView = imageEditorView else { - return false - } + guard let imageEditorView = imageEditorView else { return false } + return imageEditorView.shouldHideControls } @@ -134,7 +131,9 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD init(attachmentItem: SignalAttachmentItem) { self.attachmentItem = attachmentItem + super.init(nibName: nil, bundle: nil) + if attachment.hasError { owsFailDebug(attachment.error.debugDescription) } @@ -149,13 +148,16 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD public override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = Colors.navigationBarBackground + view.themeBackgroundColor = .backgroundSecondary view.addSubview(contentContainerView) contentContainerView.addSubview(scrollView) scrollView.addSubview(mediaMessageView) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(screenTapped)) + mediaMessageView.addGestureRecognizer(tapGesture) + if attachment.isImage, let editorView: ImageEditorView = imageEditorView { view.addSubview(editorView) @@ -292,9 +294,13 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD } // MARK: - Event Handlers + + @objc func screenTapped() { + self.view.window?.endEditing(true) + } @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { - assert(self.videoPlayer != nil) + self.view.window?.endEditing(true) self.pauseVideo() } @@ -527,25 +533,15 @@ extension AttachmentPrepViewController: UIScrollViewDelegate { // MARK: - extension AttachmentPrepViewController: ImageEditorViewDelegate { - public func imageEditor(presentFullScreenView viewController: UIViewController, - isTransparent: Bool) { - + public func imageEditor(presentFullScreenView viewController: UIViewController, isTransparent: Bool) { let navigationController = OWSNavigationController(rootViewController: viewController) - navigationController.modalPresentationStyle = (isTransparent - ? .overFullScreen - : .fullScreen) + navigationController.modalPresentationStyle = (isTransparent ? + .overFullScreen : + .fullScreen + ) navigationController.ows_prefersStatusBarHidden = true - navigationController.view.backgroundColor = Colors.navigationBarBackground - - if let navigationBar = navigationController.navigationBar as? OWSNavigationBar { - navigationBar.overrideTheme(type: .clear) - } else { - owsFailDebug("navigationBar was nil or unexpected class") - } - - self.present(navigationController, animated: false) { - // Do nothing. - } + + self.present(navigationController, animated: false, completion: nil) } public func imageEditorUpdateNavigationBar() { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift index 01bb11578..32efb128c 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift @@ -9,7 +9,7 @@ import SessionUIKit // Coincides with Android's max text message length let kMaxMessageBodyCharacterCount = 2000 -protocol AttachmentTextToolbarDelegate: class { +protocol AttachmentTextToolbarDelegate: AnyObject { func attachmentTextToolbarDidTapSend(_ attachmentTextToolbar: AttachmentTextToolbar) func attachmentTextToolbarDidBeginEditing(_ attachmentTextToolbar: AttachmentTextToolbar) func attachmentTextToolbarDidEndEditing(_ attachmentTextToolbar: AttachmentTextToolbar) @@ -55,7 +55,7 @@ class AttachmentTextToolbar: UIView, UITextViewDelegate { // sizing when used as an input accessory view. self.autoresizingMask = .flexibleHeight self.translatesAutoresizingMaskIntoConstraints = false - self.backgroundColor = UIColor.clear + self.themeBackgroundColor = .clear textView.delegate = self @@ -65,7 +65,7 @@ class AttachmentTextToolbar: UIView, UITextViewDelegate { sendButton.titleLabel?.font = .boldSystemFont(ofSize: Values.mediumFontSize) sendButton.titleLabel?.textAlignment = .center - sendButton.tintColor = Colors.accent + sendButton.themeTintColor = .primary // Increase hit area of send button sendButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8) @@ -138,12 +138,12 @@ class AttachmentTextToolbar: UIView, UITextViewDelegate { let lengthLimitLabel = UILabel() // Length Limit Label shown when the user inputs too long of a message - lengthLimitLabel.textColor = .white - lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the media message field.") + lengthLimitLabel.text = "ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED".localized() + lengthLimitLabel.themeTextColor = .textPrimary lengthLimitLabel.textAlignment = .center // Add shadow in case overlayed on white content - lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor + lengthLimitLabel.themeShadowColor = .black lengthLimitLabel.layer.shadowOffset = .zero lengthLimitLabel.layer.shadowOpacity = 0.8 lengthLimitLabel.layer.shadowRadius = 2.0 @@ -173,7 +173,7 @@ class AttachmentTextToolbar: UIView, UITextViewDelegate { private lazy var textContainer: UIView = { let textContainer = UIView() - textContainer.layer.borderColor = UIColor.white.cgColor + textContainer.themeBorderColor = .borderSeparator textContainer.layer.borderWidth = Values.separatorThickness textContainer.layer.cornerRadius = (AttachmentTextToolbar.kMinTextViewHeight / 2) textContainer.clipsToBounds = true @@ -191,11 +191,11 @@ class AttachmentTextToolbar: UIView, UITextViewDelegate { let textView = AttachmentTextView() textView.keyboardAppearance = isLightMode ? .default : .dark - textView.backgroundColor = .clear - textView.tintColor = .white + textView.themeBackgroundColor = .clear + textView.themeTintColor = .textPrimary textView.font = .systemFont(ofSize: Values.mediumFontSize) - textView.textColor = .white + textView.themeTextColor = .textPrimary textView.showsVerticalScrollIndicator = false textView.textContainerInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift index e777e120d..d88a52a31 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorBrushViewController.swift @@ -1,12 +1,10 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit import SessionUIKit @objc -public protocol ImageEditorBrushViewControllerDelegate: class { +public protocol ImageEditorBrushViewControllerDelegate: AnyObject { func brushDidComplete(currentColor: ImageEditorColor) } @@ -17,24 +15,27 @@ public class ImageEditorBrushViewController: OWSViewController { private weak var delegate: ImageEditorBrushViewControllerDelegate? private let model: ImageEditorModel - private let canvasView: ImageEditorCanvasView - private let paletteView: ImageEditorPaletteView + private let bottomInset: CGFloat // We only want to let users undo changes made in this view. // So we snapshot any older "operation id" and prevent // users from undoing it. private let firstUndoOperationId: String? - init(delegate: ImageEditorBrushViewControllerDelegate, - model: ImageEditorModel, - currentColor: ImageEditorColor) { + init( + delegate: ImageEditorBrushViewControllerDelegate, + model: ImageEditorModel, + currentColor: ImageEditorColor, + bottomInset: CGFloat + ) { self.delegate = delegate self.model = model self.canvasView = ImageEditorCanvasView(model: model) self.paletteView = ImageEditorPaletteView(currentColor: currentColor) self.firstUndoOperationId = model.currentUndoOperationId() + self.bottomInset = bottomInset super.init(nibName: nil, bundle: nil) @@ -50,16 +51,19 @@ public class ImageEditorBrushViewController: OWSViewController { public override func loadView() { self.view = UIView() - self.view.backgroundColor = Colors.navigationBarBackground + self.view.themeBackgroundColor = .backgroundSecondary self.view.isOpaque = true canvasView.configureSubviews() self.view.addSubview(canvasView) - canvasView.autoPinEdgesToSuperviewEdges() + canvasView.pin(.top, to: .top, of: self.view) + canvasView.pin(.leading, to: .leading, of: self.view) + canvasView.pin(.trailing, to: .trailing, of: self.view) + canvasView.pin(.bottom, to: .bottom, of: self.view, withInset: -bottomInset) paletteView.delegate = self self.view.addSubview(paletteView) - paletteView.autoVCenterInSuperview() + paletteView.center(.vertical, in: self.view, withInset: -(bottomInset / 2)) paletteView.autoPinEdge(toSuperviewEdge: .trailing, withInset: 0) self.view.isUserInteractionEnabled = true diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift index 92e4f93c1..57859ef50 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCanvasView.swift @@ -1,8 +1,7 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import SessionUIKit public class EditorTextLayer: CATextLayer { let itemId: String @@ -71,13 +70,13 @@ public class ImageEditorCanvasView: UIView { @objc public func configureSubviews() { - self.backgroundColor = .clear + self.themeBackgroundColor = .clear self.isOpaque = false self.srcImage = loadSrcImage() clipView.clipsToBounds = true - clipView.backgroundColor = .clear + clipView.themeBackgroundColor = .clear clipView.isOpaque = false clipView.layoutCallback = { [weak self] (_) in guard let strongSelf = self else { @@ -92,7 +91,7 @@ public class ImageEditorCanvasView: UIView { imageLayer.contentsScale = srcImage.scale } - contentView.backgroundColor = .clear + contentView.themeBackgroundColor = .clear contentView.isOpaque = false contentView.layer.addSublayer(imageLayer) contentView.layoutCallback = { [weak self] (_) in @@ -396,7 +395,7 @@ public class ImageEditorCanvasView: UIView { let shapeLayer = CAShapeLayer() shapeLayer.lineWidth = strokeWidth - shapeLayer.strokeColor = item.color.cgColor + shapeLayer.themeStrokeColorForced = .color(item.color) shapeLayer.frame = CGRect(origin: .zero, size: viewSize) // Stroke samples are specified in "image unit" coordinates, but @@ -469,7 +468,7 @@ public class ImageEditorCanvasView: UIView { } shapeLayer.path = bezierPath.cgPath - shapeLayer.fillColor = nil + shapeLayer.themeFillColor = nil shapeLayer.lineCap = CAShapeLayerLineCap.round shapeLayer.lineJoin = CAShapeLayerLineJoin.round shapeLayer.zPosition = zPositionForItem(item: item, model: model, zPositionBase: brushLayerZ) @@ -501,14 +500,17 @@ public class ImageEditorCanvasView: UIView { let fontSize = item.font.pointSize * imageFrame.size.width / item.fontReferenceImageWidth let text = item.text.filterForDisplay ?? "" - let attributedString = NSAttributedString(string: text, - attributes: [ - NSAttributedString.Key.font: item.font.withSize(fontSize), - NSAttributedString.Key.foregroundColor: item.color.color - ]) + let attributedString: NSMutableAttributedString = NSMutableAttributedString( + string: text, + attributes: [ .font: item.font.withSize(fontSize) ] + ) + attributedString.addThemeAttribute( + .foreground(item.color.color), + range: NSRange(location: 0, length: text.count) + ) let layer = EditorTextLayer(itemId: item.itemId) layer.string = attributedString - layer.foregroundColor = item.color.cgColor + layer.themeForegroundColorForced = .color(item.color.color) layer.font = CGFont(item.font.fontName as CFString) layer.fontSize = fontSize layer.isWrapped = true @@ -608,14 +610,14 @@ public class ImageEditorCanvasView: UIView { // Because CALayer.renderInContext() doesn't honor CALayer properties like frame, // transform, etc. let view = UIView() - view.backgroundColor = UIColor.clear + view.themeBackgroundColor = .clear view.isOpaque = false view.frame = CGRect(origin: .zero, size: viewSize) // Rendering a UIView to an image will not honor the root image's layer transform. // We therefore use a subview. let contentView = UIView() - contentView.backgroundColor = UIColor.clear + contentView.themeBackgroundColor = .clear contentView.isOpaque = false contentView.frame = CGRect(origin: .zero, size: viewSize) view.addSubview(contentView) diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift index fa0e74d3d..3cf771a7e 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift @@ -87,34 +87,40 @@ class ImageEditorCropViewController: OWSViewController { override func loadView() { self.view = UIView() - self.view.backgroundColor = Colors.navigationBarBackground + self.view.themeBackgroundColor = .backgroundSecondary self.view.layoutMargins = .zero // MARK: - Buttons - let rotate90Button = OWSButton(imageName: "image_editor_rotate", - tintColor: Colors.text) { [weak self] in + let rotate90Button = OWSButton( + imageName: "image_editor_rotate", + tintColor: .textPrimary + ) { [weak self] in self?.rotate90ButtonPressed() } - let flipButton = OWSButton(imageName: "image_editor_flip", - tintColor: Colors.text) { [weak self] in - self?.flipButtonPressed() + let flipButton = OWSButton( + imageName: "image_editor_flip", + tintColor: .textPrimary + ) { [weak self] in + self?.flipButtonPressed() } - let cropLockButton = OWSButton(imageName: "image_editor_crop_unlock", - tintColor: Colors.text) { [weak self] in - self?.cropLockButtonPressed() + let cropLockButton = OWSButton( + imageName: "image_editor_crop_unlock", + tintColor: .textPrimary + ) { [weak self] in + self?.cropLockButtonPressed() } self.cropLockButton = cropLockButton // MARK: - Canvas & Wrapper let wrapperView = UIView.container() - wrapperView.backgroundColor = .clear + wrapperView.themeBackgroundColor = .clear wrapperView.isOpaque = false // TODO: We could mask the clipped region with a semi-transparent overlay like WA. clipView.clipsToBounds = true - clipView.backgroundColor = .clear + clipView.themeBackgroundColor = .clear clipView.isOpaque = false clipView.layoutCallback = { [weak self] (_) in guard let strongSelf = self else { @@ -126,7 +132,7 @@ class ImageEditorCropViewController: OWSViewController { croppedImageLayer.contents = previewImage.cgImage croppedImageLayer.contentsScale = previewImage.scale - croppedContentView.backgroundColor = .clear + croppedContentView.themeBackgroundColor = .clear croppedContentView.isOpaque = false croppedContentView.layer.addSublayer(croppedImageLayer) croppedContentView.layoutCallback = { [weak self] (_) in @@ -145,7 +151,7 @@ class ImageEditorCropViewController: OWSViewController { // should be semi-transparent to distinguish it from // the content within the crop bounds. uncroppedImageLayer.opacity = 0.5 - uncroppedContentView.backgroundColor = .clear + uncroppedContentView.themeBackgroundColor = .clear uncroppedContentView.isOpaque = false uncroppedContentView.layer.addSublayer(uncroppedImageLayer) wrapperView.addSubview(uncroppedContentView) @@ -153,15 +159,17 @@ class ImageEditorCropViewController: OWSViewController { // MARK: - Footer - let footer = UIStackView(arrangedSubviews: [ - rotate90Button, - flipButton, - UIView.hStretchingSpacer(), - cropLockButton - ]) + let footer = UIStackView( + arrangedSubviews: [ + rotate90Button, + flipButton, + UIView.hStretchingSpacer(), + cropLockButton + ] + ) footer.axis = .horizontal footer.spacing = 16 - footer.backgroundColor = .clear + footer.themeBackgroundColor = .clear footer.isOpaque = false let imageMargin: CGFloat = 20 @@ -269,16 +277,18 @@ class ImageEditorCropViewController: OWSViewController { private func setCropViewAppearance() { // TODO: Tune the size. - let cornerSize = CGSize(width: min(clipView.width() * 0.5, ImageEditorCropViewController.desiredCornerSize), - height: min(clipView.height() * 0.5, ImageEditorCropViewController.desiredCornerSize)) + let cornerSize = CGSize( + width: min(clipView.width() * 0.5, ImageEditorCropViewController.desiredCornerSize), + height: min(clipView.height() * 0.5, ImageEditorCropViewController.desiredCornerSize) + ) self.cornerSize = cornerSize for cropCornerView in cropCornerViews { let cornerThickness: CGFloat = 2 let shapeLayer = CAShapeLayer() cropCornerView.layer.addSublayer(shapeLayer) - shapeLayer.fillColor = UIColor.white.cgColor - shapeLayer.strokeColor = nil + shapeLayer.themeFillColor = .white + shapeLayer.themeStrokeColor = nil cropCornerView.layoutCallback = { (view) in let shapeFrame = view.bounds.insetBy(dx: -cornerThickness, dy: -cornerThickness) shapeLayer.frame = shapeFrame @@ -329,7 +339,8 @@ class ImageEditorCropViewController: OWSViewController { shapeLayer.path = bezierPath.cgPath } } - cropView.addBorder(with: .white) + cropView.themeBorderColor = .white + cropView.layer.borderWidth = 1 } private func updateCropViewLayout() { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift index fc0f11032..00cb5a39c 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorPaletteView.swift @@ -1,10 +1,9 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import UIKit +import SessionUIKit -public protocol ImageEditorPaletteViewDelegate: class { +public protocol ImageEditorPaletteViewDelegate: AnyObject { func selectedColorDidChange() } @@ -74,7 +73,7 @@ private class PalettePreviewView: OWSLayerView { private let teardropColor = UIColor.white public var selectedColor = UIColor.white { didSet { - circleLayer.fillColor = selectedColor.cgColor + circleLayer.themeFillColorForced = .color(selectedColor) } } @@ -89,14 +88,15 @@ private class PalettePreviewView: OWSLayerView { super.init() - circleLayer.strokeColor = nil - teardropLayer.strokeColor = nil + circleLayer.themeStrokeColor = nil + teardropLayer.themeStrokeColor = nil + // Layer order matters. layer.addSublayer(teardropLayer) layer.addSublayer(circleLayer) - teardropLayer.fillColor = teardropColor.cgColor - teardropLayer.shadowColor = UIColor.black.cgColor + teardropLayer.themeFillColorForced = .color(teardropColor) + teardropLayer.themeShadowColor = .black teardropLayer.shadowRadius = 2.0 teardropLayer.shadowOpacity = 0.33 teardropLayer.shadowOffset = .zero @@ -205,12 +205,12 @@ public class ImageEditorPaletteView: UIView { private var previewConstraint: NSLayoutConstraint? private func createContents() { - self.backgroundColor = .clear + self.themeBackgroundColor = .clear self.isOpaque = false self.layoutMargins = .zero - shadowView.backgroundColor = .black - shadowView.layer.shadowColor = UIColor.black.cgColor + shadowView.themeBackgroundColor = .black + shadowView.themeShadowColor = .black shadowView.layer.shadowRadius = 2.0 shadowView.layer.shadowOpacity = 0.33 shadowView.layer.shadowOffset = .zero @@ -229,7 +229,8 @@ public class ImageEditorPaletteView: UIView { // We use an invisible margin to expand the hot area of this control. let margin: CGFloat = 20 imageView.autoPinEdgesToSuperviewEdges(with: UIEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)) - imageView.addBorder(with: .white) + imageView.themeBorderColor = .white + imageView.layer.borderWidth = 1 imageWrapper.layoutCallback = { [weak self] (view) in guard let strongSelf = self else { @@ -241,7 +242,8 @@ public class ImageEditorPaletteView: UIView { imageWrapper.autoPin(toEdgesOf: imageView) shadowView.autoPin(toEdgesOf: imageView) - selectionView.addBorder(with: .white) + selectionView.themeBorderColor = .white + selectionView.layer.borderWidth = 1 selectionView.layer.cornerRadius = selectionSize / 2 selectionView.autoSetDimensions(to: CGSize(width: selectionSize, height: selectionSize)) imageWrapper.addSubview(selectionView) @@ -329,7 +331,7 @@ public class ImageEditorPaletteView: UIView { } private func updateState() { - selectionView.backgroundColor = selectedValue.color + selectionView.themeBackgroundColorForced = .color(selectedValue.color) previewView.selectedColor = selectedValue.color guard let selectionConstraint = selectionConstraint else { diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift index 662b02f56..c45e52d8c 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorTextViewController.swift @@ -92,7 +92,7 @@ private class VAlignTextView: UITextView { // MARK: - @objc -public protocol ImageEditorTextViewControllerDelegate: class { +public protocol ImageEditorTextViewControllerDelegate: AnyObject { func textEditDidComplete(textItem: ImageEditorTextItem) func textEditDidDelete(textItem: ImageEditorTextItem) func textEditDidCancel() @@ -105,32 +105,33 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel private weak var delegate: ImageEditorTextViewControllerDelegate? private let textItem: ImageEditorTextItem - private let isNewItem: Bool - private let maxTextWidthPoints: CGFloat - private let textView = VAlignTextView(alignment: .center) - private let model: ImageEditorModel - private let canvasView: ImageEditorCanvasView - private let paletteView: ImageEditorPaletteView + private let bottomInset: CGFloat - init(delegate: ImageEditorTextViewControllerDelegate, - model: ImageEditorModel, - textItem: ImageEditorTextItem, - isNewItem: Bool, - maxTextWidthPoints: CGFloat) { + init( + delegate: ImageEditorTextViewControllerDelegate, + model: ImageEditorModel, + textItem: ImageEditorTextItem, + isNewItem: Bool, + maxTextWidthPoints: CGFloat, + bottomInset: CGFloat + ) { self.delegate = delegate self.model = model self.textItem = textItem self.isNewItem = isNewItem self.maxTextWidthPoints = maxTextWidthPoints - self.canvasView = ImageEditorCanvasView(model: model, - itemIdsToIgnore: [textItem.itemId]) + self.canvasView = ImageEditorCanvasView( + model: model, + itemIdsToIgnore: [textItem.itemId] + ) self.paletteView = ImageEditorPaletteView(currentColor: textItem.color) + self.bottomInset = bottomInset super.init(nibName: nil, bundle: nil) @@ -162,24 +163,26 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel public override func loadView() { self.view = UIView() - self.view.backgroundColor = Colors.navigationBarBackground + self.view.themeBackgroundColor = .backgroundSecondary self.view.isOpaque = true canvasView.configureSubviews() self.view.addSubview(canvasView) - canvasView.autoPinEdgesToSuperviewEdges() + canvasView.pin(.top, to: .top, of: self.view) + canvasView.pin(.leading, to: .leading, of: self.view) + canvasView.pin(.trailing, to: .trailing, of: self.view) + canvasView.pin(.bottom, to: .bottom, of: self.view, withInset: -bottomInset) let tintView = UIView() - tintView.backgroundColor = .clear - tintView.isOpaque = false + tintView.themeBackgroundColor = .black + tintView.alpha = 0 self.view.addSubview(tintView) + tintView.autoPinEdgesToSuperviewEdges() - tintView.layer.opacity = 0 - UIView.animate(withDuration: 0.25, animations: { - tintView.layer.opacity = 1 - }, completion: { (_) in - tintView.layer.opacity = 1 - }) + + UIView.animate(withDuration: 0.25) { + tintView.alpha = 0.4 + } configureTextView() @@ -207,13 +210,13 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel private func configureTextView() { textView.text = textItem.text textView.font = textItem.font - textView.textColor = textItem.color.color + textView.themeTextColorForced = .color(textItem.color.color) textView.isEditable = true - textView.backgroundColor = .clear + textView.themeBackgroundColor = .clear textView.isOpaque = false // We use a white cursor since we use a dark background. - textView.tintColor = .white + textView.themeTintColor = .primary // TODO: Limit the size of the text? // textView.delegate = self textView.isScrollEnabled = true @@ -362,6 +365,6 @@ public class ImageEditorTextViewController: OWSViewController, VAlignTextViewDel extension ImageEditorTextViewController: ImageEditorPaletteViewDelegate { public func selectedColorDidChange() { - self.textView.textColor = self.paletteView.selectedValue.color + self.textView.themeTextColorForced = .color(self.paletteView.selectedValue.color) } } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift index cfbebc097..8ea9e82a8 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorView.swift @@ -5,7 +5,7 @@ import UIKit @objc -public protocol ImageEditorViewDelegate: class { +public protocol ImageEditorViewDelegate: AnyObject { func imageEditor(presentFullScreenView viewController: UIViewController, isTransparent: Bool) func imageEditorUpdateNavigationBar() @@ -135,7 +135,12 @@ public class ImageEditorView: UIView { @objc func didTapBrush(sender: UIButton) { Logger.verbose("") - let brushView = ImageEditorBrushViewController(delegate: self, model: model, currentColor: currentColor) + let brushView = ImageEditorBrushViewController( + delegate: self, + model: model, + currentColor: currentColor, + bottomInset: ((self.superview?.frame.height ?? 0) - self.frame.height) + ) self.delegate?.imageEditor(presentFullScreenView: brushView, isTransparent: false) } @@ -446,11 +451,14 @@ public class ImageEditorView: UIView { let maxTextWidthPoints = model.srcImageSizePixels.width * ImageEditorTextItem.kDefaultUnitWidth // let maxTextWidthPoints = canvasView.imageView.width() * ImageEditorTextItem.kDefaultUnitWidth - let textEditor = ImageEditorTextViewController(delegate: self, - model: model, - textItem: textItem, - isNewItem: isNewItem, - maxTextWidthPoints: maxTextWidthPoints) + let textEditor = ImageEditorTextViewController( + delegate: self, + model: model, + textItem: textItem, + isNewItem: isNewItem, + maxTextWidthPoints: maxTextWidthPoints, + bottomInset: ((self.superview?.frame.height ?? 0) - self.frame.height) + ) self.delegate?.imageEditor(presentFullScreenView: textEditor, isTransparent: false) } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift index 49d0bb07a..84bb38c5a 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift @@ -160,7 +160,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { view.translatesAutoresizingMaskIntoConstraints = false view.contentMode = .scaleAspectFit view.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate) - view.tintColor = Colors.text + view.themeTintColor = .textPrimary view.isHidden = true // Override the image to the correct one @@ -174,9 +174,9 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { else if attachment.isUrl { view.clipsToBounds = true view.image = UIImage(named: "Link")?.withRenderingMode(.alwaysTemplate) - view.tintColor = Colors.text + view.themeTintColor = .messageBubble_outgoingText view.contentMode = .center - view.backgroundColor = (isDarkMode ? .black : UIColor.black.withAlphaComponent(0.06)) + view.themeBackgroundColor = .messageBubble_overlay view.layer.cornerRadius = 8 } @@ -202,7 +202,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { else { view.contentMode = .scaleAspectFit view.image = UIImage(named: "FileLarge")?.withRenderingMode(.alwaysTemplate) - view.tintColor = Colors.text + view.themeTintColor = .textPrimary } return view @@ -222,8 +222,14 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { let button: UIButton = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.clipsToBounds = true - button.setBackgroundImage(UIColor.white.toImage(), for: .normal) - button.setBackgroundImage(UIColor.white.darken(by: 0.2).toImage(), for: .highlighted) + button.setThemeBackgroundColorForced( + .theme(.classicLight, color: .settings_tabBackground), + for: .normal + ) + button.setThemeBackgroundColorForced( + .theme(.classicLight, color: .settings_tabHighlight), + for: .highlighted + ) button.addTarget(self, action: #selector(audioPlayPauseButtonPressed), for: .touchUpInside) button.isHidden = true @@ -254,15 +260,15 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { switch mode { case .attachmentApproval: label.font = UIFont.ows_boldFont(withSize: ScaleFromIPhone5To7Plus(16, 22)) - label.textColor = Colors.text + label.themeTextColor = .textPrimary case .large: label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24)) - label.textColor = Colors.accent + label.themeTextColor = .primary case .small: label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14)) - label.textColor = Colors.accent + label.themeTextColor = .primary } // Content @@ -309,15 +315,15 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { switch mode { case .attachmentApproval: label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(12, 18)) - label.textColor = Colors.pinIcon + label.themeTextColor = .textSecondary case .large: label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(18, 24)) - label.textColor = Colors.accent + label.themeTextColor = .primary case .small: label.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(14, 14)) - label.textColor = Colors.accent + label.themeTextColor = .primary } // Content @@ -327,13 +333,16 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { if let targetUrl: URL = URL(string: linkPreviewURL), targetUrl.scheme?.lowercased() != "https" { label.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize) label.text = "vc_share_link_previews_unsecure".localized() - label.textColor = (mode == .attachmentApproval ? Colors.pinIcon : Colors.accent) + label.themeTextColor = (mode == .attachmentApproval ? + .textSecondary : + .primary + ) } } // If we have no link preview info at this point then assume link previews are disabled else { label.text = "vc_share_link_previews_disabled_explanation".localized() - label.textColor = Colors.text + label.themeTextColor = .textPrimary label.textAlignment = .center label.numberOfLines = 0 } @@ -396,7 +405,7 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { fileTypeImageView.image = UIImage(named: "table_ic_notification_sound")? .withRenderingMode(.alwaysTemplate) - fileTypeImageView.tintColor = Colors.text + fileTypeImageView.themeTintColor = .textPrimary fileTypeImageView.isHidden = false // Note: There is an annoying bug where the MediaMessageView will fill the screen if the @@ -587,7 +596,10 @@ public class MediaMessageView: UIView, OWSAudioPlayerDelegate { else { self?.subtitleLabel.font = UIFont.ows_regularFont(withSize: Values.verySmallFontSize) self?.subtitleLabel.text = "vc_share_link_previews_error".localized() - self?.subtitleLabel.textColor = (self?.mode == .attachmentApproval ? Colors.pinIcon : Colors.accent ) + self?.subtitleLabel.themeTextColor = (self?.mode == .attachmentApproval ? + .textSecondary : + .primary + ) self?.subtitleLabel.textAlignment = .left } } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/OWSViewController+ImageEditor.swift b/SignalUtilitiesKit/Media Viewing & Editing/OWSViewController+ImageEditor.swift index a11132f07..fc9563586 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/OWSViewController+ImageEditor.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/OWSViewController+ImageEditor.swift @@ -6,13 +6,12 @@ import UIKit import SessionUIKit public extension NSObject { - - func navigationBarButton(imageName: String, - selector: Selector) -> UIView { + func navigationBarButton(imageName: String, selector: Selector) -> UIView { let button = OWSButton() button.setImage(imageName: imageName) - button.tintColor = isLightMode ? UIColor.black : UIColor.white + button.themeTintColor = .textPrimary button.addTarget(self, action: selector, for: .touchUpInside) + return button } } @@ -46,16 +45,5 @@ public extension UIViewController { stackView.frame = CGRect(origin: .zero, size: stackSize) self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: stackView) - - // Loki: Set navigation bar background color - let navigationBar = navigationController!.navigationBar - navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) - navigationBar.shadowImage = UIImage() - navigationBar.isTranslucent = false - let color = isLightMode ? UIColor(hex: 0xFCFCFC) : UIColor(hex: 0x161616) - navigationBar.barTintColor = color - navigationBar.backgroundColor = color - let backgroundImage = UIImage(color: color) - navigationBar.setBackgroundImage(backgroundImage, for: .default) } } diff --git a/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift b/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift index f9edd1de7..bebeda88c 100644 --- a/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift +++ b/SignalUtilitiesKit/Media Viewing & Editing/VideoPlayerView.swift @@ -1,9 +1,8 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit import AVFoundation +import SessionUIKit @objc public class VideoPlayerView: UIView { @@ -70,7 +69,7 @@ public class PlayerProgressBar: UIView { private let positionLabel = UILabel() private let remainingLabel = UILabel() private let slider = TrackingSlider() - private let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + private let blurView = UIVisualEffectView() weak private var progressObserver: AnyObject? private let kPreferredTimeScale: CMTimeScale = 100 @@ -107,12 +106,24 @@ public class PlayerProgressBar: UIView { override public init(frame: CGRect) { super.init(frame: frame) - - // Background - backgroundColor = UIColor.lightGray.withAlphaComponent(0.5) + + // Background & blur + let backgroundView = UIView() + backgroundView.themeBackgroundColor = .backgroundSecondary + backgroundView.alpha = Values.lowOpacity + addSubview(backgroundView) + backgroundView.pin(to: self) + if !UIAccessibility.isReduceTransparencyEnabled { - addSubview(blurEffectView) - blurEffectView.ows_autoPinToSuperviewEdges() + addSubview(blurView) + blurView.pin(to: self) + + ThemeManager.onThemeChange(observer: blurView) { [weak blurView] theme, _ in + switch theme.interfaceStyle { + case .light: blurView?.effect = UIBlurEffect(style: .light) + default: blurView?.effect = UIBlurEffect(style: .dark) + } + } } // Configure controls @@ -123,8 +134,8 @@ public class PlayerProgressBar: UIView { // We use a smaller thumb for the progress slider. slider.setThumbImage(#imageLiteral(resourceName: "sliderProgressThumb"), for: .normal) - slider.maximumTrackTintColor = UIColor.ows_black - slider.minimumTrackTintColor = UIColor.ows_black + slider.themeMinimumTrackTintColor = .backgroundPrimary + slider.themeMaximumTrackTintColor = .backgroundPrimary slider.addTarget(self, action: #selector(handleSliderTouchDown), for: .touchDown) slider.addTarget(self, action: #selector(handleSliderTouchUp), for: .touchUpInside) diff --git a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h index 122524188..0cf665823 100644 --- a/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h +++ b/SignalUtilitiesKit/Meta/SignalUtilitiesKit.h @@ -23,5 +23,4 @@ FOUNDATION_EXPORT const unsigned char SignalUtilitiesKitVersionString[]; #import #import #import -#import -#import +#import diff --git a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift b/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift index 41acffbd4..33246ebce 100644 --- a/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift +++ b/SignalUtilitiesKit/Profile Pictures/PlaceholderIcon.swift @@ -8,35 +8,36 @@ public class PlaceholderIcon { private let seed: Int // Colour palette - private var colours: [UIColor] = Theme.PrimaryColor.allCases.map { $0.color } + private var colors: [UIColor] = Theme.PrimaryColor.allCases.map { $0.color } - init(seed: Int, colours: [UIColor]? = nil) { + init(seed: Int, colors: [UIColor]? = nil) { self.seed = seed - if let colours = colours { self.colours = colours } + if let colors = colors { self.colors = colors } } - convenience init(seed: String, colours: [UIColor]? = nil) { + convenience init(seed: String, colors: [UIColor]? = nil) { // Ensure we have a correct hash var hash = seed if (hash.matches("^[0-9A-Fa-f]+$") && hash.count >= 12) { hash = seed.sha512() } guard let number = Int(hash.substring(to: 12), radix: 16) else { owsFailDebug("Failed to generate number from seed string: \(seed).") - self.init(seed: 0, colours: colours) + self.init(seed: 0, colors: colors) return } - self.init(seed: number, colours: colours) + self.init(seed: number, colors: colors) } public func generateLayer(with diameter: CGFloat, text: String) -> CALayer { - let colour = self.colours[seed % self.colours.count].cgColor - let base = getTextLayer(with: diameter, colour: colour, text: text) + let color: UIColor = self.colors[seed % self.colors.count] + let base: CALayer = getTextLayer(with: diameter, color: color, text: text) base.masksToBounds = true + return base } - private func getTextLayer(with diameter: CGFloat, colour: CGColor? = nil, text: String) -> CALayer { + private func getTextLayer(with diameter: CGFloat, color: UIColor, text: String) -> CALayer { let font = UIFont.boldSystemFont(ofSize: diameter / 2) let height = NSString(string: text).boundingRect(with: CGSize(width: diameter, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [ NSAttributedString.Key.font : font ], context: nil).height @@ -44,7 +45,7 @@ public class PlaceholderIcon { let layer = CATextLayer() layer.frame = frame - layer.foregroundColor = UIColor.white.cgColor + layer.themeForegroundColorForced = .color(.white) layer.contentsScale = UIScreen.main.scale let fontName = font.fontName @@ -52,12 +53,11 @@ public class PlaceholderIcon { layer.font = fontRef layer.fontSize = font.pointSize layer.alignmentMode = .center - layer.string = text let base = CALayer() base.frame = CGRect(x: 0, y: 0, width: diameter, height: diameter) - base.backgroundColor = colour + base.themeBackgroundColorForced = .color(color) base.addSublayer(layer) return base @@ -65,7 +65,6 @@ public class PlaceholderIcon { } private extension String { - func matches(_ regex: String) -> Bool { return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil } diff --git a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift index a56866903..edaa600af 100644 --- a/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift +++ b/SignalUtilitiesKit/Profile Pictures/ProfilePictureView.swift @@ -23,7 +23,7 @@ public final class ProfilePictureView: UIView { let result: UIView = UIView() result.translatesAutoresizingMaskIntoConstraints = false result.clipsToBounds = true - result.backgroundColor = Colors.unimportant + result.themeBackgroundColor = .backgroundSecondary return result }() @@ -154,7 +154,7 @@ public final class ProfilePictureView: UIView { imageView.contentMode = .center imageView.isHidden = false animatedImageView.isHidden = true - imageContainerView.backgroundColor = UIColor(rgbHex: 0x353535) + imageContainerView.themeBackgroundColor = .backgroundSecondary imageContainerView.layer.cornerRadius = (self.size / 2) imageViewWidthConstraint.constant = self.size imageViewHeightConstraint.constant = self.size @@ -287,7 +287,7 @@ public final class ProfilePictureView: UIView { imageView.contentMode = .scaleAspectFill animatedImageView.contentMode = .scaleAspectFill - imageContainerView.backgroundColor = Colors.unimportant + imageContainerView.themeBackgroundColor = .backgroundSecondary imageContainerView.layer.cornerRadius = (targetSize / 2) additionalImageContainerView.layer.cornerRadius = (targetSize / 2) } diff --git a/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift b/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift index a78e077ce..33afb0c86 100644 --- a/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift +++ b/SignalUtilitiesKit/Screen Lock/ScreenLockViewController.swift @@ -41,12 +41,9 @@ open class ScreenLockViewController: UIViewController { result.isHidden = true // Need to match the launch screen so force the styling to be the primary green - result.setTitleColor(Theme.PrimaryColor.green.color, for: .normal) - result.setBackgroundImage( - Theme.PrimaryColor.green.color.withAlphaComponent(0.3).toImage(), - for: .highlighted - ) - result.layer.borderColor = Theme.PrimaryColor.green.color.cgColor + result.setThemeTitleColorForced(.primary(.green), for: .normal) + result.setThemeBackgroundColorForced(.primary(.green), for: .highlighted) + result.themeBorderColorForced = .primary(.green) return result }() diff --git a/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift b/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift index 0fd4d985b..49958617b 100644 --- a/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift +++ b/SignalUtilitiesKit/Shared View Controllers/ModalActivityIndicatorViewController.swift @@ -138,23 +138,14 @@ public class ModalActivityIndicatorViewController: OWSViewController { } if canCancel { - let cancelButton: UIButton = UIButton(type: .custom) + let cancelButton: OutlineButton = OutlineButton(style: .destructive, size: .large) cancelButton.setTitle(CommonStrings.cancelButton, for: .normal) - cancelButton.setThemeTitleColor(.textPrimary, for: .normal) - cancelButton.backgroundColor = UIColor.ows_darkGray - cancelButton.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5To7Plus(18, 22)) - cancelButton.layer.cornerRadius = ScaleFromIPhone5To7Plus(4, 5) - cancelButton.clipsToBounds = true cancelButton.addTarget(self, action: #selector(cancelPressed), for: .touchUpInside) - - let buttonWidth = ScaleFromIPhone5To7Plus(140, 160) - let buttonHeight = ScaleFromIPhone5To7Plus(40, 50) self.view.addSubview(cancelButton) - cancelButton.autoHCenterInSuperview() - cancelButton.autoPinEdge(toSuperviewEdge: .bottom, withInset: 50) - cancelButton.autoSetDimension(.width, toSize: buttonWidth) - cancelButton.autoSetDimension(.height, toSize: buttonHeight) + cancelButton.center(.horizontal, in: self.view) + cancelButton.pin(.bottom, to: .bottom, of: self.view, withInset: -50) + cancelButton.set(.width, to: Values.iPadButtonWidth) } // Hide the modal until the presentation animation completes. diff --git a/SignalUtilitiesKit/Shared View Controllers/OWSNavigationController.m b/SignalUtilitiesKit/Shared View Controllers/OWSNavigationController.m index afdf21919..dc5888a64 100644 --- a/SignalUtilitiesKit/Shared View Controllers/OWSNavigationController.m +++ b/SignalUtilitiesKit/Shared View Controllers/OWSNavigationController.m @@ -146,11 +146,6 @@ NS_ASSUME_NONNULL_BEGIN - (UIStatusBarStyle)preferredStatusBarStyle { - if (OWSWindowManager.sharedManager.hasCall) { - // Status bar is overlaying the green "call banner" - return UIStatusBarStyleLightContent; - } - // Note: The view controllers contain the logic needed to get the correct statusBarStyle // for the current theme if (self.topViewController != nil) { @@ -169,9 +164,6 @@ NS_ASSUME_NONNULL_BEGIN if (!CurrentAppContext().isMainApp) { self.additionalSafeAreaInsets = UIEdgeInsetsZero; } - else if (OWSWindowManager.sharedManager.hasCall) { - self.additionalSafeAreaInsets = UIEdgeInsetsMake(20, 0, 0, 0); - } else { self.additionalSafeAreaInsets = UIEdgeInsetsZero; } diff --git a/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m b/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m index 4ed5a5c87..6d2ea863b 100644 --- a/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m +++ b/SignalUtilitiesKit/Shared View Controllers/OWSViewController.m @@ -82,15 +82,6 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) self.shouldAnimateBottomLayout = NO; } -- (void)viewDidLoad -{ - [super viewDidLoad]; - - if (self.shouldUseTheme) { - self.view.backgroundColor = [LKColors navigationBarBackground]; - } -} - #pragma mark - - (void)autoPinViewToBottomOfViewControllerOrKeyboard:(UIView *)view avoidNotch:(BOOL)avoidNotch diff --git a/SignalUtilitiesKit/Shared View Controllers/SheetViewController.swift b/SignalUtilitiesKit/Shared View Controllers/SheetViewController.swift deleted file mode 100644 index a8a142acf..000000000 --- a/SignalUtilitiesKit/Shared View Controllers/SheetViewController.swift +++ /dev/null @@ -1,223 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -import Foundation -import SessionUIKit - -@objc(OWSSheetViewControllerDelegate) -public protocol SheetViewControllerDelegate: class { - func sheetViewControllerRequestedDismiss(_ sheetViewController: SheetViewController) -} - -@objc(OWSSheetViewController) -public class SheetViewController: UIViewController { - - @objc - weak var delegate: SheetViewControllerDelegate? - - @objc - public let contentView: UIView = UIView() - - private let sheetView: SheetView = SheetView() - private let handleView: UIView = UIView() - - deinit { - Logger.verbose("") - } - - @objc - public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - self.transitioningDelegate = self - self.modalPresentationStyle = .overCurrentContext - } - - public required init?(coder aDecoder: NSCoder) { - notImplemented() - } - - // MARK: View LifeCycle - - var sheetViewVerticalConstraint: NSLayoutConstraint? - - override public func loadView() { - self.view = UIView() - - sheetView.preservesSuperviewLayoutMargins = true - - sheetView.addSubview(contentView) - contentView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom) - contentView.autoPinEdge(toSuperviewMargin: .bottom) - - view.addSubview(sheetView) - sheetView.autoPinWidthToSuperview() - sheetView.setContentHuggingVerticalHigh() - sheetView.setCompressionResistanceHigh() - self.sheetViewVerticalConstraint = sheetView.autoPinEdge(.top, to: .bottom, of: self.view) - - handleView.backgroundColor = isDarkMode ? UIColor.ows_white : UIColor.ows_gray05 - let kHandleViewHeight: CGFloat = 5 - handleView.autoSetDimensions(to: CGSize(width: 40, height: kHandleViewHeight)) - handleView.layer.cornerRadius = kHandleViewHeight / 2 - view.addSubview(handleView) - handleView.autoAlignAxis(.vertical, toSameAxisOf: sheetView) - handleView.autoPinEdge(.bottom, to: .top, of: sheetView, withOffset: -6) - - // Gestures - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapBackground)) - self.view.addGestureRecognizer(tapGesture) - - let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeDown)) - swipeDownGesture.direction = .down - self.view.addGestureRecognizer(swipeDownGesture) - } - - // MARK: Present / Dismiss animations - - fileprivate func animatePresentation(completion: @escaping (Bool) -> Void) { - guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else { - owsFailDebug("sheetViewVerticalConstraint was unexpectedly nil") - return - } - - let backgroundDuration: TimeInterval = 0.1 - UIView.animate(withDuration: backgroundDuration) { - let alpha: CGFloat = isDarkMode ? 0.7 : 0.6 - self.view.backgroundColor = UIColor.black.withAlphaComponent(alpha) - } - - self.sheetView.superview?.layoutIfNeeded() - - NSLayoutConstraint.deactivate([sheetViewVerticalConstraint]) - self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(toSuperviewEdge: .bottom) - UIView.animate(withDuration: 0.2, - delay: backgroundDuration, - options: .curveEaseOut, - animations: { - self.sheetView.superview?.layoutIfNeeded() - }, - completion: completion) - } - - fileprivate func animateDismiss(completion: @escaping (Bool) -> Void) { - guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else { - owsFailDebug("sheetVerticalConstraint was unexpectedly nil") - return - } - - self.sheetView.superview?.layoutIfNeeded() - NSLayoutConstraint.deactivate([sheetViewVerticalConstraint]) - - let dismissDuration: TimeInterval = 0.2 - self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(.top, to: .bottom, of: self.view) - UIView.animate(withDuration: dismissDuration, - delay: 0, - options: .curveEaseOut, - animations: { - self.view.backgroundColor = UIColor.clear - self.sheetView.superview?.layoutIfNeeded() - }, - completion: completion) - } - - // MARK: Actions - - @objc - func didTapBackground() { - // inform delegate to - delegate?.sheetViewControllerRequestedDismiss(self) - } - - @objc - func didSwipeDown() { - // inform delegate to - delegate?.sheetViewControllerRequestedDismiss(self) - } -} - -extension SheetViewController: UIViewControllerTransitioningDelegate { - public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return SheetViewPresentationController(sheetViewController: self) - } - - public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return SheetViewDismissalController(sheetViewController: self) - } -} - -private class SheetViewPresentationController: NSObject, UIViewControllerAnimatedTransitioning { - - let sheetViewController: SheetViewController - init(sheetViewController: SheetViewController) { - self.sheetViewController = sheetViewController - } - - // This is used for percent driven interactive transitions, as well as for - // container controllers that have companion animations that might need to - // synchronize with the main animation. - public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.3 - } - - // This method can only be a nop if the transition is interactive and not a percentDriven interactive transition. - public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - Logger.debug("") - transitionContext.containerView.addSubview(sheetViewController.view) - sheetViewController.view.autoPinEdgesToSuperviewEdges() - sheetViewController.animatePresentation { didComplete in - Logger.debug("completed: \(didComplete)") - transitionContext.completeTransition(didComplete) - } - } -} - -private class SheetViewDismissalController: NSObject, UIViewControllerAnimatedTransitioning { - - let sheetViewController: SheetViewController - init(sheetViewController: SheetViewController) { - self.sheetViewController = sheetViewController - } - - // This is used for percent driven interactive transitions, as well as for - // container controllers that have companion animations that might need to - // synchronize with the main animation. - public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.3 - } - - // This method can only be a nop if the transition is interactive and not a percentDriven interactive transition. - public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - Logger.debug("") - sheetViewController.animateDismiss { didComplete in - Logger.debug("completed: \(didComplete)") - transitionContext.completeTransition(didComplete) - } - } -} - -private class SheetView: UIView { - - override init(frame: CGRect) { - super.init(frame: frame) - self.backgroundColor = isDarkMode ? UIColor.ows_gray90 : UIColor.ows_gray05 - } - - required init?(coder aDecoder: NSCoder) { - notImplemented() - } - - override var bounds: CGRect { - didSet { - updateMask() - } - } - - private func updateMask() { - let cornerRadius: CGFloat = 16 - let path: UIBezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) - let mask = CAShapeLayer() - mask.path = path.cgPath - self.layer.mask = mask - } -} diff --git a/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift b/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift index ed7458919..67c745389 100644 --- a/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift +++ b/SignalUtilitiesKit/Shared Views/ApprovalRailCellView.swift @@ -1,9 +1,7 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import Foundation import UIKit +import SessionUIKit protocol ApprovalRailCellViewDelegate: AnyObject { func approvalRailCellView(_ approvalRailCellView: ApprovalRailCellView, didRemoveItem attachmentItem: SignalAttachmentItem) @@ -29,8 +27,8 @@ public class ApprovalRailCellView: GalleryRailCellView { } button.setImage(UIImage(named: "x-24")?.withRenderingMode(.alwaysTemplate), for: .normal) - button.tintColor = .white - button.layer.shadowColor = UIColor.black.cgColor + button.themeTintColor = .white + button.themeShadowColor = .black button.layer.shadowRadius = 2 button.layer.shadowOpacity = 0.66 button.layer.shadowOffset = .zero @@ -44,11 +42,12 @@ public class ApprovalRailCellView: GalleryRailCellView { lazy var captionIndicator: UIView = { let image = UIImage(named: "image_editor_caption")?.withRenderingMode(.alwaysTemplate) let imageView = UIImageView(image: image) - imageView.tintColor = .white - imageView.layer.shadowColor = UIColor.black.cgColor + imageView.themeTintColor = .white + imageView.themeShadowColor = .black imageView.layer.shadowRadius = 2 imageView.layer.shadowOpacity = 0.66 imageView.layer.shadowOffset = .zero + return imageView }() diff --git a/SignalUtilitiesKit/Shared Views/GalleryRailView.swift b/SignalUtilitiesKit/Shared Views/GalleryRailView.swift index ef256f75e..c41ccb715 100644 --- a/SignalUtilitiesKit/Shared Views/GalleryRailView.swift +++ b/SignalUtilitiesKit/Shared Views/GalleryRailView.swift @@ -86,9 +86,10 @@ public class GalleryRailCellView: UIView { layoutMargins = UIEdgeInsets(top: 0, left: cellBorderWidth, bottom: 0, right: cellBorderWidth) if isSelected { - contentContainer.layer.borderColor = Colors.accent.cgColor + contentContainer.themeBorderColor = .primary contentContainer.layer.borderWidth = cellBorderWidth - } else { + } + else { contentContainer.layer.borderWidth = 0 } } @@ -197,6 +198,7 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { UIView.animate( withDuration: animationDuration, animations: { [weak self] in + self?.isHidden = true self?.stackView.frame = oldFrame.offsetBy( dx: 0, dy: oldFrame.height @@ -205,7 +207,6 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { completion: { [weak self] _ in self?.stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } self?.stackView.frame = oldFrame - self?.isHidden = true self?.cellViews = [] } ) @@ -247,8 +248,8 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { } self?.cellViews = newCellViews - self?.updateFocusedItem(focusedItem) self?.stackView.layoutIfNeeded() + self?.updateFocusedItem(focusedItem) self?.isHidden = false updatedOldFrame = (self?.stackView.frame) @@ -268,6 +269,7 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { options: .curveEaseOut, animations: { [weak self] in self?.stackView.frame = oldFrame + self?.isHidden = false }, completion: nil ) @@ -276,6 +278,11 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { // If we don't have arranged subviews already we can skip the 'animateOut' guard !self.stackView.arrangedSubviews.isEmpty else { let updatedOldFrame: CGRect = layoutNewItems(self.stackView.frame) + + // Hide self again because it would have previously been hidden and we want to + // properly animate it's appearance + self.isHidden = true + animateIn(updatedOldFrame) return } diff --git a/SignalUtilitiesKit/Shared Views/OWSButton.swift b/SignalUtilitiesKit/Shared Views/OWSButton.swift index e9fc08787..c06726e35 100644 --- a/SignalUtilitiesKit/Shared Views/OWSButton.swift +++ b/SignalUtilitiesKit/Shared Views/OWSButton.swift @@ -1,18 +1,13 @@ -// // Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// import UIKit +import SessionUIKit -@objc public class OWSButton: UIButton { - - @objc var block: () -> Void = { } // MARK: - - @objc public init(block: @escaping () -> Void = { }) { super.init(frame: .zero) @@ -20,7 +15,6 @@ public class OWSButton: UIButton { addTarget(self, action: #selector(didTap), for: .touchUpInside) } - @objc public init(title: String, block: @escaping () -> Void = { }) { super.init(frame: .zero) @@ -29,26 +23,22 @@ public class OWSButton: UIButton { setTitle(title, for: .normal) } - @objc - public init(imageName: String, - tintColor: UIColor?, - block: @escaping () -> Void = { }) { + public init(imageName: String, tintColor: ThemeValue?, block: @escaping () -> Void = { }) { super.init(frame: .zero) self.block = block addTarget(self, action: #selector(didTap), for: .touchUpInside) setImage(imageName: imageName) - self.tintColor = tintColor + self.themeTintColor = tintColor } - @objc public func setImage(imageName: String) { - if let image = UIImage(named: imageName) { - setImage(image.withRenderingMode(.alwaysTemplate), for: .normal) - } else { - owsFailDebug("Missing asset: \(imageName)") - } + setImage( + UIImage(named: imageName)? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) } public required init?(coder aDecoder: NSCoder) { @@ -57,8 +47,7 @@ public class OWSButton: UIButton { // MARK: - - @objc - func didTap() { + @objc func didTap() { block() } } diff --git a/SignalUtilitiesKit/Shared Views/OWSFlatButton.swift b/SignalUtilitiesKit/Shared Views/OWSFlatButton.swift deleted file mode 100644 index cba7379d1..000000000 --- a/SignalUtilitiesKit/Shared Views/OWSFlatButton.swift +++ /dev/null @@ -1,191 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - - -@objc -public class OWSFlatButton: UIView { - - public let button: UIButton - - private var pressedBlock : (() -> Void)? - - private var upColor: UIColor? - private var downColor: UIColor? - - @objc - public override var accessibilityIdentifier: String? { - didSet { - guard let accessibilityIdentifier = self.accessibilityIdentifier else { - return - } - button.accessibilityIdentifier = "\(accessibilityIdentifier).button" - } - } - - override public var backgroundColor: UIColor? { - willSet { - owsFailDebug("Use setBackgroundColors(upColor:) instead.") - } - } - - @objc - public init() { - AssertIsOnMainThread() - - button = UIButton(type: .custom) - - super.init(frame: CGRect.zero) - - createContent() - } - - @available(*, unavailable, message:"use other constructor instead.") - required public init?(coder aDecoder: NSCoder) { - notImplemented() - } - - private func createContent() { - self.addSubview(button) - button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside) - button.ows_autoPinToSuperviewEdges() - } - - @objc - public class func button(title: String, - font: UIFont, - titleColor: UIColor, - backgroundColor: UIColor, - width: CGFloat, - height: CGFloat, - target: Any, - selector: Selector) -> OWSFlatButton { - let button = OWSFlatButton() - button.setTitle(title: title, - font: font, - titleColor: titleColor ) - button.setBackgroundColors(upColor: backgroundColor) - button.useDefaultCornerRadius() - button.setSize(width: width, height: height) - button.addTarget(target: target, selector: selector) - return button - } - - @objc - public class func button(title: String, - titleColor: UIColor, - backgroundColor: UIColor, - width: CGFloat, - height: CGFloat, - target: Any, - selector: Selector) -> OWSFlatButton { - return OWSFlatButton.button(title: title, - font: fontForHeight(height), - titleColor: titleColor, - backgroundColor: backgroundColor, - width: width, - height: height, - target: target, - selector: selector) - } - - @objc - public class func button(title: String, - font: UIFont, - titleColor: UIColor, - backgroundColor: UIColor, - target: Any, - selector: Selector) -> OWSFlatButton { - let button = OWSFlatButton() - button.setTitle(title: title, - font: font, - titleColor: titleColor ) - button.setBackgroundColors(upColor: backgroundColor) - button.useDefaultCornerRadius() - button.addTarget(target: target, selector: selector) - return button - } - - @objc - public class func fontForHeight(_ height: CGFloat) -> UIFont { - // Cap the "button height" at 40pt or button text can look - // excessively large. - let fontPointSize = round(min(40, height) * 0.45) - return UIFont.ows_mediumFont(withSize: fontPointSize) - } - - // MARK: Methods - - @objc - public func setTitle(_ title: String) { - button.setTitle(title, for: .normal) - } - - @objc - public func setTitle(title: String, font: UIFont, - titleColor: UIColor ) { - button.setTitle(title, for: .normal) - button.setTitleColor(titleColor, for: .normal) - button.titleLabel!.font = font - } - - @objc - public func setBackgroundColors(upColor: UIColor, - downColor: UIColor ) { - button.setBackgroundImage(UIImage(color: upColor), for: .normal) - button.setBackgroundImage(UIImage(color: downColor), for: .highlighted) - } - - @objc - public func setBackgroundColors(upColor: UIColor ) { - setBackgroundColors(upColor: upColor, downColor: upColor ) - } - - @objc - public func setSize(width: CGFloat, height: CGFloat) { - button.autoSetDimension(.width, toSize: width) - button.autoSetDimension(.height, toSize: height) - } - - @objc - public func useDefaultCornerRadius() { - // To my eye, this radius tends to look right regardless of button size - // (within reason) or device size. - button.layer.cornerRadius = 5 - button.clipsToBounds = true - } - - @objc - public func setEnabled(_ isEnabled: Bool) { - button.isEnabled = isEnabled - } - - @objc - public func addTarget(target: Any, - selector: Selector) { - button.addTarget(target, action: selector, for: .touchUpInside) - } - - @objc - public func setPressedBlock(_ pressedBlock: @escaping () -> Void) { - guard self.pressedBlock == nil else { - owsFailDebug("Button already has pressed block.") - return - } - self.pressedBlock = pressedBlock - } - - @objc - internal func buttonPressed() { - pressedBlock?() - } - - @objc - public func enableMultilineLabel() { - button.titleLabel?.numberOfLines = 0 - button.titleLabel?.lineBreakMode = .byWordWrapping - button.titleLabel?.textAlignment = .center - } -} diff --git a/SignalUtilitiesKit/Shared Views/OWSNavigationBar.swift b/SignalUtilitiesKit/Shared Views/OWSNavigationBar.swift index 5547ab36b..857a947db 100644 --- a/SignalUtilitiesKit/Shared Views/OWSNavigationBar.swift +++ b/SignalUtilitiesKit/Shared Views/OWSNavigationBar.swift @@ -7,7 +7,7 @@ import UIKit import SessionUIKit @objc -public protocol NavBarLayoutDelegate: class { +public protocol NavBarLayoutDelegate: AnyObject { func navBarCallLayoutDidChange(navbar: OWSNavigationBar) } @@ -20,11 +20,6 @@ public class OWSNavigationBar: UINavigationBar { @objc public let navbarWithoutStatusHeight: CGFloat = 44 - @objc - public var callBannerHeight: CGFloat { - return OWSWindowManagerCallBannerHeight() - } - @objc public var statusBarHeight: CGFloat { return CurrentAppContext().statusBarHeight @@ -47,8 +42,7 @@ public class OWSNavigationBar: UINavigationBar { super.init(frame: frame) applyTheme() - - NotificationCenter.default.addObserver(self, selector: #selector(callDidChange), name: .OWSWindowManagerCallDidChange, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(didChangeStatusBarFrame), name: UIApplication.didChangeStatusBarFrameNotification, object: nil) } @@ -72,25 +66,8 @@ public class OWSNavigationBar: UINavigationBar { return } - backgroundColor = Colors.navigationBarBackground - - tintColor = Colors.text - - if UIAccessibility.isReduceTransparencyEnabled { - blurEffectView?.isHidden = true - let color = UIColor.lokiDarkestGray() - let backgroundImage = UIImage(color: color) - self.setBackgroundImage(backgroundImage, for: .default) - } else { - // Make navbar more translucent than default. Navbars remove alpha from any assigned backgroundColor, so - // to achieve transparency, we have to assign a transparent image. - let color = UIColor.lokiDarkestGray() - let backgroundImage = UIImage(color: color) - self.setBackgroundImage(backgroundImage, for: .default) - - // remove hairline below bar. - self.shadowImage = UIImage() - } + themeBackgroundColor = .backgroundPrimary + themeTintColor = .textPrimary } @objc @@ -102,82 +79,9 @@ public class OWSNavigationBar: UINavigationBar { // MARK: Layout - @objc - public func callDidChange() { - Logger.debug("") - self.navBarLayoutDelegate?.navBarCallLayoutDidChange(navbar: self) - } - @objc public func didChangeStatusBarFrame() { Logger.debug("") self.navBarLayoutDelegate?.navBarCallLayoutDidChange(navbar: self) } - - public override func layoutSubviews() { - guard CurrentAppContext().isMainApp else { - super.layoutSubviews() - return - } - guard OWSWindowManager.shared().hasCall() else { - super.layoutSubviews() - return - } - - guard #available(iOS 11, *) else { - super.layoutSubviews() - return - } - - self.frame = CGRect(x: 0, y: callBannerHeight, width: fullWidth, height: navbarWithoutStatusHeight) - self.bounds = CGRect(x: 0, y: 0, width: fullWidth, height: navbarWithoutStatusHeight) - - super.layoutSubviews() - - // This is only necessary on iOS11, which has some private views within that lay outside of the navbar. - // They aren't actually visible behind the call status bar, but they looks strange during present/dismiss - // animations for modal VC's - for subview in self.subviews { - let stringFromClass = NSStringFromClass(subview.classForCoder) - if stringFromClass.contains("BarBackground") { - subview.frame = self.bounds - } else if stringFromClass.contains("BarContentView") { - subview.frame = self.bounds - } - } - } - - // MARK: Override Theme - - @objc - public enum NavigationBarThemeOverride: Int { - case clear, alwaysDark - } - - @objc - public func overrideTheme(type: NavigationBarThemeOverride) { - respectsTheme = false - - barStyle = .black - titleTextAttributes = [NSAttributedString.Key.foregroundColor: Colors.text] - barTintColor = Colors.navigationBarBackground.withAlphaComponent(0.6) - tintColor = Colors.text - - switch type { - case .clear: - blurEffectView?.isHidden = true - clipsToBounds = true - - // Making a toolbar transparent requires setting an empty uiimage - setBackgroundImage(UIImage(), for: .default) - shadowImage = UIImage() - backgroundColor = .clear - case .alwaysDark: - blurEffectView?.isHidden = false - clipsToBounds = false - - setBackgroundImage(nil, for: .default) - shadowImage = nil - } - } } diff --git a/SignalUtilitiesKit/Shared Views/Toast.swift b/SignalUtilitiesKit/Shared Views/Toast.swift index 28e6995c7..a98231d9f 100644 --- a/SignalUtilitiesKit/Shared Views/Toast.swift +++ b/SignalUtilitiesKit/Shared Views/Toast.swift @@ -1,34 +1,27 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -import Foundation +import UIKit import SessionUIKit -@objc -public class ToastController: NSObject, ToastViewDelegate { - +public class ToastController: ToastViewDelegate { static var currentToastController: ToastController? + private let id: UUID private let toastView: ToastView private var isDismissing: Bool // MARK: Initializers - @objc - required public init(text: String) { - toastView = ToastView() + required public init(text: String, background: ThemeValue) { + id = UUID() + toastView = ToastView(background: background) toastView.text = text isDismissing = false - - super.init() - toastView.delegate = self } // MARK: Public - @objc public func presentToastView(fromBottomOfView view: UIView, inset: CGFloat) { Logger.debug("") toastView.alpha = 0 @@ -37,11 +30,11 @@ public class ToastController: NSObject, ToastViewDelegate { toastView.autoPinEdge(.bottom, to: .bottom, of: view, withOffset: -inset) toastView.autoPinWidthToSuperview(withMargin: 24) - if let currentToastController = type(of: self).currentToastController { + if let currentToastController = ToastController.currentToastController { currentToastController.dismissToastView() - type(of: self).currentToastController = nil + ToastController.currentToastController = nil } - type(of: self).currentToastController = self + ToastController.currentToastController = self UIView.animate(withDuration: 0.1) { self.toastView.alpha = 1 @@ -72,26 +65,26 @@ public class ToastController: NSObject, ToastViewDelegate { func dismissToastView() { Logger.debug("") - guard !isDismissing else { - return - } + guard !isDismissing else { return } isDismissing = true - if type(of: self).currentToastController == self { - type(of: self).currentToastController = nil + if ToastController.currentToastController?.id == self.id { + ToastController.currentToastController = nil } - UIView.animate(withDuration: 0.1, - animations: { - self.toastView.alpha = 0 - }, - completion: { (_) in - self.toastView.removeFromSuperview() - }) + UIView.animate( + withDuration: 0.1, + animations: { + self.toastView.alpha = 0 + }, + completion: { [weak self] _ in + self?.toastView.removeFromSuperview() + } + ) } } -protocol ToastViewDelegate: class { +protocol ToastViewDelegate: AnyObject { func didTapToastView(_ toastView: ToastView) func didSwipeToastView(_ toastView: ToastView) } @@ -99,12 +92,8 @@ protocol ToastViewDelegate: class { class ToastView: UIView { var text: String? { - get { - return label.text - } - set { - label.text = newValue - } + get { return label.text } + set { label.text = newValue } } weak var delegate: ToastViewDelegate? @@ -112,17 +101,17 @@ class ToastView: UIView { // MARK: Initializers - override init(frame: CGRect) { + init(background: ThemeValue) { label = UILabel() - super.init(frame: frame) + + super.init(frame: .zero) - self.layer.cornerRadius = 4 - self.backgroundColor = (isDarkMode ? UIColor.ows_gray75 : UIColor.ows_gray60) + self.themeBackgroundColor = background self.layoutMargins = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) + label.font = .systemFont(ofSize: Values.mediumFontSize) + label.themeTextColor = .textPrimary label.textAlignment = .center - label.textColor = (isDarkMode ? UIColor.ows_white : UIColor.ows_white) - label.font = UIFont.ows_dynamicTypeBody label.numberOfLines = 0 self.addSubview(label) label.autoPinEdgesToSuperviewMargins() @@ -137,16 +126,20 @@ class ToastView: UIView { required init?(coder aDecoder: NSCoder) { notImplemented() } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layer.cornerRadius = (self.frame.height / 2) + } // MARK: Gestures - @objc - func didTap(gesture: UITapGestureRecognizer) { + @objc func didTap(gesture: UITapGestureRecognizer) { self.delegate?.didTapToastView(self) } - @objc - func didSwipe(gesture: UISwipeGestureRecognizer) { + @objc func didSwipe(gesture: UISwipeGestureRecognizer) { self.delegate?.didSwipeToastView(self) } } diff --git a/SignalUtilitiesKit/Utilities/AppSetup.swift b/SignalUtilitiesKit/Utilities/AppSetup.swift index e444bd7f4..93ae919b6 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.swift +++ b/SignalUtilitiesKit/Utilities/AppSetup.swift @@ -70,7 +70,7 @@ public enum AppSetup { SNUtilitiesKit.migrations(), SNSnodeKit.migrations(), SNMessagingKit.migrations(), - SUIKit.migrations() + SNUIKit.migrations() ], onProgressUpdate: migrationProgressChanged, onComplete: { error, needsConfigSync in diff --git a/SignalUtilitiesKit/Utilities/UIImage+OWS.swift b/SignalUtilitiesKit/Utilities/UIImage+OWS.swift deleted file mode 100644 index e675d22f1..000000000 --- a/SignalUtilitiesKit/Utilities/UIImage+OWS.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -import Foundation - -extension UIImage { - @objc - public func asTintedImage(color: UIColor) -> UIImage? { - let template = self.withRenderingMode(.alwaysTemplate) - let imageView = UIImageView(image: template) - imageView.tintColor = color - - return imageView.renderAsImage(opaque: imageView.isOpaque, scale: UIScreen.main.scale) - } -} diff --git a/SignalUtilitiesKit/Utilities/UIUtil.h b/SignalUtilitiesKit/Utilities/UIUtil.h deleted file mode 100644 index c10563a72..000000000 --- a/SignalUtilitiesKit/Utilities/UIUtil.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import -#import -#import -#import - -#define ACCESSIBILITY_IDENTIFIER_WITH_NAME(_root_view, _variable_name) \ - ([NSString stringWithFormat:@"%@.%@", _root_view.class, _variable_name]) -#define SET_SUBVIEW_ACCESSIBILITY_IDENTIFIER(_root_view, _variable_name) \ - _variable_name.accessibilityIdentifier = ACCESSIBILITY_IDENTIFIER_WITH_NAME(_root_view, (@ #_variable_name)) - -typedef void (^completionBlock)(void); - -/** - * - * UIUtil contains various class methods that centralize common app UI functionality that would otherwise be hardcoded. - * - */ - -@interface UIUtil : NSObject - -+ (void)applyRoundedBorderToImageView:(UIImageView *)imageView; -+ (void)removeRoundedBorderToImageView:(UIImageView *__strong *)imageView; - -+ (void)setupSignalAppearence; - -@end diff --git a/SignalUtilitiesKit/Utilities/UIUtil.m b/SignalUtilitiesKit/Utilities/UIUtil.m deleted file mode 100644 index 8110cc330..000000000 --- a/SignalUtilitiesKit/Utilities/UIUtil.m +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "UIUtil.h" -#import "UIColor+OWS.h" -#import - -#import - -#define CONTACT_PICTURE_VIEW_BORDER_WIDTH 0.5f - -@implementation UIUtil - -+ (void)applyRoundedBorderToImageView:(UIImageView *)imageView -{ - imageView.layer.borderWidth = CONTACT_PICTURE_VIEW_BORDER_WIDTH; - imageView.layer.borderColor = [UIColor clearColor].CGColor; - imageView.layer.cornerRadius = CGRectGetWidth(imageView.frame) / 2; - imageView.layer.masksToBounds = YES; -} - -+ (void)removeRoundedBorderToImageView:(UIImageView *__strong *)imageView -{ - [[*imageView layer] setBorderWidth:0]; - [[*imageView layer] setCornerRadius:0]; -} - -+ (void)setupSignalAppearence -{ - UINavigationBar.appearance.barTintColor = UIColor.whiteColor; - UINavigationBar.appearance.translucent = NO; - UINavigationBar.appearance.tintColor = UIColor.blackColor; - UIToolbar.appearance.barTintColor = UIColor.blackColor; - UIToolbar.appearance.translucent = NO; - UIToolbar.appearance.tintColor = UIColor.whiteColor; - - UIBarButtonItem.appearance.tintColor = UIColor.blackColor; - [UISwitch.appearance setOnTintColor:LKColors.accent]; - [UIToolbar.appearance setTintColor:LKColors.accent]; - - // If we set NSShadowAttributeName, the NSForegroundColorAttributeName value is ignored. - UINavigationBar.appearance.titleTextAttributes = @{ NSForegroundColorAttributeName : UIColor.blackColor }; -} - -@end diff --git a/SignalUtilitiesKit/Utilities/UIViewController+OWS.h b/SignalUtilitiesKit/Utilities/UIViewController+OWS.h deleted file mode 100644 index a458d978f..000000000 --- a/SignalUtilitiesKit/Utilities/UIViewController+OWS.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface UIViewController (OWS) - -- (UIViewController *)findFrontmostViewController:(BOOL)ignoringAlerts; - -/** - * Takes up a bit less space than the default system back button - * used in the MessagesViewController to help left-align the title view. - * - * **note** Using this breaks the interactive pop gesture (swipe back) unless you set/unset the - * interactivePopGesture.delegate to self/nil on viewWillAppear/Disappear - */ -- (UIBarButtonItem *)createOWSBackButton; - -+ (UIBarButtonItem *)createOWSBackButtonWithTarget:(id)target selector:(SEL)selector; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/UIViewController+OWS.m b/SignalUtilitiesKit/Utilities/UIViewController+OWS.m deleted file mode 100644 index ea3a05715..000000000 --- a/SignalUtilitiesKit/Utilities/UIViewController+OWS.m +++ /dev/null @@ -1,122 +0,0 @@ -// -// Copyright (c) 2019 Open Whisper Systems. All rights reserved. -// - -#import - -#import "UIColor+OWS.h" -#import "UIUtil.h" -#import "UIView+OWS.h" -#import "UIViewController+OWS.h" -#import -#import - -#import - -NS_ASSUME_NONNULL_BEGIN - -@implementation UIViewController (OWS) - -- (UIViewController *)findFrontmostViewController:(BOOL)ignoringAlerts -{ - NSMutableArray *visitedViewControllers = [NSMutableArray new]; - - UIViewController *viewController = self; - while (YES) { - [visitedViewControllers addObject:viewController]; - - UIViewController *_Nullable nextViewController = viewController.presentedViewController; - if (nextViewController) { - if (!ignoringAlerts || ![nextViewController isKindOfClass:[UIAlertController class]]) { - if ([visitedViewControllers containsObject:nextViewController]) { - // Cycle detected. - return viewController; - } - viewController = nextViewController; - continue; - } - } - - if ([viewController isKindOfClass:[UINavigationController class]]) { - UINavigationController *navigationController = (UINavigationController *)viewController; - nextViewController = navigationController.topViewController; - if (nextViewController) { - if ([visitedViewControllers containsObject:nextViewController]) { - // Cycle detected. - return viewController; - } - viewController = nextViewController; - } else { - break; - } - } else { - break; - } - } - - return viewController; -} - -- (UIBarButtonItem *)createOWSBackButton -{ - return [self createOWSBackButtonWithTarget:self selector:@selector(backButtonPressed:)]; -} - -- (UIBarButtonItem *)createOWSBackButtonWithTarget:(id)target selector:(SEL)selector -{ - return [[self class] createOWSBackButtonWithTarget:target selector:selector]; -} - -+ (UIBarButtonItem *)createOWSBackButtonWithTarget:(id)target selector:(SEL)selector -{ - OWSAssertDebug(target); - OWSAssertDebug(selector); - - UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom]; - BOOL isRTL = CurrentAppContext().isRTL; - - // Nudge closer to the left edge to match default back button item. - const CGFloat kExtraLeftPadding = isRTL ? +0 : -8; - - // Give some extra hit area to the back button. This is a little smaller - // than the default back button, but makes sense for our left aligned title - // view in the MessagesViewController - const CGFloat kExtraRightPadding = isRTL ? -0 : +10; - - // Extra hit area above/below - const CGFloat kExtraHeightPadding = 8; - - // Matching the default backbutton placement is tricky. - // We can't just adjust the imageEdgeInsets on a UIBarButtonItem directly, - // so we adjust the imageEdgeInsets on a UIButton, then wrap that - // in a UIBarButtonItem. - [backButton addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside]; - - UIImageConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:22 weight:UIImageSymbolWeightMedium]; - UIImage *backImage = [[UIImage systemImageNamed:@"chevron.backward" withConfiguration:config] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - OWSAssertDebug(backImage); - [backButton setImage:backImage forState:UIControlStateNormal]; - backButton.tintColor = LKColors.text; - - backButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - backButton.imageEdgeInsets = UIEdgeInsetsMake(0, kExtraLeftPadding, 0, 0); - - CGRect buttonFrame = CGRectMake(0, 0, backImage.size.width + kExtraRightPadding, backImage.size.height + kExtraHeightPadding); - backButton.frame = buttonFrame; - - UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithCustomView:backButton accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"back")]; - backItem.width = buttonFrame.size.width; - - return backItem; -} - -#pragma mark - Event Handling - -- (void)backButtonPressed:(id)sender -{ - [self.navigationController popViewControllerAnimated:YES]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift b/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift new file mode 100644 index 000000000..c9762da43 --- /dev/null +++ b/SignalUtilitiesKit/Utilities/UIViewController+OWS.swift @@ -0,0 +1,109 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUIKit + +public extension UIViewController { + func findFrontmostViewController(ignoringAlerts: Bool) -> UIViewController { + var visitedViewControllers: [UIViewController] = [] + + var viewController: UIViewController = self + + while true { + visitedViewControllers.append(viewController) + + var nextViewController: UIViewController? = viewController.presentedViewController + + if let nextViewController: UIViewController = nextViewController { + if !ignoringAlerts || !(nextViewController is UIAlertController) { + if visitedViewControllers.contains(nextViewController) { + // Cycle detected + return viewController + } + + viewController = nextViewController + continue + } + } + + if let navController: UINavigationController = viewController as? UINavigationController { + nextViewController = navController.topViewController + + if let nextViewController: UIViewController = nextViewController { + if !ignoringAlerts || !(nextViewController is UIAlertController) { + if visitedViewControllers.contains(nextViewController) { + // Cycle detected + return viewController + } + + viewController = nextViewController + continue + } + } + + break + } + + break + } + + return viewController + } + + func createOWSBackButton() -> UIBarButtonItem { + return UIViewController.createOWSBackButton(target: self, selector: #selector(backButtonPressed)) + } + + static func createOWSBackButton(target: Any?, selector: Selector) -> UIBarButtonItem { + let backButton: UIButton = UIButton(type: .custom) + + let isRTL: Bool = CurrentAppContext().isRTL + + // Nudge closer to the left edge to match default back button item. + let extraLeftPadding: CGFloat = (isRTL ? 0 : -8) + + // Give some extra hit area to the back button. This is a little smaller + // than the default back button, but makes sense for our left aligned title + // view in the MessagesViewController + let extraRightPadding: CGFloat = (isRTL ? -0 : 10) + + // Extra hit area above/below + let extraHeightPadding: CGFloat = 8 + + // Matching the default backbutton placement is tricky. + // We can't just adjust the imageEdgeInsets on a UIBarButtonItem directly, + // so we adjust the imageEdgeInsets on a UIButton, then wrap that + // in a UIBarButtonItem. + backButton.addTarget(target, action: selector, for: .touchUpInside) + + let config: UIImage.Configuration = UIImage.SymbolConfiguration(pointSize: 22, weight: .medium) + backButton.setImage( + UIImage(systemName: "chevron.backward", withConfiguration: config)? + .withRenderingMode(.alwaysTemplate), + for: .normal + ) + backButton.themeTintColor = .textPrimary + backButton.contentHorizontalAlignment = .left + backButton.imageEdgeInsets = UIEdgeInsets(top: 0, leading: extraLeftPadding, bottom: 0, trailing: 0) + backButton.frame = CGRect( + x: 0, + y: 0, + width: ((backButton.image(for: .normal)?.size.width ?? 0) + extraRightPadding), + height: ((backButton.image(for: .normal)?.size.height ?? 0) + extraHeightPadding) + ) + + let backItem: UIBarButtonItem = UIBarButtonItem( + customView: backButton, + accessibilityIdentifier: "\(type(of: self)).back" + ) + backItem.width = backButton.frame.width + + return backItem; + } + + // MARK: - Event Handling + + @objc func backButtonPressed() { + self.navigationController?.popViewController(animated: true) + } +}