-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the course, groups, materials and syllabus screens to the iOS ver…
…sion
- Loading branch information
Showing
5 changed files
with
457 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// | ||
// CourseScreen.swift | ||
// iosApp | ||
// | ||
// Created by Tornike Khintibidze on 25.09.24. | ||
// Copyright © 2024 orgName. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
import ArgosCore | ||
import Observation | ||
|
||
@Observable | ||
class CourseViewModel { | ||
private var fetchTask: Task<Void, Error>? | ||
private var coursesRepository: CoursesRepository { | ||
DiProvider.shared.coursesRepository | ||
} | ||
|
||
var state: CourseState = .loading | ||
|
||
init(courseId: String) { | ||
fetchTask = Task { | ||
for try await courseData in coursesRepository.getCourse(courseId: courseId).flow { | ||
switch onEnum(of: courseData) { | ||
case .loading: | ||
state = .loading | ||
case let .success(course): | ||
state = .success(course.value!) | ||
case .error: | ||
state = .failure | ||
} | ||
} | ||
} | ||
} | ||
|
||
deinit { | ||
fetchTask?.cancel() | ||
} | ||
} | ||
|
||
struct CourseScreen: View { | ||
|
||
let courseId: String | ||
private let viewModel: CourseViewModel | ||
|
||
init(courseId: String) { | ||
self.courseId = courseId | ||
viewModel = .init(courseId: courseId) | ||
} | ||
|
||
var body: some View { | ||
_CourseScreen( | ||
state: viewModel.state, | ||
syllabusPage: SyllabusPage(courseId: courseId), | ||
groupsPage: GroupsPage(courseId: courseId), | ||
materialsPage: MaterialsPage(courseId: courseId) | ||
) | ||
} | ||
} | ||
|
||
struct _CourseScreen<SyllabusPage: View, GroupsPage: View, MaterialsPage: View>: View { | ||
|
||
let state: CourseState | ||
let syllabusPage: SyllabusPage | ||
let groupsPage: GroupsPage | ||
let materialsPage: MaterialsPage | ||
|
||
@State private var selectedIndex = 1 // Show groups by default | ||
|
||
private var navTitle: String? { | ||
if case let .success(course) = state { | ||
return course.name | ||
} | ||
|
||
return nil | ||
} | ||
|
||
var body: some View { | ||
TabPager( | ||
selectedIndex: $selectedIndex, | ||
items: CoursePages.all, | ||
tabContent: { page in | ||
switch page { | ||
case .syllabus: | ||
Text("Syllabus") | ||
case .groups: | ||
Text("Groups") | ||
case .materials: | ||
Text("Materials") | ||
} | ||
} | ||
) { page in | ||
switch page { | ||
case .syllabus: syllabusPage | ||
case .groups: groupsPage | ||
case .materials: materialsPage | ||
} | ||
} | ||
.navigationTitleOptional(navTitle) | ||
.toolbarTitleDisplayMode(.inline) | ||
} | ||
} | ||
|
||
extension View { | ||
|
||
nonisolated func navigationTitleOptional<S: StringProtocol>(_ title: S?) -> some View { | ||
return Group { | ||
if let title { | ||
self.navigationTitle(title) | ||
} else { | ||
self | ||
} | ||
} | ||
} | ||
} | ||
|
||
enum CourseState { | ||
case loading | ||
case success(DomainCourse) | ||
case failure | ||
} | ||
|
||
enum CoursePages { | ||
case syllabus | ||
case groups | ||
case materials | ||
|
||
static var all: [CoursePages] { | ||
[.syllabus, .groups, .materials] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// | ||
// GroupPage.swift | ||
// iosApp | ||
// | ||
// Created by Tornike Khintibidze on 01.10.24. | ||
// Copyright © 2024 orgName. All rights reserved. | ||
// | ||
|
||
import Observation | ||
import SwiftUI | ||
import ArgosCore | ||
|
||
class GroupViewModel { | ||
@ObservationIgnored private var fetchTask: Task<Void, Error>? | ||
@ObservationIgnored private let scheduleResponse: DomainResponseSource<AnyObject, NSArray> | ||
|
||
var state: GroupState = .loading | ||
|
||
init(courseId: String, groupId: String) { | ||
scheduleResponse = DiProvider.shared.coursesRepository.getCourseGroupSchedule(courseId: courseId, groupId: groupId) | ||
fetchTask = Task { | ||
for try await scheduleData in scheduleResponse.flow { | ||
switch onEnum(of: scheduleData) { | ||
case .loading: | ||
state = .loading | ||
case .success(let schedule): | ||
state = .success(schedule.value! as! [DomainCourseGroupSchedule]) | ||
case .error: | ||
state = .failure | ||
} | ||
} | ||
} | ||
} | ||
|
||
deinit { | ||
fetchTask?.cancel() | ||
} | ||
} | ||
|
||
enum GroupState { | ||
case loading | ||
case success([DomainCourseGroupSchedule]) | ||
case failure | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// | ||
// GroupsPage.swift | ||
// iosApp | ||
// | ||
// Created by Tornike Khintibidze on 26.09.24. | ||
// Copyright © 2024 orgName. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
import Observation | ||
import ArgosCore | ||
|
||
@Observable | ||
class GroupsViewModel { | ||
@ObservationIgnored private var coursesRepository: CoursesRepository { | ||
DiProvider.shared.coursesRepository | ||
} | ||
@ObservationIgnored private var fetchTask: Task<Void, Error>? | ||
|
||
var state: GroupsState = .loading | ||
|
||
init(courseId: String) { | ||
fetchTask = Task { | ||
for try await groupsData in coursesRepository.getCourseGroups(courseId: courseId).flow { | ||
switch onEnum(of: groupsData) { | ||
case .loading: | ||
state = .loading | ||
case .success(let groups): | ||
state = .success(groups.value! as! [DomainCourseGroup]) | ||
case .error: | ||
state = .error | ||
} | ||
} | ||
} | ||
} | ||
|
||
deinit { | ||
fetchTask?.cancel() | ||
} | ||
} | ||
|
||
struct GroupsPage: View { | ||
|
||
let viewModel: GroupsViewModel | ||
|
||
init(courseId: String) { | ||
self.viewModel = .init(courseId: courseId) | ||
} | ||
|
||
var body: some View { | ||
_GroupsPage( | ||
state: viewModel.state, | ||
groupScreen: { group in | ||
EmptyView() | ||
} | ||
) | ||
} | ||
} | ||
|
||
struct _GroupsPage<GroupScreen: View>: View { | ||
|
||
let state: GroupsState | ||
let groupScreen: (DomainCourseGroup) -> GroupScreen | ||
|
||
@State private var selection: String? | ||
|
||
var body: some View { | ||
switch state { | ||
case .loading: | ||
ProgressView() | ||
case .success(let groups): | ||
List(groups, id: \.id) { group in | ||
GroupCard( | ||
group: group, | ||
groupScreen: { groupScreen(group) } | ||
) | ||
.listRowSeparator(.hidden) | ||
} | ||
.listStyle(.inset) | ||
.scrollIndicators(.visible) | ||
.listRowSpacing(12) | ||
.contentMargins(12, for: .scrollContent) | ||
.scrollContentBackground(.hidden) | ||
case .error: | ||
Text("Error") | ||
} | ||
} | ||
} | ||
|
||
enum GroupsState { | ||
case loading | ||
case success([DomainCourseGroup]) | ||
case error | ||
} | ||
|
||
private struct GroupCard<GroupScreen: View>: View { | ||
let group: DomainCourseGroup | ||
let groupScreen: () -> GroupScreen | ||
|
||
private var lecturersString: String { | ||
group.lecturers.map { $0.fullName }.joined(separator: ", ") | ||
} | ||
|
||
@State var isTapped: Bool = false | ||
|
||
var body: some View { | ||
NavigationLink(destination: groupScreen) { | ||
VStack(alignment: .leading) { | ||
Text(lecturersString) | ||
Text(group.name) | ||
HStack { | ||
if (group.isChosen) { | ||
Button(action: {}) { | ||
Text("Rechoose") | ||
} | ||
.tint(Color.orange) | ||
.opacity(group.rechooseError == nil ? 1.0 : 0.7) | ||
|
||
Button(action: {}) { | ||
Text("Remove") | ||
} | ||
.tint(Color.red) | ||
.opacity(group.removeError == nil ? 1.0 : 0.7) | ||
} else { | ||
Button(action: {}) { | ||
Text("Choose") | ||
} | ||
.tint(Color.green) | ||
.opacity(group.chooseError == nil ? 1.0 : 0.7) | ||
} | ||
} | ||
.buttonStyle(.borderedProminent) | ||
} | ||
} | ||
.listRowBackground( | ||
EmptyView() | ||
.background(.thinMaterial) | ||
.clipShape(RoundedRectangle(cornerRadius: 12)) | ||
.opacity(isTapped ? 0 : 1) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// | ||
// MaterialsPage.swift | ||
// iosApp | ||
// | ||
// Created by Tornike Khintibidze on 27.09.24. | ||
// Copyright © 2024 orgName. All rights reserved. | ||
// | ||
|
||
import SwiftUI | ||
import Observation | ||
import ArgosCore | ||
|
||
@Observable | ||
class MaterialsViewModel { | ||
private var fetchTask: Task<Void, Error>? | ||
private var coursesRepository: CoursesRepository { | ||
DiProvider.shared.coursesRepository | ||
} | ||
|
||
var materials: PagingItemsObservable<DomainCourseMaterial>? | ||
|
||
init(courseId: String) { | ||
fetchTask = Task { | ||
materials = PagingItemsObservable(coursesRepository.getCourseGroupMaterials(courseId: courseId)) | ||
} | ||
} | ||
|
||
deinit { | ||
fetchTask?.cancel() | ||
} | ||
} | ||
|
||
struct MaterialsPage: View { | ||
|
||
private let viewModel: MaterialsViewModel | ||
|
||
init(courseId: String) { | ||
viewModel = MaterialsViewModel(courseId: courseId) | ||
} | ||
|
||
var body: some View { | ||
_MaterialsPage(materials: viewModel.materials) | ||
} | ||
} | ||
|
||
struct _MaterialsPage: View { | ||
|
||
let materials: PagingItemsObservable<DomainCourseMaterial>? | ||
|
||
var body: some View { | ||
if let materials = materials { | ||
List(materials) { material in | ||
Text(material.name) | ||
} | ||
} | ||
} | ||
} | ||
|
||
enum MaterialsState { | ||
case loading | ||
case success([DomainCourseMaterial]) | ||
case error | ||
} |
Oops, something went wrong.