diff --git a/SPAR/SPAR/SPARApp.swift b/SPAR/SPAR/SPARApp.swift deleted file mode 100644 index 6c72084..0000000 --- a/SPAR/SPAR/SPARApp.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// SPARApp.swift -// SPAR -// -// Created by Abhijeet Cherungottil on 2/2/25. -// - -import SwiftUI -import OSLog - -@main -struct SPARApp: App { - let logger = Logger.fileLocation - - var body: some Scene { - WindowGroup { - ContentView() - .onAppear{ - logger.info("\(URL.documentsDirectory.path())") - } - } - } -} diff --git a/SPAR/SPAR/ViewModel/LoginViewModel.swift b/SPAR/SPAR/ViewModel/LoginViewModel.swift deleted file mode 100644 index af8e256..0000000 --- a/SPAR/SPAR/ViewModel/LoginViewModel.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// LoginViewModel.swift -// SPAR -// -// Created by Abhijeet Cherungottil on 4/18/25. -// - -import Foundation -import SwiftUI -import OSLog - -class LoginViewModel: ObservableObject { - @Published var username: String = "" - @Published var password: String = "" - @Published var animate: Bool = false - @Published var showPassword: Bool = false - @Published var errorMessage: String = "" - - let logger = Logger.fileLocation - private let networkManager = NetworkManager() - - weak var delegate: LoginViewModelDelegate? - - func submit() { - let isValidUsername = username.range(of: "^[a-zA-Z0-9]+$", options: .regularExpression) != nil - let disallowedCharacters = CharacterSet(charactersIn: "\"'`;/\\<>") - let isPasswordSafe = password.rangeOfCharacter(from: disallowedCharacters) == nil - - if !isValidUsername { - errorMessage = "Username can only contain letters and numbers." - return - } - - if !isPasswordSafe { - errorMessage = "Password contains unsafe characters like \", ', ;, or \\." - return - } - -// if username.lowercased() == "user" && password == "Password" { -// errorMessage = "" -// self.delegate?.didLoginSuccessfully() -// print("Login successful!") -// } else { -// errorMessage = StringConstant.incorrectCredentials -// } - - Task { - do { - let response = try await networkManager.login(username: username, password: password) - - AppSettings.shared.authToken = response.token - AppSettings.shared.userId = response.userId - - DispatchQueue.main.async { - self.delegate?.didLoginSuccessfully() - } - } catch { - DispatchQueue.main.async { - self.errorMessage = StringConstant.incorrectCredentials - } - } - } - } -} - - - -protocol LoginViewModelDelegate: AnyObject { - func didLoginSuccessfully() -} diff --git a/SPAR/SPAR.xcodeproj/project.pbxproj b/SPAR_iOS/SPAR/SPAR.xcodeproj/project.pbxproj similarity index 98% rename from SPAR/SPAR.xcodeproj/project.pbxproj rename to SPAR_iOS/SPAR/SPAR.xcodeproj/project.pbxproj index a375826..bd290f0 100644 --- a/SPAR/SPAR.xcodeproj/project.pbxproj +++ b/SPAR_iOS/SPAR/SPAR.xcodeproj/project.pbxproj @@ -70,6 +70,7 @@ 3D98C7F92D4FE04700462EF4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3D98C7F82D4FE04700462EF4 /* Preview Assets.xcassets */; }; 3DA1AA442DBDB01300678160 /* DiskIOModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D505B6E2DBACDCA00510486 /* DiskIOModel.swift */; }; 3DA1AA452DBDB08700678160 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D505B5C2DBA657B00510486 /* AppSettings.swift */; }; + 3DA1AA472DBF034200678160 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DA1AA462DBF034200678160 /* KeychainHelper.swift */; }; 3DCE45892D68C19200FACBB8 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCE45882D68C19200FACBB8 /* Constant.swift */; }; 3DCE458B2D68C19F00FACBB8 /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCE458A2D68C19F00FACBB8 /* SplashScreenView.swift */; }; 3DCE458D2D68C1A900FACBB8 /* Logger+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCE458C2D68C1A900FACBB8 /* Logger+Extension.swift */; }; @@ -141,6 +142,7 @@ 3D98C7F32D4FE04500462EF4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 3D98C7F52D4FE04700462EF4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3D98C7F82D4FE04700462EF4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 3DA1AA462DBF034200678160 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = ""; }; 3DCE45882D68C19200FACBB8 /* Constant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; 3DCE458A2D68C19F00FACBB8 /* SplashScreenView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = ""; }; 3DCE458C2D68C1A900FACBB8 /* Logger+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Logger+Extension.swift"; sourceTree = ""; }; @@ -219,6 +221,7 @@ 3D505B642DBAAE4D00510486 /* DiskUsageViewModel.swift */, 3D505B6A2DBAC5EC00510486 /* CpuUsageViewModel.swift */, 3D505B702DBACDE500510486 /* DiskIOViewModel.swift */, + 3DA1AA462DBF034200678160 /* KeychainHelper.swift */, ); path = ViewModel; sourceTree = ""; @@ -473,6 +476,7 @@ 3D5994FA2DB307B400E9215B /* HomeViewModel.swift in Sources */, 3DCE458B2D68C19F00FACBB8 /* SplashScreenView.swift in Sources */, 3DCE458D2D68C1A900FACBB8 /* Logger+Extension.swift in Sources */, + 3DA1AA472DBF034200678160 /* KeychainHelper.swift in Sources */, 3D505B4D2DB9C1BE00510486 /* DeviceOptions.swift in Sources */, 3D505B5B2DB9E3F700510486 /* ProcessViewModel.swift in Sources */, 3D505B632DBAAE2C00510486 /* DiskUsage.swift in Sources */, @@ -697,6 +701,7 @@ DEVELOPMENT_TEAM = 2WRH48XVUZ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "We use it to unlock the phone"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -732,6 +737,7 @@ DEVELOPMENT_TEAM = 2WRH48XVUZ; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSFaceIDUsageDescription = "We use it to unlock the phone"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/SPAR/SPAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SPAR_iOS/SPAR/SPAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from SPAR/SPAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to SPAR_iOS/SPAR/SPAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/SPAR/SPAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/SPAR_iOS/SPAR/SPAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from SPAR/SPAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to SPAR_iOS/SPAR/SPAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPAR.xcscheme b/SPAR_iOS/SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPAR.xcscheme similarity index 100% rename from SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPAR.xcscheme rename to SPAR_iOS/SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPAR.xcscheme diff --git a/SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPARTests.xcscheme b/SPAR_iOS/SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPARTests.xcscheme similarity index 100% rename from SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPARTests.xcscheme rename to SPAR_iOS/SPAR/SPAR.xcodeproj/xcshareddata/xcschemes/SPARTests.xcscheme diff --git a/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/SPAR_iOS/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist similarity index 96% rename from SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist rename to SPAR_iOS/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 975704e..67ba8ea 100644 --- a/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/SPAR_iOS/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -39,48 +39,48 @@ diff --git a/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcschemes/xcschememanagement.plist b/SPAR_iOS/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 100% rename from SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcschemes/xcschememanagement.plist rename to SPAR_iOS/SPAR/SPAR.xcodeproj/xcuserdata/abhijeetcherungottil.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/7.png b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/7.png similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/7.png rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/7.png diff --git a/SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D Modern analytics webpage mockup.imageset/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/9.png b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/9.png similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/9.png rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/9.png diff --git a/SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D business analytics laptop.imageset/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/8.png b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/8.png similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/8.png rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/8.png diff --git a/SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D colorful performance gauge.imageset/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/10.png b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/10.png similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/10.png rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/10.png diff --git a/SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/3D mobile showing notification.imageset/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/AccentColor.colorset/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/AccentColor.colorset/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/3.png b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/3.png similarity index 100% rename from SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/3.png rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/3.png diff --git a/SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/logo.imageset/3.png b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/logo.imageset/3.png similarity index 100% rename from SPAR/SPAR/Assets.xcassets/logo.imageset/3.png rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/logo.imageset/3.png diff --git a/SPAR/SPAR/Assets.xcassets/logo.imageset/Contents.json b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/logo.imageset/Contents.json similarity index 100% rename from SPAR/SPAR/Assets.xcassets/logo.imageset/Contents.json rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/logo.imageset/Contents.json diff --git a/SPAR/SPAR/Assets.xcassets/logo.imageset/SP Ar-2.png b/SPAR_iOS/SPAR/SPAR/Assets.xcassets/logo.imageset/SP Ar-2.png similarity index 100% rename from SPAR/SPAR/Assets.xcassets/logo.imageset/SP Ar-2.png rename to SPAR_iOS/SPAR/SPAR/Assets.xcassets/logo.imageset/SP Ar-2.png diff --git a/SPAR/SPAR/ChartUI/HalfDonutChart.swift b/SPAR_iOS/SPAR/SPAR/ChartUI/HalfDonutChart.swift similarity index 100% rename from SPAR/SPAR/ChartUI/HalfDonutChart.swift rename to SPAR_iOS/SPAR/SPAR/ChartUI/HalfDonutChart.swift diff --git a/SPAR/SPAR/ContentView.swift b/SPAR_iOS/SPAR/SPAR/ContentView.swift similarity index 100% rename from SPAR/SPAR/ContentView.swift rename to SPAR_iOS/SPAR/SPAR/ContentView.swift diff --git a/SPAR/SPAR/Data/BatteryInfo.swift b/SPAR_iOS/SPAR/SPAR/Data/BatteryInfo.swift similarity index 100% rename from SPAR/SPAR/Data/BatteryInfo.swift rename to SPAR_iOS/SPAR/SPAR/Data/BatteryInfo.swift diff --git a/SPAR/SPAR/Data/CPUUsage.swift b/SPAR_iOS/SPAR/SPAR/Data/CPUUsage.swift similarity index 100% rename from SPAR/SPAR/Data/CPUUsage.swift rename to SPAR_iOS/SPAR/SPAR/Data/CPUUsage.swift diff --git a/SPAR/SPAR/Data/ChartData.swift b/SPAR_iOS/SPAR/SPAR/Data/ChartData.swift similarity index 100% rename from SPAR/SPAR/Data/ChartData.swift rename to SPAR_iOS/SPAR/SPAR/Data/ChartData.swift diff --git a/SPAR/SPAR/Data/DeviceSpecification.swift b/SPAR_iOS/SPAR/SPAR/Data/DeviceSpecification.swift similarity index 100% rename from SPAR/SPAR/Data/DeviceSpecification.swift rename to SPAR_iOS/SPAR/SPAR/Data/DeviceSpecification.swift diff --git a/SPAR/SPAR/Data/DiskIOModel.swift b/SPAR_iOS/SPAR/SPAR/Data/DiskIOModel.swift similarity index 100% rename from SPAR/SPAR/Data/DiskIOModel.swift rename to SPAR_iOS/SPAR/SPAR/Data/DiskIOModel.swift diff --git a/SPAR/SPAR/Data/DiskUsage.swift b/SPAR_iOS/SPAR/SPAR/Data/DiskUsage.swift similarity index 100% rename from SPAR/SPAR/Data/DiskUsage.swift rename to SPAR_iOS/SPAR/SPAR/Data/DiskUsage.swift diff --git a/SPAR/SPAR/Data/LoginModel.swift b/SPAR_iOS/SPAR/SPAR/Data/LoginModel.swift similarity index 100% rename from SPAR/SPAR/Data/LoginModel.swift rename to SPAR_iOS/SPAR/SPAR/Data/LoginModel.swift diff --git a/SPAR/SPAR/Data/MemoryUsage.swift b/SPAR_iOS/SPAR/SPAR/Data/MemoryUsage.swift similarity index 100% rename from SPAR/SPAR/Data/MemoryUsage.swift rename to SPAR_iOS/SPAR/SPAR/Data/MemoryUsage.swift diff --git a/SPAR/SPAR/Data/MockData.swift b/SPAR_iOS/SPAR/SPAR/Data/MockData.swift similarity index 100% rename from SPAR/SPAR/Data/MockData.swift rename to SPAR_iOS/SPAR/SPAR/Data/MockData.swift diff --git a/SPAR/SPAR/Data/Onboarding.swift b/SPAR_iOS/SPAR/SPAR/Data/Onboarding.swift similarity index 100% rename from SPAR/SPAR/Data/Onboarding.swift rename to SPAR_iOS/SPAR/SPAR/Data/Onboarding.swift diff --git a/SPAR/SPAR/Data/ProcessStatus.swift b/SPAR_iOS/SPAR/SPAR/Data/ProcessStatus.swift similarity index 100% rename from SPAR/SPAR/Data/ProcessStatus.swift rename to SPAR_iOS/SPAR/SPAR/Data/ProcessStatus.swift diff --git a/SPAR/SPAR/Device detail Page/DeviceDetail.swift b/SPAR_iOS/SPAR/SPAR/Device detail Page/DeviceDetail.swift similarity index 100% rename from SPAR/SPAR/Device detail Page/DeviceDetail.swift rename to SPAR_iOS/SPAR/SPAR/Device detail Page/DeviceDetail.swift diff --git a/SPAR/SPAR/Device/BatteryDetailView.swift b/SPAR_iOS/SPAR/SPAR/Device/BatteryDetailView.swift similarity index 100% rename from SPAR/SPAR/Device/BatteryDetailView.swift rename to SPAR_iOS/SPAR/SPAR/Device/BatteryDetailView.swift diff --git a/SPAR/SPAR/Device/CpuUsageDetailView.swift b/SPAR_iOS/SPAR/SPAR/Device/CpuUsageDetailView.swift similarity index 100% rename from SPAR/SPAR/Device/CpuUsageDetailView.swift rename to SPAR_iOS/SPAR/SPAR/Device/CpuUsageDetailView.swift diff --git a/SPAR/SPAR/Device/DeviceOptions.swift b/SPAR_iOS/SPAR/SPAR/Device/DeviceOptions.swift similarity index 100% rename from SPAR/SPAR/Device/DeviceOptions.swift rename to SPAR_iOS/SPAR/SPAR/Device/DeviceOptions.swift diff --git a/SPAR/SPAR/Device/DiskIODetailView.swift b/SPAR_iOS/SPAR/SPAR/Device/DiskIODetailView.swift similarity index 100% rename from SPAR/SPAR/Device/DiskIODetailView.swift rename to SPAR_iOS/SPAR/SPAR/Device/DiskIODetailView.swift diff --git a/SPAR/SPAR/Device/DiskUsageDetailView.swift b/SPAR_iOS/SPAR/SPAR/Device/DiskUsageDetailView.swift similarity index 100% rename from SPAR/SPAR/Device/DiskUsageDetailView.swift rename to SPAR_iOS/SPAR/SPAR/Device/DiskUsageDetailView.swift diff --git a/SPAR/SPAR/Device/MemoryUsageDetailView.swift b/SPAR_iOS/SPAR/SPAR/Device/MemoryUsageDetailView.swift similarity index 100% rename from SPAR/SPAR/Device/MemoryUsageDetailView.swift rename to SPAR_iOS/SPAR/SPAR/Device/MemoryUsageDetailView.swift diff --git a/SPAR/SPAR/Device/ProcessDetailPage.swift b/SPAR_iOS/SPAR/SPAR/Device/ProcessDetailPage.swift similarity index 100% rename from SPAR/SPAR/Device/ProcessDetailPage.swift rename to SPAR_iOS/SPAR/SPAR/Device/ProcessDetailPage.swift diff --git a/SPAR/SPAR/Extensions/ContentSizeCategory+Extension.swift b/SPAR_iOS/SPAR/SPAR/Extensions/ContentSizeCategory+Extension.swift similarity index 100% rename from SPAR/SPAR/Extensions/ContentSizeCategory+Extension.swift rename to SPAR_iOS/SPAR/SPAR/Extensions/ContentSizeCategory+Extension.swift diff --git a/SPAR/SPAR/Extensions/Logger+Extension.swift b/SPAR_iOS/SPAR/SPAR/Extensions/Logger+Extension.swift similarity index 100% rename from SPAR/SPAR/Extensions/Logger+Extension.swift rename to SPAR_iOS/SPAR/SPAR/Extensions/Logger+Extension.swift diff --git a/SPAR/SPAR/Extensions/String+Extension.swift b/SPAR_iOS/SPAR/SPAR/Extensions/String+Extension.swift similarity index 100% rename from SPAR/SPAR/Extensions/String+Extension.swift rename to SPAR_iOS/SPAR/SPAR/Extensions/String+Extension.swift diff --git a/SPAR/SPAR/Extensions/View+Extension.swift b/SPAR_iOS/SPAR/SPAR/Extensions/View+Extension.swift similarity index 100% rename from SPAR/SPAR/Extensions/View+Extension.swift rename to SPAR_iOS/SPAR/SPAR/Extensions/View+Extension.swift diff --git a/SPAR/SPAR/HomeView.swift b/SPAR_iOS/SPAR/SPAR/HomeView.swift similarity index 64% rename from SPAR/SPAR/HomeView.swift rename to SPAR_iOS/SPAR/SPAR/HomeView.swift index e9bb473..c01dd1e 100644 --- a/SPAR/SPAR/HomeView.swift +++ b/SPAR_iOS/SPAR/SPAR/HomeView.swift @@ -7,7 +7,6 @@ import SwiftUI - struct HomeView: View { @Binding var currentView: AppView @StateObject private var viewModel = HomeViewModel() @@ -19,7 +18,6 @@ struct HomeView: View { BackgroundAnimationView(animate: $viewModel.animate) VStack(spacing: 20) { - // SPAR title HStack { Text(StringConstant.appName) @@ -93,7 +91,48 @@ struct HomeView: View { .navigationBarHidden(true) .onAppear { self.logPageVisit() - } + } + + // Show download popup when no devices are available + if viewModel.showDownloadPopup { + VStack { + Spacer(minLength: 200) + + // Cool empty state + VStack { + Image(systemName: "desktopcomputer") + .font(.system(size: 70)) + .foregroundColor(.blue) + .scaleEffect(1.2) + .opacity(0.8) + .animation(.easeInOut(duration: 1).repeatForever(autoreverses: true), value: viewModel.showDownloadPopup) + + Text("You need to have at least one Desktop APP.") + .font(.system(size: 28, weight: .bold)) + .foregroundColor(.primary) + .multilineTextAlignment(.center) + .padding(.top, 20) + .opacity(0.9) + .scaleEffect(viewModel.showDownloadPopup ? 1 : 1.05) + .animation(.easeInOut(duration: 0.6).repeatForever(autoreverses: true), value: viewModel.showDownloadPopup) + + Text("Download SPAR Desktop") + .font(.system(size: 20, weight: .semibold)) + .foregroundColor(.blue) + .underline() + .onTapGesture { + // Action to download SPAR Desktop + // You can trigger URL to download or show info here + } + .padding(.top, 10) + + Spacer() + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(40) + .transition(.opacity) + } + } } } } diff --git a/SPAR/SPAR/Login/BackgroundAnimationView.swift b/SPAR_iOS/SPAR/SPAR/Login/BackgroundAnimationView.swift similarity index 100% rename from SPAR/SPAR/Login/BackgroundAnimationView.swift rename to SPAR_iOS/SPAR/SPAR/Login/BackgroundAnimationView.swift diff --git a/SPAR/SPAR/Login/LoginView.swift b/SPAR_iOS/SPAR/SPAR/Login/LoginView.swift similarity index 79% rename from SPAR/SPAR/Login/LoginView.swift rename to SPAR_iOS/SPAR/SPAR/Login/LoginView.swift index a0093c0..42b6f79 100644 --- a/SPAR/SPAR/Login/LoginView.swift +++ b/SPAR_iOS/SPAR/SPAR/Login/LoginView.swift @@ -94,6 +94,7 @@ struct LoginView: View { Button(action: { viewModel.submit() viewModel.logger.info("\(LoggerConstant.LoginSubmitTapped)") + AppSettings.shared.hasLoggedInOnce = true // Save after first login }) { Text(StringConstant.submit) .frame(maxWidth: .infinity) @@ -105,8 +106,26 @@ struct LoginView: View { .minimumScaleFactor(sizeCategory.customMinScaleFactor) .shadow(radius: 10) } - .accessibilityElement(children: .ignore) // Ensures only the button's main label is read by screen readers - .accessibilityAddTraits(.isButton) + .accessibilityElement(children: .ignore) + .accessibilityAddTraits(.isButton) + + // Face ID Button + if AppSettings.shared.hasLoggedInOnce { + Button(action: { + viewModel.loginWithFaceID() + }) { + Image(systemName: "faceid") + .resizable() + .scaledToFit() + .frame(width: 50, height: 50) + .padding() + .background(Color.blue.opacity(0.5)) + .foregroundColor(.white) + .clipShape(Circle()) + } + .padding(.top, 10) + .shadow(radius: 10) + } } .padding(40) } @@ -114,6 +133,13 @@ struct LoginView: View { viewModel.delegate = coordinator viewModel.animate = true self.logPageVisit() + + // If already logged in once, automatically trigger FaceID + if AppSettings.shared.hasLoggedInOnce { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + viewModel.loginWithFaceID() + } + } } } @@ -130,10 +156,8 @@ struct LoginView: View { } } - struct LoginView_Previews: PreviewProvider { static var previews: some View { LoginView(currentView: .constant(.login)) } } - diff --git a/SPAR/SPAR/NetworkService/MockNetworkService.swift b/SPAR_iOS/SPAR/SPAR/NetworkService/MockNetworkService.swift similarity index 100% rename from SPAR/SPAR/NetworkService/MockNetworkService.swift rename to SPAR_iOS/SPAR/SPAR/NetworkService/MockNetworkService.swift diff --git a/SPAR/SPAR/NetworkService/NetworkManager.swift b/SPAR_iOS/SPAR/SPAR/NetworkService/NetworkManager.swift similarity index 100% rename from SPAR/SPAR/NetworkService/NetworkManager.swift rename to SPAR_iOS/SPAR/SPAR/NetworkService/NetworkManager.swift diff --git a/SPAR/SPAR/NetworkService/NetworkService.swift b/SPAR_iOS/SPAR/SPAR/NetworkService/NetworkService.swift similarity index 100% rename from SPAR/SPAR/NetworkService/NetworkService.swift rename to SPAR_iOS/SPAR/SPAR/NetworkService/NetworkService.swift diff --git a/SPAR/SPAR/Onboarding/OnBoardingScreenView.swift b/SPAR_iOS/SPAR/SPAR/Onboarding/OnBoardingScreenView.swift similarity index 100% rename from SPAR/SPAR/Onboarding/OnBoardingScreenView.swift rename to SPAR_iOS/SPAR/SPAR/Onboarding/OnBoardingScreenView.swift diff --git a/SPAR/SPAR/Onboarding/OnboardingView.swift b/SPAR_iOS/SPAR/SPAR/Onboarding/OnboardingView.swift similarity index 100% rename from SPAR/SPAR/Onboarding/OnboardingView.swift rename to SPAR_iOS/SPAR/SPAR/Onboarding/OnboardingView.swift diff --git a/SPAR/SPAR/Preview Content/Preview Assets.xcassets/Contents.json b/SPAR_iOS/SPAR/SPAR/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from SPAR/SPAR/Preview Content/Preview Assets.xcassets/Contents.json rename to SPAR_iOS/SPAR/SPAR/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/SPAR_iOS/SPAR/SPAR/SPARApp.swift b/SPAR_iOS/SPAR/SPAR/SPARApp.swift new file mode 100644 index 0000000..86deea3 --- /dev/null +++ b/SPAR_iOS/SPAR/SPAR/SPARApp.swift @@ -0,0 +1,33 @@ +// +// SPARApp.swift +// SPAR +// +// Created by Abhijeet Cherungottil on 2/2/25. +// + +import SwiftUI +import OSLog + +@main +struct SPARApp: App { + init() { + // Check if we're running in UI Test mode + if ProcessInfo.processInfo.arguments.contains("-resetDefaults") { + let domain = Bundle.main.bundleIdentifier! + UserDefaults.standard.removePersistentDomain(forName: domain) + UserDefaults.standard.synchronize() + print("UserDefaults reset for UI Test") + } + } + + let logger = Logger.fileLocation + + var body: some Scene { + WindowGroup { + ContentView() + .onAppear{ + logger.info("\(URL.documentsDirectory.path())") + } + } + } +} diff --git a/SPAR/SPAR/SplashScreenView.swift b/SPAR_iOS/SPAR/SPAR/SplashScreenView.swift similarity index 100% rename from SPAR/SPAR/SplashScreenView.swift rename to SPAR_iOS/SPAR/SPAR/SplashScreenView.swift diff --git a/SPAR/SPAR/Utilities/AppSettings.swift b/SPAR_iOS/SPAR/SPAR/Utilities/AppSettings.swift similarity index 77% rename from SPAR/SPAR/Utilities/AppSettings.swift rename to SPAR_iOS/SPAR/SPAR/Utilities/AppSettings.swift index 1e27629..1964b73 100644 --- a/SPAR/SPAR/Utilities/AppSettings.swift +++ b/SPAR_iOS/SPAR/SPAR/Utilities/AppSettings.swift @@ -15,6 +15,12 @@ final class AppSettings { private let onboardingKey = "hasCompletedOnboarding" private let tokenKey = "authToken" private let userIdKey = "userId" + private let hasLoggedInOnceKey = "hasLoggedInOnce" + + var hasLoggedInOnce: Bool { + get { UserDefaults.standard.bool(forKey: hasLoggedInOnceKey) } + set { UserDefaults.standard.set(newValue, forKey: hasLoggedInOnceKey) } + } var hasCompletedOnboarding: Bool { get { UserDefaults.standard.bool(forKey: onboardingKey) } diff --git a/SPAR/SPAR/Utilities/Constant.swift b/SPAR_iOS/SPAR/SPAR/Utilities/Constant.swift similarity index 100% rename from SPAR/SPAR/Utilities/Constant.swift rename to SPAR_iOS/SPAR/SPAR/Utilities/Constant.swift diff --git a/SPAR/SPAR/Utilities/InfoRow.swift b/SPAR_iOS/SPAR/SPAR/Utilities/InfoRow.swift similarity index 100% rename from SPAR/SPAR/Utilities/InfoRow.swift rename to SPAR_iOS/SPAR/SPAR/Utilities/InfoRow.swift diff --git a/SPAR/SPAR/Utilities/NavigationButton.swift b/SPAR_iOS/SPAR/SPAR/Utilities/NavigationButton.swift similarity index 100% rename from SPAR/SPAR/Utilities/NavigationButton.swift rename to SPAR_iOS/SPAR/SPAR/Utilities/NavigationButton.swift diff --git a/SPAR/SPAR/ViewModel/BatteryViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/BatteryViewModel.swift similarity index 100% rename from SPAR/SPAR/ViewModel/BatteryViewModel.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/BatteryViewModel.swift diff --git a/SPAR/SPAR/ViewModel/ChartDataContainer.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/ChartDataContainer.swift similarity index 100% rename from SPAR/SPAR/ViewModel/ChartDataContainer.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/ChartDataContainer.swift diff --git a/SPAR/SPAR/ViewModel/CpuUsageViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/CpuUsageViewModel.swift similarity index 100% rename from SPAR/SPAR/ViewModel/CpuUsageViewModel.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/CpuUsageViewModel.swift diff --git a/SPAR/SPAR/ViewModel/DiskIOViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/DiskIOViewModel.swift similarity index 100% rename from SPAR/SPAR/ViewModel/DiskIOViewModel.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/DiskIOViewModel.swift diff --git a/SPAR/SPAR/ViewModel/DiskUsageViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/DiskUsageViewModel.swift similarity index 100% rename from SPAR/SPAR/ViewModel/DiskUsageViewModel.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/DiskUsageViewModel.swift diff --git a/SPAR/SPAR/ViewModel/HomeViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/HomeViewModel.swift similarity index 87% rename from SPAR/SPAR/ViewModel/HomeViewModel.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/HomeViewModel.swift index 8c8995a..0e971ad 100644 --- a/SPAR/SPAR/ViewModel/HomeViewModel.swift +++ b/SPAR_iOS/SPAR/SPAR/ViewModel/HomeViewModel.swift @@ -13,8 +13,8 @@ class HomeViewModel: ObservableObject { @Published var searchText = "" @Published var animate: Bool = false private let networkManager = NetworkManager() - // Accepts list of DeviceSpecification @Published var devices: [DeviceSpecification] = [] + @Published var showDownloadPopup: Bool = false // Add this property to control the popup visibility init() { getDeviceData() @@ -38,6 +38,8 @@ class HomeViewModel: ObservableObject { print(response) DispatchQueue.main.async { self.devices = response + // Check if the response is empty, and if so, show the download popup + self.showDownloadPopup = response.isEmpty } } catch { diff --git a/SPAR_iOS/SPAR/SPAR/ViewModel/KeychainHelper.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/KeychainHelper.swift new file mode 100644 index 0000000..62d05f3 --- /dev/null +++ b/SPAR_iOS/SPAR/SPAR/ViewModel/KeychainHelper.swift @@ -0,0 +1,36 @@ +// +// KeychainHelper.swift +// SPAR +// +// Created by Abhijeet Cherungottil on 4/27/25. +// + +import Security +import Foundation + +class KeychainHelper { + static func save(_ data: Data, service: String, account: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: account, + kSecValueData as String: data + ] + SecItemAdd(query as CFDictionary, nil) + } + + static func read(service: String, account: String) -> Data? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: account, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne + ] + + var result: AnyObject? + SecItemCopyMatching(query as CFDictionary, &result) + return result as? Data + } +} + diff --git a/SPAR_iOS/SPAR/SPAR/ViewModel/LoginViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/LoginViewModel.swift new file mode 100644 index 0000000..a40d65c --- /dev/null +++ b/SPAR_iOS/SPAR/SPAR/ViewModel/LoginViewModel.swift @@ -0,0 +1,139 @@ +// +// LoginViewModel.swift +// SPAR +// +// Created by Abhijeet Cherungottil on 4/18/25. +// + +import Foundation +import SwiftUI +import OSLog +import LocalAuthentication + + +class LoginViewModel: ObservableObject { + @Published var username: String = "" + @Published var password: String = "" + @Published var animate: Bool = false + @Published var showPassword: Bool = false + @Published var errorMessage: String = "" + + let logger = Logger.fileLocation + private let networkManager = NetworkManager() + + weak var delegate: LoginViewModelDelegate? + + func loginWithFaceID() { + if ProcessInfo.processInfo.arguments.contains("UITestMode") { + print("Skipping Face ID for UI Tests") + return + } + authenticateWithFaceIDIfAvailable { success in + if success { + guard let usernameData = KeychainHelper.read(service: "SPAR", account: "username"), + let passwordData = KeychainHelper.read(service: "SPAR", account: "password"), + let savedUsername = String(data: usernameData, encoding: .utf8), + let savedPassword = String(data: passwordData, encoding: .utf8) else { + self.errorMessage = "No saved credentials found." + return + } + + Task { + do { + let response = try await self.networkManager.login(username: savedUsername, password: savedPassword) + + AppSettings.shared.authToken = response.token + AppSettings.shared.userId = response.userId + + DispatchQueue.main.async { + self.delegate?.didLoginSuccessfully() + } + } catch { + DispatchQueue.main.async { + self.errorMessage = StringConstant.incorrectCredentials + } + } + } + } else { + self.errorMessage = "Face ID Authentication Failed." + } + } + } + + + func submit() { + let isValidUsername = username.range(of: "^[a-zA-Z0-9]+$", options: .regularExpression) != nil + let disallowedCharacters = CharacterSet(charactersIn: "\"'`;/\\<>") + let isPasswordSafe = password.rangeOfCharacter(from: disallowedCharacters) == nil + + if !isValidUsername { + errorMessage = "Username can only contain letters and numbers." + return + } + + if !isPasswordSafe { + errorMessage = "Password contains unsafe characters like \", ', ;, or \\." + return + } + +// if username.lowercased() == "user" && password == "Password" { +// errorMessage = "" +// self.delegate?.didLoginSuccessfully() +// print("Login successful!") +// } else { +// errorMessage = StringConstant.incorrectCredentials +// } + + Task { + do { + let response = try await networkManager.login(username: username, password: password) + + AppSettings.shared.authToken = response.token + AppSettings.shared.userId = response.userId + // Save credentials securely + KeychainHelper.save(Data(username.utf8), service: "SPAR", account: "username") + KeychainHelper.save(Data(password.utf8), service: "SPAR", account: "password") + + + DispatchQueue.main.async { + self.delegate?.didLoginSuccessfully() + } + } catch { + DispatchQueue.main.async { + self.errorMessage = StringConstant.incorrectCredentials + } + } + } + } +} + +extension LoginViewModel { + func authenticateWithFaceIDIfAvailable(completion: @escaping (Bool) -> Void) { + #if targetEnvironment(simulator) + // Skip authentication on Simulator + print("Skipping Face ID authentication in Simulator.") + completion(true) // Treat as successful authentication + #else + let context = LAContext() + var error: NSError? + + if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { + context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Log in with Face ID") { success, authenticationError in + DispatchQueue.main.async { + completion(success) + } + } + } else { + DispatchQueue.main.async { + completion(false) + } + } + #endif + } + +} + + +protocol LoginViewModelDelegate: AnyObject { + func didLoginSuccessfully() +} diff --git a/SPAR/SPAR/ViewModel/MemoryUsageViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/MemoryUsageViewModel.swift similarity index 100% rename from SPAR/SPAR/ViewModel/MemoryUsageViewModel.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/MemoryUsageViewModel.swift diff --git a/SPAR/SPAR/ViewModel/ProcessViewModel.swift b/SPAR_iOS/SPAR/SPAR/ViewModel/ProcessViewModel.swift similarity index 100% rename from SPAR/SPAR/ViewModel/ProcessViewModel.swift rename to SPAR_iOS/SPAR/SPAR/ViewModel/ProcessViewModel.swift diff --git a/SPAR/SPARTests/LaunchTest.swift b/SPAR_iOS/SPAR/SPARTests/LaunchTest.swift similarity index 85% rename from SPAR/SPARTests/LaunchTest.swift rename to SPAR_iOS/SPAR/SPARTests/LaunchTest.swift index bcc286c..9a091ed 100644 --- a/SPAR/SPARTests/LaunchTest.swift +++ b/SPAR_iOS/SPAR/SPARTests/LaunchTest.swift @@ -5,6 +5,7 @@ final class LaunchTest: XCTestCase { override func setUp() { continueAfterFailure = false + app.launchArguments += ["-UITestMode", "-resetDefaults"] app.launch() } diff --git a/SPAR/SPARTests/LoginViewUITests.swift b/SPAR_iOS/SPAR/SPARTests/LoginViewUITests.swift similarity index 92% rename from SPAR/SPARTests/LoginViewUITests.swift rename to SPAR_iOS/SPAR/SPARTests/LoginViewUITests.swift index d7ac399..5314600 100644 --- a/SPAR/SPARTests/LoginViewUITests.swift +++ b/SPAR_iOS/SPAR/SPARTests/LoginViewUITests.swift @@ -14,6 +14,7 @@ final class LoginViewUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false app = XCUIApplication() + app.launchArguments += ["-UITestMode", "-resetDefaults"] app.launch() } @@ -37,7 +38,7 @@ final class LoginViewUITests: XCTestCase { XCTAssertTrue(passwordField.exists) usernameField.tap() - usernameField.typeText("testuser") + usernameField.typeText("testuse\"r") passwordField.tap() passwordField.typeText("wrongpassword") @@ -45,7 +46,7 @@ final class LoginViewUITests: XCTestCase { app.buttons["Submit"].tap() // Wait for the error message to appear - let errorMessage = app.staticTexts["Incorrect username or password."] + let errorMessage = app.staticTexts["Username can only contain letters and numbers."] let exists = NSPredicate(format: "exists == 1") expectation(for: exists, evaluatedWith: errorMessage, handler: nil) diff --git a/SPAR/SPARTests/NetworkManagerTests.swift b/SPAR_iOS/SPAR/SPARTests/NetworkManagerTests.swift similarity index 100% rename from SPAR/SPARTests/NetworkManagerTests.swift rename to SPAR_iOS/SPAR/SPARTests/NetworkManagerTests.swift diff --git a/SPAR/SPARTests/OnboardingTests.swift b/SPAR_iOS/SPAR/SPARTests/OnboardingTests.swift similarity index 90% rename from SPAR/SPARTests/OnboardingTests.swift rename to SPAR_iOS/SPAR/SPARTests/OnboardingTests.swift index 4e6e7c2..6669b32 100644 --- a/SPAR/SPARTests/OnboardingTests.swift +++ b/SPAR_iOS/SPAR/SPARTests/OnboardingTests.swift @@ -12,7 +12,9 @@ final class OnboardingTests: XCTestCase { override func setUp() { continueAfterFailure = false + app.launchArguments += ["-UITestMode", "-resetDefaults"] app.launch() + } diff --git a/SPAR/SPARTests/SPARTests.swift b/SPAR_iOS/SPAR/SPARTests/SPARTests.swift similarity index 100% rename from SPAR/SPARTests/SPARTests.swift rename to SPAR_iOS/SPAR/SPARTests/SPARTests.swift