Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Better support of remote content #64

Merged
merged 6 commits into from Jul 9, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 26 additions & 2 deletions SwiftFest.xcodeproj/project.pbxproj
Expand Up @@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
3DAFBC549BE61AF4515FD037 /* Pods_SwiftFest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0EC3B5382CAE039CBC11039 /* Pods_SwiftFest.framework */; };
607F499FDBEB8356B60311FB /* libPods-SwiftFestTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3555EC926C9C4B28F29DD68E /* libPods-SwiftFestTests.a */; };
6D59507522C9352000AF6E82 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D59507422C9352000AF6E82 /* Endpoint.swift */; };
6D59507722C93AEA00AF6E82 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D59507622C93AEA00AF6E82 /* Cache.swift */; };
70C5BCBB574D4FD4D827523E /* libPods-SwiftFestUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E57AE67E0023F52BC763E91 /* libPods-SwiftFestUITests.a */; };
7A35F22320CCA64E008165F0 /* Codable+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A35F22220CCA64E008165F0 /* Codable+Utilities.swift */; };
7A5F780420C47C3A00480B1A /* UIColor+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5F780320C47C3A00480B1A /* UIColor+Utilities.swift */; };
Expand Down Expand Up @@ -91,6 +93,8 @@
0E57AE67E0023F52BC763E91 /* libPods-SwiftFestUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SwiftFestUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3555EC926C9C4B28F29DD68E /* libPods-SwiftFestTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SwiftFestTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
550A50C6129DBA1D92566DF4 /* Pods-SwiftFestTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftFestTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftFestTests/Pods-SwiftFestTests.debug.xcconfig"; sourceTree = "<group>"; };
6D59507422C9352000AF6E82 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
6D59507622C93AEA00AF6E82 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = "<group>"; };
7A35F22220CCA64E008165F0 /* Codable+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Codable+Utilities.swift"; sourceTree = "<group>"; };
7A5F780320C47C3A00480B1A /* UIColor+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Utilities.swift"; sourceTree = "<group>"; };
7A5F780520C47E8C00480B1A /* ColorUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorUtilitiesSpec.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -217,6 +221,23 @@
name = Pods;
sourceTree = "<group>";
};
6D59507022C9346100AF6E82 /* Cache */ = {
isa = PBXGroup;
children = (
6D59507622C93AEA00AF6E82 /* Cache.swift */,
);
path = Cache;
sourceTree = "<group>";
};
6D59507322C9349500AF6E82 /* Networking */ = {
isa = PBXGroup;
children = (
7AA423B520CC95CE003B17F3 /* APIClient.swift */,
6D59507422C9352000AF6E82 /* Endpoint.swift */,
);
path = Networking;
sourceTree = "<group>";
};
7AAFEB9320BC81E700520521 /* Utilities */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -281,6 +302,7 @@
9D9B0E08207950B500FA7340 /* SwiftFest */ = {
isa = PBXGroup;
children = (
6D59507322C9349500AF6E82 /* Networking */,
9D3EA5C620CC7DD900808F2F /* main.swift */,
7AAFEB9320BC81E700520521 /* Utilities */,
9DDFBAF520BB98F600756323 /* RibbonTableViewCell.swift */,
Expand All @@ -299,14 +321,14 @@
93B4D1B220B0A6DB00A6C1B5 /* InfoViewController.swift */,
CDA35886207D08BC00F8B68C /* Resources */,
7ADF868C20C45A6400223A4C /* MultiImageView.swift */,
7AA423B520CC95CE003B17F3 /* APIClient.swift */,
);
path = SwiftFest;
sourceTree = "<group>";
};
9D9B0E1D207950B500FA7340 /* SwiftFestTests */ = {
isa = PBXGroup;
children = (
9321F721208FA3FF00FA4B8C /* StaticData */,
9D3EA5C420CC6F3500808F2F /* APIClientTests.swift */,
9D9B0E1E207950B500FA7340 /* AgendaTableViewManagerSpec.swift */,
7A5F780520C47E8C00480B1A /* ColorUtilitiesSpec.swift */,
Expand All @@ -330,6 +352,7 @@
9DD27868209D4C4900804A03 /* Models */ = {
isa = PBXGroup;
children = (
6D59507022C9346100AF6E82 /* Cache */,
9D47768C209E73F2001801BE /* Agenda.swift */,
9DD27869209D4C9A00804A03 /* Session.swift */,
9321F726209123EB00FA4B8C /* AppDataController.swift */,
Expand All @@ -344,7 +367,6 @@
CDA35886207D08BC00F8B68C /* Resources */ = {
isa = PBXGroup;
children = (
9321F721208FA3FF00FA4B8C /* StaticData */,
CDA3588A207D0A0D00F8B68C /* Generated */,
9D9B0E10207950B500FA7340 /* Assets.xcassets */,
9D9B0E15207950B500FA7340 /* Info.plist */,
Expand Down Expand Up @@ -672,6 +694,7 @@
9D47768D209E73F2001801BE /* Agenda.swift in Sources */,
7AAB8CE420C4924700C44ADC /* GradientView.swift in Sources */,
9DD2786A209D4C9A00804A03 /* Session.swift in Sources */,
6D59507722C93AEA00AF6E82 /* Cache.swift in Sources */,
9314D51E209B742900BE0DAF /* SessionDetailView.swift in Sources */,
7AE5789B20C333DF00ECACBA /* TeamMember.swift in Sources */,
7ADF868F20C45C1100223A4C /* UIStackView+Utilities.swift in Sources */,
Expand All @@ -698,6 +721,7 @@
9D9B0E0A207950B500FA7340 /* AppDelegate.swift in Sources */,
7ADF868D20C45A6400223A4C /* MultiImageView.swift in Sources */,
93BD217620A205BD00E66173 /* BaseViewController.swift in Sources */,
6D59507522C9352000AF6E82 /* Endpoint.swift in Sources */,
93E2F5B3208E8A39004FE8E0 /* SpeakerDetailViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
52 changes: 52 additions & 0 deletions SwiftFest/Models/Cache/Cache.swift
@@ -0,0 +1,52 @@
//
// Cache.swift
// SwiftFest
//
// Created by Matthew Dias on 6/30/19.
// Copyright © 2019 Sean Olszewski. All rights reserved.
//

import Foundation

struct Cache {

private(set) var fileManager: FileManager
private(set) var path: String

init(fileManager: FileManager = FileManager.default,
path: String = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String)) {
self.fileManager = fileManager
self.path = path
}

func save(response data: Data, to fileName: String) {
if !fileManager.fileExists(atPath: path) {
try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
}

guard let url = NSURL(string: path)?.appendingPathComponent(fileName) else { return }

print("saving to: \(url.absoluteString)")

let urlString: String = url.absoluteString
fileManager.createFile(atPath: urlString, contents: data, attributes: nil)
}

func getFile(named name: String) -> Data? {
let url = URL(fileURLWithPath: path).appendingPathComponent(name)
if fileManager.fileExists(atPath: url.absoluteString) {
let data = try? Data(contentsOf: url)
return data
} else {
return nil
}
}

func deleteFile(named name: String) {
let cachedFilesPath = (path as NSString).appendingPathComponent(name)

if fileManager.fileExists(atPath: cachedFilesPath) {
try? fileManager.removeItem(atPath: cachedFilesPath)
}
}
}
26 changes: 18 additions & 8 deletions SwiftFest/APIClient.swift → SwiftFest/Networking/APIClient.swift
Expand Up @@ -59,25 +59,25 @@ class APIClient {
}

func fetchAgenda(using completionHandler: @escaping (Result<Agenda>) -> Void) {
let url = URL(string: "\(baseUrl)/schedule.json")!
let url = URL(string: "\(baseUrl)/\(Endpoint.schedule).json")!
let fetchDataTask = dataTask(for: url, using: completionHandler)
fetchDataTask.resume()
}

func fetchSessions(using completionHandler: @escaping (Result<[Session]>) -> Void) {
let url = URL(string: "\(baseUrl)/sessions.json")!
let url = URL(string: "\(baseUrl)/\(Endpoint.sessions).json")!
let fetchDataTask = dataTask(for: url, using: completionHandler)
fetchDataTask.resume()
}

func fetchSpeakers(using completionHandler: @escaping (Result<[Speaker]>) -> Void) {
let url = URL(string: "\(baseUrl)/speakers.json")!
let url = URL(string: "\(baseUrl)/\(Endpoint.speakers).json")!
let fetchDataTask = dataTask(for: url, using: completionHandler)
fetchDataTask.resume()
}

func fetchTeam(using completionHandler: @escaping (Result<[TeamMember]>) -> Void) {
let url = URL(string: "\(baseUrl)/team.json")!
let url = URL(string: "\(baseUrl)/\(Endpoint.team).json")!
let fetchDataTask = dataTask(for: url, using: completionHandler)
fetchDataTask.resume()
}
Expand All @@ -98,12 +98,22 @@ private extension APIClient {
func dataTask<DataType: Decodable>(for url: URL,
using completionHandler: @escaping (Result<DataType>) -> Void) -> URLSessionDataTask {
let dataTask = session.dataTask(with: url) { data, _, error in
guard let data = data else {
completionHandler(.failure(error ?? UnknownError()))
return
var responseData: Data

if data == nil {
guard let cachedData = Cache().getFile(named: url.lastPathComponent) else {
completionHandler(.failure(error ?? UnknownError()))
return
}

responseData = cachedData
} else {
responseData = data!
}

do {
let value = try JSONDecoder.default.decode(DataType.self, from: data)
let value = try JSONDecoder.default.decode(DataType.self, from: responseData)
Cache().save(response: responseData, to: url.lastPathComponent)
completionHandler(.success(value))
} catch {
completionHandler(.failure(error))
Expand Down
17 changes: 17 additions & 0 deletions SwiftFest/Networking/Endpoint.swift
@@ -0,0 +1,17 @@
//
// Endpoint.swift
// SwiftFest
//
// Created by Matthew Dias on 6/30/19.
// Copyright © 2019 Sean Olszewski. All rights reserved.
//

import Foundation

enum Endpoint: String {
case schedule
case sessions
case speakers
case sponsors
case team
}
10 changes: 5 additions & 5 deletions SwiftFestTests/APIClientTests.swift
Expand Up @@ -15,18 +15,18 @@ class APIClientTests: QuickSpec {

describe("APIClient") {
it("fetches the agenda from a server") {

waitUntil { done in

apiClient.fetchAgenda { result in

defer { done() }

guard case let Result.success(agenda) = result else {
fail("expected an agenda to be fetched")
return
}

expect(agenda.days).to(haveCount(2))
}
}
Expand Down
9 changes: 5 additions & 4 deletions SwiftFestTests/AppDataControllerSpec.swift
Expand Up @@ -6,19 +6,19 @@ class AppDataControllerSpec: QuickSpec {
override func spec() {
let subject = AppDataController.shared
describe("AppDataController") {

it("can get a session given a session id") {
let sessionOne = subject.session(forSpeaker: "1")
let sessionTwo = subject.session(forSpeaker: "3")
expect(sessionOne?.title).to(equal("Keynote: TBA"))

expect(sessionOne?.title).to(equal("Keynote: Clean Agile"))
expect(sessionTwo?.title).to(equal("Keynote: Programming with Purpose"))
}

it("can get a list of thumbnail urls indexed by session id") {
let speakersById = subject.speakersById
let speakerId: Identifier<Speaker> = "1"

expect(speakersById.count).to(beGreaterThanOrEqualTo(15))
expect(speakersById.keys).to(contain(speakerId))
}
Expand All @@ -34,6 +34,7 @@ class AppDataControllerSpec: QuickSpec {
expect(speakers.count).to(beGreaterThanOrEqualTo(15))
}

// FIXME: CI's server is in a different timezone
// it("deserializes the agenda") {
// let agenda = subject.agenda
// expect(agenda.days).to(haveCount(2))
Expand Down