diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index f29ee1a115..dee956ceed 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -92,6 +92,9 @@ AFCE353527E4ED5900FEA6C2 /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */; }; AFCE353727E4ED7B00FEA6C2 /* NCShareCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */; }; AFCE353927E5DE0500FEA6C2 /* Shareable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* Shareable.swift */; }; + AFCE353927E5DE0500FEA6C2 /* NCShare+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */; }; + B543154C2DA690B700981E7E /* NCNotificationText.swift in Sources */ = {isa = PBXBuildFile; fileRef = B543154B2DA690B700981E7E /* NCNotificationText.swift */; }; + C04E2F232A17BB4D001BAD85 /* FilesIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04E2F222A17BB4D001BAD85 /* FilesIntegrationTests.swift */; }; D575039F27146F93008DC9DC /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7A0D1342591FBC5008F8A13 /* String+Extension.swift */; }; D5B6AA7827200C7200D49C24 /* NCActivityTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */; }; F310B1EF2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = F310B1EE2BA862F1001C42F5 /* NCViewerMedia+VisionKit.swift */; }; @@ -1329,6 +1332,8 @@ AFCE353427E4ED5900FEA6C2 /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCells.swift; sourceTree = ""; }; AFCE353827E5DE0400FEA6C2 /* Shareable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shareable.swift; sourceTree = ""; }; + AFCE353827E5DE0400FEA6C2 /* NCShare+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCShare+Helper.swift"; sourceTree = ""; }; + B543154B2DA690B700981E7E /* NCNotificationText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNotificationText.swift; sourceTree = ""; }; C0046CDA2A17B98400D87C9D /* NextcloudUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C04E2F202A17BB4D001BAD85 /* NextcloudIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NextcloudIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D5B6AA7727200C7200D49C24 /* NCActivityTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCActivityTableViewCell.swift; sourceTree = ""; }; @@ -2117,6 +2122,8 @@ isa = PBXGroup; children = ( AA52EB452D42AC5A0089C348 /* Placeholder.swift */, + B543154B2DA690B700981E7E /* NCNotificationText.swift */, + AF8ED1FB2757821000B8DBC4 /* NextcloudUnitTests.swift */, ); path = NextcloudUnitTests; sourceTree = ""; @@ -4143,6 +4150,7 @@ AA52EB472D42AC9E0089C348 /* Placeholder.swift in Sources */, F372087D2BAB4C0F006B5430 /* TestConstants.swift in Sources */, F78E2D6C29AF02DB0024D4F3 /* Database.swift in Sources */, + B543154C2DA690B700981E7E /* NCNotificationText.swift in Sources */, F7817CFE29801A3500FFBC65 /* Data+Extension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Tests/NextcloudUnitTests/NCNotificationText.swift b/Tests/NextcloudUnitTests/NCNotificationText.swift new file mode 100644 index 0000000000..1a7e0b2345 --- /dev/null +++ b/Tests/NextcloudUnitTests/NCNotificationText.swift @@ -0,0 +1,85 @@ +// +// NCNotificationText.swift +// NextcloudUnitTests +// +// Created by Amrut Waghmare on 18/10/23. +// Copyright © 2023 Marino Faggiana. All rights reserved. +// + +@testable import Nextcloud +import XCTest +import NextcloudKit + +class NCNotificationText: XCTestCase { + var viewController : NCNotification! + + override func setUpWithError() throws { + // Step 1. Create an instance of UIStoryboard + let storyboard = UIStoryboard(name: "NCNotification", bundle: nil) + // Step 2. Instantiate UIViewController with Storyboard ID + viewController = storyboard.instantiateViewController(withIdentifier: "NCNotification.storyboard") as? NCNotification + + // Step 3. Make the viewDidLoad() execute. + viewController.loadViewIfNeeded() + } + + override func tearDownWithError() throws { + viewController = nil + } + + //Test that a cell with the correct reuse identifier is dequeued + func testTableViewCellDequeue() { + let notification = NKNotifications() + viewController.notifications = [notification] + let tableView = UITableView() + tableView.register(NCNotificationCell.self, forCellReuseIdentifier: "Cell") + let indexPath = IndexPath(row: 0, section: 0) + let cell = viewController.tableView(tableView, cellForRowAt: indexPath) as? NCNotificationCell + XCTAssertNotNil(cell) + XCTAssertEqual(cell?.reuseIdentifier, "Cell") + } + + //Test that the cell's icon is set image + func testTableViewCellIcon() { + let notification = NKNotifications() + viewController.notifications = [notification] + let tableView = UITableView() + tableView.register(NCNotificationCell.self, forCellReuseIdentifier: "Cell") + let indexPath = IndexPath(row: 0, section: 0) + let cell = viewController.tableView(tableView, cellForRowAt: indexPath) as? NCNotificationCell + XCTAssertNotNil(cell?.icon.image) + } + + //Test that the cell's primary and secondary buttons are set up correctly + func testTableViewCellButtons() { + let notification = NKNotifications() + notification.actions = Data("[{\"label\":\"OK\",\"primary\":true},{\"label\":\"Cancel\",\"primary\":false}]".utf8) + viewController.notifications = [notification] + let tableView = UITableView() + tableView.register(NCNotificationCell.self, forCellReuseIdentifier: "Cell") + let indexPath = IndexPath(row: 0, section: 0) + let cell = viewController.tableView(tableView, cellForRowAt: indexPath) as? NCNotificationCell + XCTAssertEqual(cell?.primary.title(for: .normal), "OK") + XCTAssertEqual(cell?.secondary.title(for: .normal), "Cancel") + } + + //Test that the cell's date label is set correctly + func testTableViewCellDate() { + let notification = NKNotifications() + notification.date = NSDate() + viewController.notifications = [notification] + let tableView = UITableView() + tableView.register(NCNotificationCell.self, forCellReuseIdentifier: "Cell") + let indexPath = IndexPath(row: 0, section: 0) + let cell = viewController.tableView(tableView, cellForRowAt: indexPath) as? NCNotificationCell + XCTAssertEqual(cell?.date.text, "less than a minute ago") + } + + //Test with a color that is image not nil + func testImageNotNil() { + let color = UIColor(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0) + let image = UIImage().imageColor(color) + XCTAssertNotNil(image, "Image should not be nil.") + + } +} diff --git a/iOSClient/Notification/NCNotification.storyboard b/iOSClient/Notification/NCNotification.storyboard index fb122ec81a..a89ba02d24 100644 --- a/iOSClient/Notification/NCNotification.storyboard +++ b/iOSClient/Notification/NCNotification.storyboard @@ -1,193 +1,163 @@ - + - + - + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - + + - - + + - - - - - - - - - - - - - - - - - - - + - + + diff --git a/iOSClient/Notification/NCNotification.swift b/iOSClient/Notification/NCNotification.swift index 1d68e226bc..dcac95d261 100644 --- a/iOSClient/Notification/NCNotification.swift +++ b/iOSClient/Notification/NCNotification.swift @@ -26,12 +26,14 @@ import UIKit import NextcloudKit import SwiftyJSON -class NCNotification: UITableViewController, NCNotificationCellDelegate { +class NCNotification: UITableViewController, NCNotificationCellDelegate, NCEmptyDataSetDelegate { let utilityFileSystem = NCUtilityFileSystem() let utility = NCUtility() var notifications: [NKNotifications] = [] var dataSourceTask: URLSessionTask? var session: NCSession.Session! + private let appDelegate = (UIApplication.shared.delegate as? AppDelegate)! + var emptyDataSet: NCEmptyDataSet? var controller: NCMainTabBarController? { self.tabBarController as? NCMainTabBarController @@ -45,31 +47,46 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { title = NSLocalizedString("_notifications_", comment: "") view.backgroundColor = .systemBackground + self.session = NCSession.shared.getSession(controller: controller) + tableView.tableFooterView = UIView() tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 50.0 tableView.backgroundColor = .systemBackground + tableView.allowsSelection = false refreshControl?.addTarget(self, action: #selector(getNetwokingNotification(_:)), for: .valueChanged) - navigationController?.navigationBar.tintColor = NCBrandColor.shared.iconImageColor - - let close = UIBarButtonItem(title: NSLocalizedString("_close_", comment: ""), style: .done) { - self.dismiss(animated: true) + // Navigation controller is being presented modally + if navigationController?.presentingViewController != nil { + navigationItem.leftBarButtonItem = UIBarButtonItem(title: NSLocalizedString("_cancel_", comment: ""), style: .plain, action: { [weak self] in + self?.dismiss(animated: true) + }) } + // Empty + let offset = (self.navigationController?.navigationBar.bounds.height ?? 0) - 20 + emptyDataSet = NCEmptyDataSet(view: tableView, offset: -offset, delegate: self) + + } - self.navigationItem.leftBarButtonItems = [close] + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + appDelegate.activeViewController = self + navigationController?.setNavigationBarAppearance() + AnalyticsHelper.shared.trackEvent(eventName: .SCREEN_EVENT__NOTIFICATIONS) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) getNetwokingNotification(nil) + NotificationCenter.default.addObserver(self, selector: #selector(initialize), name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterInitialize), object: nil) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) - + + NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: NCGlobal.shared.notificationCenterInitialize), object: nil) NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUpdateNotification) // Cancel Queue & Retrieves Properties @@ -79,14 +96,43 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { @objc func viewClose() { self.dismiss(animated: true, completion: nil) } + + // MARK: - NotificationCenter + @objc func initialize() { + getNetwokingNotification(nil) + } + + // MARK: - Empty + func emptyDataSetView(_ view: NCEmptyView) { + + if self.dataSourceTask?.state == .running { + view.emptyImage.image = UIImage(named: "networkInProgress")?.image(color: .gray, size: UIScreen.main.bounds.width) + view.emptyTitle.text = NSLocalizedString("_request_in_progress_", comment: "") + view.emptyDescription.text = "" + } else { + view.emptyImage.image = utility.loadImage(named: "bell", colors: [.gray], size: UIScreen.main.bounds.width) + view.emptyTitle.text = NSLocalizedString("_no_notification_", comment: "") + view.emptyDescription.text = "" + } + } + // MARK: - Table override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + emptyDataSet?.numberOfItemsInSection(notifications.count, section: section) return notifications.count } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + let notification = notifications[indexPath.row] + + if notification.app == "files_sharing" { + NCActionCenter.shared.viewerFile(account: session.account, fileId: notification.objectId, viewController: self) + } else { + NCApplicationHandle().didSelectNotification(notification, viewController: self) + } guard let notification = NCApplicationHandle().didSelectNotification(notifications[indexPath.row], viewController: self) else { return } do { @@ -120,69 +166,42 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { } if let image = image { - cell.icon.image = image.withTintColor(NCBrandColor.shared.getElement(account: session.account), renderingMode: .alwaysOriginal) + cell.icon.image = image.withTintColor(NCBrandColor.shared.iconColor, renderingMode: .alwaysOriginal) + } else { + cell.icon.image = utility.loadImage(named: "bell", colors: [NCBrandColor.shared.iconColor]) } // Avatar cell.avatar.isHidden = true cell.avatarLeadingMargin.constant = 10 - if let subjectRichParameters = notification.subjectRichParameters, - let json = JSON(subjectRichParameters).dictionary, - let user = json["user"]?["id"].stringValue { - cell.avatar.isHidden = false - cell.avatarLeadingMargin.constant = 50 - - let fileName = NCSession.shared.getFileName(urlBase: session.urlBase, user: user) - let results = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) - - if results.image == nil { - cell.fileAvatarImageView?.image = utility.loadUserImage(for: user, displayName: json["user"]?["name"].string, urlBase: session.urlBase) - } else { - cell.fileAvatarImageView?.image = results.image - } - - if !(results.tblAvatar?.loaded ?? false), - NCNetworking.shared.downloadAvatarQueue.operations.filter({ ($0 as? NCOperationDownloadAvatar)?.fileName == fileName }).isEmpty { - NCNetworking.shared.downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, account: session.account, view: tableView)) - } - } - cell.date.text = DateFormatter.localizedString(from: notification.date as Date, dateStyle: .medium, timeStyle: .medium) cell.notification = notification - cell.date.text = utility.getRelativeDateTitle(notification.date as Date) - cell.date.textColor = NCBrandColor.shared.iconImageColor2 + cell.date.text = utility.dateDiff(notification.date as Date) + cell.date.textColor = .gray cell.subject.text = notification.subject cell.subject.textColor = NCBrandColor.shared.textColor cell.message.text = notification.message.replacingOccurrences(of: "
", with: "\n") - cell.message.textColor = NCBrandColor.shared.textColor2 + cell.message.textColor = .gray - cell.remove.setImage(utility.loadImage(named: "xmark", colors: [NCBrandColor.shared.iconImageColor]), for: .normal) + cell.remove.setImage(UIImage(named: "xmark")!.image(color: .gray, size: 20), for: .normal) cell.primary.isEnabled = false cell.primary.isHidden = true cell.primary.titleLabel?.font = .systemFont(ofSize: 15) - cell.primary.layer.cornerRadius = 15 - cell.primary.layer.masksToBounds = true - cell.primary.layer.backgroundColor = NCBrandColor.shared.getElement(account: session.account).cgColor cell.primary.setTitleColor(.white, for: .normal) - - cell.more.isEnabled = false - cell.more.isHidden = true - cell.more.titleLabel?.font = .systemFont(ofSize: 15) - cell.more.layer.cornerRadius = 15 - cell.more.layer.masksToBounds = true - cell.more.layer.backgroundColor = NCBrandColor.shared.getElement(account: session.account).cgColor - cell.more.setTitleColor(.white, for: .normal) + cell.primary.layer.cornerRadius = 10 + cell.primary.layer.masksToBounds = true + cell.primary.layer.backgroundColor = NCBrandColor.shared.notificationAction.cgColor cell.secondary.isEnabled = false cell.secondary.isHidden = true cell.secondary.titleLabel?.font = .systemFont(ofSize: 15) - cell.secondary.layer.cornerRadius = 15 + cell.secondary.layer.cornerRadius = 10 cell.secondary.layer.masksToBounds = true cell.secondary.layer.borderWidth = 1 - cell.secondary.layer.borderColor = NCBrandColor.shared.iconImageColor2.cgColor - cell.secondary.layer.backgroundColor = UIColor.secondarySystemBackground.cgColor - cell.secondary.setTitleColor(NCBrandColor.shared.iconImageColor2, for: .normal) + cell.secondary.layer.borderColor = NCBrandColor.shared.notificationAction.cgColor + cell.secondary.layer.backgroundColor = UIColor.clear.cgColor + cell.secondary.setTitleColor(NCBrandColor.shared.notificationAction, for: .normal) // Action if let actions = notification.actions, @@ -213,11 +232,17 @@ class NCNotification: UITableViewController, NCNotificationCellDelegate { cell.secondary.setTitle(label, for: .normal) } } - } else if jsonActions.count >= 3 { + } + + let widthPrimary = cell.primary.intrinsicContentSize.width + 48; + let widthSecondary = cell.secondary.intrinsicContentSize.width + 48; - cell.more.isEnabled = true - cell.more.isHidden = false - cell.more.setTitle("…", for: .normal) + if widthPrimary > widthSecondary { + cell.primaryWidth.constant = widthPrimary + cell.secondaryWidth.constant = widthPrimary + } else { + cell.primaryWidth.constant = widthSecondary + cell.secondaryWidth.constant = widthSecondary } var buttonWidth = max(cell.primary.intrinsicContentSize.width, cell.secondary.intrinsicContentSize.width) @@ -327,7 +352,6 @@ class NCNotificationCell: UITableViewCell, NCCellProtocol { @IBOutlet weak var remove: UIButton! @IBOutlet weak var primary: UIButton! @IBOutlet weak var secondary: UIButton! - @IBOutlet weak var more: UIButton! @IBOutlet weak var avatarLeadingMargin: NSLayoutConstraint! @IBOutlet weak var primaryWidth: NSLayoutConstraint! @IBOutlet weak var secondaryWidth: NSLayoutConstraint!