Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
82B6E0630247A13D8D0E4307 /* Pods_FinanceAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 566CD2116F7311F978A11179 /* Pods_FinanceAppTests.framework */; };
88C5FF2129BAD0590053F63E /* NetworkServiceError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88C5FF2029BAD0590053F63E /* NetworkServiceError.swift */; };
93FE75D367A7C9491254C0C7 /* Pods_FinanceApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8FAC3BBBE235ADA9693D0DFD /* Pods_FinanceApp.framework */; };
98426FAA27A6EC8700B09645 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98426FA927A6EC8700B09645 /* TabBarController.swift */; };
98426FAC27A6FC8000B09645 /* UserProfileHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98426FAB27A6FC8000B09645 /* UserProfileHeaderView.swift */; };
Expand Down Expand Up @@ -68,6 +69,7 @@
15252E9092F56A7C7576698E /* Pods-FinanceAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FinanceAppTests.release.xcconfig"; path = "Target Support Files/Pods-FinanceAppTests/Pods-FinanceAppTests.release.xcconfig"; sourceTree = "<group>"; };
3534E844457A0F6B0A6158FC /* Pods-FinanceApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FinanceApp.debug.xcconfig"; path = "Target Support Files/Pods-FinanceApp/Pods-FinanceApp.debug.xcconfig"; sourceTree = "<group>"; };
566CD2116F7311F978A11179 /* Pods_FinanceAppTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FinanceAppTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
88C5FF2029BAD0590053F63E /* NetworkServiceError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkServiceError.swift; sourceTree = "<group>"; };
8FAC3BBBE235ADA9693D0DFD /* Pods_FinanceApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FinanceApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
98426FA927A6EC8700B09645 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = "<group>"; };
98426FAB27A6FC8000B09645 /* UserProfileHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileHeaderView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -250,6 +252,7 @@
isa = PBXGroup;
children = (
98584AC2277E42E80028DBEA /* FinanceService.swift */,
88C5FF2029BAD0590053F63E /* NetworkServiceError.swift */,
98584AC1277E42CB0028DBEA /* Entities */,
);
path = Service;
Expand Down Expand Up @@ -602,6 +605,7 @@
98584AF3277E50430028DBEA /* ConfirmationView.swift in Sources */,
98D786222909BE8C0076214A /* UITableViewCell+Extensions.swift in Sources */,
98584AEE277E50430028DBEA /* UserProfileViewController.swift in Sources */,
88C5FF2129BAD0590053F63E /* NetworkServiceError.swift in Sources */,
98584A6D277E32C30028DBEA /* AppDelegate.swift in Sources */,
98584A6F277E32C30028DBEA /* SceneDelegate.swift in Sources */,
98426FAC27A6FC8000B09645 /* UserProfileHeaderView.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,64 @@ import Foundation
import UIKit

protocol ActivityDetailsViewDelegate: AnyObject {

func didPressReportButton()
}

class ActivityDetailsView: UIView {
final class ActivityDetailsView: UIView {

// MARK: - Delegate
weak var delegate: ActivityDetailsViewDelegate?

let stackView: UIStackView = {

// MARK: - UIView properties
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 8
stackView.distribution = .fill

return stackView
}()

let imageView: UIImageView = {

private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "bag.circle.fill")
imageView.layer.cornerRadius = 50
imageView.clipsToBounds = true
return imageView
}()

let activityNameLabel: UILabel = {

private let activityNameLabel: UILabel = {
let label = UILabel()
label.text = "Mall"
label.textAlignment = .center
label.font = UIFont.boldSystemFont(ofSize: 17)
return label
}()

let categoryLabel: UILabel = {

private let categoryLabel: UILabel = {
let label = UILabel()
label.text = "Shopping"
label.textAlignment = .center
return label
}()

let priceContainerView: UIView = {

private let priceContainerView: UIView = {
let view = UIView()
return view
}()

let priceLabel: UILabel = {

private let priceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "$100"
label.font = UIFont.boldSystemFont(ofSize: 34)
return label
}()

let timeLabel: UILabel = {

private let timeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "8:57 AM"
return label
}()

lazy var reportIssueButton: UIButton = {

let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Report a issue", for: .normal)
Expand All @@ -89,10 +77,34 @@ class ActivityDetailsView: UIView {
return button
}()


// MARK: - Initializer
init() {
super.init(frame: .zero)
setup()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// MARK: - Protocol conformance
func show(viewModel: ActivityDetails) {
activityNameLabel.text = viewModel.name
categoryLabel.text = viewModel.category
priceLabel.text = String(viewModel.price)
timeLabel.text = viewModel.time
}

@objc
func reportButtonPressed() {
delegate?.didPressReportButton()
}

}

// TODO: Implement viewCodable procotol
private extension ActivityDetailsView {
private func setup() {
backgroundColor = .white

priceContainerView.addSubview(priceLabel)
Expand Down Expand Up @@ -125,14 +137,4 @@ class ActivityDetailsView: UIView {
reportIssueButton.heightAnchor.constraint(equalToConstant: 56)
])
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc
func reportButtonPressed() {

delegate?.didPressReportButton()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,82 @@
//

import UIKit
import RxCocoa
import RxSwift

class ActivityDetailsViewController: UIViewController {

lazy var activityDetailsView: ActivityDetailsView = {
final class ActivityDetailsViewController: UIViewController {

// MARK: - UIView properties
private lazy var activityDetailsView: ActivityDetailsView = {
let activityDetailsView = ActivityDetailsView()
activityDetailsView.delegate = self
return activityDetailsView
}()

// MARK: - DataSource / UseCase dependencies
private let service: FinanceService

// MARK: - Reactive Properties
private let disposeBag = DisposeBag()
private let activityDetailsObservable = BehaviorRelay<ActivityDetails>(
value: .init(name: "",
price: 0,
category: "",
time: "")
)

// MARK: - Initializers
init(service: FinanceService = FinanceService()) {
self.service = service
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) { nil }

// MARK: - UIViewController lifecycle methods
override func loadView() {
self.view = activityDetailsView
}

override func viewDidLoad() {
bindObservables()
Comment thread
reisdev marked this conversation as resolved.
fetchDataView()
}
}

private extension ActivityDetailsViewController {

func fetchDataView() {
service.fetchActivityDetails().subscribe(onSuccess: { [weak self] activityDetails in
guard let self = self else { return }
self.activityDetailsObservable.accept(activityDetails)
}, onFailure: { failure in
print("failure:", failure.localizedDescription)
Comment thread
r-fsantos marked this conversation as resolved.
}).disposed(by: disposeBag)
}

}

private extension ActivityDetailsViewController {

func bindObservables() {
activityDetailsObservable
.asDriver()
.drive { [weak self] activityDetails in
guard let self = self else { return }
self.activityDetailsView.show(viewModel: activityDetails)
}.disposed(by: disposeBag)
}
Comment thread
r-fsantos marked this conversation as resolved.

}

extension ActivityDetailsViewController: ActivityDetailsViewDelegate {

func didPressReportButton() {

let alertViewController = UIAlertController(title: "Report an issue", message: "The issue was reported", preferredStyle: .alert)
let action = UIAlertAction(title: "Thanks", style: .default)
alertViewController.addAction(action)
self.present(alertViewController, animated: true)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,31 @@

import Foundation
import Combine
import RxSwift

class FinanceService {
enum FinanceServiceEndpoints {
case activityDetails

var urlString: String {
switch self {
case .activityDetails:
return "https://raw.githubusercontent.com/devpass-tech/challenge-viewcode-finance/10ce6c2e9c88199ad8c4e721212099f55b26dfbb/api/activity_details_endpoint.json"
}
}
}

final class FinanceService {

private let urlSession: URLSession
private let jsonDecoder: JSONDecoder

init(urlSession: URLSession = URLSession.shared,
jsonDecoder: JSONDecoder = JSONDecoder()) {
self.urlSession = urlSession
self.jsonDecoder = jsonDecoder
}

// MARK: - fetchHomeData
func fetchHomeData() -> AnyPublisher<HomeData?, Error> {
let url = URL(string: "https://raw.githubusercontent.com/devpass-tech/challenge-finance-app/main/api/home_endpoint.json")!
let decoder = JSONDecoder()
Expand All @@ -20,36 +42,7 @@ class FinanceService {
.eraseToAnyPublisher()
}

func fetchActivityDetails(_ completion: @escaping (ActivityDetails?) -> Void) {

let url = URL(string: "https://raw.githubusercontent.com/devpass-tech/challenge-finance-app/main/api/activity_details_endpoint.json")!

let dataTask = URLSession.shared.dataTask(with: url) { data, response, error in

guard error == nil else {
completion(nil)
return
}

guard let data = data else {
completion(nil)
return
}

do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let activityDetails = try decoder.decode(ActivityDetails.self, from: data)
completion(activityDetails)
} catch {
print(error)
completion(nil)
}
}

dataTask.resume()
}

// MARK: - fetchContactList
func fetchContactList(_ completion: @escaping ([Contact]?) -> Void) {

let url = URL(string: "https://raw.githubusercontent.com/devpass-tech/challenge-finance-app/main/api/contact_list_endpoint.json")!
Expand Down Expand Up @@ -80,6 +73,7 @@ class FinanceService {
dataTask.resume()
}

// MARK: - transferAmount
func transferAmount(_ completion: @escaping (TransferResult?) -> Void) {

let url = URL(string: "https://raw.githubusercontent.com/devpass-tech/challenge-finance-app/main/api/transfer_successful_endpoint.json")!
Expand Down Expand Up @@ -121,3 +115,42 @@ class FinanceService {
}

}

// MARK: - fetchActivityDetails
extension FinanceService {
func fetchActivityDetails() -> Single<ActivityDetails> {
return Single<ActivityDetails>.create { single in

let urlInString = FinanceServiceEndpoints.activityDetails.urlString
guard let url = URL(string: urlInString) else {
single(.failure(NetworkServiceError.invalidURL))
return Disposables.create()
}

let dataTask = self.urlSession.dataTask(with: url) { data, response, error in

if let error = error {
single(.failure(error))
return
}

guard let data = data else {
single(.failure(NetworkServiceError.noData))
return
}

do {
self.jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let activityDetails = try self.jsonDecoder.decode(ActivityDetails.self, from: data)
single(.success(activityDetails))
} catch {
single(.failure(NetworkServiceError.decodeError))
}
}

dataTask.resume()

return Disposables.create { dataTask.cancel() }
}
}
}
Loading