diff --git a/Rocket.Chat.xcodeproj/project.pbxproj b/Rocket.Chat.xcodeproj/project.pbxproj index 187125964b..a8121f8d09 100644 --- a/Rocket.Chat.xcodeproj/project.pbxproj +++ b/Rocket.Chat.xcodeproj/project.pbxproj @@ -107,8 +107,21 @@ 33A5C9D1206A565800FE1C8F /* activity.json in Resources */ = {isa = PBXBuildFile; fileRef = 33A5C9C8206A565800FE1C8F /* activity.json */; }; 33A5C9D2206A565800FE1C8F /* nature.json in Resources */ = {isa = PBXBuildFile; fileRef = 33A5C9C9206A565800FE1C8F /* nature.json */; }; 33A7AD9E207A2F95000E9D0F /* ChatNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33A7AD9D207A2F95000E9D0F /* ChatNotification.swift */; }; + 33D08E2420BD5EDC008D03EF /* ThemeableViewControllers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E1F20BD5EDC008D03EF /* ThemeableViewControllers.swift */; }; + 33D08E2520BD5EDC008D03EF /* ThemeableViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E2020BD5EDC008D03EF /* ThemeableViews.swift */; }; + 33D08E2620BD5EDC008D03EF /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E2120BD5EDC008D03EF /* ThemeManager.swift */; }; + 33D08E2720BD5EDC008D03EF /* NotThemeableViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E2220BD5EDC008D03EF /* NotThemeableViews.swift */; }; + 33D08E2820BD5EDC008D03EF /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E2320BD5EDC008D03EF /* Theme.swift */; }; + 33D08E2A20BD5F24008D03EF /* TopTransparentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E2920BD5F24008D03EF /* TopTransparentViewController.swift */; }; + 33D08E2E20BD9267008D03EF /* ThemePreferenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E2C20BD9267008D03EF /* ThemePreferenceController.swift */; }; + 33D08E2F20BD9267008D03EF /* ThemePreferenceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E2D20BD9267008D03EF /* ThemePreferenceViewModel.swift */; }; + 33D08E3120BD92A5008D03EF /* ThemePreferenceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33D08E3020BD92A5008D03EF /* ThemePreferenceCell.swift */; }; 33F73B2D2073BDF400F03F29 /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33F73B2B2073BDF400F03F29 /* NotificationView.swift */; }; 33F73B302073F24200F03F29 /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33F73B2F2073F24200F03F29 /* NotificationViewController.swift */; }; + 33FB9D3E20CEF610005AF504 /* SubscriptionsSortingSeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33FB9D3D20CEF610005AF504 /* SubscriptionsSortingSeparatorView.swift */; }; + 33FB9D4220D06492005AF504 /* EditProfileStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33FB9D4120D06492005AF504 /* EditProfileStatusCell.swift */; }; + 33FB9D4420D0EE7F005AF504 /* ServerInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33FB9D4320D0EE7F005AF504 /* ServerInfoCell.swift */; }; + 33FD181D20D253AE00380BEC /* BaseNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33FD181C20D253AE00380BEC /* BaseNavigationBar.swift */; }; 35A203212022D3F900B4BE5A /* ChatMessageAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A203202022D3F900B4BE5A /* ChatMessageAttachmentView.swift */; }; 35BCD301201A57EA00B4BE5A /* Ask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35BCD300201A57EA00B4BE5A /* Ask.swift */; }; 35BCD303201A9FB800B4BE5A /* AskSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35BCD302201A9FB800B4BE5A /* AskSpec.swift */; }; @@ -838,8 +851,21 @@ 33A5C9C8206A565800FE1C8F /* activity.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = activity.json; sourceTree = ""; }; 33A5C9C9206A565800FE1C8F /* nature.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = nature.json; sourceTree = ""; }; 33A7AD9D207A2F95000E9D0F /* ChatNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatNotification.swift; sourceTree = ""; }; + 33D08E1F20BD5EDC008D03EF /* ThemeableViewControllers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeableViewControllers.swift; sourceTree = ""; }; + 33D08E2020BD5EDC008D03EF /* ThemeableViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeableViews.swift; sourceTree = ""; }; + 33D08E2120BD5EDC008D03EF /* ThemeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; + 33D08E2220BD5EDC008D03EF /* NotThemeableViews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotThemeableViews.swift; sourceTree = ""; }; + 33D08E2320BD5EDC008D03EF /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + 33D08E2920BD5F24008D03EF /* TopTransparentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopTransparentViewController.swift; sourceTree = ""; }; + 33D08E2C20BD9267008D03EF /* ThemePreferenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemePreferenceController.swift; sourceTree = ""; }; + 33D08E2D20BD9267008D03EF /* ThemePreferenceViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemePreferenceViewModel.swift; sourceTree = ""; }; + 33D08E3020BD92A5008D03EF /* ThemePreferenceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ThemePreferenceCell.swift; path = Preferences/ThemePreferenceCell.swift; sourceTree = ""; }; 33F73B2B2073BDF400F03F29 /* NotificationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; 33F73B2F2073F24200F03F29 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = ""; }; + 33FB9D3D20CEF610005AF504 /* SubscriptionsSortingSeparatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsSortingSeparatorView.swift; sourceTree = ""; }; + 33FB9D4120D06492005AF504 /* EditProfileStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileStatusCell.swift; sourceTree = ""; }; + 33FB9D4320D0EE7F005AF504 /* ServerInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerInfoCell.swift; sourceTree = ""; }; + 33FD181C20D253AE00380BEC /* BaseNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNavigationBar.swift; sourceTree = ""; }; 35A203202022D3F900B4BE5A /* ChatMessageAttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageAttachmentView.swift; sourceTree = ""; }; 35AE3FC690B1D3DC4E9DE715 /* Pods-Rocket.ChatTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Rocket.ChatTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Rocket.ChatTests/Pods-Rocket.ChatTests.release.xcconfig"; sourceTree = ""; }; 35BCD300201A57EA00B4BE5A /* Ask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ask.swift; sourceTree = ""; }; @@ -1682,6 +1708,27 @@ path = Emojis; sourceTree = ""; }; + 33D08E1E20BD5EDC008D03EF /* Theme */ = { + isa = PBXGroup; + children = ( + 33D08E1F20BD5EDC008D03EF /* ThemeableViewControllers.swift */, + 33D08E2020BD5EDC008D03EF /* ThemeableViews.swift */, + 33D08E2120BD5EDC008D03EF /* ThemeManager.swift */, + 33D08E2220BD5EDC008D03EF /* NotThemeableViews.swift */, + 33D08E2320BD5EDC008D03EF /* Theme.swift */, + ); + path = Theme; + sourceTree = ""; + }; + 33D08E2B20BD9267008D03EF /* Theme */ = { + isa = PBXGroup; + children = ( + 33D08E2C20BD9267008D03EF /* ThemePreferenceController.swift */, + 33D08E2D20BD9267008D03EF /* ThemePreferenceViewModel.swift */, + ); + path = Theme; + sourceTree = ""; + }; 33F73B292073BDD800F03F29 /* Notification */ = { isa = PBXGroup; children = ( @@ -1696,16 +1743,26 @@ children = ( 33F73B2F2073F24200F03F29 /* NotificationViewController.swift */, 333207FE20766E4F00AD3290 /* NotificationViewController.xib */, + 33D08E2920BD5F24008D03EF /* TopTransparentViewController.swift */, ); path = Notification; sourceTree = ""; }; + 33FD181B20D252FE00380BEC /* Base */ = { + isa = PBXGroup; + children = ( + 33FD181C20D253AE00380BEC /* BaseNavigationBar.swift */, + ); + path = Base; + sourceTree = ""; + }; 4102E3AB1E53272C004BAA82 /* Preferences */ = { isa = PBXGroup; children = ( 0B3A9767202C75A60019CA92 /* ChangeAppIcon */, 0B9AB2BF20444EB300ABEA05 /* Language */, 99282260204DE0AE005D2067 /* Profile */, + 33D08E2B20BD9267008D03EF /* Theme */, 9960C82E2063F091004A034C /* Web Browser */, 806728FC200789F4009FE94D /* PreferencesNavigationController.swift */, 80D41DF5208FC57100034D1F /* PreferencesViewController.swift */, @@ -1759,7 +1816,9 @@ 4153E80320504BC800FBC26B /* Preferences */ = { isa = PBXGroup; children = ( + 33FB9D4120D06492005AF504 /* EditProfileStatusCell.swift */, 4153E80420504BE700FBC26B /* ReusableViewText.swift */, + 33FB9D4320D0EE7F005AF504 /* ServerInfoCell.swift */, 412EF3C52050507C001689A3 /* ReusableViewText.xib */, ); path = Preferences; @@ -1824,6 +1883,7 @@ 99805D7320C190FC00741B8E /* TextFields */, 80D41DFB2092152A00034D1F /* ActionSheets */, 41C275DD1D847FEE003C88CF /* Avatar */, + 33FD181B20D252FE00380BEC /* Base */, 416133301D46CA3100E09DA2 /* Cells */, 41F167E51DAC450200775CCA /* Chat */, 897083CC1F8CF08100233561 /* Form */, @@ -1845,6 +1905,7 @@ 4192054A1D52F4E7004EEC5F /* Subscription */, 80235D231F7466FE00A56CA5 /* LoaderTableViewCell.swift */, 80235D251F74672D00A56CA5 /* LoaderTableViewCell.xib */, + 33D08E3020BD92A5008D03EF /* ThemePreferenceCell.swift */, ); path = Cells; sourceTree = ""; @@ -2196,6 +2257,7 @@ 4156250720BEDCD100D20576 /* ServersListViewModel.swift */, 41D3668420C0323100970B99 /* SubscriptionsSortingView.swift */, 41D3668320C0323000970B99 /* SubscriptionsSortingView.xib */, + 33FB9D3D20CEF610005AF504 /* SubscriptionsSortingSeparatorView.swift */, 41D3668520C0323100970B99 /* SubscriptionsSortingViewModel.swift */, ); path = Subscriptions; @@ -2335,6 +2397,7 @@ 41A79C0D1D2F084F00A1968E /* Models */, 0B3A9760202C4DE10019CA92 /* Resources */, 4174CB0B1D2D99170086DAC8 /* Storyboards */, + 33D08E1E20BD5EDC008D03EF /* Theme */, 4161332F1D46CA2800E09DA2 /* Views */, 41DF76E21D2C50710028DBF8 /* AppDelegate.swift */, 41DF76EE1D2C50720028DBF8 /* Info.plist */, @@ -3870,6 +3933,7 @@ 413FB6BF1FE17D7D00F010AA /* UIStoryboardExtensions.swift in Sources */, 41E9BD0D208EA4040084D4D8 /* AuthInternalExtensions.swift in Sources */, 41852E891F92BBEC00D1C499 /* ChatControllerReplyHandler.swift in Sources */, + 33D08E2E20BD9267008D03EF /* ThemePreferenceController.swift in Sources */, D1C536CC1F688B2F00EBA8D9 /* MarkdownManager.swift in Sources */, 418C4DC61DC4C07800ABED4C /* SubscriptionSectionView.swift in Sources */, 7758A7641F8FA44400FAA244 /* PickerViewDelegate.swift in Sources */, @@ -3894,6 +3958,7 @@ B5893BF41F6C4A5F00365768 /* UserReviewManager.swift in Sources */, 9977302C2072B746009AF465 /* PublicSettingsRequest.swift in Sources */, 41552F661D30308C0081438D /* AuthManager.swift in Sources */, + 33FD181D20D253AE00380BEC /* BaseNavigationBar.swift in Sources */, 4112DC5A1FFFB9B2005995E1 /* ModelMappeable.swift in Sources */, 809B53161FE3D3DC00833DD2 /* MessageReaction.swift in Sources */, D10E9C1A1F643457007F1796 /* Channel.swift in Sources */, @@ -3902,11 +3967,13 @@ 413996171F3B44500075F96E /* UploadHelper.swift in Sources */, 7798B4151F852B720074B2F4 /* SelectField.swift in Sources */, 805DEC351FFC03380033151B /* CustomEmojiManager.swift in Sources */, + 33D08E2520BD5EDC008D03EF /* ThemeableViews.swift in Sources */, 0BC0E8632032DD95004BFAAF /* DrawingViewModel.swift in Sources */, 35E892C8201CDD1600B4BE5A /* NewRoomViewController.swift in Sources */, 990FF6D620740C79007B4A53 /* RoomMentionsRequest.swift in Sources */, 8039441820AF620C002F317A /* ResourceWithError.swift in Sources */, 416133321D46CA4E00E09DA2 /* ChatMessageCell.swift in Sources */, + 33FB9D4420D0EE7F005AF504 /* ServerInfoCell.swift in Sources */, 41F3704D20A31A3E00C5449E /* UIViewControllerExtension.swift in Sources */, 8041C0422028C7EF007E21FA /* ReactorListView.swift in Sources */, 41DC7A1B1D38454500896FC0 /* Message.swift in Sources */, @@ -3992,12 +4059,14 @@ 41DE687B20C6C52B00AA5EC8 /* SubscriptionsSortingManager.swift in Sources */, 80E99F291FD8B2B800B70B59 /* UserExtensions.swift in Sources */, 4151B4541E2D1A9E00F8AA1B /* SubscriptionModelHandler.swift in Sources */, + 33D08E2F20BD9267008D03EF /* ThemePreferenceViewModel.swift in Sources */, 80113DF81F98330C0048F2C2 /* OAuthViewController.swift in Sources */, 80E9DBD6209CA8FE00A48CA9 /* Closeable.swift in Sources */, 9987B5962093E4BA007D277C /* FilesListViewController.swift in Sources */, 41C955FC20A3937A00FC8314 /* ChatMessageActionButtonsView.swift in Sources */, 339B692B2050449700F97392 /* KeyboardFrameView.swift in Sources */, 0B9AB2C320444ED600ABEA05 /* LanguageViewModel.swift in Sources */, + 33D08E2620BD5EDC008D03EF /* ThemeManager.swift in Sources */, 41BD37E11E290F2900CBC4C2 /* UserModelMapping.swift in Sources */, 412BCC871E55C6B800F7F4EE /* ChatMessageTextView.swift in Sources */, 140A95E6202F7074003FD564 /* DrawingControllerDelegate.swift in Sources */, @@ -4013,6 +4082,7 @@ 800FCD4F1F728EC800D9A692 /* ChannelInfoUserCell.swift in Sources */, 14BAFAE220308F010054A2E6 /* DrawingBrushColorViewController.swift in Sources */, 8076FDD6204864E700114F28 /* UserQueries.swift in Sources */, + 33FB9D3E20CEF610005AF504 /* SubscriptionsSortingSeparatorView.swift in Sources */, 995F710B20C7822A00B7535F /* AuthTableViewController.swift in Sources */, 800FCD4D1F728EC800D9A692 /* ChannelInfoDescriptionCell.swift in Sources */, 41DD613A203D9E6600B02D8A /* UISplitViewControllerExtensions.swift in Sources */, @@ -4052,10 +4122,12 @@ 4101BF011F8D0A1700F67E89 /* AppManager.swift in Sources */, 8041C04620290A48007E21FA /* RCEmojiKitLocalizable.swift in Sources */, 414EFF921E54FE69004F001F /* AuthExtensions.swift in Sources */, + 33FB9D4220D06492005AF504 /* EditProfileStatusCell.swift in Sources */, 414A1FFA1D46395400093E10 /* SocketManager.swift in Sources */, 800FCD4E1F728EC800D9A692 /* ChannelInfoDetailCell.swift in Sources */, 4151B45C1E2D1E5800F8AA1B /* AuthSettingsModelMapping.swift in Sources */, 806401331FB09EC400990572 /* PermissionModelMapping.swift in Sources */, + 33D08E2820BD5EDC008D03EF /* Theme.swift in Sources */, 4192054E1D52F5F1004EEC5F /* UIColorExtension.swift in Sources */, 41DCB8281DDC82E000E1197F /* SubscriptionSearchMoreView.swift in Sources */, 41597DCE1E8D3C5B00638E3E /* TwoFactorAuthTableViewController.swift in Sources */, @@ -4083,7 +4155,9 @@ 4124D835209A0533005374CD /* ChatDirectMessageHeaderCell.swift in Sources */, 413FB6DD1FE17E4500F010AA /* LoaderView.swift in Sources */, 8076FDC320485A2200114F28 /* SubscriptionUtils.swift in Sources */, + 33D08E2420BD5EDC008D03EF /* ThemeableViewControllers.swift in Sources */, 35BCD301201A57EA00B4BE5A /* Ask.swift in Sources */, + 33D08E3120BD92A5008D03EF /* ThemePreferenceCell.swift in Sources */, 992B5AB6209A14B5009C8123 /* AudioFileViewController.swift in Sources */, 8041C0402028C7A1007E21FA /* ReactorListViewController.swift in Sources */, 99D888FB204623A900E51306 /* UpdateUserRequest.swift in Sources */, @@ -4094,6 +4168,7 @@ 415DC7F61F67F5D30039FB4F /* NetworkManager.swift in Sources */, 807371A61F96A4FF00D53ADF /* LoginServiceModelHandler.swift in Sources */, 80D41DFD2092378400034D1F /* StarMessageRequest.swift in Sources */, + 33D08E2A20BD5F24008D03EF /* TopTransparentViewController.swift in Sources */, 41C45AEF1DFAD42800D9969C /* ChatDataController.swift in Sources */, 801DF8151FD7172500302CC8 /* SubscriptionUserView.swift in Sources */, 4112DC591FFFB9B2005995E1 /* ModelHandler.swift in Sources */, @@ -4110,6 +4185,7 @@ 4162E1531D651A8800AAAE49 /* UserManager.swift in Sources */, 800FCD4C1F728EC800D9A692 /* ChannelInfoCellProtocol.swift in Sources */, 800E22841F8500A200DA84F1 /* MessagesListViewController.swift in Sources */, + 33D08E2720BD5EDC008D03EF /* NotThemeableViews.swift in Sources */, 99805D7520C191BE00741B8E /* StyledTextField.swift in Sources */, 419ECCA41F3CA21A005F224B /* DownloadManager.swift in Sources */, 80307E3C1FD75BE1006AD9EF /* VOTextField.swift in Sources */, diff --git a/Rocket.Chat/API/Clients/MessagesClient.swift b/Rocket.Chat/API/Clients/MessagesClient.swift index 2dc8d49f28..7a0e80c3d4 100644 --- a/Rocket.Chat/API/Clients/MessagesClient.swift +++ b/Rocket.Chat/API/Clients/MessagesClient.swift @@ -40,7 +40,7 @@ struct MessagesClient: APIClient { realm?.add(message, update: true) } - MessageTextCacheManager.shared.update(for: message) + MessageTextCacheManager.shared.update(for: message, with: nil) } } diff --git a/Rocket.Chat/AppDelegate.swift b/Rocket.Chat/AppDelegate.swift index 31f05c5232..d1cc07f4a1 100644 --- a/Rocket.Chat/AppDelegate.swift +++ b/Rocket.Chat/AppDelegate.swift @@ -14,7 +14,7 @@ import UserNotifications class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - var notificationWindow: UIWindow? + var notificationWindow: TransparentToTouchesWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { Launcher().prepareToLaunch(with: launchOptions) @@ -50,6 +50,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { notificationWindow?.rootViewController = NotificationViewController.shared notificationWindow?.windowLevel = UIWindowLevelAlert notificationWindow?.makeKeyAndVisible() + notificationWindow?.isHidden = true } // MARK: AppDelegate LifeCycle diff --git a/Rocket.Chat/Controllers/Base/BaseNavigationController.swift b/Rocket.Chat/Controllers/Base/BaseNavigationController.swift index 399942ba12..16b24a318e 100644 --- a/Rocket.Chat/Controllers/Base/BaseNavigationController.swift +++ b/Rocket.Chat/Controllers/Base/BaseNavigationController.swift @@ -16,6 +16,19 @@ class BaseNavigationController: UINavigationController { return !(topViewController is WelcomeViewController) } + override init(rootViewController: UIViewController) { + super.init(navigationBarClass: BaseNavigationBar.self, toolbarClass: nil) + self.setViewControllers([rootViewController], animated: false) + } + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + override func viewDidLoad() { super.viewDidLoad() @@ -23,8 +36,7 @@ class BaseNavigationController: UINavigationController { let navBar = self.navigationBar navBar.isTranslucent = false - navBar.tintColor = .RCBlue() - navBar.barTintColor = .RCNavigationBarColor() + (navBar as? BaseNavigationBar)?.themeSource = self } override func popToRootViewController(animated: Bool) -> [UIViewController]? { @@ -56,19 +68,21 @@ class BaseNavigationController: UINavigationController { } func setTransparentTheme(forceRedraw: Bool = false) { - UIApplication.shared.statusBarStyle = .default let navBar = self.navigationBar + navBar.barStyle = .default navBar.setBackgroundImage(UIImage(), for: .default) navBar.shadowImage = UIImage() navBar.backgroundColor = UIColor.clear navBar.isTranslucent = true navBar.tintColor = .RCBlue() if forceRedraw { forceNavigationToRedraw() } + navBar.applyTheme() + setNeedsStatusBarAppearanceUpdate() } func setGrayTheme(forceRedraw: Bool = false) { - UIApplication.shared.statusBarStyle = .lightContent let navBar = self.navigationBar + navBar.barStyle = .black navBar.shadowImage = UIImage() navBar.backgroundColor = .RCNavBarGray() navBar.barTintColor = .RCNavBarGray() @@ -76,6 +90,8 @@ class BaseNavigationController: UINavigationController { navBar.tintColor = .white navBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white] if forceRedraw { forceNavigationToRedraw() } + navBar.applyTheme() + setNeedsStatusBarAppearanceUpdate() } func forceNavigationToRedraw() { @@ -83,3 +99,9 @@ class BaseNavigationController: UINavigationController { isNavigationBarHidden = false } } + +extension BaseNavigationController: BaseNavigationBarThemeSource { + var navgiationBarTheme: Theme? { + return topViewController?.view.theme + } +} diff --git a/Rocket.Chat/Controllers/Base/BaseTableViewController.swift b/Rocket.Chat/Controllers/Base/BaseTableViewController.swift index b78daf998d..acaca1efd5 100644 --- a/Rocket.Chat/Controllers/Base/BaseTableViewController.swift +++ b/Rocket.Chat/Controllers/Base/BaseTableViewController.swift @@ -12,6 +12,8 @@ class BaseTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() + ThemeManager.addObserver(self) + self.navigationItem.backBarButtonItem = UIBarButtonItem( title: "", style: .plain, diff --git a/Rocket.Chat/Controllers/Base/BaseViewController.swift b/Rocket.Chat/Controllers/Base/BaseViewController.swift index b1b4bbb914..086c984ddb 100644 --- a/Rocket.Chat/Controllers/Base/BaseViewController.swift +++ b/Rocket.Chat/Controllers/Base/BaseViewController.swift @@ -12,7 +12,9 @@ class BaseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - self.navigationItem.backBarButtonItem = UIBarButtonItem( + ThemeManager.addObserver(self) + + navigationItem.backBarButtonItem = UIBarButtonItem( title: "", style: .plain, target: nil, diff --git a/Rocket.Chat/Controllers/Base/MainSplitViewController.swift b/Rocket.Chat/Controllers/Base/MainSplitViewController.swift index d1e2e48dd5..4de9963fe9 100644 --- a/Rocket.Chat/Controllers/Base/MainSplitViewController.swift +++ b/Rocket.Chat/Controllers/Base/MainSplitViewController.swift @@ -42,6 +42,8 @@ final class MainSplitViewController: UISplitViewController { override func awakeFromNib() { super.awakeFromNib() + ThemeManager.addObserver(self) + delegate = self preferredDisplayMode = .allVisible @@ -49,6 +51,14 @@ final class MainSplitViewController: UISplitViewController { SocketManager.reconnect() } + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + applyTheme() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return view.theme?.appearence.statusBarStyle ?? .default + } } // MARK: UISplitViewControllerDelegate @@ -72,3 +82,13 @@ extension MainSplitViewController: SocketConnectionHandler { } } + +// MARK: Themeable + +extension MainSplitViewController { + override func applyTheme() { + guard let theme = view.theme else { return } + view.backgroundColor = theme.mutedAccent + view.subviews.first?.backgroundColor = theme.mutedAccent + } +} diff --git a/Rocket.Chat/Controllers/Chat/ChannelInfoViewController.swift b/Rocket.Chat/Controllers/Chat/ChannelInfoViewController.swift new file mode 100644 index 0000000000..5844c8fced --- /dev/null +++ b/Rocket.Chat/Controllers/Chat/ChannelInfoViewController.swift @@ -0,0 +1,276 @@ +// +// ChannelInfoViewController.swift +// Rocket.Chat +// +// Created by Rafael Kellermann Streit on 09/03/17. +// Copyright © 2017 Rocket.Chat. All rights reserved. +// + +import UIKit + +private typealias ListSegueData = (title: String, query: String?, isListingMentions: Bool) + +class ChannelInfoViewController: BaseViewController { + + var tableViewData: [[Any]] = [] { + didSet { + tableView?.reloadData() + } + } + + var subscription: Subscription? { + didSet { + guard let subscription = self.subscription else { return } + + let channelInfoData = [ + ChannelInfoDetailCellData(title: localized("chat.info.item.files"), detail: "", action: showFilesList), + subscription.canViewMembersList ? ChannelInfoDetailCellData(title: localized("chat.info.item.members"), detail: "", action: showMembersList) : nil, + ChannelInfoDetailCellData(title: localized("chat.info.item.pinned"), detail: "", action: showPinnedList), + ChannelInfoDetailCellData(title: localized("chat.info.item.starred"), detail: "", action: showStarredList), + subscription.canViewMentionsList ? ChannelInfoDetailCellData(title: localized("chat.info.item.mentions"), detail: "", action: showMentionsList) : nil + ].compactMap({$0}) + + if subscription.type == .directMessage { + tableViewData = [[ + ChannelInfoUserCellData(user: subscription.directMessageUser) + ], channelInfoData] + } else { + let topic = subscription.roomTopic?.count ?? 0 == 0 ? localized("chat.info.item.no_topic") : subscription.roomTopic + let description = subscription.roomDescription?.count ?? 0 == 0 ? localized("chat.info.item.no_description") : subscription.roomDescription + + tableViewData = [[ + ChannelInfoBasicCellData(title: "#\(subscription.displayName())"), + ChannelInfoDescriptionCellData( + title: localized("chat.info.item.topic"), + description: topic + ), + ChannelInfoDescriptionCellData( + title: localized("chat.info.item.description"), + description: description + ) + ], channelInfoData] + } + } + } + + @IBOutlet weak var tableView: UITableView! + weak var buttonFavorite: UIBarButtonItem? + + override func viewDidLoad() { + super.viewDidLoad() + ThemeManager.addObserver(self) + ThemeManager.addObserver(navigationController?.navigationBar) + title = localized("chat.info.title") + + if let settings = AuthSettingsManager.settings { + if settings.favoriteRooms { + let defaultImage = UIImage(named: "Star")?.imageWithTint(UIColor.RCGray()).withRenderingMode(.alwaysOriginal) + let buttonFavorite = UIBarButtonItem(image: defaultImage, style: .plain, target: self, action: #selector(buttonFavoriteDidPressed)) + navigationItem.rightBarButtonItem = buttonFavorite + self.buttonFavorite = buttonFavorite + updateButtonFavoriteImage() + } + } + } + + func updateButtonFavoriteImage(_ force: Bool = false, value: Bool = false) { + guard let buttonFavorite = self.buttonFavorite else { return } + let favorite = force ? value : subscription?.favorite ?? false + var image: UIImage? + + if favorite { + image = UIImage(named: "Star-Filled")?.imageWithTint(UIColor.RCFavoriteMark()) + } else { + image = UIImage(named: "Star")?.imageWithTint(UIColor.RCGray()) + } + + buttonFavorite.image = image?.withRenderingMode(.alwaysOriginal) + } + + func showMembersList() { + self.performSegue(withIdentifier: "toMembersList", sender: self) + } + + func showPinnedList() { + let data = ListSegueData( + title: localized("chat.messages.pinned.list.title"), + query: "{\"pinned\":true}", + isListingMentions: false + ) + + self.performSegue(withIdentifier: "toMessagesList", sender: data) + } + + func showStarredList() { + guard let userId = AuthManager.currentUser()?.identifier else { + alert(title: localized("error.socket.default_error.title"), message: localized("error.socket.default_error.message")) + return + } + + let data = ListSegueData( + title: localized("chat.messages.starred.list.title"), + query: "{\"starred._id\":{\"$in\":[\"\(userId)\"]}}", + isListingMentions: false + ) + + self.performSegue(withIdentifier: "toMessagesList", sender: data) + } + + func showMentionsList() { + let data = ListSegueData( + title: localized("chat.messages.mentions.list.title"), + query: nil, + isListingMentions: true + ) + + self.performSegue(withIdentifier: "toMessagesList", sender: data) + } + + func showFilesList() { + let data = ListSegueData( + title: localized("chat.messages.files.list.title"), + query: nil, + isListingMentions: false + ) + + self.performSegue(withIdentifier: "toFilesList", sender: data) + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let membersList = segue.destination as? MembersListViewController { + membersList.data.subscription = self.subscription + } + + if let messagesList = segue.destination as? MessagesListViewController { + messagesList.data.subscription = self.subscription + + if let segueData = sender as? ListSegueData { + messagesList.data.title = segueData.title + messagesList.data.query = segueData.query + messagesList.data.isListingMentions = segueData.isListingMentions + } + } + + if let filesList = segue.destination as? FilesListViewController { + filesList.data.subscription = self.subscription + + if let segueData = sender as? ListSegueData { + filesList.data.title = segueData.title + } + } + } + + // MARK: IBAction + + @objc func buttonFavoriteDidPressed(_ sender: Any) { + guard let subscription = self.subscription else { return } + + SubscriptionManager.toggleFavorite(subscription) { [unowned self] (response) in + if response.isError() { + subscription.updateFavorite(!subscription.favorite) + } + + self.updateButtonFavoriteImage() + } + + self.subscription?.updateFavorite(!subscription.favorite) + updateButtonFavoriteImage() + } + + @IBAction func buttonCloseDidPressed(_ sender: Any) { + dismiss(animated: true, completion: nil) + } + +} + +// MARK: UITableViewDelegate + +extension ChannelInfoViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let data = tableViewData[indexPath.section][indexPath.row] + + if let data = data as? ChannelInfoBasicCellData { + if let cell = tableView.dequeueReusableCell(withIdentifier: ChannelInfoBasicCell.identifier) as? ChannelInfoBasicCell { + cell.data = data + return cell + } + } + + if let data = data as? ChannelInfoDetailCellData { + if let cell = tableView.dequeueReusableCell(withIdentifier: ChannelInfoDetailCell.identifier) as? ChannelInfoDetailCell { + cell.data = data + return cell + } + } + + if let data = data as? ChannelInfoUserCellData { + if let cell = tableView.dequeueReusableCell(withIdentifier: ChannelInfoUserCell.identifier) as? ChannelInfoUserCell { + cell.data = data + return cell + } + } + + if let data = data as? ChannelInfoDescriptionCellData { + if let cell = tableView.dequeueReusableCell(withIdentifier: ChannelInfoDescriptionCell.identifier) as? ChannelInfoDescriptionCell { + cell.data = data + return cell + } + } + + return UITableViewCell() + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let data = tableViewData[indexPath.section][indexPath.row] + + if data as? ChannelInfoBasicCellData != nil { + return CGFloat(ChannelInfoBasicCell.defaultHeight) + } + + if data as? ChannelInfoDetailCellData != nil { + return CGFloat(ChannelInfoDetailCell.defaultHeight) + } + + if data as? ChannelInfoUserCellData != nil { + return CGFloat(ChannelInfoUserCell.defaultHeight) + } + + if data as? ChannelInfoDescriptionCellData != nil { + return CGFloat(ChannelInfoDescriptionCell.defaultHeight) + } + + return CGFloat(0) + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let data = tableViewData[indexPath.section][indexPath.row] + + if let data = data as? ChannelInfoDetailCellData { + guard let action = data.action else { + alert(title: localized("alert.feature.wip.title"), message: localized("alert.feature.wip.message")) + return + } + + action() + + if let selectedIndex = tableView.indexPathForSelectedRow { + tableView.deselectRow(at: selectedIndex, animated: true) + } + } + } +} + +// MARK: UITableViewDataSource + +extension ChannelInfoViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return tableViewData.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return tableViewData[section].count + } + +} diff --git a/Rocket.Chat/Controllers/Chat/ChatControllerHeaderStatus.swift b/Rocket.Chat/Controllers/Chat/ChatControllerHeaderStatus.swift new file mode 100644 index 0000000000..018c884ce8 --- /dev/null +++ b/Rocket.Chat/Controllers/Chat/ChatControllerHeaderStatus.swift @@ -0,0 +1,69 @@ +// +// ChatControllerHeaderStatus.swift +// Rocket.Chat +// +// Created by Rafael Kellermann Streit on 10/08/17. +// Copyright © 2017 Rocket.Chat. All rights reserved. +// + +import UIKit + +extension ChatViewController { + + func showHeaderStatusView() { + chatHeaderViewStatus?.removeFromSuperview() + + if let headerView = ChatHeaderViewStatus.instantiateFromNib() { + headerView.delegate = self + headerView.translatesAutoresizingMaskIntoConstraints = false + headerView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 44) + view.addSubview(headerView) + chatHeaderViewStatus = headerView + chatHeaderViewStatus?.applyTheme() + + // Shadow of the view + headerView.layer.masksToBounds = false + headerView.layer.shadowColor = UIColor.black.cgColor + headerView.layer.shadowOpacity = 0.5 + headerView.layer.shadowOffset = CGSize(width: 0, height: 0) + headerView.layer.shadowRadius = 5 + + view.addConstraints(NSLayoutConstraint.constraints( + withVisualFormat: "|-0-[headerView]-0-|", + options: .alignAllLeft, + metrics: nil, + views: ["headerView": headerView]) + ) + + view.addConstraints(NSLayoutConstraint.constraints( + withVisualFormat: "V:|-0-[headerView(44)]", + options: .alignAllLeft, + metrics: nil, + views: ["headerView": headerView]) + ) + } + } + + func hideHeaderStatusView() { + chatHeaderViewStatus?.removeFromSuperview() + } + + func showLoadingMessagesHeaderStatusView() { + showHeaderStatusView() + + chatHeaderViewStatus?.labelTitle.text = localized("chat.loading_messages") + chatHeaderViewStatus?.buttonRefresh.isHidden = true + chatPreviewModeView?.applyTheme() + chatHeaderViewStatus?.activityIndicator.startAnimating() + } +} + +// MARK: ChatHeaderViewStatusDelegate + +extension ChatViewController: ChatHeaderViewStatusDelegate { + + func viewStatusButtonRefreshDidPressed(_ view: ChatHeaderViewStatus) { + reconnect() + } + +} diff --git a/Rocket.Chat/Controllers/Chat/ChatControllerMessageActions.swift b/Rocket.Chat/Controllers/Chat/ChatControllerMessageActions.swift index c49096c018..01a3b671cf 100644 --- a/Rocket.Chat/Controllers/Chat/ChatControllerMessageActions.swift +++ b/Rocket.Chat/Controllers/Chat/ChatControllerMessageActions.swift @@ -103,6 +103,7 @@ extension ChatViewController { let edit = UIAlertAction(title: localized("chat.message.actions.edit"), style: .default, handler: { (_) in self.messageToEdit = message self.editText(message.text) + self.applyTheme() }) actions.append(edit) @@ -189,6 +190,7 @@ extension ChatViewController { if let presenter = controller.popoverPresentationController { presenter.sourceView = view presenter.sourceRect = view.bounds + presenter.backgroundColor = view.theme?.focusedBackground } controller.emojiPicked = { emoji in @@ -197,6 +199,7 @@ extension ChatViewController { } controller.customEmojis = CustomEmoji.emojis() + ThemeManager.addObserver(controller.view) if UIDevice.current.userInterfaceIdiom == .phone { self.navigationController?.pushViewController(controller, animated: true) diff --git a/Rocket.Chat/Controllers/Chat/ChatControllerMessageCellProtocol.swift b/Rocket.Chat/Controllers/Chat/ChatControllerMessageCellProtocol.swift index 1afde1984f..2e30e41361 100644 --- a/Rocket.Chat/Controllers/Chat/ChatControllerMessageCellProtocol.swift +++ b/Rocket.Chat/Controllers/Chat/ChatControllerMessageCellProtocol.swift @@ -50,6 +50,7 @@ extension ChatViewController: ChatMessageCellProtocol, UserActionSheetPresenter if let presenter = controller.popoverPresentationController { presenter.sourceView = reactionView presenter.sourceRect = reactionView.bounds + presenter.backgroundColor = view.theme?.focusedBackground } self.present(controller, animated: true) diff --git a/Rocket.Chat/Controllers/Chat/ChatControllerReplyHandler.swift b/Rocket.Chat/Controllers/Chat/ChatControllerReplyHandler.swift index d9c0bd7eac..794dd3fa80 100644 --- a/Rocket.Chat/Controllers/Chat/ChatControllerReplyHandler.swift +++ b/Rocket.Chat/Controllers/Chat/ChatControllerReplyHandler.swift @@ -12,7 +12,6 @@ import SlackTextViewController extension ChatViewController { func setupReplyView() { replyView = ReplyView.instantiateFromNib() - replyView.backgroundColor = textInputbar.addonContentView.backgroundColor replyView.frame = textInputbar.addonContentView.bounds replyView.onClose = { [weak self] in self?.stopReplying() diff --git a/Rocket.Chat/Controllers/Chat/ChatControllerSocketConnectionHandler.swift b/Rocket.Chat/Controllers/Chat/ChatControllerSocketConnectionHandler.swift new file mode 100644 index 0000000000..bcd8b3cada --- /dev/null +++ b/Rocket.Chat/Controllers/Chat/ChatControllerSocketConnectionHandler.swift @@ -0,0 +1,34 @@ +// +// ChatControllerSocketConnectionHandler.swift +// Rocket.Chat +// +// Created by Rafael Kellermann Streit on 17/12/16. +// Copyright © 2016 Rocket.Chat. All rights reserved. +// + +import UIKit + +extension ChatViewController: SocketConnectionHandler { + + func socketDidConnect(socket: SocketManager) { + hideHeaderStatusView() + + DispatchQueue.main.async { [weak self] in + guard let subscription = self?.subscription ?? .initialSubscription() else { return } + if !subscription.isInvalidated, subscription.isValid() { + self?.subscription = subscription + } + } + } + + func socketDidDisconnect(socket: SocketManager) { + showHeaderStatusView() + + chatHeaderViewStatus?.labelTitle.text = localized("connection.offline.banner.message") + chatHeaderViewStatus?.buttonRefresh.isHidden = false + } + + func socketDidReturnError(socket: SocketManager, error: SocketError) { + // Handle errors + } +} diff --git a/Rocket.Chat/Controllers/Chat/ChatDataController.swift b/Rocket.Chat/Controllers/Chat/ChatDataController.swift index bbe67ea3bd..2ab32cb45a 100644 --- a/Rocket.Chat/Controllers/Chat/ChatDataController.swift +++ b/Rocket.Chat/Controllers/Chat/ChatDataController.swift @@ -300,7 +300,7 @@ final class ChatDataController { if let oldMessage = obj.message { if !(oldMessage == message) { - MessageTextCacheManager.shared.update(for: message) + MessageTextCacheManager.shared.update(for: message, with: nil) } } diff --git a/Rocket.Chat/Controllers/Chat/ChatViewController.swift b/Rocket.Chat/Controllers/Chat/ChatViewController.swift index d1c4cd85b9..7cf5aba2d1 100644 --- a/Rocket.Chat/Controllers/Chat/ChatViewController.swift +++ b/Rocket.Chat/Controllers/Chat/ChatViewController.swift @@ -127,19 +127,11 @@ final class ChatViewController: SLKTextViewController { registerCells() } - override var preferredStatusBarStyle: UIStatusBarStyle { - return .default - } - override func viewDidLoad() { super.viewDidLoad() SocketManager.addConnectionHandler(token: socketHandlerToken, handler: self) - navigationController?.navigationBar.isTranslucent = false - navigationController?.navigationBar.barTintColor = .RCNavigationBarColor() - navigationController?.navigationBar.tintColor = .RCBlue() - if #available(iOS 11.0, *) { collectionView?.contentInsetAdjustmentBehavior = .never } @@ -181,11 +173,14 @@ final class ChatViewController: SLKTextViewController { } setupReplyView() + ThemeManager.addObserver(self) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(false, animated: animated) + ThemeManager.addObserver(navigationController?.navigationBar) + textInputbar.applyTheme() } override func viewWillLayoutSubviews() { @@ -228,12 +223,14 @@ final class ChatViewController: SLKTextViewController { view?.delegate = self navigationItem.titleView = view chatTitleView = view + chatTitleView?.applyTheme() } private func setupScrollToBottomButton() { buttonScrollToBottom.layer.cornerRadius = 25 - buttonScrollToBottom.layer.borderColor = UIColor.lightGray.cgColor buttonScrollToBottom.layer.borderWidth = 1 + buttonScrollToBottom.layer.borderColor = (view.theme?.bodyText ?? Theme.light.bodyText).cgColor + buttonScrollToBottom.tintColor = view.theme?.bodyText ?? Theme.light.bodyText } override class func collectionViewLayout(for decoder: NSCoder) -> UICollectionViewLayout { @@ -278,7 +275,6 @@ final class ChatViewController: SLKTextViewController { let sizeHeight = collectionView?.contentSize.height ?? 0 let offset = CGPoint(x: 0, y: max(sizeHeight - boundsHeight, 0)) collectionView?.setContentOffset(offset, animated: animated) - scrollToBottomButtonIsVisible = false } internal func resetScrollToBottomButtonPosition() { @@ -343,34 +339,17 @@ final class ChatViewController: SLKTextViewController { if !textInputbar.subviews.contains(textInputbarBackground) { insertTextInputbarBackground() } - - // Making the old background for textInputView, transparent - // after the safeAreaInsets are set. (Initially zero) - // This helps improve the translucency effect of the bar. - if !oldTextInputbarBgIsTransparent, view.safeAreaInsets.bottom > 0 { - textInputbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default) - textInputbar.backgroundColor = UIColor.clear - oldTextInputbarBgIsTransparent = true - } - - if let textInputbarHC = textInputbarBackgroundHeightConstraint, textInputbarHC.constant != view.safeAreaInsets.bottom { - textInputbarHC.constant = view.safeAreaInsets.bottom - } } } private func insertTextInputbarBackground() { - if #available(iOS 11.0, *) { - textInputbar.insertSubview(textInputbarBackground, at: 0) - textInputbarBackground.translatesAutoresizingMaskIntoConstraints = false - - textInputbarBackgroundHeightConstraint = textInputbarBackground.heightAnchor.constraint(equalTo: textInputbar.heightAnchor, multiplier: 1, constant: view.safeAreaInsets.bottom) - textInputbarBackgroundHeightConstraint?.isActive = true + textInputbar.insertSubview(textInputbarBackground, at: 0) + textInputbarBackground.translatesAutoresizingMaskIntoConstraints = false - textInputbarBackground.widthAnchor.constraint(equalTo: textInputbar.widthAnchor).isActive = true - textInputbarBackground.topAnchor.constraint(equalTo: textInputbar.topAnchor).isActive = true - textInputbarBackground.centerXAnchor.constraint(equalTo: textInputbar.centerXAnchor).isActive = true - } + textInputbarBackground.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + textInputbarBackground.widthAnchor.constraint(equalTo: textInputbar.widthAnchor).isActive = true + textInputbarBackground.topAnchor.constraint(equalTo: textInputbar.topAnchor).isActive = true + textInputbarBackground.centerXAnchor.constraint(equalTo: textInputbar.centerXAnchor).isActive = true } // MARK: SlackTextViewController @@ -872,6 +851,8 @@ final class ChatViewController: SLKTextViewController { chatPreviewModeView = previewView updateChatPreviewModeViewConstraints() + + previewView.applyTheme() } } @@ -1178,7 +1159,7 @@ extension ChatViewController { private func blockMessageSending(reason: String) { textInputbar.textView.placeholder = reason - textInputbar.backgroundColor = .white + textInputbar.backgroundColor = view.theme?.backgroundColor ?? .white textInputbar.isUserInteractionEnabled = false leftButton.isEnabled = false rightButton.isEnabled = false @@ -1186,7 +1167,7 @@ extension ChatViewController { private func allowMessageSending() { textInputbar.textView.placeholder = "" - textInputbar.backgroundColor = .backgroundWhite + textInputbar.backgroundColor = view.theme?.focusedBackground ?? .backgroundWhite textInputbar.isUserInteractionEnabled = true leftButton.isEnabled = true rightButton.isEnabled = true @@ -1233,3 +1214,13 @@ extension ChatViewController: SocketConnectionHandler { } } + +// MARK: Themeable + +extension ChatViewController { + override func applyTheme() { + super.applyTheme() + updateMessageSendingPermission() + setupScrollToBottomButton() + } +} diff --git a/Rocket.Chat/Controllers/Chat/MainChatViewController.swift b/Rocket.Chat/Controllers/Chat/MainChatViewController.swift new file mode 100644 index 0000000000..628d8b212c --- /dev/null +++ b/Rocket.Chat/Controllers/Chat/MainChatViewController.swift @@ -0,0 +1,188 @@ +// +// MainChatViewController.swift +// Rocket.Chat +// +// Created by Rafael Kellermann Streit on 15/03/17. +// Copyright © 2017 Rocket.Chat. All rights reserved. +// + +import UIKit +import SideMenuController + +class MainChatViewController: SideMenuController, SideMenuControllerDelegate { + let infoRequestHandler = InfoRequestHandler() + let socketHandlerToken = String.random(5) + + static var shared: MainChatViewController? { + return UIApplication.shared.windows.first?.rootViewController as? MainChatViewController + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return centerViewController.preferredStatusBarStyle + } + + class func closeSideMenuIfNeeded() { + if let instance = shared { + if instance.sidePanelVisible { + instance.toggle() + } + } + } + + required init?(coder aDecoder: NSCoder) { + SideMenuController.preferences.drawing.menuButtonImage = UIImage(named: "Menu") + SideMenuController.preferences.drawing.sidePanelPosition = .underCenterPanelLeft + + if UIDevice.current.userInterfaceIdiom == .pad { + SideMenuController.preferences.drawing.sidePanelWidth = 320 + } else { + SideMenuController.preferences.drawing.sidePanelWidth = UIScreen.main.bounds.width - + 30 + } + + SideMenuController.preferences.drawing.centerPanelShadow = true + SideMenuController.preferences.interaction.swipingEnabled = true + SideMenuController.preferences.interaction.panningEnabled = true + SideMenuController.preferences.animating.statusBarBehaviour = .slideAnimation + super.init(coder: aDecoder) + } + + override func viewDidLoad() { + super.viewDidLoad() + + delegate = self + SocketManager.addConnectionHandler(token: socketHandlerToken, handler: self) + + if let auth = AuthManager.isAuthenticated() { + if let apiHost = auth.apiHost { + infoRequestHandler.delegate = self + infoRequestHandler.validate(with: apiHost) + } + } + + performSegue(withIdentifier: "showCenterController", sender: nil) + performSegue(withIdentifier: "containSideMenu", sender: nil) + } + + // MARK: SideMenuControllerDelegate + + func sideMenuControllerWillHide(_ sideMenuController: SideMenuController) { + ChatViewController.shared?.textView.resignFirstResponder() + SubscriptionsViewController.shared?.willHide() + } + + func sideMenuControllerDidHide(_ sideMenuController: SideMenuController) { + ChatViewController.shared?.textView.resignFirstResponder() + SubscriptionsViewController.shared?.didHide() + SubscriptionsPageViewController.shared?.showSubscriptionsList(animated: false) + } + + func sideMenuControllerDidReveal(_ sideMenuController: SideMenuController) { + ChatViewController.shared?.textView.resignFirstResponder() + SubscriptionsViewController.shared?.didReveal() + } + + func sideMenuControllerWillReveal(_ sideMenuController: SideMenuController) { + ChatViewController.shared?.textView.resignFirstResponder() + SubscriptionsViewController.shared?.willReveal() + } + + // MARK: Authentication & Server management + + func logout() { + API.current()?.client(PushClient.self).deletePushToken() + + DispatchQueue.main.async { + ChatViewController.shared?.dataController.clear() + ChatViewController.shared?.collectionView?.reloadData() + } + + ChatViewController.shared?.messagesToken?.invalidate() + ChatViewController.shared?.subscriptionToken?.invalidate() + SubscriptionsViewController.shared?.currentUserToken?.invalidate() + SubscriptionsViewController.shared?.subscriptionsToken?.invalidate() + + AuthManager.logout { + AuthManager.recoverAuthIfNeeded() + AppManager.reloadApp() + } + } + + func openAddNewTeamController() { + AppManager.addServer(serverUrl: "") + } +} + +extension MainChatViewController: SocketConnectionHandler { + + func socketDidConnect(socket: SocketManager) { + + } + + func socketDidDisconnect(socket: SocketManager) { + + } + + func socketDidReturnError(socket: SocketManager, error: SocketError) { + switch error.error { + case .invalidSession: + let alert = UIAlertController( + title: localized("alert.socket_error.invalid_user.title"), + message: localized("alert.socket_error.invalid_user.message"), + preferredStyle: .alert + ) + + alert.addAction(UIAlertAction(title: localized("global.ok"), style: .default, handler: { _ in + self.logout() + })) + + self.present(alert, animated: true, completion: nil) + default: break + } + } + +} + +extension MainChatViewController: InfoRequestHandlerDelegate { + + var viewControllerToPresentAlerts: UIViewController? { return self } + + func urlNotValid() { + // Do nothing + } + + func serverIsValid() { + // Do nothing + } + + func serverChangedURL(_ newURL: String?) { + guard + let url = URL(string: newURL ?? ""), + let socketURL = url.socketURL() + else { + return + } + + let newIndex = DatabaseManager.copyServerInformation( + from: DatabaseManager.selectedIndex, + with: socketURL.absoluteString + ) + + DatabaseManager.selectDatabase(at: newIndex) + DatabaseManager.cleanInvalidDatabases() + DatabaseManager.changeDatabaseInstance() + AuthManager.recoverAuthIfNeeded() + + DispatchQueue.main.async { + guard + let auth = AuthManager.isAuthenticated(), + let apiHost = auth.apiHost + else { + return + } + + self.infoRequestHandler.validate(with: apiHost) + } + } + +} diff --git a/Rocket.Chat/Controllers/Chat/MembersListViewController.swift b/Rocket.Chat/Controllers/Chat/MembersListViewController.swift index b4bf5025b4..22036b20ea 100644 --- a/Rocket.Chat/Controllers/Chat/MembersListViewController.swift +++ b/Rocket.Chat/Controllers/Chat/MembersListViewController.swift @@ -115,6 +115,7 @@ class MembersListViewController: BaseViewController { // MARK: ViewController extension MembersListViewController { override func viewDidLoad() { + super.viewDidLoad() let refreshControl = UIRefreshControl() refreshControl.addTarget(self, action: #selector(refreshControlDidPull), for: .valueChanged) @@ -147,8 +148,8 @@ extension MembersListViewController { loadMoreMembers() guard let refreshControl = membersTableView.refreshControl else { return } - membersTableView.refreshControl?.beginRefreshing() membersTableView.contentOffset = CGPoint(x: 0, y: -refreshControl.frame.size.height) + membersTableView.refreshControl?.beginRefreshing() } } diff --git a/Rocket.Chat/Controllers/Chat/MessagesListViewController.swift b/Rocket.Chat/Controllers/Chat/MessagesListViewController.swift index 9dc6077bf4..ab701b70c2 100644 --- a/Rocket.Chat/Controllers/Chat/MessagesListViewController.swift +++ b/Rocket.Chat/Controllers/Chat/MessagesListViewController.swift @@ -181,7 +181,6 @@ class MessagesListViewData { } if !self.isSearchingMessages { self.currentPage += 1 } - self.isLoadingMoreMessages = false completion?() } @@ -282,8 +281,8 @@ extension MessagesListViewController { } guard let refreshControl = collectionView.refreshControl, !data.isSearchingMessages else { return } - collectionView.refreshControl?.beginRefreshing() collectionView.contentOffset = CGPoint(x: 0, y: -refreshControl.frame.size.height) + collectionView.refreshControl?.beginRefreshing() } func registerCells() { @@ -310,6 +309,7 @@ extension MessagesListViewController { navigationItem.rightBarButtonItem = cancelButton navigationItem.titleView = searchBar + searchBar.applyTheme() } @objc func close() { diff --git a/Rocket.Chat/Controllers/Notification/NotificationViewController.swift b/Rocket.Chat/Controllers/Notification/NotificationViewController.swift index 084ca425bf..decddac5e5 100644 --- a/Rocket.Chat/Controllers/Notification/NotificationViewController.swift +++ b/Rocket.Chat/Controllers/Notification/NotificationViewController.swift @@ -9,7 +9,7 @@ import UIKit import AudioToolbox -final class NotificationViewController: UIViewController { +final class NotificationViewController: TopTransparentViewController { static let shared = NotificationViewController(nibName: "NotificationViewController", bundle: nil) @@ -51,11 +51,8 @@ final class NotificationViewController: UIViewController { // MARK: - View controller life cycle methods override func viewDidLoad() { super.viewDidLoad() - view.layer.shadowColor = UIColor.black.cgColor - view.layer.shadowOpacity = 0.3 - view.layer.shadowRadius = 1 - view.layer.shadowOffset = CGSize(width: 0, height: 0) - view.clipsToBounds = true + + ThemeManager.addObserver(self) } override func viewWillAppear(_ animated: Bool) { @@ -90,15 +87,18 @@ final class NotificationViewController: UIViewController { // Commented out until a setting is added to toggle the sound. // playSound() + willStartDisplayingContent() UIView.animate(withDuration: animationDuration) { self.notificationViewIsHidden = false self.view.layoutIfNeeded() } timer = Timer.scheduledTimer(withTimeInterval: notificationVisibleDuration, repeats: false) { [weak self] _ in - UIView.animate(withDuration: self?.animationDuration ?? 0.0) { + UIView.animate(withDuration: self?.animationDuration ?? 0.0, animations: ({ self?.notificationViewIsHidden = true self?.view.layoutIfNeeded() + })) { (_) in + self?.didEndDisplayingContent() } } } diff --git a/Rocket.Chat/Controllers/Notification/NotificationViewController.xib b/Rocket.Chat/Controllers/Notification/NotificationViewController.xib index 5995e940ed..6c58db07fc 100644 --- a/Rocket.Chat/Controllers/Notification/NotificationViewController.xib +++ b/Rocket.Chat/Controllers/Notification/NotificationViewController.xib @@ -35,7 +35,7 @@ - + @@ -60,7 +60,7 @@ - + @@ -106,6 +106,7 @@ + diff --git a/Rocket.Chat/Controllers/Notification/TopTransparentViewController.swift b/Rocket.Chat/Controllers/Notification/TopTransparentViewController.swift new file mode 100644 index 0000000000..2ff1413c8c --- /dev/null +++ b/Rocket.Chat/Controllers/Notification/TopTransparentViewController.swift @@ -0,0 +1,23 @@ +// +// TopTransparentViewController.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 4/30/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class TopTransparentViewController: UIViewController { + func willStartDisplayingContent() { + (UIApplication.shared.delegate as? AppDelegate)?.notificationWindow?.isHidden = false + } + + func didEndDisplayingContent() { + (UIApplication.shared.delegate as? AppDelegate)?.notificationWindow?.isHidden = true + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return UIApplication.shared.statusBarStyle + } +} diff --git a/Rocket.Chat/Controllers/Preferences/ChangeAppIcon/ChangeAppIconView.swift b/Rocket.Chat/Controllers/Preferences/ChangeAppIcon/ChangeAppIconView.swift new file mode 100644 index 0000000000..221427af93 --- /dev/null +++ b/Rocket.Chat/Controllers/Preferences/ChangeAppIcon/ChangeAppIconView.swift @@ -0,0 +1,21 @@ +// +// ChangeAppIconView.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 5/3/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class ChangeAppIconView: UIView { } + +// MARK: Themeable + +extension ChangeAppIconView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + backgroundColor = theme == .light ? #colorLiteral(red: 0.9372549057, green: 0.9372549057, blue: 0.9568627477, alpha: 1) : theme.focusedBackground + } +} diff --git a/Rocket.Chat/Controllers/Preferences/ChangeAppIcon/ChangeAppIconViewController.swift b/Rocket.Chat/Controllers/Preferences/ChangeAppIcon/ChangeAppIconViewController.swift index 923a6e2fd1..0a2be5fc23 100644 --- a/Rocket.Chat/Controllers/Preferences/ChangeAppIcon/ChangeAppIconViewController.swift +++ b/Rocket.Chat/Controllers/Preferences/ChangeAppIcon/ChangeAppIconViewController.swift @@ -8,7 +8,7 @@ import UIKit -final class ChangeAppIconViewController: UIViewController { +final class ChangeAppIconViewController: BaseViewController { private let viewModel = ChangeAppIconViewModel() @@ -112,3 +112,17 @@ extension ChangeAppIconViewController: UICollectionViewDelegate { } } + +// MARK: Themeable + +extension ChangeAppIconViewController { + override func applyTheme() { + super.applyTheme() + guard let theme = view.theme else { return } + + switch theme { + case .dark, .black: view.backgroundColor = theme.focusedBackground + default: view.backgroundColor = #colorLiteral(red: 0.937, green: 0.937, blue: 0.957, alpha: 1) + } + } +} diff --git a/Rocket.Chat/Controllers/Preferences/Language/LanguageViewController.swift b/Rocket.Chat/Controllers/Preferences/Language/LanguageViewController.swift index 9c6eb2d851..63a1bdc457 100644 --- a/Rocket.Chat/Controllers/Preferences/Language/LanguageViewController.swift +++ b/Rocket.Chat/Controllers/Preferences/Language/LanguageViewController.swift @@ -8,7 +8,7 @@ import UIKit -final class LanguageViewController: UIViewController { +final class LanguageViewController: BaseViewController { private let viewModel = LanguageViewModel() override func viewDidLoad() { diff --git a/Rocket.Chat/Controllers/Preferences/PreferencesViewController.swift b/Rocket.Chat/Controllers/Preferences/PreferencesViewController.swift index c67d679497..5b3d3909ba 100644 --- a/Rocket.Chat/Controllers/Preferences/PreferencesViewController.swift +++ b/Rocket.Chat/Controllers/Preferences/PreferencesViewController.swift @@ -14,7 +14,7 @@ import SafariServices import FLEX #endif -final class PreferencesViewController: UITableViewController { +final class PreferencesViewController: BaseTableViewController { private let kSectionProfile = 0 private let kSectionSettings = 1 @@ -101,6 +101,12 @@ final class PreferencesViewController: UITableViewController { } } + @IBOutlet weak var labelTheme: UILabel! { + didSet { + labelTheme.text = viewModel.theme + } + } + @IBOutlet weak var labelDefaultWebBrowser: UILabel! { didSet { labelDefaultWebBrowser.text = WebBrowserManager.browser.name diff --git a/Rocket.Chat/Controllers/Preferences/PreferencesViewModel.swift b/Rocket.Chat/Controllers/Preferences/PreferencesViewModel.swift index ef98639ff4..5cf43d6bd9 100644 --- a/Rocket.Chat/Controllers/Preferences/PreferencesViewModel.swift +++ b/Rocket.Chat/Controllers/Preferences/PreferencesViewModel.swift @@ -23,6 +23,7 @@ final class PreferencesViewModel { internal let language = localized("myaccount.settings.language") internal let appicon = localized("myaccount.settings.appicon") internal let webBrowser = localized("myaccount.settings.web_browser") + internal let theme = localized("theme.settings.title") internal let licenseURL = URL(string: "https://github.com/RocketChat/Rocket.Chat.iOS/blob/develop/LICENSE") @@ -133,7 +134,7 @@ final class PreferencesViewModel { internal func numberOfRowsInSection(_ section: Int) -> Int { switch section { case 0: return 1 - case 1: return canChangeAppIcon ? 4 : 3 + case 1: return numberOfRowsInSectionOne() case 2: return canViewAdministrationPanel ? 1 : 0 case 3: return 3 case 4: return 1 @@ -143,6 +144,18 @@ final class PreferencesViewModel { } } + private func numberOfRowsInSectionOne() -> Int { + var totalCount = 5 + if #available(iOS 11, *) { + // Do nothing + } else { + totalCount -= 1 + } + + totalCount -= canChangeAppIcon ? 0 : 1 + return totalCount + } + // MARK: Helpers internal func appInfo(_ info: BundleInfoKey) -> String { diff --git a/Rocket.Chat/Controllers/Preferences/Profile/EditProfileTableViewController.swift b/Rocket.Chat/Controllers/Preferences/Profile/EditProfileTableViewController.swift index a2297f9b51..8a86812dcb 100644 --- a/Rocket.Chat/Controllers/Preferences/Profile/EditProfileTableViewController.swift +++ b/Rocket.Chat/Controllers/Preferences/Profile/EditProfileTableViewController.swift @@ -11,7 +11,7 @@ import MBProgressHUD import SwiftyJSON // swiftlint:disable file_length type_body_length -final class EditProfileTableViewController: UITableViewController, MediaPicker { +final class EditProfileTableViewController: BaseTableViewController, MediaPicker { static let identifier = String(describing: EditProfileTableViewController.self) @@ -202,7 +202,14 @@ final class EditProfileTableViewController: UITableViewController, MediaPicker { // MARK: State Management + var isEditingProfile = false { + didSet { + applyTheme() + } + } + @objc func beginEditing() { + isEditingProfile = true navigationItem.title = viewModel.editingTitle navigationItem.rightBarButtonItem = saveButton navigationItem.hidesBackButton = true @@ -216,6 +223,7 @@ final class EditProfileTableViewController: UITableViewController, MediaPicker { } @objc func endEditing() { + isEditingProfile = false bindUserData() navigationItem.title = viewModel.title @@ -237,21 +245,18 @@ final class EditProfileTableViewController: UITableViewController, MediaPicker { name.isEnabled = true } else { name.isEnabled = false - name.textColor = .lightGray } if authSettings?.isAllowedToEditUsername ?? false { username.isEnabled = true } else { username.isEnabled = false - username.textColor = .lightGray } if authSettings?.isAllowedToEditEmail ?? false { email.isEnabled = true } else { email.isEnabled = false - email.textColor = .lightGray } if authSettings?.isAllowedToEditName ?? false { @@ -267,11 +272,8 @@ final class EditProfileTableViewController: UITableViewController, MediaPicker { hideKeyboard() avatarButton.isEnabled = false name.isEnabled = false - name.textColor = .black username.isEnabled = false - username.textColor = .black email.isEnabled = false - email.textColor = .black } // MARK: Actions @@ -557,5 +559,22 @@ extension EditProfileTableViewController: UITextFieldDelegate { return true } +} +extension EditProfileTableViewController { + override func applyTheme() { + super.applyTheme() + guard let theme = view.theme else { return } + + switch isEditingProfile { + case false: + name.textColor = theme.titleText + username.textColor = theme.titleText + email.textColor = theme.titleText + case true: + name.textColor = (authSettings?.isAllowedToEditName ?? false) ? theme.titleText : theme.auxiliaryText + username.textColor = (authSettings?.isAllowedToEditName ?? false) ? theme.titleText : theme.auxiliaryText + email.textColor = (authSettings?.isAllowedToEditName ?? false) ? theme.titleText : theme.auxiliaryText + } + } } diff --git a/Rocket.Chat/Controllers/Preferences/Profile/NewPasswordTableViewController.swift b/Rocket.Chat/Controllers/Preferences/Profile/NewPasswordTableViewController.swift index be75110fef..465b716863 100644 --- a/Rocket.Chat/Controllers/Preferences/Profile/NewPasswordTableViewController.swift +++ b/Rocket.Chat/Controllers/Preferences/Profile/NewPasswordTableViewController.swift @@ -9,7 +9,7 @@ import UIKit import MBProgressHUD -final class NewPasswordTableViewController: UITableViewController { +final class NewPasswordTableViewController: BaseTableViewController { @IBOutlet weak var newPassword: UITextField! { didSet { diff --git a/Rocket.Chat/Controllers/Preferences/Profile/StatusTableViewController.swift b/Rocket.Chat/Controllers/Preferences/Profile/StatusTableViewController.swift index c1fe4eb5f8..c5e7167681 100644 --- a/Rocket.Chat/Controllers/Preferences/Profile/StatusTableViewController.swift +++ b/Rocket.Chat/Controllers/Preferences/Profile/StatusTableViewController.swift @@ -8,7 +8,7 @@ import UIKit -final class StatusTableViewController: UITableViewController { +final class StatusTableViewController: BaseTableViewController { let viewModel = StatusViewModel() diff --git a/Rocket.Chat/Controllers/Preferences/Theme/ThemePreferenceController.swift b/Rocket.Chat/Controllers/Preferences/Theme/ThemePreferenceController.swift new file mode 100644 index 0000000000..396b965e89 --- /dev/null +++ b/Rocket.Chat/Controllers/Preferences/Theme/ThemePreferenceController.swift @@ -0,0 +1,59 @@ +// +// ThemePreferenceController.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 4/30/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class ThemePreferenceController: BaseTableViewController { + + let viewModel = ThemePreferenceViewModel() + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.title = viewModel.title + } +} + +extension ThemePreferenceController { + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return viewModel.cellHeight + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return viewModel.themes.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: ThemePreferenceCell.identifier, for: indexPath) as? ThemePreferenceCell else { + return UITableViewCell() + } + cell.cellTheme = viewModel.themes[indexPath.row].theme + cell.titleLabel.text = viewModel.themes[indexPath.row].title + cell.baseColorView.backgroundColor = viewModel.themes[indexPath.row].theme.backgroundColor + cell.auxiliaryColorView.backgroundColor = viewModel.themes[indexPath.row].theme.bodyText + + return cell + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return viewModel.header + } + + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + return viewModel.footer + } +} + +extension ThemePreferenceController { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let selectedTheme = viewModel.themes[indexPath.row].theme + if ThemeManager.theme != selectedTheme { + ThemeManager.theme = selectedTheme + } + tableView.deselectRow(at: indexPath, animated: true) + } +} diff --git a/Rocket.Chat/Controllers/Preferences/Theme/ThemePreferenceViewModel.swift b/Rocket.Chat/Controllers/Preferences/Theme/ThemePreferenceViewModel.swift new file mode 100644 index 0000000000..f971680c5b --- /dev/null +++ b/Rocket.Chat/Controllers/Preferences/Theme/ThemePreferenceViewModel.swift @@ -0,0 +1,28 @@ +// +// ThemePreferenceViewModel.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 4/30/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import Foundation + +struct ThemePreferenceViewModel { + let themes: [(title: String, theme: Theme)] = ThemeManager.themes.map { (localized("theme." + $0.title), $0.theme) } + + let cellIdentifier = ThemePreferenceCell.identifier + let cellHeight = ThemePreferenceCell.cellHeight + + internal var title: String { + return localized("theme.settings.title") + } + + internal var header: String { + return localized("theme.settings.header") + } + + internal var footer: String { + return localized("theme.settings.footer") + } +} diff --git a/Rocket.Chat/Controllers/Preferences/Web Browser/WebBrowserTableViewController.swift b/Rocket.Chat/Controllers/Preferences/Web Browser/WebBrowserTableViewController.swift index 9a67d44211..6da89545be 100644 --- a/Rocket.Chat/Controllers/Preferences/Web Browser/WebBrowserTableViewController.swift +++ b/Rocket.Chat/Controllers/Preferences/Web Browser/WebBrowserTableViewController.swift @@ -8,7 +8,7 @@ import UIKit -class WebBrowserTableViewController: UITableViewController { +class WebBrowserTableViewController: BaseTableViewController { let viewModel = WebBrowserViewModel() var updateDefaultWebBrowser: (() -> Void)? diff --git a/Rocket.Chat/Controllers/Subscriptions/AudioFileViewController.swift b/Rocket.Chat/Controllers/Subscriptions/AudioFileViewController.swift index 4f7a95d8a7..485dfd7306 100644 --- a/Rocket.Chat/Controllers/Subscriptions/AudioFileViewController.swift +++ b/Rocket.Chat/Controllers/Subscriptions/AudioFileViewController.swift @@ -8,10 +8,11 @@ import UIKit -class AudioFileViewController: UIViewController { +class AudioFileViewController: BaseViewController { @IBOutlet weak var audioPlayerContainer: UIView! @IBOutlet weak var audioTitle: UILabel! + @IBOutlet weak var audioImage: UIImageView! var file: File! @@ -27,6 +28,7 @@ class AudioFileViewController: UIViewController { func setupAudioPlayer() { guard let view = ChatMessageAudioView.instantiateFromNib() else { return } view.file = file + view.timeLabel.isHidden = true view.translatesAutoresizingMaskIntoConstraints = false audioPlayerContainer.addSubview(view) view.topAnchor.constraint(equalTo: audioPlayerContainer.topAnchor, constant: -30).isActive = true @@ -34,5 +36,20 @@ class AudioFileViewController: UIViewController { view.trailingAnchor.constraint(equalTo: audioPlayerContainer.trailingAnchor, constant: -15).isActive = true view.bottomAnchor.constraint(equalTo: audioPlayerContainer.bottomAnchor, constant: 0).isActive = true } +} + +// MARK: Themeable + +extension AudioFileViewController { + override func applyTheme() { + super.applyTheme() + guard let theme = view.theme else { return } + switch theme { + case .light: view.backgroundColor = #colorLiteral(red: 0.937, green: 0.937, blue: 0.957, alpha: 1) + default: view.backgroundColor = theme.focusedBackground + } + + audioImage.tintColor = theme.titleText + } } diff --git a/Rocket.Chat/Controllers/Subscriptions/FilesListViewController.swift b/Rocket.Chat/Controllers/Subscriptions/FilesListViewController.swift index bb3294dfe7..841e0a9d3b 100644 --- a/Rocket.Chat/Controllers/Subscriptions/FilesListViewController.swift +++ b/Rocket.Chat/Controllers/Subscriptions/FilesListViewController.swift @@ -229,8 +229,8 @@ extension FilesListViewController { loadMoreFiles() guard let refreshControl = tableView.refreshControl else { return } - tableView.refreshControl?.beginRefreshing() tableView.contentOffset = CGPoint(x: 0, y: -refreshControl.frame.size.height) + tableView.refreshControl?.beginRefreshing() } } diff --git a/Rocket.Chat/Controllers/Subscriptions/SubscriptionsViewController.swift b/Rocket.Chat/Controllers/Subscriptions/SubscriptionsViewController.swift index 0e30a515e4..8e4b9622d3 100644 --- a/Rocket.Chat/Controllers/Subscriptions/SubscriptionsViewController.swift +++ b/Rocket.Chat/Controllers/Subscriptions/SubscriptionsViewController.swift @@ -17,6 +17,7 @@ final class SubscriptionsViewController: BaseViewController { } @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var filterSeperator: UIView! @IBOutlet weak var labelSortingTitleDescription: UILabel! { didSet { updateSortingTitleDescription() @@ -64,14 +65,20 @@ final class SubscriptionsViewController: BaseViewController { } override func viewDidLoad() { - super.viewDidLoad() - setupSearchBar() setupTitleView() updateBackButton() - + subscribeModelChanges() updateData() + + super.viewDidLoad() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + serversView?.frame = frameForDropDownOverlay + sortingView?.frame = frameForDropDownOverlay } override func viewWillAppear(_ animated: Bool) { @@ -275,6 +282,7 @@ final class SubscriptionsViewController: BaseViewController { self.searchController = searchController self.searchBar?.placeholder = localized("subscriptions.search") self.searchBar?.delegate = self + self.searchBar?.applyTheme() } func setupTitleView() { @@ -465,6 +473,7 @@ extension SubscriptionsViewController: UISearchBarDelegate { } else { sortingView = SubscriptionsSortingView.showIn(self.view) sortingView?.delegate = self + sortingView?.applyTheme() } } @@ -489,6 +498,7 @@ extension SubscriptionsViewController: UISearchBarDelegate { self.present(nav, animated: true, completion: nil) } serversView?.delegate = self + serversView?.applyTheme() } } @@ -681,3 +691,22 @@ extension SubscriptionsViewController: ServerListViewDelegate { titleView?.updateTitleImage(reverse: false) } } + +// MARK: Themeable + +extension SubscriptionsViewController { + override func applyTheme() { + super.applyTheme() + guard let theme = view.theme else { return } + filterSeperator?.backgroundColor = theme.mutedAccent + labelSortingTitleDescription?.textColor = theme.auxiliaryText + navigationController?.view.backgroundColor = view.theme?.backgroundColor + searchBar?.applyTheme() + + if serversView != nil { + titleView?.updateTitleImage(reverse: true) + } else { + titleView?.updateTitleImage(reverse: false) + } + } +} diff --git a/Rocket.Chat/Extensions/NSAttributedStringExtensions.swift b/Rocket.Chat/Extensions/NSAttributedStringExtensions.swift index ac063e61da..57f6d4ba14 100644 --- a/Rocket.Chat/Extensions/NSAttributedStringExtensions.swift +++ b/Rocket.Chat/Extensions/NSAttributedStringExtensions.swift @@ -119,7 +119,7 @@ extension NSMutableAttributedString { background = .attention font = .white } else { - background = .white + background = .clear font = .link if shouldUseRealName && !realName.isEmpty { diff --git a/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPicker.swift b/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPicker.swift index dccb0e7f54..b04de22b90 100644 --- a/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPicker.swift +++ b/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPicker.swift @@ -13,6 +13,8 @@ private typealias EmojiCategory = (name: String, emojis: [Emoji]) final class EmojiPicker: UIView, RCEmojiKitLocalizable { static let defaults = UserDefaults(suiteName: "EmojiPicker") + var isPopover = false + var customEmojis: [Emoji] = [] var customCategory: (name: String, emojis: [Emoji]) { return (name: "custom", emojis: self.customEmojis) @@ -340,3 +342,32 @@ private class EmojiPickerSectionHeaderView: UICollectionReusableView { fatalError("init(coder:) has not been implemented") } } + +// MARK: Themeable + +extension EmojiPicker { + override var theme: Theme? { + guard let theme = super.theme else { return nil } + guard isPopover else { return theme } + let popoverTheme = Theme( + backgroundColor: theme.focusedBackground, + titleText: theme.titleText, + bodyText: theme.bodyText, + controlText: theme.controlText, + auxiliaryText: theme.auxiliaryText, + tintColor: theme.tintColor, + hyperlinkColor: theme.hyperlinkColor, + focusedBackground: theme.focusedBackground, + auxiliaryBackground: theme.auxiliaryBackground, + mutedAccent: theme.mutedAccent, + strongAccent: theme.strongAccent, + appearence: theme.appearence + ) + return popoverTheme + } + + override func applyTheme() { + super.applyTheme() + skinToneButton.backgroundColor = currentSkinTone.color + } +} diff --git a/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPickerController.swift b/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPickerController.swift index f2de6f18ec..23d6619a71 100644 --- a/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPickerController.swift +++ b/Rocket.Chat/External/RCEmojiKit/Views/EmojiPicker/EmojiPickerController.swift @@ -25,6 +25,8 @@ final class EmojiPickerController: UIViewController, RCEmojiKitLocalizable { } } + emojiPicker.isPopover = presentationController?.presentationStyle == .popover + emojiPicker.customEmojis = customEmojis } } diff --git a/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactionView.swift b/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactionView.swift index 1debe2dc4b..781ca7abc6 100644 --- a/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactionView.swift +++ b/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactionView.swift @@ -54,10 +54,7 @@ final class ReactionView: UIView { countLabel.text = model.count - let colors = model.highlight ? (#colorLiteral(red: 0.3098039216, green: 0.6901960784, blue: 0.9882352941, alpha: 1), #colorLiteral(red: 0.7411764706, green: 0.8823529412, blue: 0.9960784314, alpha: 1), #colorLiteral(red: 0.9529411765, green: 0.9764705882, blue: 1, alpha: 1)) : (#colorLiteral(red: 0.6666666667, green: 0.6666666667, blue: 0.6666666667, alpha: 1), #colorLiteral(red: 0.9058823529, green: 0.9058823529, blue: 0.9058823529, alpha: 1), #colorLiteral(red: 0.9882352941, green: 0.9882352941, blue: 0.9882352941, alpha: 1)) - countLabel.textColor = colors.0 - contentView.layer.borderColor = colors.1.cgColor - contentView.backgroundColor = colors.2 + self.applyTheme() } override init(frame: CGRect) { @@ -103,3 +100,23 @@ extension ReactionView { } } } + +// MARK: Themeable + +extension ReactionView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + + let colors: (UIColor, UIColor, UIColor) = { + switch theme { + case .light: return model.highlight ? (#colorLiteral(red: 0, green: 0.56, blue: 0.9882352941, alpha: 0.69), #colorLiteral(red: 0, green: 0.5516742082, blue: 0.9960784314, alpha: 0.26), #colorLiteral(red: 0, green: 0.4999999989, blue: 1, alpha: 0.05)) : (#colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.33), #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.09), #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.01)) + default: return model.highlight ? (#colorLiteral(red: 0, green: 0.56, blue: 0.9882352941, alpha: 0.69), #colorLiteral(red: 0, green: 0.5516742082, blue: 0.9960784314, alpha: 0.26), #colorLiteral(red: 0, green: 0.4999999989, blue: 1, alpha: 0.05)) : (#colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.33), #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.09), #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0.01)) + } + }() + + countLabel.textColor = colors.0 + contentView.layer.borderColor = colors.1.cgColor + contentView.backgroundColor = colors.2 + } +} diff --git a/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListView.swift b/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListView.swift index 3fb3ad0ac8..d1a74750ba 100644 --- a/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListView.swift +++ b/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListView.swift @@ -55,6 +55,7 @@ final class ReactorListView: UIView { } } + var isPopover = false var closePressed: () -> Void = { } var selectedReactor: (String, CGRect) -> Void = { _, _ in } var configureCell: (ReactorCell) -> Void = { _ in } @@ -128,7 +129,6 @@ extension ReactorListView: UITableViewDataSource { extension ReactorListView: UITableViewDelegate { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: 40)) - view.backgroundColor = UIColor(red: 247/255, green: 247/255, blue: 247/255, alpha: 1) let stackView = UIStackView(frame: CGRect(x: 16, y: 8, width: tableView.frame.size.width - 16, height: 24)) stackView.spacing = 8 @@ -145,6 +145,9 @@ extension ReactorListView: UITableViewDelegate { view.addSubview(stackView) + view.applyTheme() + view.backgroundColor = #colorLiteral(red: 0.4980838895, green: 0.4951269031, blue: 0.5003594756, alpha: 0.09813784247) + return view } @@ -158,3 +161,25 @@ extension ReactorListView: UITableViewDelegate { selectedReactor(model.reactionViewModels[indexPath.section].reactors[indexPath.row], rect) } } + +extension ReactorListView { + override var theme: Theme? { + guard let theme = super.theme else { return nil } + guard isPopover else { return theme } + let popoverTheme = Theme( + backgroundColor: theme.focusedBackground, + titleText: theme.titleText, + bodyText: theme.bodyText, + controlText: theme.controlText, + auxiliaryText: theme.auxiliaryText, + tintColor: theme.tintColor, + hyperlinkColor: theme.hyperlinkColor, + focusedBackground: theme.focusedBackground, + auxiliaryBackground: theme.auxiliaryBackground, + mutedAccent: theme.mutedAccent, + strongAccent: theme.strongAccent, + appearence: theme.appearence + ) + return popoverTheme + } +} diff --git a/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListViewController.swift b/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListViewController.swift index 1f84159a72..40e023a8f3 100644 --- a/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListViewController.swift +++ b/Rocket.Chat/External/RCEmojiKit/Views/Reaction/ReactorListViewController.swift @@ -31,6 +31,8 @@ final class ReactorListViewController: UIViewController, Closeable { self.dismiss(animated: true, completion: nil) } + reactorListView.isPopover = presentationController?.presentationStyle == .popover + reactorListView.model = model } } @@ -55,6 +57,8 @@ final class ReactorListViewController: UIViewController, Closeable { ) title = NSLocalizedString("reactorlist.title", tableName: "RCEmojiKit", bundle: Bundle.main, value: "", comment: "") + + ThemeManager.addObserver(self) } override func viewWillAppear(_ animated: Bool) { diff --git a/Rocket.Chat/Helpers/MessageTextFontAttributes.swift b/Rocket.Chat/Helpers/MessageTextFontAttributes.swift index ddba745966..df39ae88cf 100644 --- a/Rocket.Chat/Helpers/MessageTextFontAttributes.swift +++ b/Rocket.Chat/Helpers/MessageTextFontAttributes.swift @@ -12,9 +12,17 @@ struct MessageTextFontAttributes { static let defaultFontSize = CGFloat(15) - static let defaultFontColor = UIColor.darkGray - static let systemFontColor = UIColor.lightGray - static let failedFontColor = UIColor.lightGray + static func defaultFontColor(for theme: Theme? = nil) -> UIColor { + return theme?.controlText ?? ThemeManager.theme.controlText + } + + static func systemFontColor(for theme: Theme? = ThemeManager.theme) -> UIColor { + return theme?.auxiliaryText ?? ThemeManager.theme.auxiliaryText + } + + static func failedFontColor(for theme: Theme? = ThemeManager.theme) -> UIColor { + return theme?.auxiliaryText ?? ThemeManager.theme.auxiliaryText + } static let defaultFont = UIFont.systemFont(ofSize: defaultFontSize) static let italicFont = UIFont.italicSystemFont(ofSize: defaultFontSize) diff --git a/Rocket.Chat/Info.plist b/Rocket.Chat/Info.plist index 93063da93a..66a57b4293 100644 --- a/Rocket.Chat/Info.plist +++ b/Rocket.Chat/Info.plist @@ -300,6 +300,6 @@ UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance - + diff --git a/Rocket.Chat/Managers/MessageTextCacheManager.swift b/Rocket.Chat/Managers/MessageTextCacheManager.swift index 4dae9c6ae0..77f8c04220 100644 --- a/Rocket.Chat/Managers/MessageTextCacheManager.swift +++ b/Rocket.Chat/Managers/MessageTextCacheManager.swift @@ -26,7 +26,7 @@ final class MessageTextCacheManager { cache.removeObject(forKey: cachedKey(for: identifier)) } - @discardableResult func update(for message: Message) -> NSMutableAttributedString? { + @discardableResult func update(for message: Message, with theme: Theme? = nil) -> NSMutableAttributedString? { guard let identifier = message.identifier else { return nil } let key = cachedKey(for: identifier) @@ -37,10 +37,10 @@ final class MessageTextCacheManager { if message.isSystemMessage() { text.setFont(MessageTextFontAttributes.italicFont) - text.setFontColor(MessageTextFontAttributes.systemFontColor) + text.setFontColor(MessageTextFontAttributes.systemFontColor(for: theme)) } else { text.setFont(MessageTextFontAttributes.defaultFont) - text.setFontColor(MessageTextFontAttributes.defaultFontColor) + text.setFontColor(MessageTextFontAttributes.defaultFontColor(for: theme)) text.setLineSpacing(MessageTextFontAttributes.defaultFont) } @@ -50,6 +50,14 @@ final class MessageTextCacheManager { let attributedString = text.transformMarkdown() let finalText = NSMutableAttributedString(attributedString: attributedString) + + // Set text color for markdown quotes + finalText.enumerateAttribute(.backgroundColor, in: NSRange(location: 0, length: finalText.length), options: []) { (value, range, _) in + if let backgroundColor = value as? UIColor, backgroundColor != .clear { + finalText.addAttribute(.foregroundColor, value: UIColor.darkGray, range: range) + } + } + finalText.trimCharacters(in: .whitespaces) finalText.highlightMentions(mentions, currentUsername: username) finalText.highlightChannels(channels) @@ -58,7 +66,7 @@ final class MessageTextCacheManager { return finalText } - func message(for message: Message) -> NSMutableAttributedString? { + func message(for message: Message, with theme: Theme? = nil) -> NSMutableAttributedString? { guard let identifier = message.identifier else { return nil } var resultText: NSAttributedString? @@ -66,7 +74,7 @@ final class MessageTextCacheManager { if let cachedVersion = cache.object(forKey: key) { resultText = cachedVersion - } else if let result = update(for: message) { + } else if let result = update(for: message, with: theme) { resultText = result } diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Attachments.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Attachments.imageset/Contents.json index a5a49b15fe..1097d37ed1 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Attachments.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Attachments.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Channel Small.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Channel Small.imageset/Contents.json index 6391f82390..3a969381b6 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Channel Small.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Channel Small.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Group Small.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Group Small.imageset/Contents.json index b65ec6c8f7..c152b698dc 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Group Small.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Group Small.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Members.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Members.imageset/Contents.json index 5623fe9abb..57c327fd43 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Members.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Members.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Mentions.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Mentions.imageset/Contents.json index 8db3532518..60f4e2eea5 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Mentions.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Mentions.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/More.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/More.imageset/Contents.json index 490a19ffbc..2e8f70f7c9 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/More.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/More.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Pinned.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Pinned.imageset/Contents.json index 47979a8291..77d105a0d5 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Pinned.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Pinned.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Server Selector.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Server Selector.imageset/Contents.json index ffa159ca7e..3f2cba112f 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Server Selector.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Server Selector.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Share.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Share.imageset/Contents.json index 7d75f1c2ea..00ccf2fe5e 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Share.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Share.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Favorites.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Favorites.imageset/Contents.json index 32eaa3ad0c..89f78a109c 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Favorites.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Favorites.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Type.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Type.imageset/Contents.json index fe5f9aede4..1fd8cd2e65 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Type.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Type.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Unread.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Unread.imageset/Contents.json index 32db30a3b2..04ca0c5775 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Unread.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Group Unread.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Activity.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Activity.imageset/Contents.json index 87c2b35025..7c3c6f027c 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Activity.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Activity.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Alphabetically.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Alphabetically.imageset/Contents.json index 242885fc34..397025c088 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Alphabetically.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Sorting & Grouping/Sort Alphabetically.imageset/Contents.json @@ -8,5 +8,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Star NavBar.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Star NavBar.imageset/Contents.json index dc118f7b9a..796e761f02 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Star NavBar.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Star NavBar.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Star Off.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Star Off.imageset/Contents.json index 2561605d55..08f4bd2663 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Star Off.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Star Off.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Arrow Down.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Arrow Down.imageset/Contents.json index 978f15e572..63c4cb2312 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Arrow Down.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Arrow Down.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Hashtag.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Hashtag.imageset/Contents.json index f993fcea24..71e1fee711 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Hashtag.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Hashtag.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Lock.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Lock.imageset/Contents.json index a063e88407..51c7e2e241 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Lock.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Lock.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Mention.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Mention.imageset/Contents.json index b2c4abcbae..38fc993ebb 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Mention.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/Subscriptions/Mention.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/audio.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/audio.imageset/Contents.json index 3dac193f35..d1ce668a35 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/audio.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/audio.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/audioLarge.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/audioLarge.imageset/Contents.json index f123c63473..541e27a528 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/audioLarge.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/audioLarge.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/Assets.xcassets/Icons/icon_file.imageset/Contents.json b/Rocket.Chat/Resources/Assets.xcassets/Icons/icon_file.imageset/Contents.json index 210aab1d2f..5c39bcd6d1 100644 --- a/Rocket.Chat/Resources/Assets.xcassets/Icons/icon_file.imageset/Contents.json +++ b/Rocket.Chat/Resources/Assets.xcassets/Icons/icon_file.imageset/Contents.json @@ -17,5 +17,8 @@ "info" : { "version" : 1, "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" } } \ No newline at end of file diff --git a/Rocket.Chat/Resources/cs.lproj/Localizable.strings b/Rocket.Chat/Resources/cs.lproj/Localizable.strings index aa5bf5cba3..c9df3fd2a8 100644 --- a/Rocket.Chat/Resources/cs.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/cs.lproj/Localizable.strings @@ -353,6 +353,14 @@ "user_action_sheet.remove_confirm.title" = "Remove this user?"; // TODO "user_action_sheet.remove_confirm.message" = "Are you sure you want to remove '%@' from the room?"; // TODO +// Theme +"theme.settings.title" = "Theme"; // TODO +"theme.settings.header" = "ALL THEMES"; // TODO +"theme.settings.footer" = "Applying a theme will change how the app looks."; // TODO +"theme.light" = "Light"; // TODO +"theme.dark" = "Dark"; // TODO +"theme.black" = "Black"; // TODO + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Sorting by name"; //TODO "subscriptions.sorting.title.activity" = "Sorting by activity"; //TODO @@ -360,4 +368,4 @@ "subscriptions.sorting.activity" = "Activity"; //TODO "subscriptions.grouping.type" = "Group by type"; //TODO "subscriptions.grouping.favorites" = "Group by favorites"; //TODO -"subscriptions.grouping.unread_top" = "Unread on top"; //TODO +"subscriptions.grouping.unread_top" = "Unread on top"; //TODO \ No newline at end of file diff --git a/Rocket.Chat/Resources/de.lproj/Localizable.strings b/Rocket.Chat/Resources/de.lproj/Localizable.strings index dc5c568973..aab55515fb 100644 --- a/Rocket.Chat/Resources/de.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/de.lproj/Localizable.strings @@ -349,10 +349,21 @@ // User Action Sheet "user_action_sheet.conversation" = "Konversation"; +"user_action_sheet.remove" = "Remove from room"; // TODO +"user_action_sheet.remove_confirm.title" = "Remove this user?"; // TODO +"user_action_sheet.remove_confirm.message" = "Are you sure you want to remove '%@' from the room?"; // TODO "user_action_sheet.remove" = "Aus Raum entfernen"; "user_action_sheet.remove_confirm.title" = "Diesen Benutzer entfernen?"; "user_action_sheet.remove_confirm.message" = "Sind Sie sicher, dass Sie '%@' aus dem Raum entfernen möchten?"; +// Theme +"theme.settings.title" = "Theme"; // TODO +"theme.settings.header" = "ALL THEMES"; // TODO +"theme.settings.footer" = "Applying a theme will change how the app looks."; // TODO +"theme.light" = "Light"; // TODO +"theme.dark" = "Dark"; // TODO +"theme.black" = "Black"; // TODO + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Sorting by name"; //TODO "subscriptions.sorting.title.activity" = "Sorting by activity"; //TODO diff --git a/Rocket.Chat/Resources/el.lproj/Localizable.strings b/Rocket.Chat/Resources/el.lproj/Localizable.strings index 2964759aaf..c22e97007f 100644 --- a/Rocket.Chat/Resources/el.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/el.lproj/Localizable.strings @@ -353,6 +353,14 @@ "user_action_sheet.remove_confirm.title" = "Remove this user?"; // TODO "user_action_sheet.remove_confirm.message" = "Are you sure you want to remove '%@' from the room?"; // TODO +// Theme +"theme.settings.title" = "Theme"; // TODO +"theme.settings.header" = "ALL THEMES"; // TODO +"theme.settings.footer" = "Applying a theme will change how the app looks."; // TODO +"theme.light" = "Light"; // TODO +"theme.dark" = "Dark"; // TODO +"theme.black" = "Black"; // TODO + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Sorting by name"; //TODO "subscriptions.sorting.title.activity" = "Sorting by activity"; //TODO diff --git a/Rocket.Chat/Resources/en.lproj/Localizable.strings b/Rocket.Chat/Resources/en.lproj/Localizable.strings index cf8fafbfc2..408d8d8703 100644 --- a/Rocket.Chat/Resources/en.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/en.lproj/Localizable.strings @@ -353,6 +353,14 @@ "user_action_sheet.remove_confirm.title" = "Remove this user?"; "user_action_sheet.remove_confirm.message" = "Are you sure you want to remove '%@' from the room?"; +// Theme +"theme.settings.title" = "Theme"; +"theme.settings.header" = "ALL THEMES"; +"theme.settings.footer" = "Applying a theme will change how the app looks."; +"theme.light" = "Light"; +"theme.dark" = "Dark"; +"theme.black" = "Black"; + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Sorting by name"; "subscriptions.sorting.title.activity" = "Sorting by activity"; @@ -360,4 +368,4 @@ "subscriptions.sorting.activity" = "Activity"; "subscriptions.grouping.type" = "Group by type"; "subscriptions.grouping.favorites" = "Group by favorites"; -"subscriptions.grouping.unread_top" = "Unread on top"; +"subscriptions.grouping.unread_top" = "Unread on top"; \ No newline at end of file diff --git a/Rocket.Chat/Resources/es.lproj/Localizable.strings b/Rocket.Chat/Resources/es.lproj/Localizable.strings index fc71ba2a12..80f65a25cf 100644 --- a/Rocket.Chat/Resources/es.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/es.lproj/Localizable.strings @@ -354,6 +354,14 @@ "user_action_sheet.remove_confirm.title" = "Remove this user?"; // TODO "user_action_sheet.remove_confirm.message" = "Are you sure you want to remove '%@' from the room?"; // TODO +// Theme +"theme.settings.title" = "Theme"; // TODO +"theme.settings.header" = "ALL THEMES"; // TODO +"theme.settings.footer" = "Applying a theme will change how the app looks."; // TODO +"theme.light" = "Light"; // TODO +"theme.dark" = "Dark"; // TODO +"theme.black" = "Black"; // TODO + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Sorting by name"; //TODO "subscriptions.sorting.title.activity" = "Sorting by activity"; //TODO diff --git a/Rocket.Chat/Resources/fr.lproj/Localizable.strings b/Rocket.Chat/Resources/fr.lproj/Localizable.strings index db8d1b43a3..97d49a4f66 100644 --- a/Rocket.Chat/Resources/fr.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/fr.lproj/Localizable.strings @@ -353,6 +353,14 @@ "user_action_sheet.remove_confirm.title" = "Remove this user?"; // TODO "user_action_sheet.remove_confirm.message" = "Are you sure you want to remove '%@' from the room?"; // TODO +// Theme +"theme.settings.title" = "Theme"; // TODO +"theme.settings.header" = "ALL THEMES"; // TODO +"theme.settings.footer" = "Applying a theme will change how the app looks."; // TODO +"theme.light" = "Light"; // TODO +"theme.dark" = "Dark"; // TODO +"theme.black" = "Black"; // TODO + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Sorting by name"; //TODO "subscriptions.sorting.title.activity" = "Sorting by activity"; //TODO diff --git a/Rocket.Chat/Resources/pl.lproj/Localizable.strings b/Rocket.Chat/Resources/pl.lproj/Localizable.strings index 97fe6510b1..23213a060e 100644 --- a/Rocket.Chat/Resources/pl.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/pl.lproj/Localizable.strings @@ -352,6 +352,14 @@ "user_action_sheet.remove_confirm.title" = "Remove this user?"; // TODO "user_action_sheet.remove_confirm.message" = "Are you sure you want to remove '%@' from the room?"; // TODO +// Theme +"theme.settings.title" = "Theme"; // TODO +"theme.settings.header" = "ALL THEMES"; // TODO +"theme.settings.footer" = "Applying a theme will change how the app looks."; // TODO +"theme.light" = "Light"; // TODO +"theme.dark" = "Dark"; // TODO +"theme.black" = "Black"; // TODO + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Sorting by name"; //TODO "subscriptions.sorting.title.activity" = "Sorting by activity"; //TODO diff --git a/Rocket.Chat/Resources/pt-BR.lproj/Localizable.strings b/Rocket.Chat/Resources/pt-BR.lproj/Localizable.strings index 553e211806..f473b680d7 100644 --- a/Rocket.Chat/Resources/pt-BR.lproj/Localizable.strings +++ b/Rocket.Chat/Resources/pt-BR.lproj/Localizable.strings @@ -353,6 +353,14 @@ "user_action_sheet.remove_confirm.title" = "Remove esse usuário?"; "user_action_sheet.remove_confirm.message" = "Você tem certeza que deseja remover '%@' do canal?"; +// Theme +"theme.settings.title" = "Theme"; // TODO +"theme.settings.header" = "ALL THEMES"; // TODO +"theme.settings.footer" = "Applying a theme will change how the app looks."; // TODO +"theme.light" = "Light"; // TODO +"theme.dark" = "Dark"; // TODO +"theme.black" = "Black"; // TODO + // Sorting & Grouping Subscriptions "subscriptions.sorting.title.alphabetical" = "Ordenando por nome"; "subscriptions.sorting.title.activity" = "Ordenando por atividade"; diff --git a/Rocket.Chat/Storyboards/Auth.storyboard b/Rocket.Chat/Storyboards/Auth.storyboard index 32d78f7f47..26863b1f1b 100644 --- a/Rocket.Chat/Storyboards/Auth.storyboard +++ b/Rocket.Chat/Storyboards/Auth.storyboard @@ -15,7 +15,7 @@ - + @@ -209,7 +209,7 @@ - + @@ -433,7 +433,7 @@ - + @@ -704,7 +704,7 @@ - + @@ -736,7 +736,7 @@ - + @@ -821,7 +821,7 @@ - + @@ -953,7 +953,7 @@ - + @@ -1090,7 +1090,7 @@ to mention you in messages - + @@ -1169,7 +1169,7 @@ to mention you in messages - + @@ -1216,11 +1216,11 @@ to mention you in messages - + - - + + diff --git a/Rocket.Chat/Storyboards/Chat.storyboard b/Rocket.Chat/Storyboards/Chat.storyboard index ea86207976..52dbf9fa9d 100644 --- a/Rocket.Chat/Storyboards/Chat.storyboard +++ b/Rocket.Chat/Storyboards/Chat.storyboard @@ -19,7 +19,7 @@ - + @@ -237,6 +237,7 @@ + diff --git a/Rocket.Chat/Storyboards/Drawing.storyboard b/Rocket.Chat/Storyboards/Drawing.storyboard index 011c9dd4f4..738119ee98 100644 --- a/Rocket.Chat/Storyboards/Drawing.storyboard +++ b/Rocket.Chat/Storyboards/Drawing.storyboard @@ -1,11 +1,11 @@ - + - + @@ -14,7 +14,7 @@ - + @@ -34,7 +34,7 @@ - + @@ -118,7 +118,7 @@ - + @@ -171,7 +171,7 @@ - + @@ -224,7 +224,7 @@ - + diff --git a/Rocket.Chat/Storyboards/Preferences.storyboard b/Rocket.Chat/Storyboards/Preferences.storyboard index c266437274..b79650f991 100644 --- a/Rocket.Chat/Storyboards/Preferences.storyboard +++ b/Rocket.Chat/Storyboards/Preferences.storyboard @@ -103,31 +103,31 @@ - + - + - - + @@ -147,6 +147,26 @@ + + + + + + + + + + + + + + @@ -206,7 +226,7 @@ - + @@ -328,7 +348,7 @@ - + @@ -336,9 +356,10 @@ + - + @@ -409,7 +430,7 @@ - + @@ -418,7 +439,7 @@ - + @@ -450,6 +471,8 @@ + + @@ -688,6 +711,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -881,13 +956,13 @@ - + - + diff --git a/Rocket.Chat/Storyboards/Subscriptions.storyboard b/Rocket.Chat/Storyboards/Subscriptions.storyboard index 7afa926e10..210df61698 100644 --- a/Rocket.Chat/Storyboards/Subscriptions.storyboard +++ b/Rocket.Chat/Storyboards/Subscriptions.storyboard @@ -1,5 +1,5 @@ - + @@ -14,13 +14,13 @@ - + - + - + @@ -129,7 +129,7 @@ - + @@ -193,7 +193,7 @@ - + @@ -208,6 +208,7 @@ + diff --git a/Rocket.Chat/Theme/NotThemeableViews.swift b/Rocket.Chat/Theme/NotThemeableViews.swift new file mode 100644 index 0000000000..2359db598b --- /dev/null +++ b/Rocket.Chat/Theme/NotThemeableViews.swift @@ -0,0 +1,21 @@ +// +// NotThemeableViews.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 4/29/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class NotThemeableView: UIView { + override var theme: Theme? { return nil } +} + +class NotThemeableTableView: UITableView { + override var theme: Theme? { return nil } +} + +class NotThemeableNavigationBar: UINavigationBar { + override var theme: Theme? { return nil } +} diff --git a/Rocket.Chat/Theme/Theme.swift b/Rocket.Chat/Theme/Theme.swift new file mode 100644 index 0000000000..fdc71c3f0a --- /dev/null +++ b/Rocket.Chat/Theme/Theme.swift @@ -0,0 +1,131 @@ +// +// Themeable.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 3/25/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +@objc protocol Themeable { + func applyTheme() +} + +@objc protocol ThemeProvider { + var theme: Theme? { get } +} + +class Theme: NSObject { + let backgroundColor: UIColor + let titleText: UIColor + let bodyText: UIColor + let controlText: UIColor + let auxiliaryText: UIColor + let tintColor: UIColor + let hyperlinkColor: UIColor + let focusedBackground: UIColor + let auxiliaryBackground: UIColor + let mutedAccent: UIColor + let strongAccent: UIColor + let appearence: Appearence + + struct Appearence: Equatable { + let barStyle: UIBarStyle + let keyboardAppearence: UIKeyboardAppearance + let statusBarStyle: UIStatusBarStyle + let scrollViewIndicatorStyle: UIScrollViewIndicatorStyle + + static let dark = Appearence( + barStyle: .black, + keyboardAppearence: .dark, + statusBarStyle: .lightContent, + scrollViewIndicatorStyle: .white + ) + + static let light = Appearence( + barStyle: .default, + keyboardAppearence: .default, + statusBarStyle: .default, + scrollViewIndicatorStyle: .black + ) + } + + init(backgroundColor: UIColor, + titleText: UIColor, + bodyText: UIColor, + controlText: UIColor, + auxiliaryText: UIColor, + tintColor: UIColor, + hyperlinkColor: UIColor, + focusedBackground: UIColor, + auxiliaryBackground: UIColor, + mutedAccent: UIColor, + strongAccent: UIColor?, + appearence: Appearence) { + + self.backgroundColor = backgroundColor + self.titleText = titleText + self.bodyText = bodyText + self.controlText = controlText + self.auxiliaryText = auxiliaryText + self.tintColor = tintColor + self.hyperlinkColor = hyperlinkColor + self.focusedBackground = focusedBackground + self.auxiliaryBackground = auxiliaryBackground + self.mutedAccent = mutedAccent + + if let strongAccent = strongAccent { + self.strongAccent = strongAccent + } else { + self.strongAccent = #colorLiteral(red: 0.9720572829, green: 0.3783821166, blue: 0.446572125, alpha: 1) + } + + self.appearence = appearence + } + + static let light = Theme( + backgroundColor: #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), + titleText: #colorLiteral(red: 0.05143930763, green: 0.0585193634, blue: 0.07106169313, alpha: 1), + bodyText: #colorLiteral(red: 0.1843137255, green: 0.2039215686, blue: 0.2392156863, alpha: 1), + controlText: #colorLiteral(red: 0.3294117647, green: 0.3450980392, blue: 0.368627451, alpha: 1), + auxiliaryText: #colorLiteral(red: 0.6117647059, green: 0.6352941176, blue: 0.6588235294, alpha: 1), + tintColor: .RCBlue(), + hyperlinkColor: .RCBlue(), + focusedBackground: .RCNavigationBarColor(), + auxiliaryBackground: #colorLiteral(red: 0.03921568627, green: 0.2666666667, blue: 0.4117647059, alpha: 1), + mutedAccent: #colorLiteral(red: 0.7960784314, green: 0.7960784314, blue: 0.8, alpha: 1), + strongAccent: nil, + appearence: .light + ) + + static let dark = Theme( + backgroundColor: #colorLiteral(red: 0.06482539596, green: 0.06587358546, blue: 0.06711885095, alpha: 1), + titleText: #colorLiteral(red: 0.9785086513, green: 0.9786720872, blue: 0.9784870744, alpha: 1), + bodyText: #colorLiteral(red: 0.9111283446, green: 0.9229556015, blue: 0.9294117647, alpha: 1), + controlText: #colorLiteral(red: 0.8549193462, green: 0.8697612629, blue: 0.903159703, alpha: 1), + auxiliaryText: #colorLiteral(red: 0.6980392157, green: 0.7224261515, blue: 0.7773035386, alpha: 1), + tintColor: #colorLiteral(red: 0.1176899746, green: 0.6068716645, blue: 0.9971964955, alpha: 1), + hyperlinkColor: #colorLiteral(red: 0.4039215686, green: 0.7333333333, blue: 1, alpha: 1), + focusedBackground: #colorLiteral(red: 0.08987318066, green: 0.08987318066, blue: 0.1, alpha: 1), + auxiliaryBackground: #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1), + mutedAccent: #colorLiteral(red: 0.1672673633, green: 0.1672673633, blue: 0.1769603646, alpha: 1), + strongAccent: nil, + appearence: .dark + ) + + static let black = Theme( + backgroundColor: #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), + titleText: #colorLiteral(red: 0.9785086513, green: 0.9786720872, blue: 0.9784870744, alpha: 1), + bodyText: #colorLiteral(red: 0.9111283446, green: 0.9229556015, blue: 0.9294117647, alpha: 1), + controlText: #colorLiteral(red: 0.8549193462, green: 0.8697612629, blue: 0.903159703, alpha: 1), + auxiliaryText: #colorLiteral(red: 0.6980392157, green: 0.7224261515, blue: 0.7773035386, alpha: 1), + tintColor: #colorLiteral(red: 0.1176899746, green: 0.6068716645, blue: 0.9971964955, alpha: 1), + hyperlinkColor: #colorLiteral(red: 0.4039215686, green: 0.7333333333, blue: 1, alpha: 1), + focusedBackground: #colorLiteral(red: 0.05332512842, green: 0.05332512842, blue: 0.05332512842, alpha: 1), + auxiliaryBackground: #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1), + mutedAccent: #colorLiteral(red: 0.156862745, green: 0.156862745, blue: 0.16, alpha: 1), + strongAccent: nil, + appearence: .dark + ) +} diff --git a/Rocket.Chat/Theme/ThemeManager.swift b/Rocket.Chat/Theme/ThemeManager.swift new file mode 100644 index 0000000000..9fa1c77209 --- /dev/null +++ b/Rocket.Chat/Theme/ThemeManager.swift @@ -0,0 +1,59 @@ +// +// ThemeManager.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 4/27/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import Foundation + +struct ThemeManager { + + /** + Stores a default `Theme` for the app. + + Setting a new value will cause the `applyTheme` method to be called on all the `ThemeManager.observers`. The transition is animated by default. + */ + + static var theme = themes.first(where: { $0.title == UserDefaults.standard.string(forKey: userDefaultsKey) })?.theme ?? Theme.light { + didSet { + UIView.animate(withDuration: 0.3) { + observers.forEach { $0.value?.applyTheme() } + } + let themeName = themes.first(where: { $0.theme == theme })?.title + UserDefaults.standard.set(themeName, forKey: userDefaultsKey) + } + } + + static let userDefaultsKey = "RCTheme" + static let themes: [(title: String, theme: Theme)] = [("light", .light), ("dark", .dark), ("black", .black)] + + static var observers = [Weak]() + + /** + Allows for `applyTheme` to be called automatically on the `observer` when the `ThemeManager.theme` changes. + + ThemeManager holds a weak reference to the `observer`. + */ + + static func addObserver(_ observer: Themeable?) { + observers = observers.compactMap { $0 } + guard let observer = observer else { return } + observer.applyTheme() + observers.append(Weak(observer)) + } +} + +struct Weak { + weak var value: T? + init(_ value: T) { + self.value = value + } +} + +fileprivate extension Array where Element == Weak { + mutating func filterReleasedReferences() { + self = self.compactMap { $0 } + } +} diff --git a/Rocket.Chat/Theme/ThemeableViewControllers.swift b/Rocket.Chat/Theme/ThemeableViewControllers.swift new file mode 100644 index 0000000000..1870f97e6c --- /dev/null +++ b/Rocket.Chat/Theme/ThemeableViewControllers.swift @@ -0,0 +1,24 @@ +// +// ThemeableViewControllers.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 5/2/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +extension UIViewController: Themeable { + + /** + Calls the `applyTheme` on the `view` and the `navigationController`. + + - Important: + On first initializaiton, it is recommended that the view controller be added as an observer to the ThemeManager using the `ThemeManager.addObserver(_:)` method. + */ + + func applyTheme() { + view.applyTheme() + navigationController?.navigationBar.applyTheme() + } +} diff --git a/Rocket.Chat/Theme/ThemeableViews.swift b/Rocket.Chat/Theme/ThemeableViews.swift new file mode 100644 index 0000000000..896c597e9f --- /dev/null +++ b/Rocket.Chat/Theme/ThemeableViews.swift @@ -0,0 +1,231 @@ +// +// ThemeableViews.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 5/2/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit +import SlackTextViewController + +extension UIView: Themeable { + + /** + Applies theme to the view and all of its `subviews`. + + The `Theme` returned from the `theme` property is used. To exempt a view from getting themed, override the `theme` property and return `nil`. + + The default implementation calls the `applyTheme` method on all of its subviews, and sets the background color of the views. + + Override this method to adapt the components of the view to the theme being applied. The implementation of `super` should be called somewhere in the overridden implementation to apply the theme on all of the subviews, and to adapt the `backgroundColor` of `self`. + + This method should only be called directly if the view or any of its subviews require theming after the first initialization. + + - Important: + On first initializaiton, it is recommended that the view controller for the view be added as an observer to the ThemeManager using the `ThemeManager.addObserver(_:)` method. If a view controller does not exist, the view should be added as an observer instead. + */ + + func applyTheme() { + if #available(iOS 11, *) { + guard let theme = theme else { return } + backgroundColor = theme.backgroundColor.withAlphaComponent(backgroundColor?.cgColor.alpha ?? 0.0) + self.subviews.forEach { $0.applyTheme() } + } + } +} + +extension UIView: ThemeProvider { + + /** + Returns the theme to be allied to the view. + + By default the `theme` of the `superview` is returned. If a `superview` does not exits, then the value is taken from `ThemeManager.theme` + + Overriding this property and returning `nil` will exempt the view from getting themed. + */ + + var theme: Theme? { + if #available(iOS 11, *) { + guard let superview = superview else { return ThemeManager.theme } + return superview.theme + } else { + return nil + } + } +} + +extension UILabel { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + textColor = theme.titleText + } +} + +extension UITextField { + override func applyTheme() { + guard let theme = theme else { return } + textColor = theme.titleText + tintColor = theme.tintColor + keyboardAppearance = theme.appearence.keyboardAppearence + leftView?.tintColor = theme.auxiliaryText + if let placeholder = placeholder { + attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [.foregroundColor: theme.auxiliaryText]) + } + } +} + +extension UIActivityIndicatorView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + color = theme.bodyText + } +} + +extension UIRefreshControl { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + tintColor = theme.bodyText + } +} + +extension UICollectionView { + open override func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + view.applyTheme() + } + + open override func addSubview(_ view: UIView) { + super.addSubview(view) + view.applyTheme() + } +} + +extension UITableView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + switch theme { + case .dark, .black: backgroundColor = style == .grouped ? theme.focusedBackground : theme.backgroundColor + default: backgroundColor = style == .grouped ? #colorLiteral(red: 0.937, green: 0.937, blue: 0.957, alpha: 1) : theme.backgroundColor + } + separatorColor = theme.mutedAccent + } + + open override func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + view.applyTheme() + } + + open override func addSubview(_ view: UIView) { + super.addSubview(view) + view.applyTheme() + } +} + +extension UITableViewCell { + override func applyTheme() { + subviews.filter { type(of: $0).description() != "_UITableViewCellSeparatorView" } + .forEach { $0.applyTheme() } + guard let theme = theme else { return } + backgroundColor = theme.backgroundColor.withAlphaComponent(backgroundColor?.cgColor.alpha ?? 0.0) + detailTextLabel?.textColor = theme.auxiliaryText + tintColor = theme.tintColor + } +} + +extension UITableViewHeaderFooterView { + override func applyTheme() { + super.applyTheme() + textLabel?.textColor = #colorLiteral(red: 0.431372549, green: 0.431372549, blue: 0.4509803922, alpha: 1) + } +} + +extension UITextView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + tintColor = theme.hyperlinkColor + } +} + +extension UINavigationBar { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + tintColor = theme.tintColor + barStyle = theme.appearence.barStyle + barTintColor = theme.focusedBackground + } + + open override func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + view.applyTheme() + } +} + +extension UIToolbar { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + isTranslucent = false + barTintColor = theme.focusedBackground + tintColor = theme.tintColor + barStyle = theme.appearence.barStyle + } + + open override func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + view.applyTheme() + } +} + +extension UITabBar { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + barTintColor = theme.focusedBackground + tintColor = theme.tintColor + barStyle = theme.appearence.barStyle + } + + open override func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + view.applyTheme() + } +} + +extension UIScrollView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + indicatorStyle = theme.appearence.scrollViewIndicatorStyle + } +} + +extension SLKTextInputbar { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + textView.keyboardAppearance = theme.appearence.keyboardAppearence + } + + open override func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + view.applyTheme() + } +} + +extension SLKTextView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + layer.borderColor = #colorLiteral(red: 0.497693181, green: 0.494099319, blue: 0.5004472733, alpha: 0.1518210827) + backgroundColor = #colorLiteral(red: 0.497693181, green: 0.494099319, blue: 0.5004472733, alpha: 0.1021854048) + textColor = theme.bodyText + tintColor = theme.tintColor + } +} diff --git a/Rocket.Chat/Views/Avatar/AvatarView.swift b/Rocket.Chat/Views/Avatar/AvatarView.swift index b439dcaeb8..6a5aee11f6 100644 --- a/Rocket.Chat/Views/Avatar/AvatarView.swift +++ b/Rocket.Chat/Views/Avatar/AvatarView.swift @@ -160,3 +160,11 @@ final class AvatarView: UIView { } } + +// MARK: Themeable + +extension AvatarView { + override func applyTheme() { + labelInitials?.textColor = .white + } +} diff --git a/Rocket.Chat/Views/Base/BaseNavigationBar.swift b/Rocket.Chat/Views/Base/BaseNavigationBar.swift new file mode 100644 index 0000000000..c85cd0ec45 --- /dev/null +++ b/Rocket.Chat/Views/Base/BaseNavigationBar.swift @@ -0,0 +1,24 @@ +// +// BaseNavigationBar.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 6/14/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +protocol BaseNavigationBarThemeSource { + var navgiationBarTheme: Theme? { get } +} + +class BaseNavigationBar: UINavigationBar { + var themeSource: BaseNavigationBarThemeSource? + override var theme: Theme? { + if let themeSource = themeSource { + return themeSource.navgiationBarTheme + } else { + return Theme.light + } + } +} diff --git a/Rocket.Chat/Views/Cells/Chat/AutocompleteCell.xib b/Rocket.Chat/Views/Cells/Chat/AutocompleteCell.xib index 8a2f9414c5..546cf87fe0 100644 --- a/Rocket.Chat/Views/Cells/Chat/AutocompleteCell.xib +++ b/Rocket.Chat/Views/Cells/Chat/AutocompleteCell.xib @@ -19,7 +19,7 @@ - + diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageAudioView.swift b/Rocket.Chat/Views/Cells/Chat/ChatMessageAudioView.swift index a2713dce0d..785e0544f1 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageAudioView.swift +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageAudioView.swift @@ -51,13 +51,7 @@ final class ChatMessageAudioView: ChatMessageAttachmentView { } } - @IBOutlet weak var playButton: UIButton! { - didSet { - playButton.tintColor = .gray - playButton.imageView?.tintColor = .gray - } - } - + @IBOutlet weak var playButton: UIButton! @IBOutlet weak var activityIndicator: UIActivityIndicatorView! private var player: AVAudioPlayer? { @@ -76,7 +70,7 @@ final class ChatMessageAudioView: ChatMessageAttachmentView { let pause = #imageLiteral(resourceName: "Player Pause").withRenderingMode(.alwaysTemplate) let play = #imageLiteral(resourceName: "Player Play").withRenderingMode(.alwaysTemplate) playButton.setImage(playing ? pause : play, for: .normal) - playButton.imageView?.tintColor = .RCDarkGray() + applyTheme() } } @@ -171,3 +165,14 @@ extension ChatMessageAudioView: AVAudioPlayerDelegate { self.timeSlider.value = 0.0 } } + +// MARK: Themeable + +extension ChatMessageAudioView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + playButton.tintColor = theme.titleText + playButton.imageView?.tintColor = theme.titleText + } +} diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageCell.swift b/Rocket.Chat/Views/Cells/Chat/ChatMessageCell.swift index b08d9b08df..14702fa8ee 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageCell.swift +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageCell.swift @@ -264,14 +264,12 @@ final class ChatMessageCell: UICollectionViewCell { } } - fileprivate func updateMessageContent() { - if let text = MessageTextCacheManager.shared.message(for: message) { + fileprivate func updateMessageContent(force: Bool = false) { + if let text = force ? MessageTextCacheManager.shared.update(for: message, with: theme) : MessageTextCacheManager.shared.message(for: message, with: theme) { if message.temporary { - text.setFontColor(MessageTextFontAttributes.systemFontColor) - } - - if message.failed { - text.setFontColor(MessageTextFontAttributes.failedFontColor) + text.setFontColor(MessageTextFontAttributes.systemFontColor(for: theme)) + } else if message.failed { + text.setFontColor(MessageTextFontAttributes.failedFontColor(for: theme)) } labelText.message = text @@ -322,7 +320,7 @@ extension ChatMessageCell { static func cellMediaHeightFor(message: Message, width: CGFloat, sequential: Bool = true) -> CGFloat { let fullWidth = width - let attributedString = MessageTextCacheManager.shared.message(for: message) + let attributedString = MessageTextCacheManager.shared.message(for: message, with: nil) var total = (CGFloat)(sequential ? 8 : 29) + (message.reactions.count > 0 ? 40 : 0) if attributedString?.string ?? "" != "" { @@ -366,7 +364,6 @@ extension ChatMessageCell { return total } - } // MARK: Reactions @@ -406,3 +403,15 @@ extension ChatMessageCell { } } } + +// MARK: Themeable + +extension ChatMessageCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + labelDate.textColor = theme.auxiliaryText + labelUsername.textColor = theme.titleText + updateMessageContent(force: true) + } +} diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.swift b/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.swift index e0db60073c..0815d8279d 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.swift +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.swift @@ -13,4 +13,14 @@ final class ChatMessageDaySeparator: UICollectionViewCell { static let identifier = "ChatMessageDaySeparator" @IBOutlet weak var labelTitle: UILabel! + @IBOutlet weak var seperatorLine: UIView! +} + +// MARK: Themeable + +extension ChatMessageDaySeparator { + override func applyTheme() { + super.applyTheme() + seperatorLine.backgroundColor = #colorLiteral(red: 0.491, green: 0.4938107133, blue: 0.500592351, alpha: 0.1964201627) + } } diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.xib b/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.xib index 6682b047e5..5bac00e5cb 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.xib +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageDaySeparator.xib @@ -24,9 +24,13 @@ + + + + + + + + + diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageTextView.swift b/Rocket.Chat/Views/Cells/Chat/ChatMessageTextView.swift index ddce8a4b26..b1350f395f 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageTextView.swift +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageTextView.swift @@ -125,3 +125,15 @@ final class ChatMessageTextView: UIView { } } } + +// MARK: Themeable + +extension ChatMessageTextView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + viewLeftBorder.backgroundColor = theme.auxiliaryText + labelDescription.textColor = theme.auxiliaryText + labelTitle.textColor = theme.controlText + } +} diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.swift b/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.swift index b70ffb3380..7c02284967 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.swift +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.swift @@ -23,6 +23,7 @@ final class ChatMessageURLView: UIView { } } + @IBOutlet weak var viewLeftBorder: UIView! @IBOutlet weak var imageViewURLWidthConstraint: NSLayoutConstraint! @IBOutlet weak var imageViewURL: UIImageView! { didSet { @@ -62,3 +63,14 @@ final class ChatMessageURLView: UIView { delegate?.openURLFromCell(url: url) } } + +// MARK: Themeable + +extension ChatMessageURLView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + viewLeftBorder.backgroundColor = theme.auxiliaryText + labelURLDescription.textColor = theme.auxiliaryText + } +} diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.xib b/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.xib index 39c91888f5..5738ea2db9 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.xib +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageURLView.xib @@ -1,11 +1,11 @@ - + - + @@ -66,6 +66,7 @@ + diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.swift b/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.swift index 1f837b1d4e..155fbf0e06 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.swift +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.swift @@ -13,4 +13,16 @@ final class ChatMessageUnreadSeparator: UICollectionViewCell { static let identifier = "ChatMessageUnreadSeparator" @IBOutlet weak var labelTitle: UILabel! + @IBOutlet weak var seperatorLine: UIView! +} + +// MARK: Themeable + +extension ChatMessageUnreadSeparator { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + seperatorLine.backgroundColor = theme.strongAccent + labelTitle.textColor = theme.strongAccent + } } diff --git a/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.xib b/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.xib index 7315dd27c3..d837ce4631 100644 --- a/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.xib +++ b/Rocket.Chat/Views/Cells/Chat/ChatMessageUnreadSeparator.xib @@ -1,11 +1,11 @@ - + - + @@ -26,9 +26,13 @@ + + + + + + + + @@ -50,6 +58,7 @@ + diff --git a/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoActionCell.swift b/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoActionCell.swift index 037cf853b4..1402923102 100644 --- a/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoActionCell.swift +++ b/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoActionCell.swift @@ -41,3 +41,11 @@ class ChannelInfoActionCell: UITableViewCell, ChannelInfoCellProtocol { @IBOutlet weak var imageViewIcon: UIImageView! } + +extension ChannelInfoActionCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + imageViewIcon.tintColor = theme.titleText + } +} diff --git a/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoDescriptionCell.swift b/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoDescriptionCell.swift index 8b46848912..cf4fd28354 100644 --- a/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoDescriptionCell.swift +++ b/Rocket.Chat/Views/Cells/Chat/Info/ChannelInfoDescriptionCell.swift @@ -32,5 +32,14 @@ final class ChannelInfoDescriptionCell: UITableViewCell, ChannelInfoCellProtocol labelDescription.text = data?.description } } +} + +// MARK: Themeable +extension ChannelInfoDescriptionCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + labelDescription.textColor = theme.auxiliaryText + } } diff --git a/Rocket.Chat/Views/Cells/Chat/Info/MemberCell.xib b/Rocket.Chat/Views/Cells/Chat/Info/MemberCell.xib index 622283e872..ff7e499c0e 100644 --- a/Rocket.Chat/Views/Cells/Chat/Info/MemberCell.xib +++ b/Rocket.Chat/Views/Cells/Chat/Info/MemberCell.xib @@ -1,11 +1,11 @@ - + - + @@ -16,10 +16,10 @@ - + - + diff --git a/Rocket.Chat/Views/Cells/Preferences/EditProfileStatusCell.swift b/Rocket.Chat/Views/Cells/Preferences/EditProfileStatusCell.swift new file mode 100644 index 0000000000..98aca6eb1c --- /dev/null +++ b/Rocket.Chat/Views/Cells/Preferences/EditProfileStatusCell.swift @@ -0,0 +1,26 @@ +// +// EditProfileStatusCell.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 6/12/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class EditProfileStatusCell: UITableViewCell { + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var detailLabel: UILabel! +} + +// MARK: Themeable + +extension EditProfileStatusCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + + titleLabel.textColor = theme.titleText + detailLabel.textColor = theme.auxiliaryText + } +} diff --git a/Rocket.Chat/Views/Cells/Preferences/ServerInfoCell.swift b/Rocket.Chat/Views/Cells/Preferences/ServerInfoCell.swift new file mode 100644 index 0000000000..316eeb6cd8 --- /dev/null +++ b/Rocket.Chat/Views/Cells/Preferences/ServerInfoCell.swift @@ -0,0 +1,22 @@ +// +// ServerInfoCell.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 6/13/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import Foundation + +class ServerInfoCell: UITableViewCell { } + +// MARK: Themeable + +extension ServerInfoCell { + override func applyTheme() { + super.applyTheme() + + textLabel?.textColor = theme?.titleText + detailTextLabel?.textColor = theme?.titleText + } +} diff --git a/Rocket.Chat/Views/Cells/Preferences/ThemePreferenceCell.swift b/Rocket.Chat/Views/Cells/Preferences/ThemePreferenceCell.swift new file mode 100644 index 0000000000..3de759eb2a --- /dev/null +++ b/Rocket.Chat/Views/Cells/Preferences/ThemePreferenceCell.swift @@ -0,0 +1,77 @@ +// +// ThemePreferenceCell.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 4/30/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class ThemePreferenceCell: UITableViewCell { + + static let identifier = "ThemePreferenceCell" + static let cellHeight: CGFloat = 78 + + let borderWidth: CGFloat = 1.0 + let borderColor = #colorLiteral(red: 0.4980838895, green: 0.4951269031, blue: 0.5003594756, alpha: 0.1950449486).cgColor + + var cellTheme: Theme? { + didSet { + setViewsForTheme() + } + } + + func setViewsForTheme() { + baseColorView.backgroundColor = cellTheme?.backgroundColor + auxiliaryColorView.backgroundColor = cellTheme?.bodyText + tintColor = cellTheme?.tintColor + + if ThemeManager.theme == cellTheme { + accessoryType = .checkmark + } else { + accessoryType = .none + } + } + + @IBOutlet weak var baseColorView: UIView! { + didSet { + baseColorView.layer.cornerRadius = 4 + baseColorView.layer.masksToBounds = true + baseColorView.layer.borderWidth = borderWidth + baseColorView.layer.borderColor = borderColor + } + } + + @IBOutlet weak var auxiliaryColorView: UIView! { + didSet { + auxiliaryColorView.layer.cornerRadius = 4 + auxiliaryColorView.layer.masksToBounds = true + auxiliaryColorView.layer.borderWidth = borderWidth + auxiliaryColorView.layer.borderColor = borderColor + } + } + + @IBOutlet weak var titleLabel: UILabel! +} + +extension ThemePreferenceCell { + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + setViewsForTheme() + } + + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + super.setHighlighted(highlighted, animated: animated) + setViewsForTheme() + } +} + +// MARK: Themeable + +extension ThemePreferenceCell { + override func applyTheme() { + super.applyTheme() + setViewsForTheme() + } +} diff --git a/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.swift b/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.swift index baa4147a57..c8b89d2206 100644 --- a/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.swift +++ b/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.swift @@ -23,6 +23,7 @@ final class ChatPreviewModeView: UIView { } } + @IBOutlet weak var seperatorView: UIView! @IBOutlet weak var labelTitle: UILabel! @IBOutlet weak var buttonJoin: UIButton! { didSet { @@ -58,3 +59,14 @@ final class ChatPreviewModeView: UIView { } } } + +extension ChatPreviewModeView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + labelTitle.textColor = theme.auxiliaryText + seperatorView.backgroundColor = theme.mutedAccent + buttonJoin.backgroundColor = #colorLiteral(red: 0.1843137255, green: 0.2039215686, blue: 0.2392156863, alpha: 1) + backgroundColor = theme.focusedBackground + } +} diff --git a/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.xib b/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.xib index 44d3052fca..dff364566a 100644 --- a/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.xib +++ b/Rocket.Chat/Views/Cells/Subscription/ChatPreviewModeView.xib @@ -1,11 +1,11 @@ - + - + @@ -75,6 +75,7 @@ + diff --git a/Rocket.Chat/Views/Cells/Subscription/ServerCell.swift b/Rocket.Chat/Views/Cells/Subscription/ServerCell.swift index dc6b6dbbb3..998d530e17 100644 --- a/Rocket.Chat/Views/Cells/Subscription/ServerCell.swift +++ b/Rocket.Chat/Views/Cells/Subscription/ServerCell.swift @@ -61,7 +61,7 @@ extension ServerCell { case true: self.backgroundColor = self.selectedBackgroundColor case false: - self.backgroundColor = self.defaultBackgroundColor + self.backgroundColor = self.theme?.backgroundColor ?? self.defaultBackgroundColor } } @@ -76,9 +76,9 @@ extension ServerCell { let transition = { switch highlighted { case true: - self.backgroundColor = self.highlightedBackgroundColor + self.backgroundColor = self.theme?.focusedBackground ?? self.highlightedBackgroundColor case false: - self.backgroundColor = self.defaultBackgroundColor + self.backgroundColor = self.theme?.backgroundColor ?? self.defaultBackgroundColor } } @@ -88,5 +88,22 @@ extension ServerCell { transition() } } +} + +// MARK: Themeable + +extension ServerCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + labelServerName.textColor = theme.titleText + labelServerDescription.textColor = theme.auxiliaryText + tintColor = theme.tintColor + + switch isHighlighted { + case false: backgroundColor = theme.backgroundColor + case true: backgroundColor = theme.focusedBackground + } + } } diff --git a/Rocket.Chat/Views/Cells/Subscription/SubscriptionCell.swift b/Rocket.Chat/Views/Cells/Subscription/SubscriptionCell.swift index c9f6b9f00a..040cf98199 100644 --- a/Rocket.Chat/Views/Cells/Subscription/SubscriptionCell.swift +++ b/Rocket.Chat/Views/Cells/Subscription/SubscriptionCell.swift @@ -17,8 +17,8 @@ final class SubscriptionCell: UITableViewCell { internal let labelUnreadTextColor = UIColor(rgb: 0xFFFFFF, alphaVal: 1) internal let defaultBackgroundColor = UIColor.white - internal let selectedBackgroundColor = UIColor(rgb: 0x0, alphaVal: 0.08) - internal let highlightedBackgroundColor = UIColor(rgb: 0x0, alphaVal: 0.14) + internal let selectedBackgroundColor = #colorLiteral(red: 0.4980838895, green: 0.4951269031, blue: 0.5003594756, alpha: 0.19921875) + internal let highlightedBackgroundColor = #colorLiteral(red: 0.4980838895, green: 0.4951269031, blue: 0.5003594756, alpha: 0.09530179799) var subscription: Subscription? { didSet { @@ -97,7 +97,6 @@ final class SubscriptionCell: UITableViewCell { if subscription.unread > 0 || subscription.alert { labelName.font = UIFont.systemFont(ofSize: nameFontSize, weight: .semibold) labelLastMessage.font = UIFont.systemFont(ofSize: lastMessageFontSize, weight: .medium) - labelDate.textColor = .RCBlue() if subscription.unread > 0 { labelUnread.alpha = 1 @@ -109,11 +108,12 @@ final class SubscriptionCell: UITableViewCell { } else { labelName.font = UIFont.systemFont(ofSize: nameFontSize, weight: .medium) labelLastMessage.font = UIFont.systemFont(ofSize: lastMessageFontSize, weight: .regular) - labelDate.textColor = .RCGray() labelUnread.alpha = 0 labelUnread.text = "" } + + applyTheme() } fileprivate func updateStatus(subscription: Subscription) { @@ -166,7 +166,7 @@ extension SubscriptionCell { case true: self.backgroundColor = self.selectedBackgroundColor case false: - self.backgroundColor = self.defaultBackgroundColor + self.backgroundColor = self.theme?.backgroundColor ?? self.defaultBackgroundColor } } @@ -183,7 +183,7 @@ extension SubscriptionCell { case true: self.backgroundColor = self.highlightedBackgroundColor case false: - self.backgroundColor = self.defaultBackgroundColor + self.backgroundColor = self.theme?.backgroundColor ?? self.defaultBackgroundColor } } @@ -194,3 +194,26 @@ extension SubscriptionCell { } } } + +// MARK: Themeable + +extension SubscriptionCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + labelName.textColor = theme.titleText + labelUnread.backgroundColor = theme.tintColor + labelUnread.textColor = theme.backgroundColor + labelLastMessage.textColor = theme.controlText + iconRoom.tintColor = theme.auxiliaryText + + if let subscription = self.subscription, subscription.unread > 0 || subscription.alert { + labelDate.textColor = theme.tintColor + } else { + labelDate.textColor = theme.auxiliaryText + } + + setSelected(isSelected, animated: false) + setHighlighted(isHighlighted, animated: false) + } +} diff --git a/Rocket.Chat/Views/Cells/Subscription/SubscriptionSortingCell.swift b/Rocket.Chat/Views/Cells/Subscription/SubscriptionSortingCell.swift index 92d39545df..abacd78693 100644 --- a/Rocket.Chat/Views/Cells/Subscription/SubscriptionSortingCell.swift +++ b/Rocket.Chat/Views/Cells/Subscription/SubscriptionSortingCell.swift @@ -30,7 +30,7 @@ extension SubscriptionSortingCell { case true: self.backgroundColor = self.selectedBackgroundColor case false: - self.backgroundColor = self.defaultBackgroundColor + self.backgroundColor = self.theme?.backgroundColor ?? self.defaultBackgroundColor } } @@ -45,9 +45,9 @@ extension SubscriptionSortingCell { let transition = { switch highlighted { case true: - self.backgroundColor = self.highlightedBackgroundColor + self.backgroundColor = self.theme?.focusedBackground ?? self.highlightedBackgroundColor case false: - self.backgroundColor = self.defaultBackgroundColor + self.backgroundColor = self.theme?.backgroundColor ?? self.defaultBackgroundColor } } @@ -58,3 +58,21 @@ extension SubscriptionSortingCell { } } } + +// MARK: Themeable + +extension SubscriptionSortingCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + + labelTitle.textColor = theme.controlText + imageViewIcon.tintColor = theme.controlText + tintColor = theme.tintColor + + switch isHighlighted { + case false: backgroundColor = theme.backgroundColor + case true: backgroundColor = theme.focusedBackground + } + } +} diff --git a/Rocket.Chat/Views/Chat/ChatHeaderViewStatus.swift b/Rocket.Chat/Views/Chat/ChatHeaderViewStatus.swift index 659a81dd78..cf51580530 100644 --- a/Rocket.Chat/Views/Chat/ChatHeaderViewStatus.swift +++ b/Rocket.Chat/Views/Chat/ChatHeaderViewStatus.swift @@ -43,3 +43,20 @@ final class ChatHeaderViewStatus: UIView { } } + +// MARK: Themeable + +extension ChatHeaderViewStatus { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + switch theme { + case .light: + backgroundColor = .RCLightGray() + setTextColor(.RCDarkBlue()) + default: + backgroundColor = UIColor.RCDarkGray() + setTextColor(theme.bodyText) + } + } +} diff --git a/Rocket.Chat/Views/Chat/ChatTitleView.swift b/Rocket.Chat/Views/Chat/ChatTitleView.swift index 121745a862..25b7ebde97 100644 --- a/Rocket.Chat/Views/Chat/ChatTitleView.swift +++ b/Rocket.Chat/Views/Chat/ChatTitleView.swift @@ -17,6 +17,7 @@ final class ChatTitleView: UIView { weak var delegate: ChatTitleViewProtocol? + @IBOutlet weak var buttonMore: UIButton! @IBOutlet weak var buttonTitle: UIButton! { didSet { buttonTitle.titleLabel?.textColor = .RCDarkGray() @@ -80,3 +81,14 @@ final class ChatTitleView: UIView { } } + +// MARK: Themeable + +extension ChatTitleView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + buttonMore.tintColor = theme.titleText + buttonTitle.setTitleColor(theme.titleText, for: .normal) + } +} diff --git a/Rocket.Chat/Views/Chat/ChatTitleView.xib b/Rocket.Chat/Views/Chat/ChatTitleView.xib index 6c15528041..09c34cc614 100644 --- a/Rocket.Chat/Views/Chat/ChatTitleView.xib +++ b/Rocket.Chat/Views/Chat/ChatTitleView.xib @@ -32,7 +32,7 @@ - + @@ -84,6 +84,7 @@ + diff --git a/Rocket.Chat/Views/Chat/ChatView.swift b/Rocket.Chat/Views/Chat/ChatView.swift new file mode 100644 index 0000000000..34dfa8634d --- /dev/null +++ b/Rocket.Chat/Views/Chat/ChatView.swift @@ -0,0 +1,24 @@ +// +// ChatView.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 4/27/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class ChatView: UIView { + @IBOutlet weak var scrollToBottomButton: UIButton! +} + +// MARK: Themeable + +extension ChatView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + scrollToBottomButton.tintColor = theme.titleText + scrollToBottomButton.layer.borderColor = theme.auxiliaryText.cgColor + } +} diff --git a/Rocket.Chat/Views/Chat/RCTextView.swift b/Rocket.Chat/Views/Chat/RCTextView.swift index 1200fe8075..526032cd49 100644 --- a/Rocket.Chat/Views/Chat/RCTextView.swift +++ b/Rocket.Chat/Views/Chat/RCTextView.swift @@ -173,3 +173,13 @@ extension RCTextView: UITextViewDelegate { } } + +// MARK: Themeable + +extension RCTextView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + customEmojiViews.forEach { $0.backgroundColor = theme.backgroundColor } + } +} diff --git a/Rocket.Chat/Views/Chat/ReplyView.swift b/Rocket.Chat/Views/Chat/ReplyView.swift index 084d0cda06..698f33c46a 100644 --- a/Rocket.Chat/Views/Chat/ReplyView.swift +++ b/Rocket.Chat/Views/Chat/ReplyView.swift @@ -11,6 +11,7 @@ import Foundation final class ReplyView: UIView { @IBOutlet weak var username: UILabel! @IBOutlet weak var message: UILabel! + @IBOutlet weak var closeButton: UIButton! var onClose: (() -> Void)? @@ -18,3 +19,13 @@ final class ReplyView: UIView { onClose?() } } + +extension ReplyView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + backgroundColor = theme.focusedBackground + closeButton.setTitleColor(theme.titleText, for: .normal) + message.textColor = theme.bodyText + } +} diff --git a/Rocket.Chat/Views/Chat/ReplyView.xib b/Rocket.Chat/Views/Chat/ReplyView.xib index dacfb2b682..22476bbe20 100644 --- a/Rocket.Chat/Views/Chat/ReplyView.xib +++ b/Rocket.Chat/Views/Chat/ReplyView.xib @@ -1,11 +1,11 @@ - + - + @@ -65,6 +65,7 @@ + diff --git a/Rocket.Chat/Views/Form/Cells/MentionsTextFieldTableViewCell.swift b/Rocket.Chat/Views/Form/Cells/MentionsTextFieldTableViewCell.swift new file mode 100644 index 0000000000..f299d053dd --- /dev/null +++ b/Rocket.Chat/Views/Form/Cells/MentionsTextFieldTableViewCell.swift @@ -0,0 +1,126 @@ +// +// MentionsTextFieldTableViewCell.swift +// Rocket.Chat +// +// Created by Artur Rymarz on 07.11.2017. +// Copyright © 2017 Rocket.Chat. All rights reserved. +// + +import Foundation +import RealmSwift +import TagListView +import SearchTextField + +final class MentionsTextFieldTableViewCell: UITableViewCell, FormTableViewCellProtocol, TagListViewDelegate { + static let identifier = "kMentionsTextFieldTableViewCell" + static let xibFileName = "MentionsTextFieldTableViewCell" + + static let defaultHeight: Float = -1 + weak var delegate: FormTableViewDelegate? + + var key: String? + + @IBOutlet private weak var tagListView: TagListView! + @IBOutlet weak var imgLeftIcon: UIImageView! + @IBOutlet weak var textFieldInput: SearchTextField! + @IBOutlet weak var tagViewTopConstraint: NSLayoutConstraint! + + private var users: [String: TagView] = [:] + + override func awakeFromNib() { + super.awakeFromNib() + + textFieldInput.clearButtonMode = .whileEditing + tagListView.textFont = UIFont.systemFont(ofSize: 16) + tagListView.delegate = self + tagViewTopConstraint.constant = 0 + + textFieldInput.itemSelectionHandler = { filteredResults, itemPosition in + let item = filteredResults[itemPosition] + self.addUserToInviteList(name: item.title) + self.textFieldInput.text = nil + self.delegate?.updateDictValue(key: self.key ?? "", value: self.invitedUsers()) + } + } + + private func invitedUsers() -> [String] { + return users.map { $0.key } + } + + func height() -> CGFloat { + return UITableViewAutomaticDimension + } + + func setPreviousValue(previous: Any) { + if let previous = previous as? String { + textFieldInput.text = previous + } + } + + @IBAction func textFieldInputEditingChanged(_ sender: Any) { + fetchUsers() + } + + private func fetchUsers() { + guard + let realm = Realm.current, + let name = textFieldInput.text, + name.count > 0 + else { + textFieldInput.filterStrings([]) + return + } + + let users = realm.objects(User.self).filter( + NSPredicate(format: "name CONTAINS[cd] %@ OR username CONTAINS[cd] %@", name, name) + ) + + let usernames = Array(users).compactMap { $0.username }.filter { + AuthManager.currentUser()?.username != $0 + } + + textFieldInput.filterStrings(usernames) + } + + override func prepareForReuse() { + super.prepareForReuse() + + users.removeAll() + tagListView.removeAllTags() + } + + // MARK: Tag + + func tagRemoveButtonPressed(_ title: String, tagView: TagView, sender: TagListView) { + for tag in users where tag.value === tagView { + users.removeValue(forKey: tag.key) + } + + tagListView.removeTagView(tagView) + updateTagViewConstraint() + delegate?.updateDictValue(key: key ?? "", value: self.invitedUsers()) + delegate?.updateTable(key: key ?? "") + } + + private func updateTagViewConstraint() { + tagViewTopConstraint.constant = tagListView.tagViews.count > 0 ? 12 : 0 + } + + private func addUserToInviteList(name: String) { + if users[name] == nil { + users[name] = tagListView.addTag(name) + updateTagViewConstraint() + delegate?.updateTable(key: key ?? "") + } + } +} + +// MARK: Themeable + +extension MentionsTextFieldTableViewCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + imgLeftIcon.tintColor = theme.titleText + } +} diff --git a/Rocket.Chat/Views/Form/Cells/TextFieldTableViewCell.swift b/Rocket.Chat/Views/Form/Cells/TextFieldTableViewCell.swift index 65106c5e84..b342f0436f 100644 --- a/Rocket.Chat/Views/Form/Cells/TextFieldTableViewCell.swift +++ b/Rocket.Chat/Views/Form/Cells/TextFieldTableViewCell.swift @@ -34,3 +34,13 @@ final class TextFieldTableViewCell: UITableViewCell, FormTableViewCellProtocol { } } + +// MARK: Themeable + +extension TextFieldTableViewCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + imgLeftIcon.tintColor = theme.titleText + } +} diff --git a/Rocket.Chat/Views/Loader/LoaderView.swift b/Rocket.Chat/Views/Loader/LoaderView.swift index 0e222229cb..e9f6782c8d 100644 --- a/Rocket.Chat/Views/Loader/LoaderView.swift +++ b/Rocket.Chat/Views/Loader/LoaderView.swift @@ -60,7 +60,7 @@ class LoaderView: UIView { clockwise: false ) - circleLayer.fillColor = fillColor + circleLayer.fillColor = theme?.auxiliaryBackground.cgColor ?? fillColor circleLayer.backgroundColor = nil circleLayer.path = path.cgPath circleLayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height) @@ -79,5 +79,16 @@ class LoaderView: UIView { layer.addSublayer(circle) } } +} + +// MARK: Themeable +extension LoaderView { + override func applyTheme() { + super.applyTheme() + if isAnimating { + stopAnimating() + startAnimating() + } + } } diff --git a/Rocket.Chat/Views/Notification/NotificationView.swift b/Rocket.Chat/Views/Notification/NotificationView.swift index bb02b06f5a..b16b241441 100644 --- a/Rocket.Chat/Views/Notification/NotificationView.swift +++ b/Rocket.Chat/Views/Notification/NotificationView.swift @@ -12,6 +12,7 @@ class NotificationView: UIView { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var bodyLabel: UILabel! + @IBOutlet weak var backgroundBlurView: UIVisualEffectView! @IBOutlet weak var grabber: UIView! { didSet { @@ -37,15 +38,26 @@ class NotificationView: UIView { } } - override func awakeFromNib() { - super.awakeFromNib() - self.clipsToBounds = true - } - func displayNotification(title: String, body: String, username: String) { titleLabel.text = title bodyLabel.text = body avatarView.username = username } + private func applyShadow(color: UIColor = .black) { + layer.shadowColor = color.cgColor + layer.shadowOpacity = 0.3 + layer.shadowRadius = 1 + layer.shadowOffset = CGSize(width: 0, height: 0) + } +} + +extension NotificationView { + override func applyTheme() { + guard let theme = theme else { return } + bodyLabel.textColor = theme.bodyText + titleLabel.textColor = theme.titleText + grabber.backgroundColor = theme.titleText.withAlphaComponent(0.18) + applyShadow(color: theme.titleText) + } } diff --git a/Rocket.Chat/Views/Sections/SubscriptionSearchMoreView.swift b/Rocket.Chat/Views/Sections/SubscriptionSearchMoreView.swift index 7c52204ab4..f12d63b79d 100644 --- a/Rocket.Chat/Views/Sections/SubscriptionSearchMoreView.swift +++ b/Rocket.Chat/Views/Sections/SubscriptionSearchMoreView.swift @@ -26,3 +26,11 @@ final class SubscriptionSearchMoreView: UIView { delegate?.buttonLoadMoreDidPressed() } } + +extension SubscriptionSearchMoreView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + buttonLoadMore.setTitleColor(theme.auxiliaryText, for: .normal) + } +} diff --git a/Rocket.Chat/Views/Sections/SubscriptionSectionView.swift b/Rocket.Chat/Views/Sections/SubscriptionSectionView.swift index 8af9857bae..89dda93dcb 100644 --- a/Rocket.Chat/Views/Sections/SubscriptionSectionView.swift +++ b/Rocket.Chat/Views/Sections/SubscriptionSectionView.swift @@ -17,3 +17,14 @@ final class SubscriptionSectionView: UIView { } } + +// MARK: Themeable + +extension SubscriptionSectionView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + + labelTitle.textColor = theme.bodyText + } +} diff --git a/Rocket.Chat/Views/Subscriptions/FileTableViewCell.swift b/Rocket.Chat/Views/Subscriptions/FileTableViewCell.swift index caf9ade9bf..e0311285a2 100644 --- a/Rocket.Chat/Views/Subscriptions/FileTableViewCell.swift +++ b/Rocket.Chat/Views/Subscriptions/FileTableViewCell.swift @@ -136,3 +136,13 @@ class FileTableViewCell: UITableViewCell { Nuke.cancelRequest(for: filePreview) } } + +// MARK: Themeable + +extension FileTableViewCell { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + filePreview.tintColor = theme.titleText + } +} diff --git a/Rocket.Chat/Views/Subscriptions/ServersListView.swift b/Rocket.Chat/Views/Subscriptions/ServersListView.swift index cf88ac7664..c851033449 100644 --- a/Rocket.Chat/Views/Subscriptions/ServersListView.swift +++ b/Rocket.Chat/Views/Subscriptions/ServersListView.swift @@ -17,6 +17,7 @@ final class ServersListView: UIView { private let viewModel = ServersListViewModel() weak var delegate: ServerListViewDelegate? + @IBOutlet weak var titleSeperator: UIView! @IBOutlet weak var labelTitle: UILabel! { didSet { labelTitle.text = viewModel.title @@ -198,3 +199,15 @@ extension ServersListView: UITableViewDelegate { } } + +// MARK: Themeable + +extension ServersListView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + buttonAddNewServer.setTitleColor(theme.tintColor, for: .normal) + labelTitle.textColor = theme.auxiliaryText + titleSeperator.backgroundColor = theme.mutedAccent + } +} diff --git a/Rocket.Chat/Views/Subscriptions/ServersListView.xib b/Rocket.Chat/Views/Subscriptions/ServersListView.xib index 6eba3390da..d169f48b05 100644 --- a/Rocket.Chat/Views/Subscriptions/ServersListView.xib +++ b/Rocket.Chat/Views/Subscriptions/ServersListView.xib @@ -1,5 +1,5 @@ - + @@ -91,10 +91,13 @@ + + + diff --git a/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingSeparatorView.swift b/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingSeparatorView.swift new file mode 100644 index 0000000000..aa05374800 --- /dev/null +++ b/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingSeparatorView.swift @@ -0,0 +1,45 @@ +// +// SubscriptionsSortingSeparatorView.swift +// Rocket.Chat +// +// Created by Samar Sunkaria on 6/11/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit + +class SubscriptionsSortingSeparatorView: UIView { + + let separator: UIView + + override init(frame: CGRect) { + + let horizontalSpacing = 16.0 + self.separator = UIView(frame: CGRect( + x: horizontalSpacing, + y: Double(frame.height) / 2, + width: Double(frame.width) - horizontalSpacing * 2, + height: 0.5 + )) + + super.init(frame: frame) + separator.autoresizingMask = .flexibleWidth + addSubview(separator) + applyTheme() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +// MARK: Themeable + +extension SubscriptionsSortingSeparatorView { + override func applyTheme() { + guard let theme = theme else { return } + + backgroundColor = theme.backgroundColor + separator.backgroundColor = theme.mutedAccent + } +} diff --git a/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.swift b/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.swift index 431d6493fc..a19d6fa14a 100644 --- a/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.swift +++ b/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.swift @@ -31,6 +31,7 @@ final class SubscriptionsSortingView: UIView { } } + @IBOutlet weak var separatorView: UIView! @IBOutlet weak var buttonClose: UIButton! @IBOutlet weak var headerView: UIView! @IBOutlet weak var tableView: UITableView! { @@ -141,22 +142,12 @@ extension SubscriptionsSortingView: UITableViewDataSource { func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { if section == 0 { - let viewHeight = viewModel.listSeparatorHeight - let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: viewHeight)) - view.backgroundColor = .white - - let horizontalSpacing = 16.0 - let separator = UIView(frame: CGRect( - x: horizontalSpacing, - y: Double(viewHeight) / 2, - width: Double(tableView.bounds.width) - horizontalSpacing * 2, - height: 0.5 - )) - - separator.backgroundColor = .RCLightGray() - view.addSubview(separator) - - return view + return SubscriptionsSortingSeparatorView(frame: CGRect( + x: 0, + y: 0, + width: tableView.bounds.width, + height: viewModel.listSeparatorHeight) + ) } return UIView() @@ -199,3 +190,16 @@ extension SubscriptionsSortingView: UITableViewDelegate { } } + +// MARK: Themeable + +extension SubscriptionsSortingView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + + labelTitle.textColor = theme.auxiliaryText + buttonClose.tintColor = theme.auxiliaryText + separatorView.backgroundColor = theme.mutedAccent + } +} diff --git a/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.xib b/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.xib index dc472ff010..6ed8ea8b54 100644 --- a/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.xib +++ b/Rocket.Chat/Views/Subscriptions/SubscriptionsSortingView.xib @@ -1,5 +1,5 @@ - + @@ -90,6 +90,7 @@ + diff --git a/Rocket.Chat/Views/Subscriptions/SubscriptionsTitleView.swift b/Rocket.Chat/Views/Subscriptions/SubscriptionsTitleView.swift index 49414cccd6..10b4a40e8e 100644 --- a/Rocket.Chat/Views/Subscriptions/SubscriptionsTitleView.swift +++ b/Rocket.Chat/Views/Subscriptions/SubscriptionsTitleView.swift @@ -48,7 +48,7 @@ final class SubscriptionsTitleView: UIView { } func updateTitleImage(reverse: Bool = false) { - if let image = UIImage(named: "Server Selector")?.imageWithTint(.RCBlue()) { + if let image = UIImage(named: "Server Selector")?.imageWithTint(theme?.tintColor ?? .RCBlue()) { if reverse, let cgImage = image.cgImage { let rotatedImage = UIImage(cgImage: cgImage, scale: image.scale, orientation: .downMirrored) buttonServer.setImage(rotatedImage, for: .normal) @@ -85,3 +85,14 @@ final class SubscriptionsTitleView: UIView { } } + +// MARK: Themeable + +extension SubscriptionsTitleView { + override func applyTheme() { + super.applyTheme() + guard let theme = theme else { return } + buttonServer.setTitleColor(theme.tintColor, for: .normal) + buttonServer.tintColor = theme.tintColor + } +} diff --git a/Rocket.ChatTests/API/Clients/CommandsClientSpec.swift b/Rocket.ChatTests/API/Clients/CommandsClientSpec.swift index c0ee004883..e31e103db9 100644 --- a/Rocket.ChatTests/API/Clients/CommandsClientSpec.swift +++ b/Rocket.ChatTests/API/Clients/CommandsClientSpec.swift @@ -33,12 +33,12 @@ class CommandsClientSpec: XCTestCase, RealmTestCase { client.fetchCommands(realm: realm) let expectation = XCTestExpectation(description: "two commands added to realm") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: { if realm.objects(Command.self).count == 2 { expectation.fulfill() } }) - wait(for: [expectation], timeout: 2) + wait(for: [expectation], timeout: 3) } func testRunCommand() { diff --git a/Rocket.ChatTests/Preferences/PreferencesViewModelSpec.swift b/Rocket.ChatTests/Preferences/PreferencesViewModelSpec.swift index 9a82ecfc7c..d44222b3d2 100644 --- a/Rocket.ChatTests/Preferences/PreferencesViewModelSpec.swift +++ b/Rocket.ChatTests/Preferences/PreferencesViewModelSpec.swift @@ -63,7 +63,13 @@ class PreferencesViewModelSpec: XCTestCase { func testNumberOfRowsInSection() { XCTAssertEqual(model.numberOfSections, 7) XCTAssertEqual(model.numberOfRowsInSection(0), 1) - XCTAssertEqual(model.numberOfRowsInSection(1), 4) + + if #available(iOS 11, *) { + XCTAssertEqual(model.numberOfRowsInSection(1), 5) + } else { + XCTAssertEqual(model.numberOfRowsInSection(1), 4) + } + XCTAssertEqual(model.numberOfRowsInSection(2), 1) XCTAssertEqual(model.numberOfRowsInSection(3), 3) XCTAssertEqual(model.numberOfRowsInSection(4), 1) diff --git a/Rocket.ChatTests/Theme/ThemeManagerSpec.swift b/Rocket.ChatTests/Theme/ThemeManagerSpec.swift new file mode 100644 index 0000000000..f0c2755592 --- /dev/null +++ b/Rocket.ChatTests/Theme/ThemeManagerSpec.swift @@ -0,0 +1,71 @@ +// +// ThemeManagerSpec.swift +// Rocket.ChatTests +// +// Created by Samar Sunkaria on 5/2/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import XCTest +@testable import Rocket_Chat + +class ThemeManagerSpec: XCTestCase { + + class MockObserver: Themeable { + var themeApplied = false + func applyTheme() { + themeApplied = true + } + } + + var mockObserver = MockObserver() + + override func setUp() { + super.setUp() + mockObserver = MockObserver() + } + + override func tearDown() { + super.tearDown() + ThemeManager.observers.removeAll() + ThemeManager.theme = .light + UserDefaults.standard.set(nil, forKey: ThemeManager.userDefaultsKey) + } + + func testAddingObserver() { + ThemeManager.addObserver(mockObserver) + XCTAssert(ThemeManager.observers.contains(where: { + guard let value = $0.value else { return false } + return value === mockObserver + }), "Theme manager should have a reference to the observer") + } + + func testRemovingObserver() { + ThemeManager.addObserver(mockObserver) + mockObserver = MockObserver() // Should release the old reference + XCTAssert(ThemeManager.observers.compactMap({ $0.value }).isEmpty, "Theme manager should not retain a reference to the observer") + } + + func testApplyThemeCalled() { + ThemeManager.addObserver(mockObserver) + XCTAssert(mockObserver.themeApplied, "applyTheme should be called") + mockObserver.themeApplied = false + ThemeManager.theme = .dark + XCTAssert(mockObserver.themeApplied, "applyTheme should be called") + } + + func testStoringThemeInUserDefaults() { + let theme = Theme.dark + ThemeManager.theme = theme + let themeName = UserDefaults.standard.string(forKey: ThemeManager.userDefaultsKey) + XCTAssertNotNil(themeName, "The theme should be stored in the User Deafults") + XCTAssertEqual(ThemeManager.themes.first(where: { $0.theme == theme })?.title, themeName, "The theme stored in user defaults should be the same as the theme assigned to the theme manager.") + } + + func testRetrievingThemeFromUserDefaultsWhenKeyIsNotStored() { + if UserDefaults.standard.string(forKey: ThemeManager.userDefaultsKey) == nil { + XCTAssert(ThemeManager.theme == .light, "The light theme should be the default theme in case value is not stored in user defaults") + return + } + } +} diff --git a/Rocket.ChatTests/Theme/ThemeableSpec.swift b/Rocket.ChatTests/Theme/ThemeableSpec.swift new file mode 100644 index 0000000000..8c2200e5c2 --- /dev/null +++ b/Rocket.ChatTests/Theme/ThemeableSpec.swift @@ -0,0 +1,68 @@ +// +// ThemeableSpec.swift +// Rocket.ChatTests +// +// Created by Samar Sunkaria on 5/2/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import XCTest +@testable import Rocket_Chat + +class ThemeableSpec: XCTestCase { + + class MockThemeableView: UIView { + var themeApplied = false + + override func applyTheme() { + super.applyTheme() + themeApplied = true + } + + override var theme: Theme? { return internalTheme } + var internalTheme: Theme? = Theme.light + } + + func testApplyThemeCalledOnSubview() { + let view = MockThemeableView() + let subview = MockThemeableView() + view.addSubview(subview) + view.applyTheme() + XCTAssert(subview.themeApplied, "Apply theme should be called on subview.") + } + + func testSettingThemeOnSuperview() { + let view = MockThemeableView() + let subview = UIView() + view.addSubview(subview) + view.internalTheme = .dark + XCTAssertEqual(subview.theme, view.internalTheme, "View should acquire its superview's theme.") + } + + func testSettingThemeOnSubview() { + let view = UIView() + let subview = MockThemeableView() + view.addSubview(subview) + subview.internalTheme = nil + XCTAssertEqual(subview.theme, nil, "View should be able to override its parent's theme.") + } + + func testNotThemeableView() { + let view = NotThemeableView() + XCTAssertEqual(view.theme, nil, "NotThemeableView should have a nil theme.") + } + + func testApplyThemeOnViewController() { + let controller = UIViewController() + let view = MockThemeableView() + controller.view.addSubview(view) + controller.applyTheme() + XCTAssert(view.themeApplied, "View controller should call applyTheme on its view.") + } + + func testBaseViewController() { + let controller = BaseViewController() + controller.viewDidLoad() + XCTAssert(ThemeManager.observers.contains { $0.value === controller }, "BaseViewController should add itself as an observer") + } +}