Skip to content

Commit

Permalink
Merge pull request #25 from Ditectrev/develop
Browse files Browse the repository at this point in the history
added bookmark feature
  • Loading branch information
b3n3w authored Jun 5, 2024
2 parents 7baf030 + d6d96f7 commit e1649aa
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 45 deletions.
4 changes: 4 additions & 0 deletions CloudMaster.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
8D26A31C2C0EA9C100E9B015 /* QuestionNavbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D26A31B2C0EA9C100E9B015 /* QuestionNavbar.swift */; };
8D26A31E2C0EE3F400E9B015 /* QuestionImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D26A31D2C0EE3F400E9B015 /* QuestionImages.swift */; };
8D26A3202C0EE4A000E9B015 /* QuestionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D26A31F2C0EE4A000E9B015 /* QuestionView.swift */; };
8D26A3222C101C5000E9B015 /* BookmarksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D26A3212C101C5000E9B015 /* BookmarksView.swift */; };
8D8D8A862C05A23600ACC61C /* CloudMasterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8D8A852C05A23600ACC61C /* CloudMasterTests.swift */; };
8D8D8A902C05A23600ACC61C /* CloudMasterUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8D8A8F2C05A23600ACC61C /* CloudMasterUITests.swift */; };
8D8D8A922C05A23600ACC61C /* CloudMasterUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D8D8A912C05A23600ACC61C /* CloudMasterUITestsLaunchTests.swift */; };
Expand Down Expand Up @@ -67,6 +68,7 @@
8D26A31B2C0EA9C100E9B015 /* QuestionNavbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionNavbar.swift; sourceTree = "<group>"; };
8D26A31D2C0EE3F400E9B015 /* QuestionImages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionImages.swift; sourceTree = "<group>"; };
8D26A31F2C0EE4A000E9B015 /* QuestionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuestionView.swift; sourceTree = "<group>"; };
8D26A3212C101C5000E9B015 /* BookmarksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksView.swift; sourceTree = "<group>"; };
8D8D8A712C05A23400ACC61C /* CloudMaster Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CloudMaster Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; };
8D8D8A812C05A23600ACC61C /* CloudMasterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CloudMasterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8D8D8A852C05A23600ACC61C /* CloudMasterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudMasterTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -225,6 +227,7 @@
isa = PBXGroup;
children = (
8D8D8AA42C05A27800ACC61C /* CourseView.swift */,
8D26A3212C101C5000E9B015 /* BookmarksView.swift */,
);
path = Views;
sourceTree = "<group>";
Expand Down Expand Up @@ -603,6 +606,7 @@
8D8D8AE12C05A27800ACC61C /* Courses.swift in Sources */,
8D26A31C2C0EA9C100E9B015 /* QuestionNavbar.swift in Sources */,
8D8D8AD12C05A27800ACC61C /* CourseView.swift in Sources */,
8D26A3222C101C5000E9B015 /* BookmarksView.swift in Sources */,
8DABB7742C0D7D0300B40E25 /* DownloadViewModel.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
87 changes: 87 additions & 0 deletions CloudMaster/Features/Course/Views/BookmarksView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import SwiftUI

struct BookmarksView: View {
@State private var bookmarks: [Bookmark] = []

var body: some View {
NavigationView {

if bookmarks.isEmpty {
VStack {
Image(systemName: "bookmark")
.resizable()
.frame(width: 80, height: 100)
.foregroundColor(.gray)
.padding(.bottom, 20)
Text("No questions saved")
.font(.title)
.foregroundColor(.gray)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
List {
ForEach(bookmarks) { bookmark in
NavigationLink(destination: QuestionDetailView(question: bookmark.question, bookmarks: $bookmarks)) {
VStack(alignment: .leading) {
Text(bookmark.question.question.prefix(40) + "...")
.font(.headline)
.lineLimit(2)
.padding(.vertical,5)
}
}
}
}
}
}
.navigationTitle("Bookmarks")
.onAppear {
bookmarks = FavoritesStorage.shared.loadBookmarks()
}
}
}

struct QuestionDetailView: View {
let question: Question
@Binding var bookmarks: [Bookmark]
@State private var isBookmarked: Bool = false
@Environment(\.presentationMode) var presentationMode

var body: some View {
QuestionView(
mode: .bookmarked,
question: question,
selectedChoices: nil,
isMultipleResponse: question.multipleResponse,
isResultShown: true,
onChoiceSelected: { _ in }
)
.navigationTitle("Question")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing: bookmarkButton)
.onAppear {
isBookmarked = FavoritesStorage.shared.isBookmarked(question)
}
}

private var bookmarkButton: some View {
Button(action: {
toggleBookmark()
}) {
Image(systemName: isBookmarked ? "bookmark.fill" : "bookmark")

}
}

private func toggleBookmark() {
if isBookmarked {
FavoritesStorage.shared.removeBookmarkByQuestionText(question.question)
bookmarks = FavoritesStorage.shared.loadBookmarks()
presentationMode.wrappedValue.dismiss()
} else {
let newBookmark = Bookmark(id: UUID(), question: question, answer: question.choices)
FavoritesStorage.shared.addBookmark(newBookmark)
bookmarks = FavoritesStorage.shared.loadBookmarks()
}
isBookmarked.toggle()
}
}
81 changes: 68 additions & 13 deletions CloudMaster/Features/Course/Views/CourseView.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import SwiftUI

struct CourseView: View {
@State private var isLoading = false
@State private var downloadProgress: [Course: Progress] = [:]
@State private var userTrainingData = UserTrainingData()
@State private var showingNotificationSettings = false
@State private var notificationsEnabled = false
@State private var showingInfoPopup = false

@StateObject private var viewModel = DownloadViewModel()
@StateObject private var questionLoader: QuestionLoader

@Environment(\.colorScheme) var colorScheme

let course: Course

init(course: Course) {
Expand Down Expand Up @@ -41,7 +44,7 @@ struct CourseView: View {

Spacer()
VStack(spacing: 20) {
NavigationLink(destination: TrainingView(course: course, questionLoader: questionLoader)) {
NavigationLink(destination: TrainingView(course: course)) {
VStack {
Text("Training")
.font(.title)
Expand All @@ -55,7 +58,7 @@ struct CourseView: View {
.cornerRadius(10)
}

NavigationLink(destination: TrainingView(course: course, questionLoader: questionLoader)) {
NavigationLink(destination: TrainingView(course: course)) {
VStack {
Text("Intelligent Training")
.font(.title)
Expand Down Expand Up @@ -85,14 +88,16 @@ struct CourseView: View {
}
Spacer()

HStack(spacing: 20) {
Link("Certification", destination: URL(string: course.url)!)
.padding()
.font(.subheadline)

Link("Sources", destination: URL(string: course.repositoryURL)!)
.padding()
.font(.subheadline)
NavigationLink(destination: BookmarksView()) {
HStack {
Image(systemName: "bookmark")
.font(.title3)
.foregroundColor(colorScheme == .dark ? .white : .black)
Text("Bookmarks")
.font(.title3)
.foregroundColor(colorScheme == .dark ? .white : .black)
}
.cornerRadius(10)
}
}
.onAppear {
Expand All @@ -104,14 +109,20 @@ struct CourseView: View {
}
}
.navigationBarTitle(course.shortName, displayMode: .inline)
.navigationBarItems(trailing: notificationButton)
.navigationBarItems(trailing: HStack {
notificationButton
infoButton
})
.navigationBarBackButtonHidden(false)
.sheet(isPresented: $showingNotificationSettings) {
NotificationSettingsView(isPresented: $showingNotificationSettings, notificationsEnabled: $notificationsEnabled, course: course)
.onDisappear {
checkNotificationSettings()
}
}
.sheet(isPresented: $showingInfoPopup) {
CourseInformationPopup(course: course)
}
.overlay(
DownloadOverlayView(
isShowing: $viewModel.isDownloading,
Expand All @@ -135,6 +146,14 @@ struct CourseView: View {
}
}

private var infoButton: some View {
Button(action: {
showingInfoPopup = true
}) {
Image(systemName: "info.circle")
}
}

func loadUserTrainingData(for course: Course) {
if let data = UserDefaults.standard.data(forKey: course.shortName) {
if let decodedData = try? JSONDecoder().decode(UserTrainingData.self, from: data) {
Expand Down Expand Up @@ -163,7 +182,7 @@ struct CourseView: View {
func downloadCourse() {
viewModel.downloadCourse(course)
viewModel.$isDownloading.sink { isDownloading in
if !isDownloading {
if (!isDownloading) {
DispatchQueue.main.async {
questionLoader.reloadQuestions(from: course.shortName + ".json")
}
Expand All @@ -181,3 +200,39 @@ struct CourseView: View {
.store(in: &viewModel.cancellables)
}
}

struct CourseInformationPopup: View {
let course: Course

var body: some View {
VStack(spacing: 20) {
Text("Course information")
.font(.title2)
.multilineTextAlignment(.center)

VStack(spacing: 10) {
HStack {
Spacer()
Image(systemName: "book.pages.fill")
Text("Certification")
Spacer()
}
Link(course.url, destination: URL(string: course.url)!)
}

VStack(spacing: 10) {
HStack {
Spacer()
Image(systemName: "link")
Text("Sources")
Spacer()
}
Link(course.repositoryURL, destination: URL(string: course.repositoryURL)!)
}

Spacer()
}
.padding()
}

}
9 changes: 6 additions & 3 deletions CloudMaster/Features/Exam/Views/ExamModesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ struct ExamModeView: View {
let course: Course
@ObservedObject var examDataStore = UserExamDataStore.shared

@Environment(\.colorScheme) var colorScheme

var body: some View {
VStack {

Expand Down Expand Up @@ -68,12 +70,13 @@ struct ExamModeView: View {
NavigationLink(destination: PreviousExamsView(exams: filteredExams)) {
HStack {
Image(systemName: "clock.arrow.circlepath")
.font(.title3)
.foregroundColor(colorScheme == .dark ? .white : .black)
Text("Exam History")
.font(.title3)
.foregroundColor(filteredExams.isEmpty ? .gray : (colorScheme == .dark ? .white : .black))
}
.padding()
.background(filteredExams.isEmpty ? Color.customAccent : Color.customPrimary)
.foregroundColor(.white)
.cornerRadius(10)
}
.disabled(filteredExams.isEmpty)
}
Expand Down
19 changes: 18 additions & 1 deletion CloudMaster/Features/Shared/Components/QuestionNavbar.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import SwiftUI

struct QuestionNavbar: View {
@Environment(\.presentationMode) var presentationMode
let currentQuestionIndex: Int
let totalQuestions: Int
let question: Question

@Binding var isBookmarked: Bool

var body: some View {
HStack {
Button(action: {
Expand All @@ -22,6 +24,21 @@ struct QuestionNavbar: View {
.foregroundColor(.secondary)

Spacer()

Button(action: {
if isBookmarked {
FavoritesStorage.shared.removeBookmarkByQuestionText(question.question)
isBookmarked = false
} else {
let newBookmark = Bookmark(id: UUID(), question: question, answer: question.choices)
FavoritesStorage.shared.addBookmark(newBookmark)
isBookmarked = true
}
}) {
Image(systemName: isBookmarked ? "bookmark.fill" : "bookmark")
.font(.title3)
.foregroundColor(.blue)
}
}
.padding()
}
Expand Down
34 changes: 25 additions & 9 deletions CloudMaster/Features/Shared/Components/QuestionView.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
//
// QuestionView.swift
// CloudMaster
//
// Created by Benedikt Wagner on 04.06.24.
//

import Foundation
import SwiftUI

struct QuestionView: View {
enum Mode {
case training
case exam
case bookmarked
}

let mode: Mode
Expand Down Expand Up @@ -67,12 +61,17 @@ struct QuestionView: View {
isResultShown: isResultShown ?? false,
onChoiceSelected: onChoiceSelected
)
} else {
} else if mode == .exam {
ExamChoice(
choice: choice,
isSelected: selectedChoices?.contains(choice.id) == true,
onChoiceSelected: onChoiceSelected
)
} else if mode == .bookmarked {
BookmarkedChoice(
choice: choice,
isSelected: selectedChoices?.contains(choice.id) == true
)
}
}
}
Expand Down Expand Up @@ -163,7 +162,24 @@ struct ExamChoice: View {
.background(isSelected ? Color.gray.opacity(0.3) : Color.clear)
.cornerRadius(10)
.padding(.horizontal)
.foregroundColor(.white)

Divider()
}
}

struct BookmarkedChoice: View {
let choice: Choice
let isSelected: Bool

var body: some View {
Text(choice.text)
.padding()
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.multilineTextAlignment(.center)
.background(choice.correct ? Color.correct : (isSelected ? Color.wrong : Color.gray.opacity(0.3)))
.foregroundColor(.white)
.cornerRadius(10)
.padding(.horizontal)

Divider()
}
Expand Down
Loading

0 comments on commit e1649aa

Please sign in to comment.