diff --git a/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ad0468c..898596b 100644 --- a/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5b3893af96db1c7a5e8d26d4b6f7b87fb1b4833e12c80c07842b211a8ba6e753", + "originHash" : "639d5d90ba6550e8200b28dab400da314adf7395e4d9c9622a0c3a880ab8780b", "pins" : [ { "identity" : "abseil-cpp-binary", diff --git a/VITTY/VITTY/Academics/View/CourseRefs.swift b/VITTY/VITTY/Academics/View/CourseRefs.swift index e43f3ab..615a5eb 100644 --- a/VITTY/VITTY/Academics/View/CourseRefs.swift +++ b/VITTY/VITTY/Academics/View/CourseRefs.swift @@ -5,17 +5,9 @@ // Created by Rujin Devkota on 2/27/25. -// -// Academics.swift -// VITTY -// -// Created by Rujin Devkota on 2/27/25. - - import SwiftUI import SwiftData -struct OCourseRefs: View { struct OCourseRefs: View { var courseName: String var courseInstitution: String @@ -25,7 +17,6 @@ struct OCourseRefs: View { @State private var showBottomSheet = false @State private var showReminderSheet = false @State private var showNotes = false - @State private var showNotes = false @State private var navigateToNotesEditor = false @State var showCourseNotes: Bool = false @State private var selectedNote: CreateNoteModel? @@ -45,7 +36,6 @@ struct OCourseRefs: View { @Environment(\.dismiss) private var dismiss @Environment(\.modelContext) private var modelContext - @Environment(\.modelContext) private var modelContext private let maxVisible = 4 @@ -53,19 +43,6 @@ struct OCourseRefs: View { @Query private var courseNotes: [CreateNoteModel] @Query private var courseFiles: [UploadedFile] - enum ContentType: String, CaseIterable { - case notes = "Notes" - case files = "Files" - - var icon: String { - switch self { - case .notes: return "doc.text" - case .files: return "folder" - } - } - } - @Query private var courseFiles: [UploadedFile] - enum ContentType: String, CaseIterable { case notes = "Notes" case files = "Files" @@ -93,7 +70,6 @@ struct OCourseRefs: View { let notesPredicate = #Predicate { $0.courseId == courseCode - $0.courseId == courseCode } _courseNotes = Query( FetchDescriptor(predicate: notesPredicate, sortBy: [SortDescriptor(\.createdAt, order: .reverse)]) @@ -118,34 +94,6 @@ struct OCourseRefs: View { } } - private var filteredFiles: [UploadedFile] { - if searchText.isEmpty { - return courseFiles - } else { - return courseFiles.filter { file in - file.fileName.localizedCaseInsensitiveContains(searchText) - } - } - - - let filesPredicate = #Predicate { - $0.courseCode == courseCode - } - _courseFiles = Query( - FetchDescriptor(predicate: filesPredicate, sortBy: [SortDescriptor(\.uploadDate, order: .reverse)]) - ) - } - - private var filteredNotes: [CreateNoteModel] { - if searchText.isEmpty { - return courseNotes - } else { - return courseNotes.filter { note in - note.noteName.localizedCaseInsensitiveContains(searchText) - } - } - } - private var filteredFiles: [UploadedFile] { if searchText.isEmpty { return courseFiles @@ -180,14 +128,6 @@ struct OCourseRefs: View { Spacer() - if selectedContentType == .files && !courseFiles.isEmpty { - Button("View All") { - showFileGallery = true - } - .font(.system(size: 14, weight: .medium)) - .foregroundColor(Color("Secondary")) - - if selectedContentType == .files && !courseFiles.isEmpty { Button("View All") { showFileGallery = true @@ -200,7 +140,6 @@ struct OCourseRefs: View { HStack { Spacer() - TextField(selectedContentType == .notes ? "Search notes..." : "Search files...", text: $searchText) TextField(selectedContentType == .notes ? "Search notes..." : "Search files...", text: $searchText) .padding(10) .frame(width: UIScreen.main.bounds.width * 0.85) @@ -208,18 +147,12 @@ struct OCourseRefs: View { .clipShape(RoundedRectangle(cornerRadius: 10)) .padding(.horizontal) .foregroundColor(.white) - .foregroundColor(.white) Spacer() } - Spacer().frame(height: 15) - - - - Spacer().frame(height: 15) Text("\(courseName) - \(courseInstitution)") @@ -245,24 +178,6 @@ struct OCourseRefs: View { } .padding(.horizontal) .padding(.top, 10) - - HStack(spacing: 12) { - ForEach(ContentType.allCases, id: \.self) { contentType in - ContentTypeTab( - contentType: contentType, - isSelected: selectedContentType == contentType, - count: contentType == .notes ? filteredNotes.count : filteredFiles.count - ) { - withAnimation(.easeInOut(duration: 0.2)) { - selectedContentType = contentType - searchText = "" - } - } - } - Spacer() - } - .padding(.horizontal) - .padding(.top, 10) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { @@ -284,16 +199,6 @@ struct OCourseRefs: View { VStack(alignment: .leading, spacing: 15) { if selectedContentType == .notes { - if filteredNotes.isEmpty { - EmptyStateView( - icon: searchText.isEmpty ? "doc.text" : "magnifyingglass", - title: searchText.isEmpty ? "No notes found for this course" : "No notes match your search", - subtitle: searchText.isEmpty ? nil : "Try searching with different keywords" - ) - } else { - ForEach(filteredNotes, id: \.createdAt) { note in - if selectedContentType == .notes { - if filteredNotes.isEmpty { EmptyStateView( icon: searchText.isEmpty ? "doc.text" : "magnifyingglass", @@ -339,50 +244,6 @@ struct OCourseRefs: View { } } - if filteredFiles.count > 6 { - Button("View All \(filteredFiles.count) Files") { - showFileGallery = true - } - .font(.system(size: 14, weight: .medium)) - .foregroundColor(Color("Secondary")) - .padding(.top, 8) - .frame(maxWidth: .infinity) - } - description: note.cachedPlainText, - isLoading: loadingNoteId == note.createdAt, - onDelete: { - noteToDelete = note - showDeleteAlert = true - } - ) - .onTapGesture { - openNote(note) - } - } - } - } else { - - if filteredFiles.isEmpty { - EmptyStateView( - icon: searchText.isEmpty ? "folder" : "magnifyingglass", - title: searchText.isEmpty ? "No files found for this course" : "No files match your search", - subtitle: searchText.isEmpty ? "Upload some files to get started" : "Try searching with different keywords" - ) - } else { - LazyVGrid(columns: [ - GridItem(.flexible()), - GridItem(.flexible()) - ], spacing: 12) { - ForEach(Array(filteredFiles.prefix(6)), id: \.id) { file in - CompactFileCard(file: file) { - - - showimgDeleteAlert = true - fileToDelete = file - } - } - } - if filteredFiles.count > 6 { Button("View All \(filteredFiles.count) Files") { showFileGallery = true @@ -535,27 +396,9 @@ struct OCourseRefs: View { .sheet(isPresented: $showFileGallery) { FileGalleryView(courseCode: courseCode) } - .sheet(isPresented: $showFileUpload) { - FileUploadView(courseName: courseName, courseCode: courseCode) - } - .sheet(isPresented: $showFileGallery) { - FileGalleryView(courseCode: courseCode) - } .navigationDestination(isPresented: $navigateToNotesEditor) { NoteEditorView(courseCode: courseCode, courseName: courseName, courseIns: courseInstitution, courseSlot: slot) } - .sheet(isPresented: $showNotes, content: { - NoteEditorView( - existingNote: selectedNote, - preloadedAttributedString: preloadedAttributedString, - courseCode: courseCode, - courseName: courseName, - courseIns: courseInstitution, - courseSlot: slot - ) - }) - NoteEditorView(courseCode: courseCode, courseName: courseName, courseIns: courseInstitution, courseSlot: slot) - } .sheet(isPresented: $showNotes, content: { NoteEditorView( existingNote: selectedNote, @@ -963,361 +806,37 @@ struct CompactFileCard: View { enum NoteLoadingError: Error { case invalidData case unarchiveFailed - guard let data = Data(base64Encoded: note.noteContent) else { - throw NoteLoadingError.invalidData - } - - if let attributedString = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSAttributedString.self, from: data) { - return attributedString - } else { - throw NoteLoadingError.unarchiveFailed - } - } - - private func deleteNote() { - guard let note = noteToDelete else { return } - - let impactFeedback = UIImpactFeedbackGenerator(style: .medium) - impactFeedback.impactOccurred() - - modelContext.delete(note) - - do { - try modelContext.save() - } catch { - print("Failed to delete note: \(error)") - } - - showDeleteAlert = false - noteToDelete = nil - } - - private func deleteFile(){ - guard let file = fileToDelete else{return} - let impactFeedback = UIImpactFeedbackGenerator(style: .medium) - impactFeedback.impactOccurred() - - modelContext.delete(file) - do { - try modelContext.save() - } catch { - print("Failed to delete file: \(error)") - } - showimgDeleteAlert = false - - } - } - -struct ContentTypeTab: View { - let contentType: OCourseRefs.ContentType - let isSelected: Bool - let count: Int - let action: () -> Void +struct BottomSheetButton: View { + var icon: String + var title: String + var action: (() -> Void)? = nil var body: some View { - Button(action: action) { - HStack(spacing: 8) { - Image(systemName: contentType.icon) - .font(.system(size: 14)) - - Text(contentType.rawValue) - .font(.system(size: 14, weight: .medium)) - - if count > 0 { - Text("(\(count))") - .font(.system(size: 12)) - .opacity(0.8) - } + Button(action: { + action?() + }) { + VStack { + Image(icon) + .font(.title) + .padding() + .background(Color.white) + .clipShape(Circle()) + Text(title) + .font(.footnote) + .foregroundColor(.white) } - .padding(.horizontal, 16) - .padding(.vertical, 8) - .background(isSelected ? Color("Accent") : Color("Secondary")) - .foregroundColor(isSelected ? .black : .white) - .cornerRadius(20) + .frame(maxWidth: .infinity) + .padding(.bottom, 10) } } } -struct EmptyStateView: View { - let icon: String - let title: String - let subtitle: String? - - var body: some View { - VStack(spacing: 16) { - Image(systemName: icon) - .font(.system(size: 48)) - .foregroundColor(.gray.opacity(0.6)) - - Text(title) - .foregroundColor(.gray) - .font(.system(size: 16, weight: .medium)) - .multilineTextAlignment(.center) - - if let subtitle = subtitle { - Text(subtitle) - .foregroundColor(.gray.opacity(0.8)) - .font(.system(size: 14)) - .multilineTextAlignment(.center) - } - } - .frame(maxWidth: .infinity) - .padding(.top, 60) - } -} -struct CompactFileCard: View { - let file: UploadedFile - let onDelete: (() -> Void)? - - @State private var showFileViewer = false - @State private var showActionSheet = false - @State private var fileImage: UIImage? - @State private var imageLoadError = false - @State private var isLoading = true - - init(file: UploadedFile, onDelete: (() -> Void)? = nil) { - self.file = file - self.onDelete = onDelete - } - - var body: some View { - VStack(spacing: 8) { - - if file.isImage && !imageLoadError { - Group { - if let image = fileImage { - Image(uiImage: image) - .resizable() - .aspectRatio(contentMode: .fill) - } else if isLoading { - Rectangle() - .fill(Color.gray.opacity(0.3)) - .overlay( - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white)) - .scaleEffect(0.8) - ) - } else { - Rectangle() - .fill(Color.red.opacity(0.3)) - .overlay( - VStack(spacing: 4) { - Image(systemName: "exclamationmark.triangle") - .foregroundColor(.red) - .font(.system(size: 16)) - Text("Not found") - .font(.caption2) - .foregroundColor(.red) - } - ) - } - } - .frame(height: 80) - .clipped() - .cornerRadius(8) - } else { - Rectangle() - .fill(getFileTypeColor(file.fileType).opacity(0.2)) - .frame(height: 80) - .overlay( - VStack(spacing: 4) { - Image(systemName: getFileTypeIcon(file.fileType)) - .font(.system(size: 24)) - .foregroundColor(getFileTypeColor(file.fileType)) - - Text(file.fileType.uppercased()) - .font(.caption2) - .fontWeight(.bold) - .foregroundColor(getFileTypeColor(file.fileType)) - } - ) - .cornerRadius(8) - } - - - VStack(alignment: .leading, spacing: 2) { - Text(file.fileName) - .font(.caption) - .fontWeight(.medium) - .foregroundColor(.white) - .lineLimit(2) - .multilineTextAlignment(.leading) - - Text(FileManagerHelper.shared.formatFileSize(file.fileSize)) - .font(.caption2) - .foregroundColor(.gray) - } - .frame(maxWidth: .infinity, alignment: .leading) - } - .onTapGesture { - showFileViewer = true - } - .onLongPressGesture(minimumDuration: 0.5) { - let impactFeedback = UIImpactFeedbackGenerator(style: .medium) - impactFeedback.impactOccurred() - showActionSheet = true - } - .onAppear { - if file.isImage { - loadImageFile() - } - } - .sheet(isPresented: $showFileViewer) { - EnhancedFileViewerSheet(file: file) - } - .confirmationDialog("File Options", isPresented: $showActionSheet, titleVisibility: .visible) { - Button("Share") { - shareFile() - } - - if let onDelete = onDelete { - Button("Delete", role: .destructive) { - onDelete() - } - } - - Button("Cancel", role: .cancel) {} - } message: { - Text("Choose an action for \(file.fileName)") - } - } - - // MARK: - File Loading Methods - - private func loadImageFile() { - isLoading = true - imageLoadError = false - - Task { - let imagePaths = [file.thumbnailPath, file.localPath].compactMap { $0 } - var loadedImage: UIImage? - - for path in imagePaths { - if let data = FileManagerHelper.shared.loadFileWithFallback(from: path, courseCode: file.courseCode), - let image = UIImage(data: data) { - loadedImage = image - break - } - } - - await MainActor.run { - if let image = loadedImage { - self.fileImage = image - self.imageLoadError = false - } else { - self.imageLoadError = true - } - self.isLoading = false - } - } - } - - private func shareFile() { - guard let data = FileManagerHelper.shared.loadFileWithFallback(from: file.localPath, courseCode: file.courseCode) else { - print("Cannot share file: File not found") - return - } - - let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(file.fileName) - - do { - if FileManager.default.fileExists(atPath: tempURL.path) { - try FileManager.default.removeItem(at: tempURL) - } - try data.write(to: tempURL) - - let activityVC = UIActivityViewController(activityItems: [tempURL], applicationActivities: nil) - - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first, - let rootViewController = window.rootViewController { - - if let popover = activityVC.popoverPresentationController { - popover.sourceView = window - popover.sourceRect = CGRect(x: window.bounds.midX, y: window.bounds.midY, width: 0, height: 0) - popover.permittedArrowDirections = [] - } - - rootViewController.present(activityVC, animated: true) - } - } catch { - print("Error sharing file: \(error)") - } - } - - private func getFileTypeIcon(_ fileType: String) -> String { - switch fileType.lowercased() { - case "pdf": - return "doc.richtext.fill" - case "txt": - return "doc.text.fill" - case "rtf", "rtfd": - return "doc.richtext.fill" - case "doc", "docx": - return "doc.fill" - case "jpg", "jpeg", "png", "gif", "heic": - return "photo.fill" - default: - return "doc.fill" - } - } - - private func getFileTypeColor(_ fileType: String) -> Color { - switch fileType.lowercased() { - case "pdf": - return .red - case "txt": - return .blue - case "rtf", "rtfd": - return .purple - case "doc", "docx": - return .blue - case "jpg", "jpeg", "png", "gif", "heic": - return .green - default: - return .gray - } - } -} - -// MARK: - Error Handling -enum NoteLoadingError: Error { - case invalidData - case unarchiveFailed -} - - -struct BottomSheetButton: View { - var icon: String - var title: String - var action: (() -> Void)? = nil - - var body: some View { - Button(action: { - action?() - }) { - VStack { - Image(icon) - .font(.title) - .padding() - .background(Color.white) - .clipShape(Circle()) - Text(title) - .font(.footnote) - .foregroundColor(.white) - } - .frame(maxWidth: .infinity) - .padding(.bottom, 10) - } - } -} - -struct TagView: View { - var reminder: Remainder - +struct TagView: View { + var reminder: Remainder + var body: some View { HStack { Circle() @@ -1346,7 +865,6 @@ struct TagView: View { } } - struct MoreTagView: View { var count: Int @@ -1369,11 +887,6 @@ struct CourseCardNotes: View { @State private var showComingSoonAlert = false - var isLoading: Bool = false - var onDelete: () -> Void - - @State private var showComingSoonAlert = false - var body: some View { HStack { VStack(alignment: .leading, spacing: 6) { @@ -1389,52 +902,6 @@ struct CourseCardNotes: View { Spacer() - if isLoading { - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white)) - .scaleEffect(0.8) - .padding(.trailing, 8) - } else { - Menu { - Button(role: .destructive) { - let feedback = UISelectionFeedbackGenerator() - feedback.selectionChanged() - onDelete() - } label: { - Label("Delete", systemImage: "trash") - } - - Button { - let feedback = UISelectionFeedbackGenerator() - feedback.selectionChanged() - showComingSoonAlert = true - } label: { - Label("Export Markdown", systemImage: "square.and.arrow.down") - } - - } label: { - Image(systemName: "ellipsis") - .rotationEffect(.degrees(90)) - .foregroundColor(.white) - .font(.system(size: 20, weight: .medium)) - .padding(8) - .clipShape(Circle()) - } - } - HStack { - VStack(alignment: .leading, spacing: 6) { - Text(title) - .font(.headline) - .foregroundColor(.white) - - Text(description) - .font(.subheadline) - .foregroundColor(.gray) - .lineLimit(2) - } - - Spacer() - if isLoading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) @@ -1477,11 +944,6 @@ struct CourseCardNotes: View { .alert("Feature coming soon", isPresented: $showComingSoonAlert) { Button("OK", role: .cancel) { } } - .opacity(isLoading ? 0.7 : 1.0) - .animation(.easeInOut(duration: 0.2), value: isLoading) - .alert("Feature coming soon", isPresented: $showComingSoonAlert) { - Button("OK", role: .cancel) { } - } } } diff --git a/VITTY/VITTY/Academics/View/Notes.swift b/VITTY/VITTY/Academics/View/Notes.swift index 50465eb..0058060 100644 --- a/VITTY/VITTY/Academics/View/Notes.swift +++ b/VITTY/VITTY/Academics/View/Notes.swift @@ -4,12 +4,6 @@ // // Created by Rujin Devkota on 2/27/25. -// -// Academics.swift -// VITTY -// -// Created by Rujin Devkota on 2/27/25. - import SwiftUI import UIKit @@ -19,7 +13,6 @@ struct RichTextView: UIViewRepresentable { @Binding var typingAttributes: [NSAttributedString.Key: Any] @Binding var isEmpty: Bool - func makeUIView(context: Context) -> UITextView { let textView = UITextView() @@ -32,7 +25,6 @@ struct RichTextView: UIViewRepresentable { textView.textColor = .white - textView.attributedText = attributedText textView.selectedRange = selectedRange @@ -41,33 +33,26 @@ struct RichTextView: UIViewRepresentable { func updateUIView(_ uiView: UITextView, context: Context) { - if context.coordinator.isUpdating { return } - if !uiView.attributedText.isEqual(to: attributedText) { let previousSelectedRange = uiView.selectedRange context.coordinator.isUpdating = true uiView.attributedText = attributedText - if previousSelectedRange.location <= uiView.attributedText.length { let maxRange = min(previousSelectedRange.location + previousSelectedRange.length, uiView.attributedText.length) let validRange = NSRange(location: previousSelectedRange.location, length: maxRange - previousSelectedRange.location) uiView.selectedRange = validRange - let maxRange = min(previousSelectedRange.location + previousSelectedRange.length, uiView.attributedText.length) - let validRange = NSRange(location: previousSelectedRange.location, length: maxRange - previousSelectedRange.location) - uiView.selectedRange = validRange } context.coordinator.isUpdating = false } - if !NSEqualRanges(uiView.selectedRange, selectedRange) && selectedRange.location <= uiView.attributedText.length && NSMaxRange(selectedRange) <= uiView.attributedText.length { @@ -77,7 +62,6 @@ struct RichTextView: UIViewRepresentable { } - if !NSDictionary(dictionary: uiView.typingAttributes).isEqual(to: typingAttributes) { uiView.typingAttributes = typingAttributes } @@ -97,27 +81,21 @@ struct RichTextView: UIViewRepresentable { func textViewDidChange(_ textView: UITextView) { - guard !isUpdating else { return } isUpdating = true defer { isUpdating = false } - parent.attributedText = NSMutableAttributedString(attributedString: textView.attributedText) parent.isEmpty = textView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - parent.typingAttributes = textView.typingAttributes - - parent.typingAttributes = textView.typingAttributes } func textViewDidChangeSelection(_ textView: UITextView) { - guard !isUpdating else { return } isUpdating = true @@ -140,28 +118,11 @@ struct RichTextView: UIViewRepresentable { - - - if textView.selectedRange.length == 0 && textView.selectedRange.location > 0 { - - let location = min(textView.selectedRange.location - 1, textView.attributedText.length - 1) - if location >= 0 { - let attributes = textView.attributedText.attributes(at: location, effectiveRange: nil) - parent.typingAttributes = attributes - } - } - } - } -} - - - struct NoteEditorView: View { @Environment(\.dismiss) private var dismiss @Environment(AcademicsViewModel.self) private var academicsViewModel @Environment(AuthViewModel.self) private var authViewModel @Environment(\.presentationMode) var presentationMode - @Environment(\.presentationMode) var presentationMode @State private var attributedText = NSMutableAttributedString() @State private var selectedRange = NSRange(location: 0, length: 0) @@ -172,7 +133,6 @@ struct NoteEditorView: View { let existingNote: CreateNoteModel? let preloadedAttributedString: NSAttributedString? // Pre-processed content - let preloadedAttributedString: NSAttributedString? // Pre-processed content @State private var selectedFont: UIFont = UIFont.systemFont(ofSize: 18) @State private var selectedColor: Color = .white @State private var showFontPicker = false @@ -181,21 +141,16 @@ struct NoteEditorView: View { @State private var hasUnsavedChanges = false @State private var isInitialized = false @State private var goback = false - @State private var goback = false @Environment(\.modelContext) private var modelContext let courseCode: String let courseName: String let courseIns : String let courseSlot : String - let courseIns : String - let courseSlot : String - init(existingNote: CreateNoteModel? = nil, preloadedAttributedString: NSAttributedString? = nil, courseCode: String, courseName: String,courseIns: String , courseSlot: String) { init(existingNote: CreateNoteModel? = nil, preloadedAttributedString: NSAttributedString? = nil, courseCode: String, courseName: String,courseIns: String , courseSlot: String) { self.existingNote = existingNote self.preloadedAttributedString = preloadedAttributedString - self.preloadedAttributedString = preloadedAttributedString self.courseCode = existingNote?.courseId ?? courseCode self.courseName = existingNote?.courseName ?? courseName self.courseIns = courseIns @@ -221,17 +176,6 @@ struct NoteEditorView: View { isInitialized = true } else { - Task { @MainActor in - await loadNoteContent(note) - isInitialized = true - } - if let preloaded = preloadedAttributedString { - - attributedText = NSMutableAttributedString(attributedString: preloaded) - isEmpty = preloaded.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - isInitialized = true - } else { - Task { @MainActor in await loadNoteContent(note) isInitialized = true @@ -239,7 +183,6 @@ struct NoteEditorView: View { } } else { - attributedText = NSMutableAttributedString() isEmpty = true isInitialized = true @@ -256,15 +199,6 @@ struct NoteEditorView: View { } - do { - - if let cachedAttributedString = note.cachedAttributedString { - attributedText = NSMutableAttributedString(attributedString: cachedAttributedString) - isEmpty = cachedAttributedString.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - return - } - - do { guard let data = Data(base64Encoded: note.noteContent) else { print("Failed to decode base64 data") @@ -290,12 +224,10 @@ struct NoteEditorView: View { func saveContent() { guard hasUnsavedChanges || existingNote == nil else { - handleBackNavigation() handleBackNavigation() return } - do { let data = try NSKeyedArchiver.archivedData(withRootObject: attributedText, requiringSecureCoding: false) let dataString = data.base64EncodedString() @@ -307,8 +239,6 @@ struct NoteEditorView: View { note.createdAt = Date.now CreateNoteModel.clearCache() - - CreateNoteModel.clearCache() } else { let newNote = CreateNoteModel( noteName: title, @@ -356,26 +286,21 @@ struct NoteEditorView: View { if isInitialized { VStack { - headerView - textEditorView - toolbarView } } else { - ProgressView("Loading...") .foregroundColor(.white) } - if showFontPicker { fontPickerOverlay } @@ -402,11 +327,9 @@ struct NoteEditorView: View { private var headerView: some View { HStack { - Button(action: { handleBackNavigation() }) { Button(action: { handleBackNavigation() }) { Image(systemName: "chevron.left") .foregroundColor(Color("Accent")).font(.title2) - .foregroundColor(Color("Accent")).font(.title2) } Spacer() Text("Note") @@ -447,7 +370,6 @@ struct NoteEditorView: View { private var toolbarView: some View { HStack(spacing: 20) { - Button(action: { showFontPicker.toggle() showFontSizePicker = false @@ -457,7 +379,6 @@ struct NoteEditorView: View { } - Button(action: { showFontSizePicker.toggle() showFontPicker = false @@ -473,13 +394,11 @@ struct NoteEditorView: View { } - formatButton(action: toggleBold, icon: "bold", isActive: isBoldActive()) formatButton(action: toggleItalic, icon: "italic", isActive: isItalicActive()) formatButton(action: toggleUnderline, icon: "underline", isActive: isUnderlineActive()) - ColorPicker("", selection: $selectedColor, supportsOpacity: false) .labelsHidden() .frame(width: 30, height: 30) @@ -488,7 +407,6 @@ struct NoteEditorView: View { } - Button(action: addBulletPoints) { Image(systemName: "list.bullet") .foregroundColor(Color("Accent")) @@ -588,7 +506,6 @@ struct NoteEditorView: View { } - func addBulletPoints() { guard selectedRange.length > 0 else { return } @@ -606,12 +523,10 @@ struct NoteEditorView: View { func isBoldActive() -> Bool { return checkTraitActive(.traitBold) - return checkTraitActive(.traitBold) } func isItalicActive() -> Bool { return checkTraitActive(.traitItalic) - return checkTraitActive(.traitItalic) } func isUnderlineActive() -> Bool { @@ -641,28 +556,6 @@ struct NoteEditorView: View { } } - private func checkTraitActive(_ trait: UIFontDescriptor.SymbolicTraits) -> Bool { - if selectedRange.length > 0 { - var hasTraitThroughout = true - let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) - - attributedText.enumerateAttribute(.font, in: NSRange(location: selectedRange.location, length: endLocation - selectedRange.location), options: []) { value, range, stop in - if let font = value as? UIFont { - if !font.fontDescriptor.symbolicTraits.contains(trait) { - hasTraitThroughout = false - stop.pointee = true - } - } - } - return hasTraitThroughout - } else { - if let font = typingAttributes[.font] as? UIFont { - return font.fontDescriptor.symbolicTraits.contains(trait) - } - return false - } - } - private func getCurrentFont() -> UIFont { if selectedRange.length > 0 && selectedRange.location < attributedText.length { return attributedText.attribute(.font, at: selectedRange.location, effectiveRange: nil) as? UIFont ?? UIFont.systemFont(ofSize: 18) @@ -680,8 +573,6 @@ struct NoteEditorView: View { } func applyFontFamily(_ font: UIFont) { - let size = getCurrentFont().pointSize - let newFont = UIFont(name: font.fontName, size: size) ?? font let size = getCurrentFont().pointSize let newFont = UIFont(name: font.fontName, size: size) ?? font applyAttribute(.font, value: newFont) @@ -699,38 +590,6 @@ struct NoteEditorView: View { let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) let range = NSRange(location: selectedRange.location, length: endLocation - selectedRange.location) - mutableAttributedString.enumerateAttribute(.font, in: range, options: []) { value, subRange, _ in - if let font = value as? UIFont { - var traits = font.fontDescriptor.symbolicTraits - if traits.contains(.traitBold) { - traits.remove(.traitBold) - } else { - traits.insert(.traitBold) - } - if let newFontDescriptor = font.fontDescriptor.withSymbolicTraits(traits) { - let newFont = UIFont(descriptor: newFontDescriptor, size: font.pointSize) - mutableAttributedString.addAttribute(.font, value: newFont, range: subRange) - } - } - } - attributedText = mutableAttributedString - } else { - let currentFont = typingAttributes[.font] as? UIFont ?? UIFont.systemFont(ofSize: 18) - var traits = currentFont.fontDescriptor.symbolicTraits - if traits.contains(.traitBold) { - traits.remove(.traitBold) - } else { - traits.insert(.traitBold) - } - if let newFontDescriptor = currentFont.fontDescriptor.withSymbolicTraits(traits) { - let newFont = UIFont(descriptor: newFontDescriptor, size: currentFont.pointSize) - typingAttributes[.font] = newFont - } - if selectedRange.length > 0 { - let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) - let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) - let range = NSRange(location: selectedRange.location, length: endLocation - selectedRange.location) - mutableAttributedString.enumerateAttribute(.font, in: range, options: []) { value, subRange, _ in if let font = value as? UIFont { var traits = font.fontDescriptor.symbolicTraits @@ -760,7 +619,6 @@ struct NoteEditorView: View { } } hasUnsavedChanges = true - hasUnsavedChanges = true } func toggleItalic() { @@ -769,38 +627,6 @@ struct NoteEditorView: View { let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) let range = NSRange(location: selectedRange.location, length: endLocation - selectedRange.location) - mutableAttributedString.enumerateAttribute(.font, in: range, options: []) { value, subRange, _ in - if let font = value as? UIFont { - var traits = font.fontDescriptor.symbolicTraits - if traits.contains(.traitItalic) { - traits.remove(.traitItalic) - } else { - traits.insert(.traitItalic) - } - if let newFontDescriptor = font.fontDescriptor.withSymbolicTraits(traits) { - let newFont = UIFont(descriptor: newFontDescriptor, size: font.pointSize) - mutableAttributedString.addAttribute(.font, value: newFont, range: subRange) - } - } - } - attributedText = mutableAttributedString - } else { - let currentFont = typingAttributes[.font] as? UIFont ?? UIFont.systemFont(ofSize: 18) - var traits = currentFont.fontDescriptor.symbolicTraits - if traits.contains(.traitItalic) { - traits.remove(.traitItalic) - } else { - traits.insert(.traitItalic) - } - if let newFontDescriptor = currentFont.fontDescriptor.withSymbolicTraits(traits) { - let newFont = UIFont(descriptor: newFontDescriptor, size: currentFont.pointSize) - typingAttributes[.font] = newFont - } - if selectedRange.length > 0 { - let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) - let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) - let range = NSRange(location: selectedRange.location, length: endLocation - selectedRange.location) - mutableAttributedString.enumerateAttribute(.font, in: range, options: []) { value, subRange, _ in if let font = value as? UIFont { var traits = font.fontDescriptor.symbolicTraits @@ -830,10 +656,8 @@ struct NoteEditorView: View { } } hasUnsavedChanges = true - hasUnsavedChanges = true } - func toggleUnderline() { if selectedRange.length > 0 { let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) @@ -852,23 +676,6 @@ struct NoteEditorView: View { typingAttributes[.underlineStyle] = newUnderline } hasUnsavedChanges = true - if selectedRange.length > 0 { - let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) - let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) - let range = NSRange(location: selectedRange.location, length: endLocation - selectedRange.location) - - mutableAttributedString.enumerateAttribute(.underlineStyle, in: range, options: []) { value, subRange, _ in - let currentUnderline = value as? Int ?? 0 - let newUnderline = currentUnderline == NSUnderlineStyle.single.rawValue ? 0 : NSUnderlineStyle.single.rawValue - mutableAttributedString.addAttribute(.underlineStyle, value: newUnderline, range: subRange) - } - attributedText = mutableAttributedString - } else { - let currentUnderline = typingAttributes[.underlineStyle] as? Int ?? 0 - let newUnderline = currentUnderline == NSUnderlineStyle.single.rawValue ? 0 : NSUnderlineStyle.single.rawValue - typingAttributes[.underlineStyle] = newUnderline - } - hasUnsavedChanges = true } func applyAttribute(_ key: NSAttributedString.Key, value: Any) { @@ -877,9 +684,6 @@ struct NoteEditorView: View { let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) let range = NSRange(location: selectedRange.location, length: endLocation - selectedRange.location) mutableAttributedString.addAttribute(key, value: value, range: range) - let endLocation = min(selectedRange.location + selectedRange.length, attributedText.length) - let range = NSRange(location: selectedRange.location, length: endLocation - selectedRange.location) - mutableAttributedString.addAttribute(key, value: value, range: range) attributedText = mutableAttributedString } else { typingAttributes[key] = value diff --git a/VITTY/VITTY/Academics/View/RemindersData.swift b/VITTY/VITTY/Academics/View/RemindersData.swift index f6937a5..08722b0 100644 --- a/VITTY/VITTY/Academics/View/RemindersData.swift +++ b/VITTY/VITTY/Academics/View/RemindersData.swift @@ -7,12 +7,10 @@ import SwiftUI import SwiftData - struct RemindersView: View { @Environment(\.modelContext) private var modelContext @Query private var allReminders: [Remainder] @Query private var timeTables: [TimeTable] - @Query private var timeTables: [TimeTable] @State private var searchText = "" @State private var selectedTab = 0 @@ -67,17 +65,10 @@ struct RemindersView: View { }.sorted { $0.daysToGo < $1.daysToGo } } - // Extract courses from timetable - private var availableCourses: [Course] { - let courses = timeTables.first.map { extractCourses(from: $0) } ?? [] - return courses - } - var body: some View { ScrollView { VStack(spacing: 0) { - HStack { Image(systemName: "magnifyingglass") .foregroundColor(.gray) diff --git a/VITTY/VITTY/Auth/ViewModels/AuthViewModel.swift b/VITTY/VITTY/Auth/ViewModels/AuthViewModel.swift index 5181cb3..7805525 100644 --- a/VITTY/VITTY/Auth/ViewModels/AuthViewModel.swift +++ b/VITTY/VITTY/Auth/ViewModels/AuthViewModel.swift @@ -57,9 +57,6 @@ class AuthViewModel: NSObject, ASAuthorizationControllerDelegate { - - - var isLoading: Bool = false var isLoadingApple: Bool = false let firebaseAuth = Auth.auth() @@ -166,11 +163,9 @@ class AuthViewModel: NSObject, ASAuthorizationControllerDelegate { func signInServer(username: String, regNo: String) async { - logger.info("Signing into server... from uuid \(self.loggedInFirebaseUser?.uid ?? "empty")") logger.info("Signing into server... from uuid \(self.loggedInFirebaseUser?.uid ?? "empty")") do { - self.loggedInBackendUser = try await AuthAPIService.shared .signInUser( with: AuthRequestBody( @@ -189,8 +184,6 @@ class AuthViewModel: NSObject, ASAuthorizationControllerDelegate { } print("this is kinda empty : \(self.loggedInBackendUser?.name ?? "")") logger.info("Signed into server \(self.loggedInBackendUser?.name ?? "empty")") - print("this is kinda empty : \(self.loggedInBackendUser?.name ?? "")") - logger.info("Signed into server \(self.loggedInBackendUser?.name ?? "empty")") } diff --git a/VITTY/VITTY/Connect/Models/CircleModel.swift b/VITTY/VITTY/Connect/Models/CircleModel.swift index 7323d35..eaea8f2 100644 --- a/VITTY/VITTY/Connect/Models/CircleModel.swift +++ b/VITTY/VITTY/Connect/Models/CircleModel.swift @@ -82,7 +82,6 @@ struct CircleUserTemp: Codable { struct CircleUserResponseTemp: Codable { let data: [CircleUserTemp] - enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey { case data } diff --git a/VITTY/VITTY/Connect/View/Circles/Components/CirclesRow.swift b/VITTY/VITTY/Connect/View/Circles/Components/CirclesRow.swift index 94420d9..6808ae4 100644 --- a/VITTY/VITTY/Connect/View/Circles/Components/CirclesRow.swift +++ b/VITTY/VITTY/Connect/View/Circles/Components/CirclesRow.swift @@ -18,30 +18,6 @@ struct CirclesRow: View { } - private var busyCount: Int { - circleMembers.filter { - $0.status != nil && $0.status != "available" && $0.status != "free" - }.count - } - - private var availableCount: Int { - circleMembers.filter { - $0.status == nil || $0.status == "available" || $0.status == "free" - }.count - } - - private var isLoadingMembers: Bool { - communityPageViewModel.isLoadingCircleMembers(for: circle.circleID) - } - @Environment(CommunityPageViewModel.self) private var communityPageViewModel - @Environment(AuthViewModel.self) private var authViewModel - - - private var circleMembers: [CircleUserTemp] { - communityPageViewModel.circleMembers(for: circle.circleID) - } - - private var busyCount: Int { circleMembers.filter { $0.status != nil && $0.status != "available" && $0.status != "free" @@ -99,40 +75,6 @@ struct CirclesRow: View { } - if circleMembers.isEmpty && !isLoadingMembers { - Text("No members") - .font(Font.custom("Poppins-Regular", size: 12)) - .foregroundStyle(Color("Accent").opacity(0.7)) - } - } - } - if isLoadingMembers { - HStack { - ProgressView() - .scaleEffect(0.7) - Text("Loading...") - .font(Font.custom("Poppins-Regular", size: 12)) - .foregroundStyle(Color("Accent")) - } - } else { - HStack { - - if busyCount > 0 { - Image("inclass").resizable().frame(width: 20, height: 20) - Text("\(busyCount) busy").foregroundStyle(Color("Accent")) - - if availableCount > 0 { - Spacer().frame(width: 20) - } - } - - - if availableCount > 0 { - Image("available").resizable().frame(width: 20, height: 20) - Text("\(availableCount) available").foregroundStyle(Color("Accent")) - } - - if circleMembers.isEmpty && !isLoadingMembers { Text("No members") .font(Font.custom("Poppins-Regular", size: 12)) @@ -163,7 +105,6 @@ struct CirclesRow: View { func cleanName(_ fullName: String) -> String { - let pattern = "\\b\\d{2}[A-Z]+\\d+\\b" let pattern = "\\b\\d{2}[A-Z]+\\d+\\b" let regex = try? NSRegularExpression(pattern: pattern, options: []) diff --git a/VITTY/VITTY/Connect/View/Circles/Components/CreateGroup.swift b/VITTY/VITTY/Connect/View/Circles/Components/CreateGroup.swift index 1b2df7b..61567ce 100644 --- a/VITTY/VITTY/Connect/View/Circles/Components/CreateGroup.swift +++ b/VITTY/VITTY/Connect/View/Circles/Components/CreateGroup.swift @@ -6,7 +6,6 @@ import SwiftUI import Alamofire -import Alamofire struct CreateGroup: View { let screenHeight = UIScreen.main.bounds.height @@ -44,7 +43,6 @@ struct CreateGroup: View { Spacer().frame(height: 20) - Button(action: { showImagePicker = true }) { @@ -122,7 +120,6 @@ struct CreateGroup: View { Button(action: { showFriendSelector = true - showFriendSelector = true }) { Image(systemName: "person.badge.plus") .foregroundColor(.white) @@ -226,7 +223,6 @@ struct CreateGroup: View { Spacer() Button(action: { createGroup() - createGroup() }) { HStack { if isCreatingGroup { @@ -243,7 +239,6 @@ struct CreateGroup: View { .cornerRadius(10) } .disabled(groupName.isEmpty || isCreatingGroup) - .disabled(groupName.isEmpty || isCreatingGroup) .padding(.trailing, 20) } .padding(.bottom, 20) diff --git a/VITTY/VITTY/Connect/View/Circles/Components/JoinGroup.swift b/VITTY/VITTY/Connect/View/Circles/Components/JoinGroup.swift index 3ed9272..00a4f94 100644 --- a/VITTY/VITTY/Connect/View/Circles/Components/JoinGroup.swift +++ b/VITTY/VITTY/Connect/View/Circles/Components/JoinGroup.swift @@ -3,21 +3,14 @@ // // Created by Rujin Devkota on 2/28/25. // -// JoinGroup.swift -// VITTY -// -// Created by Rujin Devkota on 2/28/25. -// import SwiftUI import AVFoundation import UIKit -import UIKit struct JoinGroup: View { let screenHeight = UIScreen.main.bounds.height let screenWidth = UIScreen.main.bounds.width - @Binding var groupCode: String @State private var isScanning = false @State private var scannedCode: String = "" @@ -33,18 +26,6 @@ struct JoinGroup: View { @Environment(CommunityPageViewModel.self) private var communityPageViewModel @Environment(\.dismiss) private var dismiss - @State private var showingAlert = false - @State private var alertMessage = "" - @State private var isJoining = false - @State private var showToast = false - @State private var toastMessage = "" - @State private var circleName = "" - @State private var localGroupCode = "" - - @Environment(AuthViewModel.self) private var authViewModel - @Environment(CommunityPageViewModel.self) private var communityPageViewModel - @Environment(\.dismiss) private var dismiss - var body: some View { ZStack { VStack(spacing: 20) { diff --git a/VITTY/VITTY/Connect/View/Circles/View/InsideCircle.swift b/VITTY/VITTY/Connect/View/Circles/View/InsideCircle.swift index 24f0fac..3056419 100644 --- a/VITTY/VITTY/Connect/View/Circles/View/InsideCircle.swift +++ b/VITTY/VITTY/Connect/View/Circles/View/InsideCircle.swift @@ -382,7 +382,6 @@ struct InsideCircle: View { }) { Image(systemName: "chevron.left") .foregroundColor(.white).font(.title2) - .foregroundColor(.white).font(.title2) } Spacer() Text("Circle") @@ -391,13 +390,10 @@ struct InsideCircle: View { Spacer() Button(action: { showCircleMenu = true - showCircleMenu = true }) { - Image(systemName: "ellipsis") Image(systemName: "ellipsis") .foregroundColor(.white) .font(.system(size: 18)) - .font(.system(size: 18)) } } .padding() @@ -413,7 +409,6 @@ struct InsideCircle: View { .foregroundColor(.white) Spacer() - } Spacer().frame(height: 5) HStack { diff --git a/VITTY/VITTY/Connect/View/ConnectPage.swift b/VITTY/VITTY/Connect/View/ConnectPage.swift index 238cb83..d04ac36 100644 --- a/VITTY/VITTY/Connect/View/ConnectPage.swift +++ b/VITTY/VITTY/Connect/View/ConnectPage.swift @@ -34,16 +34,12 @@ struct ConnectPage: View { @State private var activeSheet: SheetType? @State private var showCircleMenu = false @Environment(\.dismiss) private var dismiss - @State private var activeSheet: SheetType? - @State private var showCircleMenu = false - @Environment(\.dismiss) private var dismiss @Binding var isCreatingGroup : Bool @State private var isAddFriendsViewPresented = false @State private var selectedTab = 0 @State private var hasLoadedInitialData = false - @State private var hasLoadedInitialData = false var body: some View { ZStack { @@ -72,7 +68,6 @@ struct ConnectPage: View { .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } - if isCircleView == false { Button(action: { isShowingRequestView.toggle() @@ -106,22 +101,15 @@ struct ConnectPage: View { } ) .offset(x: UIScreen.main.bounds.width*0.4228, y: UIScreen.main.bounds.height*0.38901*(-1)) - } else { - ) - .offset(x: UIScreen.main.bounds.width*0.4228, y: UIScreen.main.bounds.height*0.38901*(-1)) } else { Button(action: { showCircleMenu = true - showCircleMenu = true }) { - Image(systemName: "ellipsis") Image(systemName: "ellipsis") .foregroundColor(.white) .font(.system(size: 18)) - .font(.system(size: 18)) } .offset(x: UIScreen.main.bounds.width*0.4228, y: UIScreen.main.bounds.height*0.38901*(-1)) - .offset(x: UIScreen.main.bounds.width*0.4228, y: UIScreen.main.bounds.height*0.38901*(-1)) } } .overlay( diff --git a/VITTY/VITTY/Connect/View/Freinds/View/Freinds.swift b/VITTY/VITTY/Connect/View/Freinds/View/Freinds.swift index 5c2a02c..f477d6a 100644 --- a/VITTY/VITTY/Connect/View/Freinds/View/Freinds.swift +++ b/VITTY/VITTY/Connect/View/Freinds/View/Freinds.swift @@ -6,7 +6,6 @@ // import SwiftUI - struct FriendsView: View { @State private var searchText = "" @State private var selectedFilterOption = 0 @@ -21,8 +20,6 @@ struct FriendsView: View { Spacer().frame(height: 8) - - HStack { FilterPill(title: "Available", isSelected: selectedFilterOption == 0) .onTapGesture { @@ -38,7 +35,6 @@ struct FriendsView: View { Spacer().frame(height: 7) - if communityPageViewModel.errorFreinds { Spacer() VStack(spacing: 5) { @@ -58,35 +54,17 @@ struct FriendsView: View { Spacer() } else { - let filteredFriends = communityPageViewModel.friends.filter { friend in - let matchesSearch: Bool - let matchesSearch: Bool if searchText.isEmpty { matchesSearch = true - matchesSearch = true } else { - matchesSearch = friend.username.localizedCaseInsensitiveContains(searchText) || matchesSearch = friend.username.localizedCaseInsensitiveContains(searchText) || (friend.name.localizedCaseInsensitiveContains(searchText) ?? false) } - let matchesFilter: Bool - switch selectedFilterOption { - case 0: - matchesFilter = friend.currentStatus.status == "free" - case 1: - matchesFilter = true - default: - matchesFilter = true - } - - return matchesSearch && matchesFilter - - let matchesFilter: Bool switch selectedFilterOption { case 0: @@ -116,26 +94,11 @@ struct FriendsView: View { .font(Font.custom("Poppins-Regular", size: 16)) .foregroundColor(.white) .multilineTextAlignment(.center) - VStack(spacing: 5) { - if selectedFilterOption == 0 && !searchText.isEmpty { - Text("No available friends match your search") - } else if selectedFilterOption == 0 { - Text("No friends are currently available") - } else if !searchText.isEmpty { - Text("No friends match your search") - } else { - Text("You don't have any friends yet") - } - } - .font(Font.custom("Poppins-Regular", size: 16)) - .foregroundColor(.white) - .multilineTextAlignment(.center) Spacer() } else { ScrollView { VStack(spacing: 10) { ForEach(filteredFriends, id: \.username) { friend in - NavigationLink(destination: TimeTableView(friend: friend,isFriendsTimeTable: true)) { NavigationLink(destination: TimeTableView(friend: friend,isFriendsTimeTable: true)) { FriendRow(friend: friend) } diff --git a/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift b/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift index 186343a..8ed78a1 100644 --- a/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift +++ b/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift @@ -5,7 +5,6 @@ // Created by Chandram Dutta on 04/01/24. // // -// import Foundation import Alamofire @@ -17,28 +16,19 @@ class CommunityPageViewModel { var circles = [CircleModel]() var circleRequests = [CircleRequest]() - var circleRequests = [CircleRequest]() - var loadingFreinds = false var loadingCircle = false var loadingCircleMembers = false var loadingCircleRequests = false var loadingRequestAction = false - var loadingCircleRequests = false - var loadingRequestAction = false var errorFreinds = false var errorCircle = false var errorCircleMembers = false var errorCircleRequests = false - var errorCircleRequests = false - var circleMembers = [CircleUserTemp]() - var circleMembersDict: [String: [CircleUserTemp]] = [:] - var loadingCircleMembersDict: [String: Bool] = [:] - var circleMembersDict: [String: [CircleUserTemp]] = [:] var loadingCircleMembersDict: [String: Bool] = [:] @@ -66,26 +56,14 @@ class CommunityPageViewModel { DispatchQueue.main.async { self.loadingFreinds = false - switch response.result { - DispatchQueue.main.async { - self.loadingFreinds = false - switch response.result { case .success(let data): self.friends = data.data self.errorFreinds = false - self.errorFreinds = false - case .failure(let error): self.logger.error("Error fetching friends: \(error)") - if self.friends.isEmpty { - self.errorFreinds = true - } - } - self.logger.error("Error fetching friends: \(error)") - if self.friends.isEmpty { self.errorFreinds = true } @@ -103,15 +81,6 @@ class CommunityPageViewModel { } - self.errorCircle = false - - func fetchCircleData(from url: String, token: String, loading: Bool = false) { - - if loading || circles.isEmpty { - self.loadingCircle = true - } - - self.errorCircle = false AF.request(url, method: .get, headers: ["Authorization": "Token \(token)"]) @@ -120,19 +89,12 @@ class CommunityPageViewModel { DispatchQueue.main.async { self.loadingCircle = false - switch response.result { - DispatchQueue.main.async { - self.loadingCircle = false - switch response.result { case .success(let data): self.circles = data.data self.errorCircle = false print("Successfully fetched circles: \(data.data)") - self.errorCircle = false - print("Successfully fetched circles: \(data.data)") - case .failure(let error): self.logger.error("Error fetching circles: \(error)") @@ -285,8 +247,6 @@ class CommunityPageViewModel { AF.request(url, method: .get, headers: ["Authorization": "Token \(token)"]) .validate() .responseDecodable(of: CircleUserResponseTemp.self) { response in - DispatchQueue.main.async { - switch response.result { DispatchQueue.main.async { switch response.result { case .success(let data): @@ -299,29 +259,9 @@ class CommunityPageViewModel { } print("Successfully fetched circle members: \(data.data)") - if let circleID = circleID { - self.circleMembersDict[circleID] = data.data - self.loadingCircleMembersDict[circleID] = false - } else { - self.circleMembers = data.data - self.loadingCircleMembers = false - } - print("Successfully fetched circle members: \(data.data)") - case .failure(let error): self.logger.error("Error fetching circle members: \(error)") - if let circleID = circleID { - self.loadingCircleMembersDict[circleID] = false - } else { - self.loadingCircleMembers = false - if self.circleMembers.isEmpty { - self.errorCircleMembers = true - } - } - } - self.logger.error("Error fetching circle members: \(error)") - if let circleID = circleID { self.loadingCircleMembersDict[circleID] = false } else { @@ -335,12 +275,7 @@ class CommunityPageViewModel { } } - //MARK : Circle Leave - func fetchCircleLeave(from url: String, token: String, loading: Bool = false) { - if loading { - self.loadingCircleMembers = true - } func fetchCircleLeave(from url: String, token: String, loading: Bool = false) { if loading { self.loadingCircleMembers = true @@ -349,31 +284,20 @@ class CommunityPageViewModel { AF.request(url, method: .get, headers: ["Authorization": "Token \(token)"]) .validate() .responseDecodable(of: CircleUserResponseTemp.self) { response in - DispatchQueue.main.async { - self.loadingCircleMembers = false DispatchQueue.main.async { self.loadingCircleMembers = false - switch response.result { switch response.result { case .success(let data): self.circleMembers = data.data print("Successfully fetched circle members after leave: \(data.data)") - self.circleMembers = data.data - print("Successfully fetched circle members after leave: \(data.data)") - case .failure(let error): self.logger.error("Error fetching circle members: \(error)") if self.circleMembers.isEmpty { self.errorCircleMembers = true } } - self.logger.error("Error fetching circle members: \(error)") - if self.circleMembers.isEmpty { - self.errorCircleMembers = true - } - } } } } diff --git a/VITTY/VITTY/Settings/ViewModel/SettingsViewModel.swift b/VITTY/VITTY/Settings/ViewModel/SettingsViewModel.swift index af0eb8a..ae92f18 100644 --- a/VITTY/VITTY/Settings/ViewModel/SettingsViewModel.swift +++ b/VITTY/VITTY/Settings/ViewModel/SettingsViewModel.swift @@ -3,7 +3,6 @@ import SwiftUI import UserNotifications class SettingsViewModel : ObservableObject{ - @Published var notificationsEnabled: Bool = false { @Published var notificationsEnabled: Bool = false { didSet { UserDefaults.standard.set(notificationsEnabled, forKey: "notificationsEnabled") @@ -18,15 +17,12 @@ class SettingsViewModel : ObservableObject{ } } - @Published var timetable: TimeTable? - @Published var showNotificationDisabledAlert = false @Published var timetable: TimeTable? @Published var showNotificationDisabledAlert = false init(timetable: TimeTable? = nil) { self.timetable = timetable - self.notificationsEnabled = UserDefaults.standard.bool(forKey: "notificationsEnabled") checkNotificationAuthorization() } @@ -59,9 +55,6 @@ class SettingsViewModel : ObservableObject{ // Clear existing notifications first UNUserNotificationCenter.current().removeAllPendingNotificationRequests() - // Clear existing notifications first - UNUserNotificationCenter.current().removeAllPendingNotificationRequests() - let weekdays: [(Int, [Lecture])] = [ (2, timetable.monday), // Monday = 2 (3, timetable.tuesday), // Tuesday = 3 @@ -70,13 +63,6 @@ class SettingsViewModel : ObservableObject{ (6, timetable.friday), // Friday = 6 (7, timetable.saturday), // Saturday = 7 (1, timetable.sunday) // Sunday = 1 - (2, timetable.monday), // Monday = 2 - (3, timetable.tuesday), // Tuesday = 3 - (4, timetable.wednesday), // Wednesday = 4 - (5, timetable.thursday), // Thursday = 5 - (6, timetable.friday), // Friday = 6 - (7, timetable.saturday), // Saturday = 7 - (1, timetable.sunday) // Sunday = 1 ] for (weekday, lectures) in weekdays { @@ -87,24 +73,14 @@ class SettingsViewModel : ObservableObject{ } - guard let startDate = parseLectureTime(lecture.startTime, weekday: weekday) else { - print("Failed to parse time for lecture: \(lecture.name) with time: \(lecture.startTime)") - continue - } - - scheduleNotification(for: lecture.name, at: startDate, title: "Class Starting", minutesBefore: 0) - - scheduleNotification(for: lecture.name, at: startDate, title: "Upcoming Class", minutesBefore: 10) } } print("Scheduled notifications for all lectures") - - print("Scheduled notifications for all lectures") } private func scheduleNotification(for lectureName: String, at date: Date, title: String, minutesBefore: Int) { @@ -118,10 +94,8 @@ class SettingsViewModel : ObservableObject{ let triggerComponents = Calendar.current.dateComponents([.weekday, .hour, .minute], from: triggerDate) let trigger = UNCalendarNotificationTrigger(dateMatching: triggerComponents, repeats: true) - let identifier = "\(lectureName)-\(title)-\(minutesBefore)min-weekday\(triggerComponents.weekday ?? 0)" let identifier = "\(lectureName)-\(title)-\(minutesBefore)min-weekday\(triggerComponents.weekday ?? 0)" let request = UNNotificationRequest( - identifier: identifier, identifier: identifier, content: content, trigger: trigger @@ -134,18 +108,9 @@ class SettingsViewModel : ObservableObject{ print("Successfully scheduled notification: \(identifier)") } } - UNUserNotificationCenter.current().add(request) { error in - if let error = error { - print("Error scheduling notification: \(error)") - } else { - print("Successfully scheduled notification: \(identifier)") - } - } } - - private func parseLectureTime(_ timeString: String, weekday: Int) -> Date? { let formattedTimeString = formatTime(time: timeString) diff --git a/VITTY/VITTY/TimeTable/Models/TimeTable.swift b/VITTY/VITTY/TimeTable/Models/TimeTable.swift index d958067..da1201d 100644 --- a/VITTY/VITTY/TimeTable/Models/TimeTable.swift +++ b/VITTY/VITTY/TimeTable/Models/TimeTable.swift @@ -209,7 +209,6 @@ class Lecture: Codable, Identifiable, Comparable { } } - extension TimeTable { var isEmpty: Bool { monday.isEmpty && tuesday.isEmpty && wednesday.isEmpty && @@ -220,26 +219,14 @@ extension TimeTable { let formattedTime = formatTime(time: lecture.startTime) - guard formattedTime != "Failed to parse the time string." else { return nil } - - private func extractStartTime(from lecture: Lecture) -> Date? { - let formattedTime = formatTime(time: lecture.startTime) - - guard formattedTime != "Failed to parse the time string." else { return nil } - let formatter = DateFormatter() - formatter.dateFormat = "h:mm a" - formatter.locale = Locale(identifier: "en_US_POSIX") let formatter = DateFormatter() formatter.dateFormat = "h:mm a" formatter.locale = Locale(identifier: "en_US_POSIX") return formatter.date(from: formattedTime) } - return formatter.date(from: formattedTime) - } - func classesFor(date: Date) -> [Classes] { @@ -266,10 +253,6 @@ extension TimeTable { ) } - // Sort using the original lecture objects instead of formatted strings - return lectures.sorted { lecture1, lecture2 in - guard let time1 = extractStartTime(from: lecture1), - let time2 = extractStartTime(from: lecture2) else { // Sort using the original lecture objects instead of formatted strings return lectures.sorted { lecture1, lecture2 in guard let time1 = extractStartTime(from: lecture1), @@ -285,15 +268,6 @@ extension TimeTable { ) } } - return time1 < time2 - }.map { - Classes( - title: $0.name, - time: "\(formatTime(time: $0.startTime)) - \(formatTime(time: $0.endTime))", - slot: $0.slot - ) - } - } private func formatTime(time: String) -> String { var timeComponents = time.components(separatedBy: "T").last ?? "" @@ -312,26 +286,6 @@ extension TimeTable { } } - func isDifferentFrom(_ other: TimeTable) -> Bool { - return monday != other.monday || - tuesday != other.tuesday || - wednesday != other.wednesday || - thursday != other.thursday || - friday != other.friday || - saturday != other.saturday || - sunday != other.sunday - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "HH:mm:ss" - if let date = dateFormatter.date(from: timeComponents) { - dateFormatter.dateFormat = "h:mm a" - let formattedTime = dateFormatter.string(from: date) - return (formattedTime) - } - else { - return ("Failed to parse the time string.") - } - } - func isDifferentFrom(_ other: TimeTable) -> Bool { return monday != other.monday || tuesday != other.tuesday || diff --git a/VITTY/VITTY/TimeTable/ViewModel/TimeTableViewModel.swift b/VITTY/VITTY/TimeTable/ViewModel/TimeTableViewModel.swift index 7eb83d0..e86dcac 100644 --- a/VITTY/VITTY/TimeTable/ViewModel/TimeTableViewModel.swift +++ b/VITTY/VITTY/TimeTable/ViewModel/TimeTableViewModel.swift @@ -14,12 +14,8 @@ public enum Stage { case loading case error case data - case loading - case error - case data } - extension TimeTableView { @Observable class TimeTableViewModel { @@ -248,9 +244,6 @@ extension TimeTableView { } } - var updatedTimeTable: TimeTable? { - timeTable - } var updatedTimeTable: TimeTable? { timeTable } @@ -260,11 +253,4 @@ extension TimeTableView { logger.debug("Sync status reset") } } - func resetSyncStatus() { - hasSyncedThisSession = false - logger.debug("Sync status reset") - } - } } - - diff --git a/VITTY/VITTY/TimeTable/Views/LectureDetailView.swift b/VITTY/VITTY/TimeTable/Views/LectureDetailView.swift index 4140f97..989713c 100644 --- a/VITTY/VITTY/TimeTable/Views/LectureDetailView.swift +++ b/VITTY/VITTY/TimeTable/Views/LectureDetailView.swift @@ -105,15 +105,4 @@ struct LectureDetailView: View { return ("Failed to parse the time string.") } } - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "HH:mm:ss" - if let date = dateFormatter.date(from: timeComponents) { - dateFormatter.dateFormat = "h:mm a" - let formattedTime = dateFormatter.string(from: date) - return (formattedTime) - } - else { - return ("Failed to parse the time string.") - } - } } diff --git a/VITTY/VITTY/TimeTable/Views/TimeTableView.swift b/VITTY/VITTY/TimeTable/Views/TimeTableView.swift index f7ec92a..2795344 100644 --- a/VITTY/VITTY/TimeTable/Views/TimeTableView.swift +++ b/VITTY/VITTY/TimeTable/Views/TimeTableView.swift @@ -1,5 +1,4 @@ - import OSLog import SwiftData import SwiftUI @@ -8,22 +7,15 @@ struct TimeTableView: View { @Environment(AuthViewModel.self) private var authViewModel @Environment(\.modelContext) private var context @Environment(\.scenePhase) private var scenePhase - @Environment(\.scenePhase) private var scenePhase private let daysOfWeek = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] - @State private var viewModel = TimeTableViewModel() @State private var selectedLecture: Lecture? = nil @Query private var timetableItem: [TimeTable] @Environment(\.dismiss) private var dismiss - @Query private var timetableItem: [TimeTable] - @Environment(\.dismiss) private var dismiss let friend: Friend? - var isFriendsTimeTable: Bool - - var isFriendsTimeTable: Bool private let logger = Logger( @@ -33,9 +25,7 @@ struct TimeTableView: View { ) ) - var body: some View { - NavigationStack { NavigationStack { ZStack { BackgroundView() @@ -50,18 +40,6 @@ struct TimeTableView: View { }.padding(8) } - switch viewModel.stage { - VStack { - if isFriendsTimeTable { - HStack { - Button(action: { dismiss() }) { - Image(systemName: "chevron.left") - .foregroundColor(Color("Accent")).font(.title2) - } - Spacer() - }.padding(8) - } - switch viewModel.stage { case .loading: VStack { @@ -86,12 +64,10 @@ struct TimeTableView: View { Text(day) .foregroundStyle(daysOfWeek[viewModel.dayNo] == day ? Color("Background") : Color("Accent")) - ? Color("Background") : Color("Accent")) .frame(width: 60, height: 54) .background( daysOfWeek[viewModel.dayNo] == day ? Color("Accent") : Color.clear - ? Color("Accent") : Color.clear ) .onTapGesture { withAnimation { @@ -110,7 +86,6 @@ struct TimeTableView: View { .clipShape(RoundedRectangle(cornerRadius: 10)) .padding(.horizontal) - if viewModel.lectures.isEmpty { Spacer() Text("No classes today!") diff --git a/VITTY/VITTY/UserProfileSideBar/SideBar.swift b/VITTY/VITTY/UserProfileSideBar/SideBar.swift index 8bc6646..f6fa929 100644 --- a/VITTY/VITTY/UserProfileSideBar/SideBar.swift +++ b/VITTY/VITTY/UserProfileSideBar/SideBar.swift @@ -16,14 +16,12 @@ struct UserProfileSidebar: View { ZStack(alignment: .topTrailing) { Button { isPresented = false - isPresented = false } label: { Image(systemName: "xmark") .foregroundColor(.white) .padding() } - VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 8) { UserImage( @@ -40,34 +38,22 @@ struct UserProfileSidebar: View { } .padding(.top, 40) - Divider().background(Color.clear) - NavigationLink { EmptyClassRoom() } label: { MenuOption(icon: "emptyclassroom", title: "Find Empty Classroom") } - NavigationLink { SettingsView() } label: { MenuOption(icon: "settings", title: "Settings") } - Divider().background(Color.clear) -// MenuOption(icon: "share", title: "Share") - MenuOption(icon: "support", title: "Support").onTapGesture { - let supportUrl = URL(string: "https://github.com/GDGVIT/vitty-ios/issues/new?template=bug_report.md") - UIApplication.shared.open(supportUrl!) - } -// MenuOption(icon: "about", title: "About") - - // MenuOption(icon: "share", title: "Share") MenuOption(icon: "support", title: "Support").onTapGesture { let supportUrl = URL(string: "https://github.com/GDGVIT/vitty-ios/issues/new?template=bug_report.md") @@ -77,7 +63,6 @@ struct UserProfileSidebar: View { Divider().background(Color.clear) - VStack(alignment: .leading, spacing: 4) { Text("Ghost Mode") .font(Font.custom("Poppins-Medium", size: 16)) @@ -102,29 +87,10 @@ struct UserProfileSidebar: View { .foregroundColor(.white) } } - - HStack { - Toggle("", isOn: $ghostMode) - .labelsHidden() - .toggleStyle(SwitchToggleStyle(tint: Color("Accent"))) - .disabled(isUpdatingGhostMode) - .padding(.top, 4) - .onChange(of: ghostMode) { oldValue, newValue in - updateGhostMode(enabled: newValue) - } - - if isUpdatingGhostMode { - ProgressView() - .scaleEffect(0.8) - .foregroundColor(.white) - } - } } - Spacer() - Button { authViewModel.signOut() do{ @@ -176,68 +142,6 @@ struct UserProfileSidebar: View { isUpdatingGhostMode = true - let endpoint = enabled ? "ghost" : "alive" - let urlString = "\(APIConstants.base_url)friends/\(endpoint)/\(username)" - - guard let url = URL(string: urlString) else { - isUpdatingGhostMode = false - return - } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - URLSession.shared.dataTask(with: request) { data, response, error in - DispatchQueue.main.async { - isUpdatingGhostMode = false - - if let error = error { - print("Ghost mode update failed: \(error.localizedDescription)") - - ghostMode = !enabled - return - } - - if let httpResponse = response as? HTTPURLResponse { - if httpResponse.statusCode == 200 { - - UserDefaults.standard.set(enabled, forKey: "ghostMode_\(username)") - print("Ghost mode \(enabled ? "enabled" : "disabled") successfully") - } else { - print("Ghost mode update failed with status code: \(httpResponse.statusCode)") - - ghostMode = !enabled - } - } - } - }.resume() - .transition(.move(edge: .trailing)) - } - .animation(.easeInOut(duration: 0.3), value: isPresented) - .onAppear { - loadGhostModeState() - } - } - - // MARK: - Ghost Mode Functions - - private func loadGhostModeState() { - - let username = authViewModel.loggedInBackendUser?.username ?? "" - ghostMode = UserDefaults.standard.bool(forKey: "ghostMode_\(username)") - } - - private func updateGhostMode(enabled: Bool) { - guard let username = authViewModel.loggedInBackendUser?.username, - let token = authViewModel.loggedInBackendUser?.token else { - return - } - - isUpdatingGhostMode = true - - let endpoint = enabled ? "ghost" : "alive" let urlString = "\(APIConstants.base_url)friends/\(endpoint)/\(username)" @@ -283,8 +187,6 @@ struct MenuOption: View { let title: String - - var body: some View { HStack(spacing: 16) { Image(icon)