From cffde50c0cb5bdc6e657e496982383ffc6f17992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Sch=C3=BCtz?= Date: Thu, 11 Jun 2020 18:03:48 +0200 Subject: [PATCH] Home notifications & refactor (#267) * Rewrite home to use table view, add notifications * Animate notification dot --- CoEpi.xcodeproj/project.pbxproj | 12 +++ CoEpi/Dependencies.swift | 5 +- CoEpi/common/EnvInfos.swift | 18 ++++ CoEpi/domain/model/Alert.swift | 2 +- CoEpi/domain/model/SymptomInputs.swift | 2 +- CoEpi/domain/model/UnixTime.swift | 2 +- CoEpi/en.lproj/Localizable.strings | 15 ++- CoEpi/generated/Equatable.generated.swift | 30 ++++++ CoEpi/generated/strings.swift | 25 +++-- CoEpi/ui/home/HomeItemCell.swift | 87 +++++++++++++++ CoEpi/ui/home/HomeItemView.xib | 82 ++++++++++++++ CoEpi/ui/home/HomeViewController.swift | 125 +++++++++------------- CoEpi/ui/home/HomeViewController.xib | 66 +++--------- CoEpi/ui/home/HomeViewModel.swift | 93 +++++++++++++++- CoEpi/ui/styles/ButtonStyles.swift | 7 +- 15 files changed, 420 insertions(+), 151 deletions(-) create mode 100644 CoEpi/generated/Equatable.generated.swift create mode 100644 CoEpi/ui/home/HomeItemCell.swift create mode 100644 CoEpi/ui/home/HomeItemView.xib diff --git a/CoEpi.xcodeproj/project.pbxproj b/CoEpi.xcodeproj/project.pbxproj index cab76a1..cd85cf2 100644 --- a/CoEpi.xcodeproj/project.pbxproj +++ b/CoEpi.xcodeproj/project.pbxproj @@ -105,6 +105,9 @@ 8498115D243630E500EBD1DA /* ScannedCensHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8498115C243630E500EBD1DA /* ScannedCensHandler.swift */; }; 849B3F7D247F00F7003CCE4B /* SymptomView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849B3F7C247F00F7003CCE4B /* SymptomView.xib */; }; 849C6A46249253790047BC10 /* AutoEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C6A45249253790047BC10 /* AutoEquatable.swift */; }; + 849C6A5224925B5C0047BC10 /* Equatable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C6A5124925B5C0047BC10 /* Equatable.generated.swift */; }; + 849C6A55249260A00047BC10 /* HomeItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C6A53249260A00047BC10 /* HomeItemCell.swift */; }; + 849C6A5A24927C340047BC10 /* HomeItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849C6A5924927C340047BC10 /* HomeItemView.xib */; }; 849CEFA3242FEE23003F9284 /* UIColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D41B0D16242F5A2A00A50447 /* UIColor+Hex.swift */; }; 849D1AFC2439D7B300804511 /* RxSwiftExt in Frameworks */ = {isa = PBXBuildFile; productRef = 849D1AFB2439D7B300804511 /* RxSwiftExt */; }; 849D1AFF2439D90C00804511 /* Completable+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D1AFE2439D90C00804511 /* Completable+Convenience.swift */; }; @@ -329,6 +332,9 @@ 8498115C243630E500EBD1DA /* ScannedCensHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannedCensHandler.swift; sourceTree = ""; }; 849B3F7C247F00F7003CCE4B /* SymptomView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SymptomView.xib; sourceTree = ""; }; 849C6A45249253790047BC10 /* AutoEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoEquatable.swift; sourceTree = ""; }; + 849C6A5124925B5C0047BC10 /* Equatable.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Equatable.generated.swift; sourceTree = ""; }; + 849C6A53249260A00047BC10 /* HomeItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeItemCell.swift; sourceTree = ""; }; + 849C6A5924927C340047BC10 /* HomeItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeItemView.xib; sourceTree = ""; }; 849D1AFE2439D90C00804511 /* Completable+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Completable+Convenience.swift"; sourceTree = ""; }; 849D1B072439EAD200804511 /* UINotifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UINotifier.swift; sourceTree = ""; }; 849D1B092439F31D00804511 /* DaoError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaoError.swift; sourceTree = ""; }; @@ -481,6 +487,7 @@ 054A34FA244E68F700282E17 /* generated */ = { isa = PBXGroup; children = ( + 849C6A5124925B5C0047BC10 /* Equatable.generated.swift */, 054A34FB244E691500282E17 /* strings.swift */, ); path = generated; @@ -1040,6 +1047,8 @@ 84FA00FE2427ED5E00318104 /* HomeViewController.swift */, 84FA00FF2427ED5E00318104 /* HomeViewController.xib */, 8486BDDF242BC8B600A63DD5 /* HomeViewModel.swift */, + 849C6A53249260A00047BC10 /* HomeItemCell.swift */, + 849C6A5924927C340047BC10 /* HomeItemView.xib */, ); path = home; sourceTree = ""; @@ -1266,6 +1275,7 @@ 849D7B3A248E8E8600C9DDBD /* LogsViewController.xib in Resources */, 622AC2F4245FD34300A6EB90 /* FeverTempViewController.xib in Resources */, 84DE6A24243F99A200CCF4A6 /* AlertsViewController.xib in Resources */, + 849C6A5A24927C340047BC10 /* HomeItemView.xib in Resources */, 30EF957A24379E01007D61C7 /* CoEpiLaunchScreen.storyboard in Resources */, 848434572438C046006A5839 /* ErrorView.xib in Resources */, 622AC2CC245FC5E000A6EB90 /* CoughTypeViewController.xib in Resources */, @@ -1419,6 +1429,7 @@ 849DFE702427D3750008ED65 /* CoEpi.xcdatamodeld in Sources */, 30EF94FB242CFC9F007D61C7 /* AlertsViewController.swift in Sources */, 8486BDD9242BBCE700A63DD5 /* DebugBleViewController.swift in Sources */, + 849C6A55249260A00047BC10 /* HomeItemCell.swift in Sources */, 622AC2D4245FC65A00A6EB90 /* CoughDaysViewController.swift in Sources */, 84D069672455E6E40004E8B6 /* DateFormatters.swift in Sources */, 849C6A46249253790047BC10 /* AutoEquatable.swift in Sources */, @@ -1438,6 +1449,7 @@ 84FA00F22427E13000318104 /* Dependencies.swift in Sources */, 841D39A0244E3EE200A9CD64 /* StartPermissions.swift in Sources */, 84B48A9F24530AAB001908E8 /* RootNav.swift in Sources */, + 849C6A5224925B5C0047BC10 /* Equatable.generated.swift in Sources */, 848434642438C71F006A5839 /* UIViewController+Rx.swift in Sources */, 849327B42452D92C009589E6 /* FetchAlertsBackgroundRegisterer.swift in Sources */, 8420169A244CA1AC000EE281 /* ActivityIndicatorView.swift in Sources */, diff --git a/CoEpi/Dependencies.swift b/CoEpi/Dependencies.swift index e7d422b..d91712c 100644 --- a/CoEpi/Dependencies.swift +++ b/CoEpi/Dependencies.swift @@ -48,7 +48,10 @@ class Dependencies { } private func registerViewModels(container: DependencyContainer) { - container.register { HomeViewModel(startPermissions: try container.resolve(), rootNav: try container.resolve()) } + container.register { HomeViewModel(startPermissions: try container.resolve(), + rootNav: try container.resolve(), + alertRepo: try container.resolve(), + envInfos: try container.resolve()) } container.register { OnboardingViewModel() } container.register { ThankYouViewModel(rootNav: try container.resolve()) } diff --git a/CoEpi/common/EnvInfos.swift b/CoEpi/common/EnvInfos.swift index b99ae80..f4d9a9d 100644 --- a/CoEpi/common/EnvInfos.swift +++ b/CoEpi/common/EnvInfos.swift @@ -18,3 +18,21 @@ class EnvInfos { UIDevice.current.systemVersion } } +// +//private func getVersionNumber() -> String{ +// +// guard let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String +// else{ +// fatalError("Failed to read bundle version") +// } +// print("Version : \(version)"); +// return "\(L10n.Ux.Home.Footer.version): \(version)" +//} +// +//private func getBuildNumber() -> String { +// guard let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else { +// fatalError("Failed to read build number") +// } +// print("Build : \(build)") +// return "\(L10n.Ux.Home.Footer.build): \(build)" +//} diff --git a/CoEpi/domain/model/Alert.swift b/CoEpi/domain/model/Alert.swift index 3f1ac17..1f9e876 100644 --- a/CoEpi/domain/model/Alert.swift +++ b/CoEpi/domain/model/Alert.swift @@ -1,4 +1,4 @@ -struct Alert { +struct Alert: AutoEquatable { let id: String let contactTime: UnixTime let reportTime: UnixTime diff --git a/CoEpi/domain/model/SymptomInputs.swift b/CoEpi/domain/model/SymptomInputs.swift index b65d479..6c7318a 100644 --- a/CoEpi/domain/model/SymptomInputs.swift +++ b/CoEpi/domain/model/SymptomInputs.swift @@ -52,7 +52,7 @@ struct SymptomInputs { } -enum UserInput { +enum UserInput: AutoEquatable { case none, some(_ value: T) func map(f: (T) -> U) -> UserInput { diff --git a/CoEpi/domain/model/UnixTime.swift b/CoEpi/domain/model/UnixTime.swift index 8419829..fd3dd19 100644 --- a/CoEpi/domain/model/UnixTime.swift +++ b/CoEpi/domain/model/UnixTime.swift @@ -1,6 +1,6 @@ import Foundation -struct UnixTime: Codable { +struct UnixTime: Codable, AutoEquatable { let value: Int64 static func minTimestamp() -> UnixTime { diff --git a/CoEpi/en.lproj/Localizable.strings b/CoEpi/en.lproj/Localizable.strings index 49eeb69..cb8b8c6 100644 --- a/CoEpi/en.lproj/Localizable.strings +++ b/CoEpi/en.lproj/Localizable.strings @@ -11,9 +11,6 @@ "alerts.subtitle" = "Click each alert to learn more"; "alerts.buttonLabel" = "What are exposure alerts?"; "alerts.moreInfoTitle" = "Exposure Alert"; -"alerts.count.none" = "No new exposure alerts"; -"alerts.count.one" = "1 new exposure alert"; -"alerts.count.some" = "%d new exposure alerts"; "alerts.title" = "Alerts"; "alerts.label.no_symptoms_reported" = "No symptoms reported"; "alerts.label.fever.mild" = "Mild Fever"; @@ -119,16 +116,18 @@ //homescreen "UX.home.title" = "CoEpi"; -"UX.home.report1" = " Symptom Reporting\n\n"; -"UX.home.report2" = " Share how you are feeling"; -"UX.home.alerts1" = " Exposure Alerts\n\n"; -"UX.home.alerts2" = " Review your potential exposures"; -"UX.home.detected" = " new exposures detected\n\n"; +"UX.home.report1" = "Symptom Reporting"; +"UX.home.report2" = "Share how you are feeling"; +"UX.home.alerts1" = "Exposure Alerts"; +"UX.home.alerts2" = "Review your potential exposures"; +"UX.home.detected" = "new exposures detected"; "UX.home.how" = "How is my data being used?"; "UX.home.share" = "Share"; "UX.home.footer.build" = "Build"; "UX.home.footer.debug" = "Debug"; "UX.home.footer.version" = "Version"; +"home.items.alerts.notification.one" = "1 new exposure alert"; +"home.items.alerts.notification.some" = "%d new exposure alerts"; // symptom days "UX.symptomsdays.heading" = "Follow up: Earliest Symptoms"; diff --git a/CoEpi/generated/Equatable.generated.swift b/CoEpi/generated/Equatable.generated.swift new file mode 100644 index 0000000..a4b5b53 --- /dev/null +++ b/CoEpi/generated/Equatable.generated.swift @@ -0,0 +1,30 @@ +// Generated using Sourcery 0.18.0 — https://github.com/krzysztofzablocki/Sourcery +// DO NOT EDIT + + +// MARK: Alert Equatable +extension Alert: Equatable { + static func ==(lhs: Alert, rhs: Alert) -> Bool { + guard lhs.id == rhs.id else { return false } + guard lhs.contactTime == rhs.contactTime else { return false } + guard lhs.reportTime == rhs.reportTime else { return false } + guard lhs.earliestSymptomTime == rhs.earliestSymptomTime else { return false } + guard lhs.feverSeverity == rhs.feverSeverity else { return false } + guard lhs.coughSeverity == rhs.coughSeverity else { return false } + guard lhs.breathlessness == rhs.breathlessness else { return false } + return true + } +} +// MARK: UnixTime Equatable +extension UnixTime: Equatable { + static func ==(lhs: UnixTime, rhs: UnixTime) -> Bool { + guard lhs.value == rhs.value else { return false } + return true + } +} +// MARK: UserInput Equatable +extension UserInput: Equatable { + static func ==(lhs: UserInput, rhs: UserInput) -> Bool { + return true + } +} diff --git a/CoEpi/generated/strings.swift b/CoEpi/generated/strings.swift index 7ea79cd..9a1f170 100644 --- a/CoEpi/generated/strings.swift +++ b/CoEpi/generated/strings.swift @@ -211,14 +211,12 @@ internal enum L10n { /// Alerts internal static let title = L10n.tr("Localizable", "alerts.title") internal enum Count { - /// No new exposure alerts + /// No new contact alerts internal static let `none` = L10n.tr("Localizable", "alerts.count.none") - /// 1 new exposure alert + /// Click the alert to learn more internal static let one = L10n.tr("Localizable", "alerts.count.one") - /// %d new exposure alerts - internal static func some(_ p1: Int) -> String { - return L10n.tr("Localizable", "alerts.count.some", p1) - } + /// Click each alert to learn more + internal static let some = L10n.tr("Localizable", "alerts.count.some") } internal enum Details { /// Exposure Alert @@ -260,6 +258,21 @@ internal enum L10n { } } + internal enum Home { + internal enum Items { + internal enum Alerts { + internal enum Notification { + /// 1 new exposure alert + internal static let one = L10n.tr("Localizable", "home.items.alerts.notification.one") + /// %d new exposure alerts + internal static func some(_ p1: Int) -> String { + return L10n.tr("Localizable", "home.items.alerts.notification.some", p1) + } + } + } + } + } + internal enum WhatExposure { ///

Exposure alerts indicate that you have been in close proximity (e.g. within several feet) to someone with symptoms also using a compatible app.

How does CoEpi work?

CoEpi generates exposure alerts if your device has flagged another tracing app user who may have been infectious during your contact.

Based on the symptom report in the exposure alert, you can:

• Monitor yourself for symptoms in the days following the potential exposure

• Self-isolate within your household to reduce the risk of transmitting to others

• Talk with a healthcare provider about your exposure internal static let htmlBody = L10n.tr("Localizable", "whatExposure.html_body") diff --git a/CoEpi/ui/home/HomeItemCell.swift b/CoEpi/ui/home/HomeItemCell.swift new file mode 100644 index 0000000..361ba36 --- /dev/null +++ b/CoEpi/ui/home/HomeItemCell.swift @@ -0,0 +1,87 @@ +import UIKit + +class HomeItemCell: UITableViewCell { + + private var homeItemView: HomeItemView? + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupUI() + + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + backgroundColor = .clear + contentView.backgroundColor = .clear + + selectionStyle = .none + + let view: HomeItemView = HomeItemView.fromNib() + contentView.addSubview(view) + view.pinAllEdgesToParent() + self.homeItemView = view + } + + public func setup(viewData: HomeItemViewData) { + homeItemView?.setup(item: viewData) + } +} + + +class HomeItemView: UIView { + public var onAcknowledged: (() ->())? + + @IBOutlet weak var notificationView: UIView! + @IBOutlet weak var notificationLabel: UILabel! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var descrLabel: UILabel! + @IBOutlet weak var backgroundView: UIView! + + override func awakeFromNib() { + super.awakeFromNib() + notificationView.layer.cornerRadius = 20 + + backgroundView.layer.cornerRadius = 15 + backgroundView.layer.shadowColor = UIColor.black.cgColor + backgroundView.layer.shadowOffset = CGSize(width: 4, height: 4) + backgroundView.layer.shadowRadius = 4 + backgroundView.layer.shadowOpacity = 0.25 + } + + func setup(item: HomeItemViewData) { + notificationView.isHidden = item.notification == nil + notificationLabel.isHidden = item.notification == nil + notificationLabel.text = item.notification?.text + titleLabel.text = item.title + descrLabel.text = item.descr + + if item.notification != nil { + notificationView.transform = CGAffineTransform(scaleX: 0, y: 0) + notificationLabel.alpha = 0 + UIView.animate(withDuration: 0.5, delay: 0.5, usingSpringWithDamping: + 0.6, initialSpringVelocity: 0.6, options: .curveEaseOut, animations: { + self.notificationView.transform = CGAffineTransform(scaleX: 1, y: 1) + self.notificationLabel.alpha = 1 + }, completion: nil) + } + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + backgroundView.backgroundColor = .coEpiLightGray + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + backgroundView.backgroundColor = .white + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + backgroundView.backgroundColor = .white + } +} diff --git a/CoEpi/ui/home/HomeItemView.xib b/CoEpi/ui/home/HomeItemView.xib new file mode 100644 index 0000000..6e3789d --- /dev/null +++ b/CoEpi/ui/home/HomeItemView.xib @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CoEpi/ui/home/HomeViewController.swift b/CoEpi/ui/home/HomeViewController.swift index 1428545..95a15df 100644 --- a/CoEpi/ui/home/HomeViewController.swift +++ b/CoEpi/ui/home/HomeViewController.swift @@ -1,23 +1,18 @@ import UIKit import SafariServices +import RxSwift +import RxCocoa class HomeViewController: UIViewController { private let viewModel: HomeViewModel - - @IBOutlet weak var redCircle: UIImageView! - - @IBOutlet weak var reportButtonLabel: UIButton! - @IBOutlet weak var alertButtonLabel: UIButton! + @IBOutlet weak var howDataUsedLabel: UIButton! - - @IBAction func reportButtonAction(_ sender: Any) { - viewModel.quizTapped() - } - - @IBAction func alertButtonAction(_ sender: Any) { - viewModel.seeAlertsTapped() - } - + + @IBOutlet weak var tableView: UITableView! + private var dataSource = HomeItemsDataSource() + + private let disposeBag = DisposeBag() + @IBAction func howDataUsedButton(_ sender: Any) { if let url = URL(string: "https://www.coepi.org/privacy/") { let config = SFSafariViewController.Configuration() @@ -30,9 +25,7 @@ class HomeViewController: UIViewController { @IBOutlet weak var versionLabel: UILabel! @IBOutlet weak var buildLabel: UILabel! - @IBOutlet weak var debugButton: UIButton! - - + init(viewModel: HomeViewModel) { self.viewModel = viewModel super.init(nibName: String(describing: Self.self), bundle: nil) @@ -55,65 +48,51 @@ class HomeViewController: UIViewController { super.viewDidLoad() view.backgroundColor = UIColor(patternImage: #imageLiteral(resourceName: "Background_purple")) - - let paragraphStyle = NSMutableParagraphStyle() - - paragraphStyle.lineHeightMultiple = 1.07 - - let share = UIBarButtonItem(title: L10n.Ux.Home.share, style: .plain, target: self, action: #selector(share(sender:))) - share.tintColor = UIColor.black - navigationItem.rightBarButtonItem = share - - //setup button labels - - ButtonStyles.applyHomeCard(to: reportButtonLabel) - ButtonStyles.applyHomeCard(to: alertButtonLabel) - + howDataUsedLabel.setTitle(L10n.Ux.Home.how, for: .normal) - - let attributedTextReport = NSMutableAttributedString(string: L10n.Ux.Home.report1, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 24)]) - attributedTextReport.append(NSMutableAttributedString(string: L10n.Ux.Home.report2, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20)])) - reportButtonLabel.setAttributedTitle(attributedTextReport, for: .normal) - - let tempNumbnerOfNotifications = 0 //temp for testing will need to be replaced with checking if new alerts are available - - if tempNumbnerOfNotifications == 0{ - redCircle.isHidden = true - let attributedTextAlert = NSMutableAttributedString(string: L10n.Ux.Home.alerts1, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 24)]) - attributedTextAlert.append(NSMutableAttributedString(string: L10n.Ux.Home.alerts2, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20)])) - alertButtonLabel.setAttributedTitle(attributedTextAlert, for: .normal) - } - else{ - redCircle.isHidden = false - let attributedTextAlert = NSMutableAttributedString(string: L10n.Ux.Home.detected, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 20), .foregroundColor: UIColor.red]) - attributedTextAlert.append(NSMutableAttributedString(string: L10n.Ux.Home.alerts1, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 24)])) - - attributedTextAlert.append(NSMutableAttributedString(string: L10n.Ux.Home.alerts2, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20)])) - - alertButtonLabel.setAttributedTitle(attributedTextAlert, for: .normal) - } - - //debug - versionLabel.text = getVersionNumber() - buildLabel.text = getBuildNumber() - debugButton.setTitle(L10n.Ux.Home.Footer.debug, for: .normal) + + tableView.rowHeight = UITableView.automaticDimension + tableView.estimatedRowHeight = 200 + + versionLabel.text = viewModel.versionNameString + buildLabel.text = viewModel.buildString + + tableView.register(cellClass: HomeItemCell.self) + + viewModel.items + .drive(tableView.rx.items(dataSource: dataSource)) + .disposed(by: disposeBag) } - - private func getVersionNumber() -> String{ - - guard let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - else{ - fatalError("Failed to read bundle version") +} + +private class HomeItemsDataSource: NSObject, RxTableViewDataSourceType { + private(set) var items: [HomeItemViewData] = [] + + func tableView(_ tableView: UITableView, observedEvent: RxSwift.Event<[HomeItemViewData]>) { + if case let .next(items) = observedEvent { + self.items = items + tableView.reloadData() } - print("Version : \(version)"); - return "\(L10n.Ux.Home.Footer.version): \(version)" } - - private func getBuildNumber() -> String { - guard let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String else { - fatalError("Failed to read build number") - } - print("Build : \(build)") - return "\(L10n.Ux.Home.Footer.build): \(build)" +} + +extension HomeItemsDataSource: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + items.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell: HomeItemCell = tableView.dequeue(cellClass: HomeItemCell.self, forIndexPath: indexPath) + cell.setup(viewData: items[indexPath.row]) + return cell + } +} + + +extension HomeViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + viewModel.onClick(item: dataSource.items[indexPath.row]) } } diff --git a/CoEpi/ui/home/HomeViewController.xib b/CoEpi/ui/home/HomeViewController.xib index 361fbce..e478690 100644 --- a/CoEpi/ui/home/HomeViewController.xib +++ b/CoEpi/ui/home/HomeViewController.xib @@ -9,12 +9,9 @@ - - - - + @@ -65,39 +62,8 @@ - - - - - - - - - + + + + + + + + + - - - - - + - - - - - + + + - - - + - diff --git a/CoEpi/ui/home/HomeViewModel.swift b/CoEpi/ui/home/HomeViewModel.swift index f995c16..020d7f1 100644 --- a/CoEpi/ui/home/HomeViewModel.swift +++ b/CoEpi/ui/home/HomeViewModel.swift @@ -1,20 +1,75 @@ import RxSwift +import RxCocoa + +enum HomeItemId { + case reportSymptoms, alerts +} + +struct HomeItemNotification { + let text: String +} + +struct HomeItemViewData { + let id: HomeItemId + let title: String + let descr: String + var notification: HomeItemNotification? = nil +} class HomeViewModel { + private let rootNav: RootNav + private let alertRepo: AlertRepo + private let envInfos: EnvInfos + let title = L10n.Ux.Home.title - private let disposeBag = DisposeBag() + private let itemSelectTrigger = PublishRelay() - private let rootNav: RootNav + private static let items = [ + HomeItemViewData( + id: .reportSymptoms, + title: L10n.Ux.Home.report1, + descr: L10n.Ux.Home.report2 + ), + HomeItemViewData( + id: .alerts, + title: L10n.Ux.Home.alerts1, + descr: L10n.Ux.Home.alerts2 + ) + ] + + lazy var items: Driver<[HomeItemViewData]> = alertRepo.alerts + .startWith([]) + .distinctUntilChanged() + .map { alerts in + Self.items.updateNotifications(with: alerts) + } + .observeOn(MainScheduler.instance) + .asDriver(onErrorJustReturn: []) + + lazy var versionNameString = L10n.Ux.Home.Footer.version + ": " + envInfos.appVersionName + lazy var buildString = L10n.Ux.Home.Footer.build + ": " + envInfos.appVersionCode - init(startPermissions: StartPermissions, rootNav: RootNav) { + private let disposeBag = DisposeBag() + + init(startPermissions: StartPermissions, rootNav: RootNav, alertRepo: AlertRepo, envInfos: EnvInfos) { self.rootNav = rootNav + self.alertRepo = alertRepo + self.envInfos = envInfos startPermissions.granted.subscribe(onNext: { granted in log.d("Start permissions granted: \(granted)") }).disposed(by: disposeBag) startPermissions.request() + + itemSelectTrigger + .subscribe(onNext: { item in + switch item.id { + case .reportSymptoms: rootNav.navigate(command: .to(destination: .symptomReport)) + case .alerts: rootNav.navigate(command: .to(destination: .alerts)) + } + }).disposed(by: disposeBag) } func debugTapped() { @@ -22,11 +77,41 @@ class HomeViewModel { } func quizTapped() { -// rootNav.navigate(command: .to(destination: .quiz)) rootNav.navigate(command: .to(destination: .symptomReport)) } func seeAlertsTapped() { rootNav.navigate(command: .to(destination: .alerts)) } + + func onClick(item: HomeItemViewData) { + itemSelectTrigger.accept(item) + } +} + +private extension Array where Element == HomeItemViewData { + + func updateNotifications(with alerts: [Alert]) -> [HomeItemViewData] { + map { item in + if (item.id == .alerts) { + if alerts.isEmpty { + return item + } else { + var item = item + item.notification = HomeItemNotification(text: alertsNotificationTitle(alertsCount: alerts.count)) + return item + } + } else { + return item + } + } + } +} + +private func alertsNotificationTitle(alertsCount: Int) -> String { + if alertsCount == 1 { + return L10n.Home.Items.Alerts.Notification.one + } else { + return L10n.Home.Items.Alerts.Notification.some(alertsCount) + } } diff --git a/CoEpi/ui/styles/ButtonStyles.swift b/CoEpi/ui/styles/ButtonStyles.swift index 5499450..b8ae42d 100644 --- a/CoEpi/ui/styles/ButtonStyles.swift +++ b/CoEpi/ui/styles/ButtonStyles.swift @@ -4,12 +4,7 @@ import UIKit struct ButtonStyles{ private init(){} - - static func applyHomeCard(to button: UIButton){ - button.layer.cornerRadius = 15 - applyShadows(to: button) - } - + static func applySelected(to button : UIButton){ button.backgroundColor = .coEpiPurple button.setTitleColor(.white, for:.normal)