Skip to content
Merged
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
8 changes: 4 additions & 4 deletions iOSBooks/iOSBooks.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
0E3964D82314BB470093738B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3964D72314BB470093738B /* AppDelegate.swift */; };
0E3964DF2314BB4A0093738B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E3964DE2314BB4A0093738B /* Assets.xcassets */; };
0E3964E22314BB4A0093738B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E3964E02314BB4A0093738B /* LaunchScreen.storyboard */; };
0E3964ED2314BB4A0093738B /* iOSBooksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3964EC2314BB4A0093738B /* iOSBooksTests.swift */; };
0E3964ED2314BB4A0093738B /* BookDetailViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3964EC2314BB4A0093738B /* BookDetailViewModelTests.swift */; };
477DA993F6CBE89C179034EE /* Pods_iOSBooksTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28F20F388C60D311258C5680 /* Pods_iOSBooksTests.framework */; };
7447D35E2316C91700E01BD3 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7447D35D2316C91700E01BD3 /* APIClient.swift */; };
7447D3602316CB1200E01BD3 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7447D35F2316CB1200E01BD3 /* Reachability.swift */; };
Expand Down Expand Up @@ -59,7 +59,7 @@
0E3964E12314BB4A0093738B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
0E3964E32314BB4A0093738B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0E3964E82314BB4A0093738B /* iOSBooksTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSBooksTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0E3964EC2314BB4A0093738B /* iOSBooksTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSBooksTests.swift; sourceTree = "<group>"; };
0E3964EC2314BB4A0093738B /* BookDetailViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookDetailViewModelTests.swift; sourceTree = "<group>"; };
0E3964EE2314BB4A0093738B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
18F9E4DBC4A549B0B6E5DFAB /* Pods-iOSBooksTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSBooksTests.release.xcconfig"; path = "Target Support Files/Pods-iOSBooksTests/Pods-iOSBooksTests.release.xcconfig"; sourceTree = "<group>"; };
275296C36B2B5A1EBD78A5F1 /* Pods-iOSBooks.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOSBooks.release.xcconfig"; path = "Target Support Files/Pods-iOSBooks/Pods-iOSBooks.release.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -156,7 +156,7 @@
0E3964EB2314BB4A0093738B /* iOSBooksTests */ = {
isa = PBXGroup;
children = (
0E3964EC2314BB4A0093738B /* iOSBooksTests.swift */,
0E3964EC2314BB4A0093738B /* BookDetailViewModelTests.swift */,
0E3964EE2314BB4A0093738B /* Info.plist */,
);
path = iOSBooksTests;
Expand Down Expand Up @@ -535,7 +535,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0E3964ED2314BB4A0093738B /* iOSBooksTests.swift in Sources */,
0E3964ED2314BB4A0093738B /* BookDetailViewModelTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class BookDetailViewController: UIViewController {

// MARK: - Actions
@IBAction func saveBook(_ sender: UIButton) {
viewModel?.saveBookIfNeeded()
viewModel?.saveBookIfNeeded(andImage: bookImageView?.image)
toggleFavoriteButton(sender)
}

Expand Down
12 changes: 6 additions & 6 deletions iOSBooks/iOSBooks/BookDetail/ViewModel/BookDetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2019 Guilherme Antunes. All rights reserved.
//

import Foundation
import UIKit

protocol BookDetailViewModelProtocol {
func getBookTitle() -> String
Expand All @@ -17,7 +17,7 @@ protocol BookDetailViewModelProtocol {
func presentPreviousStep()
func getScreenTitle() -> String?
func getBookBuyLinkURL() -> URL?
func saveBookIfNeeded()
func saveBookIfNeeded(andImage image: UIImage?)
var savedBook: Bool { get }
}

Expand All @@ -27,7 +27,7 @@ class BookDetailViewModel: BookDetailViewModelProtocol {
var selectedBook: Item?
weak var view: BookDetailViewControllerPresentable?
var coordinator: AppCoordinatorProtocol?
var service = CoreDataClient()
var service: CoreDataClientProtocol?
var savedBook: Bool = false

// MARK: - ViewModel Protocol Methods
Expand Down Expand Up @@ -77,18 +77,18 @@ class BookDetailViewModel: BookDetailViewModelProtocol {
return nil
}

func saveBookIfNeeded() {
func saveBookIfNeeded(andImage image: UIImage? = nil) {
if savedBook {
deleteBook()
return
}
savedBook = true
service.saveBook(selectedBook)
service?.saveBook(selectedBook, withThumbnail: image)
}

func deleteBook() {
savedBook = false
service.deleteBook(selectedBook)
service?.deleteBook(selectedBook)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UIKit

extension BooksListViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel?.numberOfItemsInSection() ?? 0
return viewModel?.numberOfItemsInSection(section) ?? 0
}

func numberOfSections(in collectionView: UICollectionView) -> Int {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ extension BooksListViewController: BooksListViewControllerPresentable {
guard var viewModel = viewModel else { return }
if !viewModel.showFavorites {
viewModel.showFavorites.toggle()
viewModel.loadSavedBooks(shouldShowOnScreen: true)
viewModel.loadSavedBooks()
reloadView()
return
}
viewModel.showFavorites.toggle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BooksListViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupNavigationBar()
viewModel?.loadSavedBooks(shouldShowOnScreen: false)
viewModel?.loadSavedBooks()
viewModel?.showFavorites = false
viewModel?.loadBooks()
}
Expand Down
15 changes: 3 additions & 12 deletions iOSBooks/iOSBooks/BooksList/View/Subviews/BooksCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,14 @@ class BooksCell: UICollectionViewCell {

// MARK: - Properties
var selectedBook: Book?
lazy var imageCache = ImageCache(name: selectedBook?.title ?? "")

func setup(withBook book: Book?) {
func setup(withBook book: Book?, andImage image: UIImage? = nil) {
selectedBook = book
if let image = imageCache.retrieveImageInMemoryCache(forKey: selectedBook?.subtitle ?? "") {
if let image = image {
bookImageView?.image = image
} else {
if let book = book, let bookURLString = book.imageLinks?.smallThumbnail, let url = URL(string: bookURLString) {
bookImageView?.kf.setImage(with: url, options: [.cacheOriginalImage], completionHandler: { (result) in
switch result {
case .success(let result):
self.imageCache.store(result.image, forKey: self.selectedBook?.subtitle ?? "")
case .failure(let error):
print(error.localizedDescription)
}
})

bookImageView?.kf.setImage(with: url)
}
}

Expand Down
36 changes: 15 additions & 21 deletions iOSBooks/iOSBooks/BooksList/ViewModel/BooksListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,19 @@ import UIKit

protocol BooksListViewModelProtocol {
func loadBooks()
func loadSavedBooks(shouldShowOnScreen reloadView: Bool)
func loadSavedBooks()
func selectBook(atIndexPath indexPath: IndexPath)
func presentPreviousStep()
func numberOfSections() -> Int
func numberOfItemsInSection() -> Int
func numberOfItemsInSection(_ section: Int) -> Int
func cellForItem(inCollectionView collectionView: UICollectionView, atIndexPath indexPath: IndexPath) -> UICollectionViewCell
var showFavorites: Bool { get set }
}

class BooksListViewModel: BooksListViewModelProtocol {

var items: [Item] = []
var savedItems: [Item] = []
var service = BooksClient()
var savedItems: (books: [Item], images: [UIImage]) = ([],[])
var service: BooksClientProtocol?
var startingIndex = 0
weak var view: BooksListViewControllerPresentable?
var selectedBook: Item?
Expand All @@ -35,34 +34,33 @@ class BooksListViewModel: BooksListViewModelProtocol {
return 1
}

func numberOfItemsInSection() -> Int {
return showFavorites ? savedItems.count : items.count
func numberOfItemsInSection(_ section: Int = 0) -> Int {
return showFavorites ? savedItems.books.count : items.count
}

func cellForItem(inCollectionView collectionView: UICollectionView, atIndexPath indexPath: IndexPath) -> UICollectionViewCell {
let cell: BooksCell = collectionView.dequeueReusableCell(for: indexPath)
let book: Book?
var image: UIImage? = nil
if showFavorites {
book = savedItems[indexPath.item].book
book = savedItems.books[indexPath.item].book
image = savedItems.images[indexPath.item]
} else {
book = items[indexPath.item].book
}
cell.setup(withBook: book)
cell.setup(withBook: book, andImage: image)
if indexPath.item == (items.count - 1) && !showFavorites {
loadBooks()
}
return cell
}

func loadSavedBooks(shouldShowOnScreen reloadView: Bool = false) {
savedItems = service.fetchSavedBooks()
if reloadView {
view?.reloadView()
}
func loadSavedBooks() {
savedItems = service?.fetchSavedBooks() ?? ([],[])
}

func loadBooks() {
service.fetchBooksList(startingIndex: startingIndex).done { [weak self] (list) in
service?.fetchBooksList(startingIndex: startingIndex).done { [weak self] (list) in
guard let self = self, let items = list.items else { return }
self.items.append(contentsOf: items)
self.startingIndex = items.count
Expand All @@ -79,12 +77,8 @@ class BooksListViewModel: BooksListViewModelProtocol {

func selectBook(atIndexPath indexPath: IndexPath) {
selectedBook = nil
selectedBook = showFavorites ? savedItems[indexPath.item] : items[indexPath.item]
selectedBookIsFavorite = savedItems.contains(where: { $0.id == selectedBook?.id })
selectedBook = showFavorites ? savedItems.books[indexPath.item] : items[indexPath.item]
selectedBookIsFavorite = savedItems.books.contains(where: { $0.id == selectedBook?.id })
coordinator?.presentNextStep()
}

func presentPreviousStep() {
coordinator?.presentPreviousStep()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ class AppCoordinatorDependencyInjector {
}()

lazy var booksListViewModel: BooksListViewModel = {
return BooksListViewModel()
let viewModel = BooksListViewModel()
viewModel.service = BooksClient()
return viewModel
}()

lazy var bookDetailViewController: BookDetailViewController = {
Expand All @@ -38,6 +40,7 @@ class AppCoordinatorDependencyInjector {
lazy var bookDetailViewModel: BookDetailViewModel = {
let viewModel = BookDetailViewModel()
viewModel.selectedBook = booksListViewModel.selectedBook
viewModel.service = CoreDataClient()
return viewModel
}()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
<attribute name="bookDescription" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="buyLink" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="id" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="image" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="isFavorite" optional="YES" attributeType="Boolean" usesScalarValueType="YES" syncable="YES"/>
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<elements>
<element name="CoreDataBook" positionX="-63" positionY="-18" width="128" height="135"/>
<element name="CoreDataBook" positionX="-63" positionY="-18" width="128" height="150"/>
</elements>
</model>
9 changes: 7 additions & 2 deletions iOSBooks/iOSBooks/Data/Clients/BooksClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
import Foundation
import PromiseKit

class BooksClient {
protocol BooksClientProtocol: class {
func fetchBooksList(startingIndex index: Int) -> Promise<BooksList>
func fetchSavedBooks() -> (books: [Item], images: [UIImage])
}

class BooksClient: BooksClientProtocol {

let apiClient: APIClient
let coreDataClient: CoreDataClient
Expand All @@ -23,7 +28,7 @@ class BooksClient {
return apiClient.request(model: BooksList.self, BooksAPI.list(startingIndex: index).request)
}

func fetchSavedBooks() -> [Item] {
func fetchSavedBooks() -> (books: [Item], images: [UIImage]) {
return coreDataClient.fetchAllSavedBooks()
}

Expand Down
33 changes: 24 additions & 9 deletions iOSBooks/iOSBooks/Data/Clients/CoreDataClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,27 @@
//

import CoreData
import Foundation
import UIKit

class CoreDataClient {
func saveBook(_ item: Item?) {
guard let item = item, let context = context, let entity = NSEntityDescription.entity(forEntityName: "CoreDataBook", in: context) else { return }
protocol CoreDataClientProtocol: class {
func saveBook(_ item: Item?, withThumbnail thumbnail: UIImage?)
func fetchAllSavedBooks() -> (books: [Item], images: [UIImage])
func deleteBook(_ book: Item?)
}

class CoreDataClient: CoreDataClientProtocol {

func saveBook(_ item: Item?, withThumbnail thumbnail: UIImage? = nil) {
guard let item = item, let context = context, let entity = NSEntityDescription.entity(forEntityName: "CoreDataBook", in: context), let imageData = thumbnail?.pngData() else { return }

let coreDataBook = NSManagedObject(entity: entity, insertInto: context)
coreDataBook.setValue(item.book?.title, forKey: "title")
coreDataBook.setValue(item.id, forKey: "id")
coreDataBook.setValue(item.book?.description, forKey: "bookDescription")
coreDataBook.setValue(item.salesInfo?.buyLink, forKey: "buyLink")
coreDataBook.setValue(true, forKey: "isFavorite")
coreDataBook.setValue(item.book?.authors?.first, forKey: "author")
coreDataBook.setValue(imageData, forKey: "image")

do {
try context.save()
Expand All @@ -28,12 +36,13 @@ class CoreDataClient {
}
}

func fetchAllSavedBooks() -> [Item] {
func fetchAllSavedBooks() -> (books: [Item], images: [UIImage]) {
var books: [Item] = []
guard let context = context else { return [] }
var images: [UIImage] = []
guard let context = context else { return ([], []) }
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreDataBook")
do {
guard let result = try context.fetch(fetchRequest) as? [NSManagedObject] else { return [] }
guard let result = try context.fetch(fetchRequest) as? [NSManagedObject] else { return ([], []) }
for data in result {
let item = Item()
item.book = Book()
Expand All @@ -46,11 +55,17 @@ class CoreDataClient {
item.salesInfo?.buyLink = data.value(forKey: "buyLink") as? String
item.book?.title = data.value(forKey: "title") as? String
books.append(item)

if let bookImageData = data.value(forKey: "image") as? Data, let bookImage = UIImage(data: bookImageData) {
images.append(bookImage)
} else {
images.append(UIImage())
}
}
return books
return (books, images)
} catch {
print("Failed to load data from CoreData")
return []
return ([], [])
}
}

Expand Down
Loading