From 1b35a20a0f61f2fbaa78a6104357c47800551a4d Mon Sep 17 00:00:00 2001 From: rujin2003 Date: Fri, 1 Aug 2025 00:15:55 +0530 Subject: [PATCH 1/2] feat: latest update --- VITTY/VITTY.xcodeproj/project.pbxproj | 16 +++------ .../xcshareddata/swiftpm/Package.resolved | 34 +++++++++---------- VITTY/VITTY/Academics/View/CourseRefs.swift | 2 +- .../Connect/View/Freinds/View/FriendRow.swift | 3 +- VITTY/VITTY/Info.plist | 9 +++-- .../Utilities/Constants/APIConstants.swift | 7 ++-- .../Constants/AppStorageConstants.swift | 2 +- VITTY/VITTY/VITTY.entitlements | 2 +- VITTY/VITTY/VITTYRelease.entitlements | 2 +- VITTY/VittyWidgetExtension.entitlements | 2 +- 10 files changed, 40 insertions(+), 39 deletions(-) diff --git a/VITTY/VITTY.xcodeproj/project.pbxproj b/VITTY/VITTY.xcodeproj/project.pbxproj index 070abed..4a7fb3c 100644 --- a/VITTY/VITTY.xcodeproj/project.pbxproj +++ b/VITTY/VITTY.xcodeproj/project.pbxproj @@ -28,10 +28,8 @@ 4B183EEA2D7C793800C9D801 /* RemindersData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B183EE92D7C791400C9D801 /* RemindersData.swift */; }; 4B183EEC2D7CB15800C9D801 /* CourseRefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B183EEB2D7CB11500C9D801 /* CourseRefs.swift */; }; 4B1BDBCC2E1396B1008C2DE9 /* ToolTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1BDBCB2E1396A9008C2DE9 /* ToolTip.swift */; }; - 4B2D1F0F2E26060C002AFD25 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D1F0E2E26060C002AFD25 /* GoogleService-Info.plist */; }; 4B2D648F2E20BA6300412CB7 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D648E2E20BA5A00412CB7 /* NetworkMonitor.swift */; }; 4B2D64902E20BA6300412CB7 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D648E2E20BA5A00412CB7 /* NetworkMonitor.swift */; }; - 4B2D64922E20C1AC00412CB7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4B2D64912E20C1AC00412CB7 /* GoogleService-Info.plist */; }; 4B2DD6952E0A703300BC3B67 /* CircleRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2DD6942E0A702D00BC3B67 /* CircleRequests.swift */; }; 4B341C0E2E1802910073906B /* FreindRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B341C0D2E18028A0073906B /* FreindRequestModel.swift */; }; 4B341C102E1803070073906B /* FreindRequestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B341C0F2E1802FC0073906B /* FreindRequestViewModel.swift */; }; @@ -66,6 +64,7 @@ 4B8B32DC2D6D75F6004F01BA /* VittyWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4B8B32C82D6D75F4004F01BA /* VittyWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4B8B33752D7029AA004F01BA /* SideBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8B33742D7029A3004F01BA /* SideBar.swift */; }; 4B8FB2C42E374A6700E50AE2 /* AppStorageConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3109639E27824F6F0009A29C /* AppStorageConstants.swift */; }; + 4B8FB2C82E39D29F00E50AE2 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4B8FB2C72E39D29F00E50AE2 /* GoogleService-Info.plist */; }; 4BA03C1D2D7584F3000756B0 /* AddFriend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA03C1C2D7584EA000756B0 /* AddFriend.swift */; }; 4BA6DFE92E33D5CE00B0411A /* NotesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB00322D957A6A003B8FE2 /* NotesModel.swift */; }; 4BBB00312D955163003B8FE2 /* AcademicsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB00302D95515C003B8FE2 /* AcademicsViewModel.swift */; }; @@ -145,7 +144,6 @@ 52D5AB912B6FE90100B2E66D /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D5AB902B6FE90100B2E66D /* Constants.swift */; }; 52D5AB972B6FFC8F00B2E66D /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D5AB962B6FFC8F00B2E66D /* LoginView.swift */; }; 52DBBE882B47B6B30014C57A /* FriendCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DBBE872B47B6B30014C57A /* FriendCard.swift */; }; - 52EE849E2CB9CD1F00CD864C /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 52EE849D2CB9CD1F00CD864C /* GoogleService-Info.plist */; }; 5D72EDD82AB98D6D00704BF5 /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D72EDD72AB98D6D00704BF5 /* URLSession.swift */; }; 5D72EDDD2AB990BB00704BF5 /* AuthRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D72EDDC2AB990BB00704BF5 /* AuthRequestBody.swift */; }; 5D7F04F72AAB9E9900ECED15 /* APIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D7F04F62AAB9E9900ECED15 /* APIConstants.swift */; }; @@ -200,9 +198,7 @@ 4B183EE92D7C791400C9D801 /* RemindersData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindersData.swift; sourceTree = ""; }; 4B183EEB2D7CB11500C9D801 /* CourseRefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseRefs.swift; sourceTree = ""; }; 4B1BDBCB2E1396A9008C2DE9 /* ToolTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolTip.swift; sourceTree = ""; }; - 4B2D1F0E2E26060C002AFD25 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 4B2D648E2E20BA5A00412CB7 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; - 4B2D64912E20C1AC00412CB7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 4B2DD6942E0A702D00BC3B67 /* CircleRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleRequests.swift; sourceTree = ""; }; 4B341C0D2E18028A0073906B /* FreindRequestModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreindRequestModel.swift; sourceTree = ""; }; 4B341C0F2E1802FC0073906B /* FreindRequestViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FreindRequestViewModel.swift; sourceTree = ""; }; @@ -233,6 +229,7 @@ 4B8B32C82D6D75F4004F01BA /* VittyWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VittyWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 4B8B32C92D6D75F4004F01BA /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; 4B8B33742D7029A3004F01BA /* SideBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBar.swift; sourceTree = ""; }; + 4B8FB2C72E39D29F00E50AE2 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 4BA03C1C2D7584EA000756B0 /* AddFriend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFriend.swift; sourceTree = ""; }; 4BBB00302D95515C003B8FE2 /* AcademicsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcademicsViewModel.swift; sourceTree = ""; }; 4BBB00322D957A6A003B8FE2 /* NotesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesModel.swift; sourceTree = ""; }; @@ -279,7 +276,6 @@ 52D5AB902B6FE90100B2E66D /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 52D5AB962B6FFC8F00B2E66D /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; 52DBBE872B47B6B30014C57A /* FriendCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendCard.swift; sourceTree = ""; }; - 52EE849D2CB9CD1F00CD864C /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 5D72EDD72AB98D6D00704BF5 /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = ""; }; 5D72EDDC2AB990BB00704BF5 /* AuthRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRequestBody.swift; sourceTree = ""; }; 5D7F04F62AAB9E9900ECED15 /* APIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIConstants.swift; sourceTree = ""; }; @@ -418,9 +414,7 @@ isa = PBXGroup; children = ( 4BC853C52DF6F71B0092B2E2 /* VittyWidgetExtension.entitlements */, - 4B2D64912E20C1AC00412CB7 /* GoogleService-Info.plist */, - 52EE849D2CB9CD1F00CD864C /* GoogleService-Info.plist */, - 4B2D1F0E2E26060C002AFD25 /* GoogleService-Info.plist */, + 4B8FB2C72E39D29F00E50AE2 /* GoogleService-Info.plist */, 5251A7FF2B46E3C000D44CFE /* .swift-format */, 314A408E27383BEC0058082F /* VITTYApp.swift */, 314A409027383BEC0058082F /* ContentView.swift */, @@ -1125,11 +1119,9 @@ 31128CFE2772F57E0084C9EA /* Poppins-MediumItalic.ttf in Resources */, 31128CFD2772F57E0084C9EA /* Poppins-SemiBold.ttf in Resources */, 31128CF92772F57E0084C9EA /* Poppins-Medium.ttf in Resources */, - 4B2D1F0F2E26060C002AFD25 /* GoogleService-Info.plist in Resources */, 31128CFA2772F57E0084C9EA /* Poppins-SemiBoldItalic.ttf in Resources */, 31128CFC2772F57E0084C9EA /* Poppins-Regular.ttf in Resources */, - 52EE849E2CB9CD1F00CD864C /* GoogleService-Info.plist in Resources */, - 4B2D64922E20C1AC00412CB7 /* GoogleService-Info.plist in Resources */, + 4B8FB2C82E39D29F00E50AE2 /* GoogleService-Info.plist in Resources */, 314A409627383BEE0058082F /* Preview Assets.xcassets in Resources */, 314A409327383BEE0058082F /* Assets.xcassets in Resources */, ); diff --git a/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 898596b..3afd24c 100644 --- a/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/VITTY/VITTY.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/google/abseil-cpp-binary.git", "state" : { - "revision" : "748c7837511d0e6a507737353af268484e1745e2", - "version" : "1.2024011601.1" + "revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27", + "version" : "1.2024011602.0" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Alamofire/Alamofire.git", "state" : { - "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", - "version" : "5.9.1" + "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", + "version" : "5.10.2" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/openid/AppAuth-iOS.git", "state" : { - "revision" : "c89ed571ae140f8eb1142735e6e23d7bb8c34cb2", - "version" : "1.7.5" + "revision" : "2781038865a80e2c425a1da12cc1327bcd56501f", + "version" : "1.7.6" } }, { @@ -51,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk", "state" : { - "revision" : "e57841b296d04370ea23580f908881b0ccab17b9", - "version" : "10.28.1" + "revision" : "eca84fd638116dd6adb633b5a3f31cc7befcbb7d", + "version" : "10.29.0" } }, { @@ -159,17 +159,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser.git", "state" : { - "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", - "version" : "1.4.0" + "revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3", + "version" : "1.6.1" } }, { "identity" : "swift-cmark", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-cmark.git", + "location" : "https://github.com/swiftlang/swift-cmark.git", "state" : { - "revision" : "3bc2f3e25df0cecc5dc269f7ccae65d0f386f06a", - "version" : "0.4.0" + "revision" : "b022b08312decdc46585e0b3440d97f6f22ef703", + "version" : "0.6.0" } }, { @@ -186,8 +186,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-markdown.git", "state" : { - "revision" : "4aae40bf6fff5286e0e1672329d17824ce16e081", - "version" : "0.4.0" + "revision" : "ea79e83c8744d2b50b0dc2d5bbd1e857e1253bf9", + "version" : "0.6.0" } }, { @@ -195,8 +195,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { - "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb", - "version" : "1.26.0" + "revision" : "102a647b573f60f73afdce5613a51d71349fe507", + "version" : "1.30.0" } }, { diff --git a/VITTY/VITTY/Academics/View/CourseRefs.swift b/VITTY/VITTY/Academics/View/CourseRefs.swift index 926a3a6..4a38753 100644 --- a/VITTY/VITTY/Academics/View/CourseRefs.swift +++ b/VITTY/VITTY/Academics/View/CourseRefs.swift @@ -610,7 +610,7 @@ struct CompactFileCard: View { if let image = fileImage { Image(uiImage: image) .resizable() - .aspectRatio(contentMode: .fill) + .aspectRatio(contentMode: .fit) } else if isLoading { Rectangle() .fill(Color.gray.opacity(0.3)) diff --git a/VITTY/VITTY/Connect/View/Freinds/View/FriendRow.swift b/VITTY/VITTY/Connect/View/Freinds/View/FriendRow.swift index 28ddae7..7845b90 100644 --- a/VITTY/VITTY/Connect/View/Freinds/View/FriendRow.swift +++ b/VITTY/VITTY/Connect/View/Freinds/View/FriendRow.swift @@ -71,6 +71,7 @@ struct ActionResultAlert: View { .foregroundColor(.white) .multilineTextAlignment(.center) + Button(action: { onDismiss() }) { @@ -79,7 +80,7 @@ struct ActionResultAlert: View { .padding(.vertical, 8) .frame(maxWidth: .infinity) .background(Color("Accent")) - .foregroundColor(.white) + .foregroundColor(.black) .cornerRadius(8) } } diff --git a/VITTY/VITTY/Info.plist b/VITTY/VITTY/Info.plist index 827548b..260984c 100644 --- a/VITTY/VITTY/Info.plist +++ b/VITTY/VITTY/Info.plist @@ -13,7 +13,7 @@ Google SignIn CFBundleURLSchemes - com.googleusercontent.apps.266303676876-77duk9tr18717lspccrvjuqcnuv0dp2s + com.googleusercontent.apps.272763363329-i8n51oo9m30h9it7qq9ufmd0lahnmm63 @@ -30,7 +30,7 @@ FirebaseAppDelegateProxyEnabled GIDClientID - 266303676876-77duk9tr18717lspccrvjuqcnuv0dp2s.apps.googleusercontent.com + 272763363329-i8n51oo9m30h9it7qq9ufmd0lahnmm63.apps.googleusercontent.com LSApplicationQueriesSchemes comgooglemaps @@ -63,5 +63,10 @@ Allow photo library access to save or attach notes, files . UIViewControllerBasedStatusBarAppearance + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + diff --git a/VITTY/VITTY/Utilities/Constants/APIConstants.swift b/VITTY/VITTY/Utilities/Constants/APIConstants.swift index 74bf1c7..cf063ff 100644 --- a/VITTY/VITTY/Utilities/Constants/APIConstants.swift +++ b/VITTY/VITTY/Utilities/Constants/APIConstants.swift @@ -13,10 +13,13 @@ struct APIConstants { - static let base_url = "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v2/" +// static let base_url = "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v2/" +// +// static let base_urlv3 = "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v3/" - static let base_urlv3 = "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v3/" + static let base_url = "http://68.233.117.217:3000/api/v2/" + static let base_urlv3 = "http://68.233.117.217:3000/api/v3/" static let createCircle = "circles/create/" diff --git a/VITTY/VITTY/Utilities/Constants/AppStorageConstants.swift b/VITTY/VITTY/Utilities/Constants/AppStorageConstants.swift index 043692e..1929154 100644 --- a/VITTY/VITTY/Utilities/Constants/AppStorageConstants.swift +++ b/VITTY/VITTY/Utilities/Constants/AppStorageConstants.swift @@ -9,6 +9,6 @@ import Foundation struct AppConstants { - static let VITTYappgroup = "group.com.gdscvit.vittyios.shared" + static let VITTYappgroup = "group.com.gdscvit.vittyioswidget" } diff --git a/VITTY/VITTY/VITTY.entitlements b/VITTY/VITTY/VITTY.entitlements index 20abe12..f808008 100644 --- a/VITTY/VITTY/VITTY.entitlements +++ b/VITTY/VITTY/VITTY.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.com.gdscvit.vittyios.shared + group.com.gdscvit.vittyioswidget keychain-access-groups diff --git a/VITTY/VITTY/VITTYRelease.entitlements b/VITTY/VITTY/VITTYRelease.entitlements index 20abe12..f808008 100644 --- a/VITTY/VITTY/VITTYRelease.entitlements +++ b/VITTY/VITTY/VITTYRelease.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.com.gdscvit.vittyios.shared + group.com.gdscvit.vittyioswidget keychain-access-groups diff --git a/VITTY/VittyWidgetExtension.entitlements b/VITTY/VittyWidgetExtension.entitlements index 94339c6..f7c7be3 100644 --- a/VITTY/VittyWidgetExtension.entitlements +++ b/VITTY/VittyWidgetExtension.entitlements @@ -4,7 +4,7 @@ com.apple.security.application-groups - group.com.gdscvit.vittyios.shared + group.com.gdscvit.vittyioswidget From a43a065aba7aa295386af49fdf75a30e8de0ae43 Mon Sep 17 00:00:00 2001 From: rujin2003 Date: Mon, 4 Aug 2025 23:59:56 +0530 Subject: [PATCH 2/2] fix: freinds timetable view , server maintainace feat --- VITTY/ServerStatus.swift | 175 +++++++++++ VITTY/VITTY.xcodeproj/project.pbxproj | 8 + VITTY/VITTY/Connect/View/ConnectPage.swift | 146 ++++++--- .../ViewModel/CommunityPageViewModel.swift | 22 +- .../Instruction/Views/InstructionView.swift | 289 ++++++++++++------ VITTY/VITTY/Shared/Constants.swift | 2 +- .../Views/FriendsTimetableView.swift | 145 +++++---- .../Utilities/Constants/APIConstants.swift | 13 +- 8 files changed, 588 insertions(+), 212 deletions(-) create mode 100644 VITTY/ServerStatus.swift diff --git a/VITTY/ServerStatus.swift b/VITTY/ServerStatus.swift new file mode 100644 index 0000000..96b8f06 --- /dev/null +++ b/VITTY/ServerStatus.swift @@ -0,0 +1,175 @@ +// +// ServerStatus.swift +// VITTY +// +// Created by Rujin Devkota on 8/4/25. +// + +import Foundation +import Alamofire +import SwiftUI +import OSLog + +@Observable +class ServerStatusManager { + var isServerDown = false + var isCheckingServer = false + var showMaintenanceAlert = false + + private let logger = Logger( + subsystem: Bundle.main.bundleIdentifier!, + category: String(describing: ServerStatusManager.self) + ) + + + func checkServerStatus(completion: @escaping (Bool) -> Void) { + isCheckingServer = true + + let url = APIConstants.base_url + + AF.request(url, method: .get) + .validate() + .responseString { response in + DispatchQueue.main.async { + self.isCheckingServer = false + + switch response.result { + case .success(let responseString): + + let isServerUp = responseString.contains("Welcome to VITTY API!🎉") + self.isServerDown = !isServerUp + + if !isServerUp { + self.showMaintenanceAlert = true + self.logger.warning("Server is under maintenance") + } + + completion(isServerUp) + + case .failure(let error): + self.logger.error("Server check failed: \(error)") + self.isServerDown = true + self.showMaintenanceAlert = true + completion(false) + } + } + } + } + + + // function for testing +// func checkServerStatus(completion: @escaping (Bool) -> Void) { +// isCheckingServer = true +// +// +// DispatchQueue.main.async { +// self.isCheckingServer = false +// self.isServerDown = false +// self.showMaintenanceAlert = false +// +// +// completion(true) +// } +// } + + func hideMaintenanceAlert() { + showMaintenanceAlert = false + } + + func retryServerCheck(completion: @escaping (Bool) -> Void) { + checkServerStatus(completion: completion) + } +} + +// MARK: - Maintenance Alert View +struct MaintenanceAlertView: View { + @Binding var isPresented: Bool + let onRetry: () -> Void + let onContinue: () -> Void + + var body: some View { + ZStack { + Color.black.opacity(0.5) + .edgesIgnoringSafeArea(.all) + + VStack(spacing: 20) { + + HStack { + Spacer() + Button(action: { + isPresented = false + onContinue() + }) { + Image(systemName: "xmark") + .foregroundColor(.gray) + .font(.system(size: 18)) + } + } + + + RoundedRectangle(cornerRadius: 16) + .fill(Color.gray.opacity(0.3)) + .frame(width: 80, height: 80) + .overlay( + Image(systemName: "wrench.and.screwdriver") + .foregroundColor(.white) + .font(.system(size: 32)) + ) + + + Text("Server Maintenance") + .font(.custom("Poppins-SemiBold", size: 24)) + .foregroundColor(.white) + + + Text("The server is currently under maintenance. Some features may be temporarily unavailable.") + .font(.custom("Poppins-Regular", size: 16)) + .foregroundColor(.gray) + .multilineTextAlignment(.center) + .padding(.horizontal, 20) + + // Buttons + HStack(spacing: 15) { + Button(action: { + onRetry() + }) { + Text("Retry") + .font(.custom("Poppins-Medium", size: 16)) + .foregroundColor(.black) + .frame(maxWidth: .infinity) + .padding(.vertical, 12) + .background(Color.white) + .cornerRadius(8) + } + + Button(action: { + isPresented = false + onContinue() + }) { + Text("Continue") + .font(.custom("Poppins-Medium", size: 16)) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 12) + .background(Color.clear) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color.gray, lineWidth: 1) + ) + } + } + .padding(.horizontal, 20) + + + Text("Thank you for your patience") + .font(.custom("Poppins-Regular", size: 14)) + .foregroundColor(.gray) + .padding(.top, 10) + } + .padding(30) + .background(Color("Background")) + .cornerRadius(20) + .padding(.horizontal, 30) + } + } +} diff --git a/VITTY/VITTY.xcodeproj/project.pbxproj b/VITTY/VITTY.xcodeproj/project.pbxproj index 4a7fb3c..f522f5b 100644 --- a/VITTY/VITTY.xcodeproj/project.pbxproj +++ b/VITTY/VITTY.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 4B183EE82D7C78B600C9D801 /* Courses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B183EE72D7C78B300C9D801 /* Courses.swift */; }; 4B183EEA2D7C793800C9D801 /* RemindersData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B183EE92D7C791400C9D801 /* RemindersData.swift */; }; 4B183EEC2D7CB15800C9D801 /* CourseRefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B183EEB2D7CB11500C9D801 /* CourseRefs.swift */; }; + 4B1A500F2E3E61530060314D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4B1A500E2E3E61530060314D /* GoogleService-Info.plist */; }; + 4B1A50112E409F8C0060314D /* ServerStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1A50102E409F860060314D /* ServerStatus.swift */; }; 4B1BDBCC2E1396B1008C2DE9 /* ToolTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1BDBCB2E1396A9008C2DE9 /* ToolTip.swift */; }; 4B2D648F2E20BA6300412CB7 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D648E2E20BA5A00412CB7 /* NetworkMonitor.swift */; }; 4B2D64902E20BA6300412CB7 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2D648E2E20BA5A00412CB7 /* NetworkMonitor.swift */; }; @@ -197,6 +199,8 @@ 4B183EE72D7C78B300C9D801 /* Courses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Courses.swift; sourceTree = ""; }; 4B183EE92D7C791400C9D801 /* RemindersData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemindersData.swift; sourceTree = ""; }; 4B183EEB2D7CB11500C9D801 /* CourseRefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseRefs.swift; sourceTree = ""; }; + 4B1A500E2E3E61530060314D /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 4B1A50102E409F860060314D /* ServerStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerStatus.swift; sourceTree = ""; }; 4B1BDBCB2E1396A9008C2DE9 /* ToolTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolTip.swift; sourceTree = ""; }; 4B2D648E2E20BA5A00412CB7 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; }; 4B2DD6942E0A702D00BC3B67 /* CircleRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleRequests.swift; sourceTree = ""; }; @@ -414,10 +418,12 @@ isa = PBXGroup; children = ( 4BC853C52DF6F71B0092B2E2 /* VittyWidgetExtension.entitlements */, + 4B1A500E2E3E61530060314D /* GoogleService-Info.plist */, 4B8FB2C72E39D29F00E50AE2 /* GoogleService-Info.plist */, 5251A7FF2B46E3C000D44CFE /* .swift-format */, 314A408E27383BEC0058082F /* VITTYApp.swift */, 314A409027383BEC0058082F /* ContentView.swift */, + 4B1A50102E409F860060314D /* ServerStatus.swift */, 314A409227383BEE0058082F /* Assets.xcassets */, AFEFCB6C27C90233007B2029 /* VITTY.entitlements */, AFEFCB6B27C90042007B2029 /* VITTYRelease.entitlements */, @@ -1122,6 +1128,7 @@ 31128CFA2772F57E0084C9EA /* Poppins-SemiBoldItalic.ttf in Resources */, 31128CFC2772F57E0084C9EA /* Poppins-Regular.ttf in Resources */, 4B8FB2C82E39D29F00E50AE2 /* GoogleService-Info.plist in Resources */, + 4B1A500F2E3E61530060314D /* GoogleService-Info.plist in Resources */, 314A409627383BEE0058082F /* Preview Assets.xcassets in Resources */, 314A409327383BEE0058082F /* Assets.xcassets in Resources */, ); @@ -1213,6 +1220,7 @@ 4B7DA5E72D71AC54007354A3 /* CirclesRow.swift in Sources */, 4B7DA5E52D70B2CA007354A3 /* Circles.swift in Sources */, 525F759D2B809F8400E3B418 /* LectureDetailView.swift in Sources */, + 4B1A50112E409F8C0060314D /* ServerStatus.swift in Sources */, 520BA6432B47FFF900124850 /* SuggestedFriendsViewModel.swift in Sources */, 314A408F27383BEC0058082F /* VITTYApp.swift in Sources */, 52978F8E2C3A7924008CF46C /* BottomBarView.swift in Sources */, diff --git a/VITTY/VITTY/Connect/View/ConnectPage.swift b/VITTY/VITTY/Connect/View/ConnectPage.swift index e8418ff..142522e 100644 --- a/VITTY/VITTY/Connect/View/ConnectPage.swift +++ b/VITTY/VITTY/Connect/View/ConnectPage.swift @@ -29,6 +29,8 @@ struct ConnectPage: View { @Environment(CommunityPageViewModel.self) private var communityPageViewModel @Environment(FriendRequestViewModel.self) private var friendRequestViewModel @Environment(RequestsViewModel.self) private var requestsViewModel + @State private var serverStatusManager = ServerStatusManager() + @State private var isShowingRequestView = false @State var isCircleView = false @State private var activeSheet: SheetType? @@ -41,6 +43,7 @@ struct ConnectPage: View { @State private var isAddFriendsViewPresented = false @State private var selectedTab = 0 @State private var hasLoadedInitialData = false + @State private var hasCheckedServer = false var body: some View { ZStack { @@ -71,7 +74,9 @@ struct ConnectPage: View { if isCircleView == false { Button(action: { - isShowingRequestView.toggle() + checkServerAndExecute { + isShowingRequestView.toggle() + } }) { ZStack { @@ -104,7 +109,9 @@ struct ConnectPage: View { .offset(x: UIScreen.main.bounds.width*0.4228, y: UIScreen.main.bounds.height*0.38901*(-1)) } else { Button(action: { - showCircleMenu = true + checkServerAndExecute { + showCircleMenu = true + } }) { Image(systemName: "ellipsis") .foregroundColor(.white) @@ -112,19 +119,43 @@ struct ConnectPage: View { } .offset(x: UIScreen.main.bounds.width*0.4228, y: UIScreen.main.bounds.height*0.38901*(-1)) } + + + if serverStatusManager.showMaintenanceAlert { + MaintenanceAlertView( + isPresented: $serverStatusManager.showMaintenanceAlert, + onRetry: { + serverStatusManager.retryServerCheck { isServerUp in + if isServerUp { + loadInitialData() + } + } + }, + onContinue: { + + serverStatusManager.hideMaintenanceAlert() + } + ) + } } .overlay( Group { if showCircleMenu { ConnectCircleMenuView( onCreateGroup: { - activeSheet = .createGroup + checkServerAndExecute { + activeSheet = .createGroup + } }, onJoinGroup: { - activeSheet = .joinGroup + checkServerAndExecute { + activeSheet = .joinGroup + } }, onGroupRequests: { - activeSheet = .groupRequests + checkServerAndExecute { + activeSheet = .groupRequests + } }, onCancel: { showCircleMenu = false @@ -144,55 +175,86 @@ struct ConnectPage: View { case .groupRequests: CircleRequestsView() } - }.onChange(of: navigationCoordinator.shouldNavigateToCircles) { _, shouldNavigate in + } + .onChange(of: navigationCoordinator.shouldNavigateToCircles) { _, shouldNavigate in if shouldNavigate { selectedTab = 0 } + checkServerAndExecute { + communityPageViewModel.fetchCircleData( + from: "\(APIConstants.base_urlv3)circles", + token: authViewModel.loggedInBackendUser?.token ?? "", + loading: true + ) + } + } + .onAppear { + if !hasCheckedServer { + checkServerAndLoadData() + } + } + } + + // MARK: - Helper Methods + + private func checkServerAndLoadData() { + hasCheckedServer = true + serverStatusManager.checkServerStatus { isServerUp in + if isServerUp || !serverStatusManager.showMaintenanceAlert { + loadInitialData() + } + } + } + + private func checkServerAndExecute(_ action: @escaping () -> Void) { + if serverStatusManager.isServerDown { + serverStatusManager.showMaintenanceAlert = true + } else { + serverStatusManager.checkServerStatus { isServerUp in + if isServerUp { + action() + } + } + } + } + + private func loadInitialData() { + if navigationCoordinator.shouldNavigateToCircles { + selectedTab = 0 + } + + let shouldShowLoading = !hasLoadedInitialData + + requestsViewModel.fetchFriendRequests( + token: authViewModel.loggedInBackendUser?.token ?? "", + loading: shouldShowLoading + ) + + if communityPageViewModel.friends.isEmpty || !hasLoadedInitialData { + communityPageViewModel.fetchFriendsData( + from: "\(APIConstants.base_url)friends/\(authViewModel.loggedInBackendUser?.username ?? "")/", + token: authViewModel.loggedInBackendUser?.token ?? "", + loading: shouldShowLoading + ) + } + + if communityPageViewModel.circles.isEmpty || !hasLoadedInitialData { communityPageViewModel.fetchCircleData( from: "\(APIConstants.base_urlv3)circles", token: authViewModel.loggedInBackendUser?.token ?? "", - loading: true + loading: shouldShowLoading ) } - .onAppear { - if navigationCoordinator.shouldNavigateToCircles { - selectedTab = 0 - } - let shouldShowLoading = !hasLoadedInitialData - - requestsViewModel.fetchFriendRequests( - token: authViewModel.loggedInBackendUser?.token ?? "", + if communityPageViewModel.circleRequests.isEmpty || !hasLoadedInitialData { + friendRequestViewModel.fetchFriendRequests( + from: URL(string: "\(APIConstants.base_urlv3)requests/")!, + authToken: authViewModel.loggedInBackendUser?.token ?? "", loading: shouldShowLoading ) - - - if communityPageViewModel.friends.isEmpty || !hasLoadedInitialData { - communityPageViewModel.fetchFriendsData( - from: "\(APIConstants.base_url)friends/\(authViewModel.loggedInBackendUser?.username ?? "")/", - token: authViewModel.loggedInBackendUser?.token ?? "", - loading: shouldShowLoading - ) - } - - if communityPageViewModel.circles.isEmpty || !hasLoadedInitialData { - communityPageViewModel.fetchCircleData( - from: "\(APIConstants.base_urlv3)circles", - token: authViewModel.loggedInBackendUser?.token ?? "", - loading: shouldShowLoading - ) - } - - if communityPageViewModel.circleRequests.isEmpty || !hasLoadedInitialData { - friendRequestViewModel.fetchFriendRequests( - from: URL(string: "\(APIConstants.base_urlv3)requests/")!, - authToken: authViewModel.loggedInBackendUser?.token ?? "", - loading: shouldShowLoading - ) - } - - hasLoadedInitialData = true } + + hasLoadedInitialData = true } } struct ConnectCircleMenuView: View { diff --git a/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift b/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift index b764003..ee8033c 100644 --- a/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift +++ b/VITTY/VITTY/Connect/ViewModel/CommunityPageViewModel.swift @@ -757,21 +757,22 @@ class CommunityPageViewModel { DispatchQueue.main.async { switch response.result { case .success(let data): - self.activeFriends = Set(data.data) + + let activeUsernames = data.data.map { $0.friend_username } + self.activeFriends = Set(activeUsernames) self.hasInitialActiveFriendsFetch = true - self.logger.info("Successfully fetched active friends: \(data.data)") + self.logger.info("Successfully fetched active friends: \(activeUsernames)") - + if !self.friends.isEmpty { let allFriendUsernames = Set(self.friends.map { $0.username }) - let activeSet = Set(data.data) + let activeSet = Set(activeUsernames) let friendsToGhost = allFriendUsernames.subtracting(activeSet) - self.ghostedFriends.formUnion(friendsToGhost) self.saveGhostStateToUserDefaults() - self.logger.info("Active friends: \(data.data)") + self.logger.info("Active friends: \(activeUsernames)") self.logger.info("Ghosted friends: \(Array(self.ghostedFriends))") } @@ -779,7 +780,6 @@ class CommunityPageViewModel { case .failure(let error): self.logger.error("Error fetching active friends: \(error)") - self.loadGhostStateFromUserDefaults() completion(false) } @@ -904,9 +904,15 @@ class CommunityPageViewModel { } struct ActiveFriendsResponse: Codable { - let data: [String] + let data: [ActiveFriend] + } + + struct ActiveFriend: Codable { + let friend_username: String + let hide: Bool } + struct APIResponse: Codable { let data: String } diff --git a/VITTY/VITTY/Instruction/Views/InstructionView.swift b/VITTY/VITTY/Instruction/Views/InstructionView.swift index e73c7fd..4067901 100644 --- a/VITTY/VITTY/Instruction/Views/InstructionView.swift +++ b/VITTY/VITTY/Instruction/Views/InstructionView.swift @@ -8,103 +8,194 @@ import SwiftUI struct InstructionView: View { - @Environment(AuthViewModel.self) private var authViewModel - var body: some View { - NavigationStack { - ZStack { - BackgroundView() - VStack(alignment: .leading) { - VStack(alignment: .leading) { - Text("Account Details") - .font(Font.custom("Poppins-SemiBold", size: 20)) - .foregroundColor(Color.white) - .padding(.vertical, 5) - Text( - "Name: \(authViewModel.loggedInFirebaseUser?.displayName ?? "-")" - ) - Text( - "Signed in with: \(authViewModel.loggedInFirebaseUser?.providerID ?? "-")" - ) - Text( - "Email: \(authViewModel.loggedInFirebaseUser?.email ?? "-")" - ) - } - .padding() - .frame(maxWidth: .infinity, alignment: .topLeading) - .font(Font.custom("Poppins-Regular", size: 16)) - .background{ - RoundedRectangle(cornerRadius: 12, style: .circular) - .fill(Color("Secondary")) - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(Color("Accent"), lineWidth: 1.2) - ) - } - VStack(alignment: .leading) { - Text("Setup Instructions") - .font(Font.custom("Poppins-SemiBold", size: 20)) - .padding(.vertical, 5) - VStack(alignment: .leading) { - Text("1. Upload the timetable on") - Link( - destination: URL(string: "https://dscv.it/vittyconnect")!, - label: { - Text(StringConstants.websiteURL) - .underline() - } - ) - Text("2. Log in with the same Apple/Google Account as shown above") - Text("3. Upload a screenshot of your timetable") - Text("4. Review it") - Text("5. When done, click on Upload") - Text("BRAVO! That's it. You did it!") - .padding(.vertical) - } - } - .padding() - .frame(maxWidth: .infinity, alignment: .topLeading) - .font(Font.custom("Poppins-Regular", size: 16)) - .background{ - RoundedRectangle(cornerRadius: 12, style: .circular) - .fill(Color("Secondary")) - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(Color("Accent"), lineWidth: 1.2) - ) - } - Spacer() - NavigationLink(destination: { - if authViewModel.loggedInBackendUser == nil { - UsernameView() - } - else { - HomeView() - } - }) { - Spacer() - Text("Done") - .fontWeight(.bold) - .foregroundColor(Color.white) - .padding(.vertical, 16) - - Spacer() - } - .background(Color("Secondary")) - .cornerRadius(18) - } - .padding() - } - .toolbar { - Button(action: { - authViewModel.signOut() - }) { - Image(systemName: "arrow.right.square") - } - .foregroundStyle(.white) - } - .navigationTitle("Sync Timetable") - } - } + @Environment(AuthViewModel.self) private var authViewModel + @State private var serverStatusManager = ServerStatusManager() + + var body: some View { + NavigationStack { + ZStack { + BackgroundView() + + if serverStatusManager.isServerDown { + + VStack(spacing: 30) { + Spacer() + + + RoundedRectangle(cornerRadius: 20) + .fill(Color.gray.opacity(0.3)) + .frame(width: 100, height: 100) + .overlay( + Image(systemName: "wrench.and.screwdriver") + .foregroundColor(.white) + .font(.system(size: 40)) + ) + + + Text("Server Under Maintenance") + .font(.custom("Poppins-SemiBold", size: 28)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + + + + + Text("We're currently performing server maintenance to improve your experience.") + .font(.custom("Poppins-Regular", size: 16)) + .foregroundColor(.gray) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + + Text("Please check back in a few minutes. We'll be back online soon!") + .font(.custom("Poppins-Regular", size: 14)) + .foregroundColor(.gray) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + + + Button(action: { + serverStatusManager.retryServerCheck { isUp in + + } + }) { + HStack { + if serverStatusManager.isCheckingServer { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .black)) + .scaleEffect(0.8) + } else { + Text("Try Again") + .font(.custom("Poppins-Medium", size: 16)) + } + } + .foregroundColor(.black) + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(Color.white) + .cornerRadius(12) + } + .disabled(serverStatusManager.isCheckingServer) + .padding(.horizontal, 40) + + + Button(action: { + exit(0) + }) { + Text("Exit App") + .font(.custom("Poppins-Regular", size: 16)) + .foregroundColor(.gray) + } + .padding(.top, 10) + + Spacer() + + + Text("Thank you for your patience") + .font(.custom("Poppins-Regular", size: 14)) + .foregroundColor(.gray) + .padding(.bottom, 30) + } + } else { + + VStack(alignment: .leading) { + VStack(alignment: .leading) { + Text("Account Details") + .font(Font.custom("Poppins-SemiBold", size: 20)) + .foregroundColor(Color.white) + .padding(.vertical, 5) + Text( + "Name: \(authViewModel.loggedInFirebaseUser?.displayName ?? "-")" + ) + Text( + "Signed in with: \(authViewModel.loggedInFirebaseUser?.providerID ?? "-")" + ) + Text( + "Email: \(authViewModel.loggedInFirebaseUser?.email ?? "-")" + ) + } + .padding() + .frame(maxWidth: .infinity, alignment: .topLeading) + .font(Font.custom("Poppins-Regular", size: 16)) + .background{ + RoundedRectangle(cornerRadius: 12, style: .circular) + .fill(Color("Secondary")) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color("Accent"), lineWidth: 1.2) + ) + } + + VStack(alignment: .leading) { + Text("Setup Instructions") + .font(Font.custom("Poppins-SemiBold", size: 20)) + .padding(.vertical, 5) + VStack(alignment: .leading) { + Text("1. Upload the timetable on") + Link( + destination: URL(string: "https://dscv.it/vittyconnect")!, + label: { + Text(StringConstants.websiteURL) + .underline() + } + ) + Text("2. Log in with the same Apple/Google Account as shown above") + Text("3. Upload a screenshot of your timetable") + Text("4. Review it") + Text("5. When done, click on Upload") + Text("BRAVO! That's it. You did it!") + .padding(.vertical) + } + } + .padding() + .frame(maxWidth: .infinity, alignment: .topLeading) + .font(Font.custom("Poppins-Regular", size: 16)) + .background{ + RoundedRectangle(cornerRadius: 12, style: .circular) + .fill(Color("Secondary")) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color("Accent"), lineWidth: 1.2) + ) + } + + Spacer() + + NavigationLink(destination: { + if authViewModel.loggedInBackendUser == nil { + UsernameView() + } + else { + HomeView() + } + }) { + Spacer() + Text("Done") + .fontWeight(.bold) + .foregroundColor(Color.white) + .padding(.vertical, 16) + Spacer() + } + .background(Color("Secondary")) + .cornerRadius(18) + } + .padding() + } + } + .toolbar { + Button(action: { + authViewModel.signOut() + }) { + Image(systemName: "arrow.right.square") + } + .foregroundStyle(.white) + } + .navigationTitle(serverStatusManager.isServerDown ? "" : "Sync Timetable") + .onAppear { + + serverStatusManager.checkServerStatus { isUp in + + } + } + } + } } - - diff --git a/VITTY/VITTY/Shared/Constants.swift b/VITTY/VITTY/Shared/Constants.swift index 5a8ed9d..c7c0141 100644 --- a/VITTY/VITTY/Shared/Constants.swift +++ b/VITTY/VITTY/Shared/Constants.swift @@ -12,7 +12,7 @@ class Constants { // "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v2/" - "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v2/" + "https://53392be09f64.ngrok-free.app/api/v2/" // "https://f4df-2409-40e3-30a4-8539-6d49-631b-ddd8-60a3.ngrok-free.app/api/v2/" diff --git a/VITTY/VITTY/TimeTable/Views/FriendsTimetableView.swift b/VITTY/VITTY/TimeTable/Views/FriendsTimetableView.swift index 5f710fe..66eb5b8 100644 --- a/VITTY/VITTY/TimeTable/Views/FriendsTimetableView.swift +++ b/VITTY/VITTY/TimeTable/Views/FriendsTimetableView.swift @@ -45,8 +45,6 @@ struct FriendsTimeTableView: View { .foregroundColor(.white) Spacer() - - } .padding(.horizontal) .padding(.bottom, 8) @@ -64,42 +62,6 @@ struct FriendsTimeTableView: View { Spacer() } - case .error: - VStack { - Spacer() - Image(systemName: "exclamationmark.triangle") - .font(.system(size: 50)) - .foregroundColor(.orange) - .padding(.bottom, 16) - - Text("Can't show timetable right now") - .font(Font.custom("Poppins-Bold", size: 24)) - .padding(.bottom, 8) - - Text("Unable to display \(friend.name ?? friend.username)'s timetable at the moment") - .font(.subheadline) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - .padding(.horizontal) - .padding(.bottom, 20) - - Button(action: { - - }) { - HStack { - Image(systemName: "arrow.clockwise") - Text("Try Again") - } - .foregroundColor(.black) - .padding() - .background(Color("Accent")) - .cornerRadius(10) - } - .disabled(isRefreshing) - - Spacer() - } - case .empty: VStack { Spacer() @@ -123,7 +85,7 @@ struct FriendsTimeTableView: View { case .data: VStack(spacing: 0) { - // Day selector + ScrollViewReader { proxy in ScrollView(.horizontal) { HStack { @@ -200,6 +162,7 @@ struct FriendsTimeTableView: View { .padding(.top, 12) .padding(.bottom, 100) } + .scrollIndicators(.hidden) } } } @@ -219,7 +182,6 @@ struct FriendsTimeTableView: View { private func loadFriendsTimetable() { logger.debug("Loading friend's timetable from API") - let calendar = Calendar.current let today = calendar.component(.weekday, from: Date()) let dayIndex = (today == 1) ? 6 : today - 2 @@ -297,7 +259,7 @@ extension FriendsTimeTableView { guard !friendUsername.isEmpty && !authToken.isEmpty else { logger.error("Missing friend username or auth token") - stage = .error + stage = .empty return } @@ -330,11 +292,13 @@ extension FriendsTimeTableView { do { logger.info("Fetching friend's timetable from API using /users/\(friendUsername) endpoint") - - let friendTimeTable = try await TimeTableAPIService.shared.getFriendTimeTable( + let friendResponse = try await TimeTableAPIService.shared.getFriendResponse( username: friendUsername, authToken: authToken ) + + // Extract the timetable from the response + let friendTimeTable = friendResponse.timetable.data if isTimeTableEmpty(friendTimeTable) { logger.info("Friend's timetable is empty") @@ -354,7 +318,7 @@ extension FriendsTimeTableView { logger.error("Failed to fetch friend's timetable: \(error.localizedDescription)") - stage = .error + stage = .empty } } @@ -370,28 +334,68 @@ extension FriendsTimeTableView { } } - extension FriendsTimeTableView { enum Stage { case loading case data case empty - case error } } - enum APIError: Error { case serverError(code: String, message: String) case networkError case decodingError case unauthorized - } +// MARK: - Friend Response Models +struct FriendResponse: Codable { + let campus: String + let email: String + let friendStatus: String + let friendsCount: Int + let mutualFriendsCount: Int + let name: String + let picture: String + let timetable: FriendTimetableWrapper + let username: String + + enum CodingKeys: String, CodingKey { + case campus, email, name, picture, timetable, username + case friendStatus = "friend_status" + case friendsCount = "friends_count" + case mutualFriendsCount = "mutual_friends_count" + } +} + +struct FriendTimetableWrapper: Codable { + let data: TimeTable +} + + +struct TimeTableFromAPI: Codable { + let monday: [Lecture] + let tuesday: [Lecture] + let wednesday: [Lecture] + let thursday: [Lecture] + let friday: [Lecture] + let saturday: [Lecture] + let sunday: [Lecture] + + enum CodingKeys: String, CodingKey { + case monday = "Monday" + case tuesday = "Tuesday" + case wednesday = "Wednesday" + case thursday = "Thursday" + case friday = "Friday" + case saturday = "Saturday" + case sunday = "Sunday" + } +} extension TimeTableAPIService { - func getFriendTimeTable(username: String, authToken: String) async throws -> TimeTable { + func getFriendResponse(username: String, authToken: String) async throws -> FriendResponse { guard let url = URL(string: "\(APIConstants.base_urlv3)users/\(username)") else { throw APIError.networkError } @@ -404,12 +408,9 @@ extension TimeTableAPIService { let (data, response) = try await URLSession.shared.data(for: request) if let httpResponse = response as? HTTPURLResponse { - if httpResponse.statusCode == 500 { - - if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data), - errorResponse.code == "1811" { - throw APIError.serverError(code: errorResponse.code, message: errorResponse.error) - } + + guard httpResponse.statusCode != 500 else { + throw APIError.serverError(code: "500", message: "Server error") } guard httpResponse.statusCode == 200 else { @@ -418,11 +419,41 @@ extension TimeTableAPIService { } let decoder = JSONDecoder() - return try decoder.decode(TimeTable.self, from: data) + + + var friendResponse = try decoder.decode(FriendResponse.self, from: data) + + + let apiTimeTable = try JSONDecoder().decode(TimeTableFromAPI.self, from: + try JSONEncoder().encode(friendResponse.timetable.data)) + + let convertedTimeTable = TimeTable( + monday: apiTimeTable.monday, + tuesday: apiTimeTable.tuesday, + wednesday: apiTimeTable.wednesday, + thursday: apiTimeTable.thursday, + friday: apiTimeTable.friday, + saturday: apiTimeTable.saturday, + sunday: apiTimeTable.sunday + ) + + + friendResponse = FriendResponse( + campus: friendResponse.campus, + email: friendResponse.email, + friendStatus: friendResponse.friendStatus, + friendsCount: friendResponse.friendsCount, + mutualFriendsCount: friendResponse.mutualFriendsCount, + name: friendResponse.name, + picture: friendResponse.picture, + timetable: FriendTimetableWrapper(data: convertedTimeTable), + username: friendResponse.username + ) + + return friendResponse } } - struct ErrorResponse: Codable { let code: String let error: String diff --git a/VITTY/VITTY/Utilities/Constants/APIConstants.swift b/VITTY/VITTY/Utilities/Constants/APIConstants.swift index cf063ff..e8a2f50 100644 --- a/VITTY/VITTY/Utilities/Constants/APIConstants.swift +++ b/VITTY/VITTY/Utilities/Constants/APIConstants.swift @@ -13,13 +13,16 @@ struct APIConstants { -// static let base_url = "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v2/" -// -// static let base_urlv3 = "https://visiting-eba-vitty-d61856bb.koyeb.app/api/v3/" + static let base_url = "https://00951cf40596.ngrok-free.app/api/v2/" - static let base_url = "http://68.233.117.217:3000/api/v2/" + static let base_urlv3 = "https://00951cf40596.ngrok-free.app/api/v3/" - static let base_urlv3 = "http://68.233.117.217:3000/api/v3/" +// static let base_url = "http://68.233.117.217:3000/api/v2/" +// +// static let base_urlv3 = "http://68.233.117.217:3000/api/v3/" +// +// static let base_url = "https://50e460598f74.ngrok-fre.app/api/v2/" +// static let base_urlv3 = "https://50e460598f74.ngrok-fre.app/api/v3/" static let createCircle = "circles/create/"