Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature [RM85] Display Cards #86

Merged
merged 5 commits into from
Aug 19, 2021
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
2 changes: 1 addition & 1 deletion Rick-and-Morty/CharacterRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ final class CharacterRepository: CharacterRepositoryProtocol {

func getCharacters(completion: @escaping (([Character]) -> Void)) {
if let url = characterPageURL {
rickAndMortyService.fetchData(url: url) { (charactersResponse: CharactersResponse) in
rickAndMortyService.fetchData(url: url) { (charactersResponse: CharacterResponse) in
if let nextURLString = charactersResponse.info.next {
if let nextURL = URL(string: nextURLString) {
self.characterPageURL = nextURL
Expand Down
34 changes: 34 additions & 0 deletions Rick-and-Morty/EpisodeRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// EpisodeRepository.swift
// Rick And Morty
//
// Created by Scottie Gray on 2021-08-13.
// Copyright © 2021 Novoda. All rights reserved.
//

import Foundation

protocol EpisodeRepositoryProtocol {
func getEpisode(from urlString: String, completion: @escaping ((Episode) -> Void))
}

final class EpisodeRepository: EpisodeRepositoryProtocol {
private let rickAndMortyService: RickAndMortyServiceProtocol = RickAndMortyService()
static private var cachedEpisodeNames: [String : Episode] = [:]

func getEpisode(from urlString: String, completion: @escaping ((Episode) -> Void)) {
if let episode = EpisodeRepository.cachedEpisodeNames[urlString] {
completion(episode)
} else {
if let url = URL(string: urlString) {
rickAndMortyService.fetchData(url: url) { (episode: Episode) in
EpisodeRepository.cachedEpisodeNames[urlString] = episode
completion(episode)
} error: { error in
print(error.debugDescription)
return
}
}
}
}
}
43 changes: 42 additions & 1 deletion Rick-and-Morty/Rick And Morty.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
174B064B26C6611D0080ADD0 /* RickAndMortyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B064A26C6611D0080ADD0 /* RickAndMortyService.swift */; };
174B064D26C661470080ADD0 /* RickAndMortyServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B064C26C661470080ADD0 /* RickAndMortyServiceProtocol.swift */; };
174B065126C671580080ADD0 /* CharacterRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B065026C671580080ADD0 /* CharacterRepository.swift */; };
174B065426C6740E0080ADD0 /* CharacterListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B065326C6740E0080ADD0 /* CharacterListViewModel.swift */; };
174B065726C6751F0080ADD0 /* CharacterCardStateFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B065626C6751F0080ADD0 /* CharacterCardStateFactory.swift */; };
174B065926C680850080ADD0 /* EpisodeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B065826C680850080ADD0 /* EpisodeRepository.swift */; };
174B065B26C680E20080ADD0 /* Episode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B065A26C680E20080ADD0 /* Episode.swift */; };
174B065D26C6861E0080ADD0 /* RemoteImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B065C26C6861E0080ADD0 /* RemoteImage.swift */; };
174B065F26C6ABB00080ADD0 /* CharacterCardViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174B065E26C6ABB00080ADD0 /* CharacterCardViewModel.swift */; };
17588BAC26C1750B008ECC31 /* Character.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17588BAB26C1750B008ECC31 /* Character.swift */; };
17588BAF26C273BB008ECC31 /* CharacterCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17588BAE26C273BB008ECC31 /* CharacterCard.swift */; };
B811686D1CFF1C9900301A0A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B811686C1CFF1C9900301A0A /* AppDelegate.swift */; };
Expand All @@ -34,6 +40,12 @@
174B064A26C6611D0080ADD0 /* RickAndMortyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RickAndMortyService.swift; sourceTree = "<group>"; };
174B064C26C661470080ADD0 /* RickAndMortyServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RickAndMortyServiceProtocol.swift; sourceTree = "<group>"; };
174B065026C671580080ADD0 /* CharacterRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterRepository.swift; sourceTree = "<group>"; };
174B065326C6740E0080ADD0 /* CharacterListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterListViewModel.swift; sourceTree = "<group>"; };
174B065626C6751F0080ADD0 /* CharacterCardStateFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCardStateFactory.swift; sourceTree = "<group>"; };
174B065826C680850080ADD0 /* EpisodeRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodeRepository.swift; sourceTree = "<group>"; };
174B065A26C680E20080ADD0 /* Episode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Episode.swift; sourceTree = "<group>"; };
174B065C26C6861E0080ADD0 /* RemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImage.swift; sourceTree = "<group>"; };
174B065E26C6ABB00080ADD0 /* CharacterCardViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCardViewModel.swift; sourceTree = "<group>"; };
17588BAB26C1750B008ECC31 /* Character.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Character.swift; sourceTree = "<group>"; };
17588BAE26C273BB008ECC31 /* CharacterCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCard.swift; sourceTree = "<group>"; };
B81168691CFF1C9900301A0A /* Rick And Morty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Rick And Morty.app"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -77,14 +89,34 @@
isa = PBXGroup;
children = (
174B065026C671580080ADD0 /* CharacterRepository.swift */,
174B065826C680850080ADD0 /* EpisodeRepository.swift */,
);
name = Repositories;
path = ..;
sourceTree = "<group>";
};
174B065226C673FB0080ADD0 /* ViewModels */ = {
isa = PBXGroup;
children = (
174B065326C6740E0080ADD0 /* CharacterListViewModel.swift */,
174B065E26C6ABB00080ADD0 /* CharacterCardViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
174B065526C674FD0080ADD0 /* Factories */ = {
isa = PBXGroup;
children = (
174B065626C6751F0080ADD0 /* CharacterCardStateFactory.swift */,
);
path = Factories;
sourceTree = "<group>";
};
17588BAA26C174FB008ECC31 /* Model */ = {
isa = PBXGroup;
children = (
17588BAB26C1750B008ECC31 /* Character.swift */,
174B065A26C680E20080ADD0 /* Episode.swift */,
);
path = Model;
sourceTree = "<group>";
Expand All @@ -94,6 +126,7 @@
children = (
1711B39D26B1898100BE935B /* CharacterListView.swift */,
17588BAE26C273BB008ECC31 /* CharacterCard.swift */,
174B065C26C6861E0080ADD0 /* RemoteImage.swift */,
);
path = Views;
sourceTree = "<group>";
Expand All @@ -104,7 +137,6 @@
B811686B1CFF1C9900301A0A /* Rick And Morty */,
B81168821CFF1C9900301A0A /* Rick And MortyTests */,
B811686A1CFF1C9900301A0A /* Products */,
174B064E26C66FAD0080ADD0 /* Repositories */,
);
sourceTree = "<group>";
};
Expand All @@ -120,10 +152,13 @@
B811686B1CFF1C9900301A0A /* Rick And Morty */ = {
isa = PBXGroup;
children = (
174B065526C674FD0080ADD0 /* Factories */,
174B065226C673FB0080ADD0 /* ViewModels */,
174B064926C660FF0080ADD0 /* Services */,
17588BAD26C273A2008ECC31 /* Views */,
17588BAA26C174FB008ECC31 /* Model */,
B811686C1CFF1C9900301A0A /* AppDelegate.swift */,
174B064E26C66FAD0080ADD0 /* Repositories */,
B81168751CFF1C9900301A0A /* Assets.xcassets */,
B81168771CFF1C9900301A0A /* LaunchScreen.storyboard */,
B811687A1CFF1C9900301A0A /* Info.plist */,
Expand Down Expand Up @@ -247,10 +282,16 @@
174B064D26C661470080ADD0 /* RickAndMortyServiceProtocol.swift in Sources */,
174B065126C671580080ADD0 /* CharacterRepository.swift in Sources */,
174B064B26C6611D0080ADD0 /* RickAndMortyService.swift in Sources */,
174B065D26C6861E0080ADD0 /* RemoteImage.swift in Sources */,
174B065F26C6ABB00080ADD0 /* CharacterCardViewModel.swift in Sources */,
174B065726C6751F0080ADD0 /* CharacterCardStateFactory.swift in Sources */,
17588BAF26C273BB008ECC31 /* CharacterCard.swift in Sources */,
174B065B26C680E20080ADD0 /* Episode.swift in Sources */,
174B065426C6740E0080ADD0 /* CharacterListViewModel.swift in Sources */,
17588BAC26C1750B008ECC31 /* Character.swift in Sources */,
1711B39E26B1898100BE935B /* CharacterListView.swift in Sources */,
B811686D1CFF1C9900301A0A /* AppDelegate.swift in Sources */,
174B065926C680850080ADD0 /* EpisodeRepository.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "66.jpeg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// CharacterCardStateFactory.swift
// Rick And Morty
//
// Created by Scottie Gray on 2021-08-13.
// Copyright © 2021 Novoda. All rights reserved.
//

import Foundation
import SwiftUI

final class CharacterCardStateFactory {
private func getStatusColor(character: Character) -> Color {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you consider this to be a private extension on Character status?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be static function

switch character.status {
case .alive:
return .green
case .dead:
return .red
default:
return .yellow
}
}

private func getStatusText(character: Character) -> String {
var str = ""

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to avoid names of variables that are not descriptive

switch character.status {
case .alive:
str = "Alive"
case .dead:
str = "Dead"
default:
str = "unknown"
}

return str + " - " + character.species
}

func createCharacterCardState(from character: Character) -> CharacterCardState {
let characterCardState = CharacterCardState(
id: character.id,
name: character.name,
imageURL: character.imageURL,
statusColor: getStatusColor(character: character),
species: character.species,
lastLocation: character.lastLocation.name,
firstEpisodeName: "",
statusText: getStatusText(character: character)
)

return characterCardState
}
}
10 changes: 8 additions & 2 deletions Rick-and-Morty/Rick And Morty/Model/Character.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import Foundation

struct CharactersResponse: Codable {
struct CharacterResponse: Codable {
enum CodingKeys: String, CodingKey {
case info = "info"
case characters = "results"
Expand All @@ -26,6 +26,12 @@ struct CharacterResponseInfo: Codable {
}

struct Character: Codable {
enum Status: String, Codable {
case alive = "Alive"
case dead = "Dead"
case unkown = "unknown"
}

enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
Expand All @@ -40,7 +46,7 @@ struct Character: Codable {
let name: String
let species: String
let lastLocation: LastLocation
let status: String
let status: Status
let imageURL: String
let episodeURLs: [String]
}
Expand Down
13 changes: 13 additions & 0 deletions Rick-and-Morty/Rick And Morty/Model/Episode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Episode.swift
// Rick And Morty
//
// Created by Scottie Gray on 2021-08-13.
// Copyright © 2021 Novoda. All rights reserved.
//

import Foundation

struct Episode: Codable {
let name: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// CharacterCardViewModel.swift
// Rick And Morty
//
// Created by Scottie Gray on 2021-08-13.
// Copyright © 2021 Novoda. All rights reserved.
//

import Foundation
import SwiftUI


struct CharacterCardState {
var id: Int
var name: String
var imageURL: String
var statusColor: Color
var species: String
var lastLocation: String
var firstEpisodeName: String
var statusText: String
}

final class CharacterCardViewModel: ObservableObject {
@Published var cardState: CharacterCardState

let character: Character
private let episodeRepository: EpisodeRepositoryProtocol = EpisodeRepository()

init(character: Character) {
let cardStateFactory = CharacterCardStateFactory()

self.character = character
self.cardState = cardStateFactory.createCharacterCardState(from: character)

loadFirstEpisodeName()
}

func loadFirstEpisodeName() {
if let firstURL = character.episodeURLs.first {
episodeRepository.getEpisode(from: firstURL) { episode in
self.cardState.firstEpisodeName = episode.name
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// CharacterListViewModel.swift
// Rick And Morty
//
// Created by Scottie Gray on 2021-08-13.
// Copyright © 2021 Novoda. All rights reserved.
//

import Foundation


struct CharacterListViewState {
let title: String = "Characters"
var characters: [Character]
}

final class CharacterListViewModel: ObservableObject {
@Published var characterListViewState: CharacterListViewState = CharacterListViewState(characters: [])

private let characterRepository: CharacterRepositoryProtocol = CharacterRepository()
private let characterCardStateFactory = CharacterCardStateFactory()

init() {
loadCharacters()
}

func loadCharacters() {
characterRepository.getCharacters { characters in
for character in characters {
self.characterListViewState.characters.append(character)
}
}
}

func loadIfNeeded(characterID: Int) {
if characterID == characterListViewState.characters.last?.id {
loadCharacters()
}
}
}
Loading