Skip to content

Commit

Permalink
feat: navigate to course component from course dates tab (#189)
Browse files Browse the repository at this point in the history
* feat: navigate to course component from course dates tab

* chore: renamed methods and used already fetched course data for course hyperlinks

* refactor: address review feedback
  • Loading branch information
saeedbashir authored Dec 11, 2023
1 parent e7eb4c8 commit a7eb8e1
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 26 deletions.
12 changes: 6 additions & 6 deletions Course/Course/Data/CourseRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import Core
public protocol CourseRepositoryProtocol {
func getCourseDetails(courseID: String) async throws -> CourseDetails
func getCourseBlocks(courseID: String) async throws -> CourseStructure
func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails
func getCourseBlocksOffline(courseID: String) throws -> CourseStructure
func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails
func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure
func enrollToCourse(courseID: String) async throws -> Bool
func blockCompletionRequest(courseID: String, blockID: String) async throws
func getHandouts(courseID: String) async throws -> String?
Expand Down Expand Up @@ -48,7 +48,7 @@ public class CourseRepository: CourseRepositoryProtocol {
return response
}

public func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails {
public func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails {
return try persistence.loadCourseDetails(courseID: courseID)
}

Expand All @@ -61,7 +61,7 @@ public class CourseRepository: CourseRepositoryProtocol {
return parsedStructure
}

public func getCourseBlocksOffline(courseID: String) throws -> CourseStructure {
public func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure {
let localData = try persistence.loadCourseStructure(courseID: courseID)
return parseCourseStructure(course: localData)
}
Expand Down Expand Up @@ -259,7 +259,7 @@ class CourseRepositoryMock: CourseRepositoryProtocol {
}
}

func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails {
func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails {
return CourseDetails(
courseID: "courseID",
org: "Organization",
Expand All @@ -276,7 +276,7 @@ class CourseRepositoryMock: CourseRepositoryProtocol {
)
}

func getCourseBlocksOffline(courseID: String) throws -> CourseStructure {
func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure {
let decoder = JSONDecoder()
let jsonData = Data(courseStructureJson.utf8)
let courseBlocks = try decoder.decode(DataLayer.CourseStructure.self, from: jsonData)
Expand Down
12 changes: 6 additions & 6 deletions Course/Course/Domain/CourseInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public protocol CourseInteractorProtocol {
func getCourseDetails(courseID: String) async throws -> CourseDetails
func getCourseBlocks(courseID: String) async throws -> CourseStructure
func getCourseVideoBlocks(fullStructure: CourseStructure) -> CourseStructure
func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails
func getCourseBlocksOffline(courseID: String) async throws -> CourseStructure
func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails
func getLoadedCourseBlocks(courseID: String) async throws -> CourseStructure
func enrollToCourse(courseID: String) async throws -> Bool
func blockCompletionRequest(courseID: String, blockID: String) async throws
func getHandouts(courseID: String) async throws -> String?
Expand Down Expand Up @@ -62,12 +62,12 @@ public class CourseInteractor: CourseInteractorProtocol {
)
}

public func getCourseDetailsOffline(courseID: String) async throws -> CourseDetails {
return try await repository.getCourseDetailsOffline(courseID: courseID)
public func getLoadedCourseDetails(courseID: String) async throws -> CourseDetails {
return try await repository.getLoadedCourseDetails(courseID: courseID)
}

public func getCourseBlocksOffline(courseID: String) async throws -> CourseStructure {
return try repository.getCourseBlocksOffline(courseID: courseID)
public func getLoadedCourseBlocks(courseID: String) async throws -> CourseStructure {
return try repository.getLoadedCourseBlocks(courseID: courseID)
}

public func enrollToCourse(courseID: String) async throws -> Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public class CourseContainerViewModel: BaseCourseViewModel {
}
}
} else {
courseStructure = try await interactor.getCourseBlocksOffline(courseID: courseID)
courseStructure = try await interactor.getLoadedCourseBlocks(courseID: courseID)
}
courseVideosStructure = interactor.getCourseVideoBlocks(fullStructure: courseStructure!)
setDownloadsStates()
Expand Down
10 changes: 10 additions & 0 deletions Course/Course/Presentation/CourseRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public protocol CourseRouter: BaseRouter {
router: Course.CourseRouter,
cssInjector: CSSInjector
)

func showCourseComponent(
componentID: String,
courseStructure: CourseStructure
)
}

// Mark - For testing and SwiftUI preview
Expand Down Expand Up @@ -119,5 +124,10 @@ public class CourseRouterMock: BaseRouterMock, CourseRouter {
cssInjector: CSSInjector
) {}

public func showCourseComponent(
componentID: String,
courseStructure: CourseStructure
) {}

}
#endif
8 changes: 6 additions & 2 deletions Course/Course/Presentation/Dates/CourseDatesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ struct CourseDateListView: View {
lastDate: viewModel.sortedDates.last,
allHaveSameStatus: allHaveSameStatus)

BlockStatusView(block: block,
BlockStatusView(viewModel: viewModel,
block: block,
allHaveSameStatus: allHaveSameStatus,
blocks: blocks)

Expand All @@ -159,6 +160,7 @@ struct CourseDateListView: View {
}

struct BlockStatusView: View {
let viewModel: CourseDatesViewModel
let block: CourseDateBlock
let allHaveSameStatus: Bool
let blocks: [CourseDateBlock]
Expand Down Expand Up @@ -227,7 +229,9 @@ struct BlockStatusView: View {
}
}())
.onTapGesture {

Task {
await viewModel.showCourseDetails(componentID: block.firstComponentBlockID)
}
}
}

Expand Down
14 changes: 14 additions & 0 deletions Course/Course/Presentation/Dates/CourseDatesViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class CourseDatesViewModel: ObservableObject {
let cssInjector: CSSInjector
let router: CourseRouter
let connectivity: ConnectivityProtocol
let courseID: String

public init(
interactor: CourseInteractorProtocol,
Expand All @@ -39,6 +40,7 @@ public class CourseDatesViewModel: ObservableObject {
self.router = router
self.cssInjector = cssInjector
self.connectivity = connectivity
self.courseID = courseID
}

var sortedDates: [Date] {
Expand Down Expand Up @@ -69,4 +71,16 @@ public class CourseDatesViewModel: ObservableObject {
}
}
}

func showCourseDetails(componentID: String) async {
do {
let courseStructure = try await interactor.getLoadedCourseBlocks(courseID: courseID)
router.showCourseComponent(
componentID: componentID,
courseStructure: courseStructure
)
} catch _ {
errorMessage = CourseLocalization.Error.componentNotFount
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class CourseDetailsViewModel: ObservableObject {

isShowProgress = false
} else {
courseDetails = try await interactor.getCourseDetailsOffline(courseID: courseID)
courseDetails = try await interactor.getLoadedCourseDetails(courseID: courseID)
if let isEnrolled = courseDetails?.isEnrolled {
self.courseDetails?.isEnrolled = isEnrolled
}
Expand Down
2 changes: 2 additions & 0 deletions Course/Course/SwiftGen/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public enum CourseLocalization {
public static let viewCourse = CourseLocalization.tr("Localizable", "DETAILS.VIEW_COURSE", fallback: "View course")
}
public enum Error {
/// Course component not found, please reload
public static let componentNotFount = CourseLocalization.tr("Localizable", "ERROR.COMPONENT_NOT_FOUNT", fallback: "Course component not found, please reload")
/// You are not connected to the Internet. Please check your Internet connection.
public static let noInternet = CourseLocalization.tr("Localizable", "ERROR.NO_INTERNET", fallback: "You are not connected to the Internet. Please check your Internet connection.")
/// Reload
Expand Down
1 change: 1 addition & 0 deletions Course/Course/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

"ERROR.NO_INTERNET" = "You are not connected to the Internet. Please check your Internet connection.";
"ERROR.RELOAD" = "Reload";
"ERROR.COMPONENT_NOT_FOUNT" = "Course component not found, please reload";

"ALERT.ROTATE_DEVICE" = "Rotate your device to view this video in full screen.";

Expand Down
1 change: 1 addition & 0 deletions Course/Course/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

"ERROR.NO_INTERNET" = "Ви не підключені до Інтернету. Перевірте підключення до Інтернету і спробуйте ще.";
"ERROR.RELOAD" = "Перезавантажити";
"ERROR.COMPONENT_NOT_FOUNT" = "Course component not found, please reload";

"ALERT.ROTATE_DEVICE" = "Поверніть пристрій, щоб переглянути це відео на весь екран.";

Expand Down
20 changes: 10 additions & 10 deletions Course/CourseTests/CourseMock.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1649,13 +1649,13 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock {
return __value
}

open func getCourseDetailsOffline(courseID: String) throws -> CourseDetails {
addInvocation(.m_getCourseDetailsOffline__courseID_courseID(Parameter<String>.value(`courseID`)))
let perform = methodPerformValue(.m_getCourseDetailsOffline__courseID_courseID(Parameter<String>.value(`courseID`))) as? (String) -> Void
perform?(`courseID`)
open func getLoadedCourseDetails(courseID: String) throws -> CourseDetails {
addInvocation(.m_getCourseDetailsOffline__courseID_courseID(Parameter<String>.value(courseID)))
let perform = methodPerformValue(.m_getCourseDetailsOffline__courseID_courseID(Parameter<String>.value(courseID))) as? (String) -> Void
perform?(courseID)
var __value: CourseDetails
do {
__value = try methodReturnValue(.m_getCourseDetailsOffline__courseID_courseID(Parameter<String>.value(`courseID`))).casted()
__value = try methodReturnValue(.m_getCourseDetailsOffline__courseID_courseID(Parameter<String>.value(courseID))).casted()
} catch MockError.notStubed {
onFatalFailure("Stub return value not specified for getCourseDetailsOffline(courseID: String). Use given")
Failure("Stub return value not specified for getCourseDetailsOffline(courseID: String). Use given")
Expand All @@ -1665,13 +1665,13 @@ open class CourseInteractorProtocolMock: CourseInteractorProtocol, Mock {
return __value
}

open func getCourseBlocksOffline(courseID: String) throws -> CourseStructure {
addInvocation(.m_getCourseBlocksOffline__courseID_courseID(Parameter<String>.value(`courseID`)))
let perform = methodPerformValue(.m_getCourseBlocksOffline__courseID_courseID(Parameter<String>.value(`courseID`))) as? (String) -> Void
perform?(`courseID`)
open func getLoadedCourseBlocks(courseID: String) throws -> CourseStructure {
addInvocation(.m_getCourseBlocksOffline__courseID_courseID(Parameter<String>.value(courseID)))
let perform = methodPerformValue(.m_getCourseBlocksOffline__courseID_courseID(Parameter<String>.value(courseID))) as? (String) -> Void
perform?(courseID)
var __value: CourseStructure
do {
__value = try methodReturnValue(.m_getCourseBlocksOffline__courseID_courseID(Parameter<String>.value(`courseID`))).casted()
__value = try methodReturnValue(.m_getCourseBlocksOffline__courseID_courseID(Parameter<String>.value(courseID))).casted()
} catch MockError.notStubed {
onFatalFailure("Stub return value not specified for getCourseBlocksOffline(courseID: String). Use given")
Failure("Stub return value not specified for getCourseBlocksOffline(courseID: String). Use given")
Expand Down
28 changes: 28 additions & 0 deletions OpenEdX/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,34 @@ public class Router: AuthorizationRouter,
navigationController.pushViewController(controller, animated: true)
}

public func showCourseComponent(
componentID: String,
courseStructure: CourseStructure) {
courseStructure.childs.enumerated().forEach { chapterIndex, chapter in
chapter.childs.enumerated().forEach { sequentialIndex, sequential in
sequential.childs.enumerated().forEach { verticalIndex, vertical in
vertical.childs.forEach { block in
if block.id == componentID {
DispatchQueue.main.async { [weak self] in
guard let self else { return }
self.showCourseUnit(
courseName: courseStructure.displayName,
blockId: block.blockId,
courseID: courseStructure.id,
sectionName: sequential.displayName,
verticalIndex: verticalIndex,
chapters: courseStructure.childs,
chapterIndex: chapterIndex,
sequentialIndex: sequentialIndex)
}
return
}
}
}
}
}
}

public func replaceCourseUnit(
courseName: String,
blockId: String,
Expand Down

0 comments on commit a7eb8e1

Please sign in to comment.