Skip to content

Commit

Permalink
Merge pull request #17 from htmlprogrammist/develop
Browse files Browse the repository at this point in the history
Release 2.3
  • Loading branch information
htmlprogrammist authored Jul 6, 2022
2 parents 607570e + 59accfc commit 887a646
Show file tree
Hide file tree
Showing 67 changed files with 1,580 additions and 293 deletions.
259 changes: 228 additions & 31 deletions Agenda.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

This file was deleted.

Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,27 @@
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>PlaygroundChart (Playground) 1.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>PlaygroundChart (Playground) 2.xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>PlaygroundChart (Playground).xcscheme</key>
<dict>
<key>isShown</key>
<false/>
<key>orderHint</key>
<integer>1</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
Expand Down
10 changes: 10 additions & 0 deletions Agenda.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Agenda.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Binary file not shown.
25 changes: 15 additions & 10 deletions Agenda/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,36 @@
import UIKit

final class AppCoordinator {

/// Service is used for using Core Data in the project
public let coreDataManager = CoreDataManager(containerName: "Agenda")
/// View controllers to set in the tab bar controller
public var viewControllers = [UIViewController]()

/// `UIWindow` of the application, provided from the SceneDelegate
private let window: UIWindow
/// Root view controller of the application
private let tabBarController = UITabBarController()

init(window: UIWindow) {
self.window = window
}

/// This method setup tab bar controller with 3 modules and set root view controller for the `UIWindow`
func start() {
setupAgenda()
setupHistory()
setupSummary()

/// Setup tab bar appearence like in earlier versions of iOS, because in iOS 15 it does not look good
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .systemBackground
tabBarController.tabBar.standardAppearance = appearance
tabBarController.tabBar.scrollEdgeAppearance = appearance
}
tabBarController.setViewControllers(viewControllers, animated: false)

window.rootViewController = tabBarController
window.makeKeyAndVisible()
}
Expand All @@ -37,7 +50,6 @@ private extension AppCoordinator {

let agendaViewController = createNavController(viewController: container.viewController, itemName: Labels.goals, itemImage: Icons.calendar)
viewControllers.append(agendaViewController)
subscribeToCoreDataManager(vc: container.viewController)
}

func setupHistory() {
Expand All @@ -46,27 +58,20 @@ private extension AppCoordinator {

let historyViewController = createNavController(viewController: container.viewController, itemName: Labels.History.title, itemImage: Icons.history)
viewControllers.append(historyViewController)
subscribeToCoreDataManager(vc: container.viewController)
}

func setupSummary() {
let context = SummaryContext(moduleOutput: nil, moduleDependency: coreDataManager)
let container = SummaryContainer.assemble(with: context)
let summaryViewController = createNavController(viewController: container.viewController, itemName: Labels.Summary.title, itemImage: Icons.summary)
viewControllers.append(summaryViewController)
subscribeToCoreDataManager(vc: container.viewController)
}

/// Creates navigation controller and set tab bar item to it
func createNavController(viewController: UIViewController, itemName: String, itemImage: UIImage) -> UINavigationController {

let navController = UINavigationController(rootViewController: viewController)
navController.tabBarItem = UITabBarItem(title: itemName, image: itemImage, tag: 0)
navController.navigationBar.prefersLargeTitles = true
return navController
}

func subscribeToCoreDataManager(vc: UIViewController) {
guard let vc = vc as? CoreDataManagerDelegate else { return }
coreDataManager.viewControllers.append(vc)
}
}
1 change: 1 addition & 0 deletions Agenda/Extensions/Date.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation

extension Date {
/// Format date with format `dd.MM.yyyy` to the provided template with the first capitalized letter
func formatTo(_ template: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd.MM.yyyy"
Expand Down
4 changes: 2 additions & 2 deletions Agenda/Extensions/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import Foundation

extension String {
// For simplify localizing texts in the whole application
/// For simplify localizing texts in the whole application
var localized: String {
NSLocalizedString(self, comment: "")
}

/// Capitalizes first letter of the string
func capitalizingFirstLetter() -> String {
return prefix(1).uppercased() + self.lowercased().dropFirst()
}
Expand Down
1 change: 1 addition & 0 deletions Agenda/Extensions/UIKit/UITextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import UIKit

extension UITextField {
/// Convenience init with a lot of different properties that I need. Althouth, the only place where it is being used is `GoalTableViewCell`, but it helps me not to repeat myself (DRY)
convenience init(keyboardType: UIKeyboardType, placeholder: String, textAlignment: NSTextAlignment = .right, borderStyle: BorderStyle) {
self.init()
self.isHidden = true
Expand Down
8 changes: 7 additions & 1 deletion Agenda/Extensions/UIKit/UIViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import UIKit

extension UIViewController {

/// Shows simple alert with the same style
func alertForError(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
Expand All @@ -17,6 +17,11 @@ extension UIViewController {
present(alert, animated: true, completion: nil)
}

/// Shows action sheet with 2 actions. It is being used in deleting goal or month. Allows you to comply with the DRY principle.
/// - Parameters:
/// - title: Title for the action sheet
/// - message: Description to add more details for the operation
/// - completion: completion block that is being called when user selects _"yes"_. It contains the deletion logic when calling this method
func alertForDeletion(title: String, message: String, completion: @escaping () -> ()) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
let yes = UIAlertAction(title: Labels.yes, style: .destructive, handler: { _ in
Expand All @@ -32,6 +37,7 @@ extension UIViewController {
present(alert, animated: true)
}

/// Allows to hide keyboard when user taps around by using `UITapGestureRecognizer` with action `dismissKeyboard` defined lower
func hideKeyboardWhenTappedAround() {
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
Expand Down
2 changes: 1 addition & 1 deletion Agenda/Modules/AddGoal/AddGoalInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ final class AddGoalInteractor {
weak var output: AddGoalInteractorOutput?

private let coreDataManager: CoreDataManagerProtocol

/// Month in which new goal will be added
public var month: Month!

init(coreDataManager: CoreDataManagerProtocol) {
Expand Down
2 changes: 1 addition & 1 deletion Agenda/Modules/AddGoal/AddGoalViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UIKit
final class AddGoalViewController: GoalViewController {

private let output: AddGoalViewOutput

/// Data for a goal that will be filled from the text fields using delegate
public override var goalData: GoalData {
didSet {
checkBarButtonEnabled()
Expand Down
6 changes: 6 additions & 0 deletions Agenda/Modules/Agenda/AgendaContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import UIKit

/// This module is used as Agenda and MonthDetails (you will encounter this more than once).
/// Agenda is being created in AppCoordinator. MonthDetails is being called from the History module.
final class AgendaContainer {
let input: AgendaModuleInput
let viewController: UIViewController
Expand Down Expand Up @@ -40,6 +42,10 @@ final class AgendaContainer {
}
}

/**
For both modules, you need Core Data manager, but `month` and `moduleOutput` are being provided only in MonthDetails module.
In Agenda you do not need to set these properties: month is fetched from the Core Data manager and it is shown in tab bar controller (no reason for providing output of the module
*/
struct AgendaContext {
typealias ModuleDependency = CoreDataManagerProtocol

Expand Down
2 changes: 1 addition & 1 deletion Agenda/Modules/Agenda/AgendaInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ extension AgendaInteractor: AgendaInteractorInput {

func checkForOnboarding() {
let settings = UserSettings()
if let hasOnboarded = settings.hasOnboarded, !hasOnboarded {
if settings.hasOnboarded == nil {
output?.showOnboarding()
}
}
Expand Down
4 changes: 3 additions & 1 deletion Agenda/Modules/Agenda/AgendaPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ extension AgendaPresenter: AgendaViewOutput {

extension AgendaPresenter: AgendaInteractorOutput {
func monthDidFetch(viewModels: [GoalViewModel], monthInfo: DateViewModel, date: String) {
view?.setMonthData(viewModels: viewModels, monthInfo: monthInfo, title: date)
DispatchQueue.main.async { [unowned self] in
view?.setMonthData(viewModels: viewModels, monthInfo: monthInfo, title: date)
}
}

func dataDidNotFetch() {
Expand Down
35 changes: 18 additions & 17 deletions Agenda/Modules/Agenda/Views/AgendaViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import UIKit
final class AgendaViewController: UIViewController {

private let output: AgendaViewOutput
/// Goal view models of the month
private var viewModels = [GoalViewModel]()

/// Depending on this property, month data is being displayed
Expand Down Expand Up @@ -70,28 +71,30 @@ final class AgendaViewController: UIViewController {

setupView()
setConstraints()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchData()

/**
There is no possibility to update view models in month details, when you change goal details.
So, the only solution we have is to update every time `viewWillAppear` method is being called.
This will not affect negatively on the app's perfomance, because in Agenda this method is called only one time and month details is used by user rarely.
*/
output.fetchData()
NotificationCenter.default.addObserver(self, selector: #selector(fetchData), name: Notification.Name(rawValue: "agendaNotification"), object: nil)
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

output.checkForOnboarding()
}

/// We need to remove observer when the MonthDetails module is being deinited
deinit {
NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: "agendaNotification"), object: nil)
}
}

// MARK: - AgendaViewInput
extension AgendaViewController: AgendaViewInput {
/// Sets month's data with data provided in parameters
/// - Parameters:
/// - viewModels: view model of the goals in month
/// - monthInfo: contains date and value of the progress of the month (to set value in `monthProgressView` (used in Agenda module)
/// - title: title with month's name and year (used in MonthDetails module)
func setMonthData(viewModels: [GoalViewModel], monthInfo: DateViewModel, title: String) {
self.viewModels = viewModels

Expand All @@ -106,13 +109,18 @@ extension AgendaViewController: AgendaViewInput {
tableView.reloadData()
}

/// Shows alert with provided message when something goes wrong
func showAlert(title: String, message: String) {
alertForError(title: title, message: message)
}
}

// MARK: - Helper methods
private extension AgendaViewController {
@objc func fetchData() {
output.fetchData()
}

@objc func addNewGoal() {
output.addNewGoal()
}
Expand Down Expand Up @@ -212,10 +220,3 @@ extension AgendaViewController: UITableViewDelegate, UITableViewDataSource {
}
}
}

// MARK: - CoreDataManagerDelegate
extension AgendaViewController: CoreDataManagerDelegate {
func updateViewModel() {
output.fetchData()
}
}
41 changes: 41 additions & 0 deletions Agenda/Modules/Charts/ChartsContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// ChartsContainer.swift
// Agenda
//
// Created by Егор Бадмаев on 03.07.2022.
//

import UIKit

final class ChartsContainer {
let input: ChartsModuleInput
let viewController: UIViewController
private(set) weak var router: ChartsRouterInput!

static func assemble(with context: ChartsContext) -> ChartsContainer {
let router = ChartsRouter()
let interactor = ChartsInteractor(months: context.months)
let presenter = ChartsPresenter(router: router, interactor: interactor)
let viewController = ChartsViewController(output: presenter)

presenter.view = viewController
presenter.moduleOutput = context.moduleOutput

interactor.output = presenter
viewController.summary = context.data

return ChartsContainer(view: viewController, input: presenter, router: router)
}

private init(view: UIViewController, input: ChartsModuleInput, router: ChartsRouterInput) {
self.viewController = view
self.input = input
self.router = router
}
}

struct ChartsContext {
weak var moduleOutput: ChartsModuleOutput?
var data: Summary
var months: [Month]
}
Loading

0 comments on commit 887a646

Please sign in to comment.