Skip to content

Bookmark-Feature #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading