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

Commit

Permalink
Merge pull request #18 from NEO-TAT/feature/cache-curriculum
Browse files Browse the repository at this point in the history
[Feature] Add cache
  • Loading branch information
jamfly authored Oct 2, 2019
2 parents c0294f2 + e09e9d7 commit fb72db7
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 56 deletions.
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)
}
}

0 comments on commit fb72db7

Please sign in to comment.