Skip to content

Commit

Permalink
Add the course, groups, materials and syllabus screens to the iOS ver…
Browse files Browse the repository at this point in the history
…sion
  • Loading branch information
X1nto committed Oct 26, 2024
1 parent 807f794 commit 640db4f
Show file tree
Hide file tree
Showing 5 changed files with 457 additions and 0 deletions.
132 changes: 132 additions & 0 deletions iosApp/iosApp/Course Screen/CourseScreen.swift
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]
}
}
44 changes: 44 additions & 0 deletions iosApp/iosApp/Course Screen/GroupPage.swift
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
}
142 changes: 142 additions & 0 deletions iosApp/iosApp/Course Screen/GroupsPage.swift
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)
)
}
}
63 changes: 63 additions & 0 deletions iosApp/iosApp/Course Screen/MaterialsPage.swift
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
}
Loading

0 comments on commit 640db4f

Please sign in to comment.