Skip to content
This repository has been archived by the owner on Feb 12, 2020. It is now read-only.

[Feature] Add cache #18

Merged
merged 3 commits into from
Oct 2, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Domain/UseCases/CurriculumsUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ import RxSwift

public protocol CurriculumsUseCase {
func semesters(targetStudentId: String) -> Observable<[Semester]>
func courses(targetStudentId: String, year: String, semester: String) -> Observable<CurriculumCourses>
func courses(targetStudentId: String, year: String, semester: String) -> Observable<[[Course]]>
}
46 changes: 44 additions & 2 deletions NetworkPlatform/UseCases/CurriculumsUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,53 @@ final class CurriculumsUseCase: Domain.CurriculumsUseCase {
.map([Domain.Semester].self)
}

func courses(targetStudentId: String, year: String, semester: String) -> Observable<Domain.CurriculumCourses> {
return provider.rx.request(.courses(targetStudentId: targetStudentId, year: year, semester: semester))
func courses(targetStudentId: String, year: String, semester: String) -> Observable<[[Domain.Course]]> {
return provider.rx.request(.courses(targetStudentId: targetStudentId,
year: year,
semester: semester))
.asObservable()
.filterSuccessfulStatusCodes()
.map(Domain.CurriculumCourses.self)
.map { [weak self] (curriculumCourses) -> [[Domain.Course]] in
guard let self = self else { return [] }
return self.generateCourses(from: curriculumCourses)
}
}

private func generateCourses(from curriculumCourses: Domain.CurriculumCourses) -> [[Domain.Course]] {
var array: [[Domain.Course]] = self.initCourses()
for course in curriculumCourses.courses {
for (day, period) in course.periods.enumerated() {
self.reshapCourses(period: period,
day: day,
course: course,
array: &array)
}
}
return array
}

private func reshapCourses(period: String, day: Int, course: Course, array: inout[[Domain.Course]]) {
if period.count > 0 {
let periods = period.split(separator: " ")
periods.forEach { (period) in
let section = Int(String(period), radix: 16) ?? 0
array[section - 1][day] = course
}
}
}

private func initCourses() -> [[Domain.Course]] {
let array = [[Domain.Course]].init(
repeating: [Course].init(
repeating: Course(id: "",
name: "",
instructor: [],
periods: [],
classroom: []),
count: 7),
count: 13)
return array
}

}
13 changes: 8 additions & 5 deletions TAT/CurriculumViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class CurriculumViewController: UIViewController {

private lazy var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
activityIndicator.color = .blue
activityIndicator.color = .systemPink
return activityIndicator
}()

Expand Down Expand Up @@ -82,8 +82,10 @@ final class CurriculumViewController: UIViewController {
}

private func bindViewModel() {
let viewDidLoadTrigger = Observable.just(())
let searchTrigger = Observable.merge(viewDidLoadTrigger, leftBarItem.rx.tap.asObservable())
let input = CurriculumViewModel.Input(targetStudentId: Observable.just("104440026"),
searchTrigger: leftBarItem.rx.tap.asObservable())
searchTrigger: searchTrigger)
let output = viewModel.transform(input: input)

output.state
Expand All @@ -99,9 +101,10 @@ final class CurriculumViewController: UIViewController {

output.semesters
.subscribe(onNext: { [weak self] (semesters) in
print(semesters)
let semsterString = semesters.map { "\($0.year) 學年 第\($0.semester)學期" }
self?.updateTitleView(by: semsterString)
}, onError: { (error) in
print(error)
})
.disposed(by: rx.disposeBag)

Expand All @@ -119,12 +122,12 @@ final class CurriculumViewController: UIViewController {
}

private func setUpLayouts() {
setUpActivityIndicator()
setUpCollectionView()
setUpActivityIndicator()
}

private func setUpActivityIndicator() {
view.addSubview(activityIndicator)
collectionView.addSubview(activityIndicator)
activityIndicator.snp.makeConstraints { (make) in
make.center.equalToSuperview()
}
Expand Down
4 changes: 4 additions & 0 deletions TAT/Login/LoginViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ extension LoginViewModel {
.subscribe(onNext: { (_) in
UserDefaults.standard.removeObject(forKey: "studentId")
UserDefaults.standard.removeObject(forKey: "password")
UserDefaults.standard.removeObject(forKey: "semesters")
UserDefaults.standard.removeObject(forKey: "courses")
UserDefaults.standard.removeObject(forKey: "targetStudentId")
UserDefaults.standard.removeObject(forKey: "token")
})
.disposed(by: rx.disposeBag)

Expand Down
67 changes: 28 additions & 39 deletions TAT/ViewModels/CourseViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,56 +49,45 @@ extension CourseViewModel {
let inputData = Observable.combineLatest(input.year,
input.semester,
input.targetStudentId)
let state = PublishSubject<State>()
let state = ReplaySubject<State>.create(bufferSize: 1)

state.subscribe(onNext: { (state) in
print(state)
}).disposed(by: rx.disposeBag)

let courses = input.searchTrigger
.withLatestFrom(inputData)
.flatMap { [unowned self] (year, semester, targetStudentId) -> Observable<CurriculumCourses> in
.filter { $0 != "" && $1 != "" && $2 != "" }
.flatMap { [unowned self] (year, semester, targetStudentId) -> Observable<[[Domain.Course]]> in
state.onNext(.loading)
return self.curriculumsUseCase.courses(targetStudentId: targetStudentId,
year: year,
semester: semester)
}

let coursesObseravle = courses
.asObservable()
.flatMap({ [unowned self] (curriculumCourses) -> Observable<[[Domain.Course]]> in
var array: [[Domain.Course]] = self.initCourses()

for course in curriculumCourses.courses {
for (day, period) in course.periods.enumerated() {
self.reshapCourses(period: period, day: day, course: course, array: &array)
}
}
return self.generateCourses(year: year, semester: semester, targetStudentId: targetStudentId)
}
.share(replay: 1)

courses
.subscribe(onNext: { (courses) in
state.onNext(.success)
return Observable.just(array)
if UserDefaults.standard.object(forKey: "courses") == nil {
guard let courses = try? JSONEncoder().encode(courses) else { return }
UserDefaults.standard.set(courses, forKey: "courses")
}
}, onError: { (error) in
print(error)
state.onNext(.error(message: "cannot get courses"))
})
.disposed(by: rx.disposeBag)

return Output(state: state, courses: coursesObseravle)
return Output(state: state, courses: courses)
}

private func reshapCourses(period: String, day: Int, course: Course, array: inout[[Domain.Course]]) {
if period.count > 0 {
let periods = period.split(separator: " ")
periods.forEach { (period) in
let section = Int(String(period), radix: 16) ?? 0
array[section - 1][day] = course
}
private func generateCourses(year: String, semester: String, targetStudentId: String) -> Observable<[[Domain.Course]]> {
guard let cachedData = UserDefaults.standard.object(forKey: "courses") as? Data,
let cachedCourses = try? JSONDecoder().decode([[Domain.Course]].self, from: cachedData) else {
return self.curriculumsUseCase.courses(targetStudentId: targetStudentId,
year: year,
semester: semester)
}
}

private func initCourses() -> [[Domain.Course]] {
let array = [[Domain.Course]].init(
repeating: [Course].init(
repeating: Course(id: "",
name: "",
instructor: [],
periods: [],
classroom: []),
count: 7),
count: 13)
return array
return Observable.just(cachedCourses)
}

}
10 changes: 4 additions & 6 deletions TAT/ViewModels/CurriculumViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,16 @@ extension CurriculumViewModel {
func transform(input: Input) -> Output {
let semesterInput = SemesterViewModel.Input(targetStudentId: input.targetStudentId)

let semesters = semesterViewModel.transform(input: semesterInput).semesters
let semesterOutput = semesterViewModel.transform(input: semesterInput)

let courseInput = CourseViewModel.Input(year: Observable.just("108"),
semester: Observable.just("1"),
targetStudentId: input.targetStudentId,
searchTrigger: input.searchTrigger)
let courseOutput = courseViewModel.transform(input: courseInput)
let state = courseOutput.state
let courses = courseOutput.courses
return Output(state: state,
semesters: semesters,
courses: courses)
return Output(state: courseOutput.state,
semesters: semesterOutput.semesters,
courses: courseOutput.courses)
}

}
30 changes: 27 additions & 3 deletions TAT/ViewModels/SemesterViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,36 @@ final class SemesterViewModel: NSObject, ViewModelType {
extension SemesterViewModel {

func transform(input: SemesterViewModel.Input) -> SemesterViewModel.Output {
let semesters = input.targetStudentId
.filter { $0 != "" }
.flatMap { [unowned self] (targetStudentId) -> Observable<[Semester]> in
self.generateSemesters(from: targetStudentId)
}
.share()

let semesters = input.targetStudentId.flatMap { [unowned self] (targetStudentId) -> Observable<[Semester]> in
return self.curriculumsUseCase.semesters(targetStudentId: targetStudentId)
}
semesters
.subscribe(onNext: { (semesters) in
if UserDefaults.standard.object(forKey: "semesters") == nil {
guard let semesters = try? JSONEncoder().encode(semesters) else { return }
UserDefaults.standard.set(semesters, forKey: "semesters")
}
}, onError: { (error) in
print(error)
})
.disposed(by: rx.disposeBag)

return Output(semesters: semesters)
}

private func generateSemesters(from targetStudentId: String) -> Observable<[Semester]> {
guard let cachedTargetStudentId = UserDefaults.standard.string(forKey: "targetStudentId"),
cachedTargetStudentId == targetStudentId else {
UserDefaults.standard.set(targetStudentId, forKey: "targetStudentId")
return curriculumsUseCase.semesters(targetStudentId: targetStudentId)
}
guard let cachedData = UserDefaults.standard.object(forKey: "semesters") as? Data,
let cachedSemesters = try? JSONDecoder().decode([Semester].self, from: cachedData)
else { fatalError("cannot cast to semesters") }
return Observable.just(cachedSemesters)
}
}