UI updates for labwise branding
This commit is contained in:
@@ -400,11 +400,14 @@
|
|||||||
DEVELOPMENT_TEAM = YK2DB9NT3S;
|
DEVELOPMENT_TEAM = YK2DB9NT3S;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||||
|
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -432,11 +435,14 @@
|
|||||||
DEVELOPMENT_TEAM = YK2DB9NT3S;
|
DEVELOPMENT_TEAM = YK2DB9NT3S;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
|
||||||
|
INFOPLIST_KEY_NSCameraUsageDescription = "Camera Access needed for scanning chemical labels for adding to inventory.";
|
||||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x3D",
|
||||||
|
"green" : "0x18",
|
||||||
|
"red" : "0xD4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
20
LabWise/Assets.xcassets/BrandMuted.colorset/Contents.json
Normal file
20
LabWise/Assets.xcassets/BrandMuted.colorset/Contents.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xF3",
|
||||||
|
"green" : "0xF5",
|
||||||
|
"red" : "0xF0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x6F",
|
||||||
|
"green" : "0x7A",
|
||||||
|
"red" : "0x5A"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
20
LabWise/Assets.xcassets/BrandPrimary.colorset/Contents.json
Normal file
20
LabWise/Assets.xcassets/BrandPrimary.colorset/Contents.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0x4A",
|
||||||
|
"green" : "0x5A",
|
||||||
|
"red" : "0x2D"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0xF0",
|
||||||
|
"green" : "0xF3",
|
||||||
|
"red" : "0xE8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
15
LabWise/Assets.xcassets/GoogleLogo.imageset/Contents.json
vendored
Normal file
15
LabWise/Assets.xcassets/GoogleLogo.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "google-logo.svg",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"preserves-vector-representation" : true
|
||||||
|
}
|
||||||
|
}
|
||||||
6
LabWise/Assets.xcassets/GoogleLogo.imageset/google-logo.svg
vendored
Normal file
6
LabWise/Assets.xcassets/GoogleLogo.imageset/google-logo.svg
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
|
||||||
|
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
||||||
|
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z" fill="#FBBC05"/>
|
||||||
|
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 689 B |
21
LabWise/Assets.xcassets/Logo.imageset/Contents.json
vendored
Normal file
21
LabWise/Assets.xcassets/Logo.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "logo.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
LabWise/Assets.xcassets/Logo.imageset/logo.png
vendored
Normal file
BIN
LabWise/Assets.xcassets/Logo.imageset/logo.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// ContentView.swift
|
|
||||||
// LabWise
|
|
||||||
//
|
|
||||||
// Created by Aditya Pulipaka on 3/19/26.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
Image(systemName: "globe")
|
|
||||||
.imageScale(.large)
|
|
||||||
.foregroundStyle(.tint)
|
|
||||||
Text("Hello, world!")
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
ContentView()
|
|
||||||
}
|
|
||||||
22
LabWise/DashboardView.swift
Normal file
22
LabWise/DashboardView.swift
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct DashboardView: View {
|
||||||
|
var body: some View {
|
||||||
|
TabView {
|
||||||
|
Tab("Chemicals", systemImage: "flask.fill") {
|
||||||
|
ChemicalsListView()
|
||||||
|
}
|
||||||
|
Tab("Scan", systemImage: "camera.fill") {
|
||||||
|
ScanView()
|
||||||
|
}
|
||||||
|
Tab("Profile", systemImage: "person.fill") {
|
||||||
|
ProfileView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tint(Color("Brand/BrandPrimary"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
DashboardView()
|
||||||
|
}
|
||||||
200
LabWise/LoginView.swift
Normal file
200
LabWise/LoginView.swift
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import AuthenticationServices
|
||||||
|
import LabWiseKit
|
||||||
|
|
||||||
|
struct LoginView: View {
|
||||||
|
@Environment(AppState.self) private var appState
|
||||||
|
|
||||||
|
@State private var email = ""
|
||||||
|
@State private var password = ""
|
||||||
|
@State private var isLoading = false
|
||||||
|
@State private var isGoogleLoading = false
|
||||||
|
@State private var errorMessage: String?
|
||||||
|
|
||||||
|
private let authClient = AuthClient()
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
ZStack {
|
||||||
|
Color(UIColor.systemGroupedBackground)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
VStack(spacing: 32) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
// Logo
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
Image("Logo")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(height: 52)
|
||||||
|
Text("Chemical Inventory Management")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(Color("Brand/BrandMutedForeground"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
VStack(spacing: 12) {
|
||||||
|
TextField("Email", text: $email)
|
||||||
|
.keyboardType(.emailAddress)
|
||||||
|
.textContentType(.emailAddress)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.background(Color(UIColor.secondarySystemGroupedBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.strokeBorder(Color("Brand/BrandPrimary").opacity(0.2), lineWidth: 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
SecureField("Password", text: $password)
|
||||||
|
.textContentType(.password)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.background(Color(UIColor.secondarySystemGroupedBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.strokeBorder(Color("Brand/BrandPrimary").opacity(0.2), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let errorMessage {
|
||||||
|
Text(errorMessage)
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
.font(.footnote)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
Task { await signIn() }
|
||||||
|
} label: {
|
||||||
|
Group {
|
||||||
|
if isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.tint(.white)
|
||||||
|
} else {
|
||||||
|
Text("Sign In")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 44)
|
||||||
|
}
|
||||||
|
.background(Color("Brand/BrandPrimary"))
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.disabled(email.isEmpty || password.isEmpty || isLoading || isGoogleLoading)
|
||||||
|
.opacity((email.isEmpty || password.isEmpty || isLoading || isGoogleLoading) ? 0.5 : 1)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color("Brand/BrandPrimary").opacity(0.15))
|
||||||
|
.frame(height: 1)
|
||||||
|
Text("or")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundStyle(Color("Brand/BrandMutedForeground"))
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color("Brand/BrandPrimary").opacity(0.15))
|
||||||
|
.frame(height: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
GoogleSignInButton(isLoading: isGoogleLoading) {
|
||||||
|
Task { await signInWithGoogle() }
|
||||||
|
}
|
||||||
|
.disabled(isLoading || isGoogleLoading)
|
||||||
|
}
|
||||||
|
.padding(24)
|
||||||
|
.background(Color(UIColor.secondarySystemGroupedBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
.shadow(color: Color("Brand/BrandPrimary").opacity(0.06), radius: 12, x: 0, y: 4)
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarHidden(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
|
||||||
|
private func signIn() async {
|
||||||
|
isLoading = true
|
||||||
|
errorMessage = nil
|
||||||
|
defer { isLoading = false }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let user = try await authClient.signIn(email: email, password: password)
|
||||||
|
await MainActor.run { appState.signedIn(user: user) }
|
||||||
|
} catch APIError.httpError(let code, _) where code == 401 {
|
||||||
|
await MainActor.run { errorMessage = "Invalid email or password." }
|
||||||
|
} catch APIError.httpError(_, let data) {
|
||||||
|
let msg = String(data: data, encoding: .utf8) ?? "Sign in failed."
|
||||||
|
await MainActor.run { errorMessage = msg }
|
||||||
|
} catch {
|
||||||
|
await MainActor.run { errorMessage = "Sign in failed. Please check your connection." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func signInWithGoogle() async {
|
||||||
|
guard let window = UIApplication.shared.connectedScenes
|
||||||
|
.compactMap({ $0 as? UIWindowScene })
|
||||||
|
.flatMap({ $0.windows })
|
||||||
|
.first(where: { $0.isKeyWindow }) else { return }
|
||||||
|
|
||||||
|
isGoogleLoading = true
|
||||||
|
errorMessage = nil
|
||||||
|
defer { isGoogleLoading = false }
|
||||||
|
|
||||||
|
do {
|
||||||
|
let user = try await authClient.signInWithGoogle(presentingWindow: window)
|
||||||
|
appState.signedIn(user: user)
|
||||||
|
} catch ASWebAuthenticationSessionError.canceledLogin {
|
||||||
|
// User cancelled — no error shown
|
||||||
|
} catch {
|
||||||
|
errorMessage = "Google sign-in failed. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Google button
|
||||||
|
|
||||||
|
struct GoogleSignInButton: View {
|
||||||
|
let isLoading: Bool
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack(spacing: 10) {
|
||||||
|
if isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
} else {
|
||||||
|
Image(systemName: "globe")
|
||||||
|
.foregroundStyle(Color("Brand/BrandPrimary"))
|
||||||
|
Text("Continue with Google")
|
||||||
|
.fontWeight(.medium)
|
||||||
|
.foregroundStyle(Color(UIColor.label))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 44)
|
||||||
|
.background(Color(UIColor.systemBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.strokeBorder(Color("Brand/BrandPrimary").opacity(0.25), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
LoginView()
|
||||||
|
.environment(AppState.shared)
|
||||||
|
}
|
||||||
8
LabWiseKit/.gitignore
vendored
Normal file
8
LabWiseKit/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/configuration/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
||||||
26
LabWiseKit/Package.swift
Normal file
26
LabWiseKit/Package.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// swift-tools-version: 6.2
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "LabWiseKit",
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||||
|
.library(
|
||||||
|
name: "LabWiseKit",
|
||||||
|
targets: ["LabWiseKit"]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package and products from dependencies.
|
||||||
|
.target(
|
||||||
|
name: "LabWiseKit"
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "LabWiseKitTests",
|
||||||
|
dependencies: ["LabWiseKit"]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
2
LabWiseKit/Sources/LabWiseKit/LabWiseKit.swift
Normal file
2
LabWiseKit/Sources/LabWiseKit/LabWiseKit.swift
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// The Swift Programming Language
|
||||||
|
// https://docs.swift.org/swift-book
|
||||||
164
LabWiseKit/Sources/LabWiseKit/Models/Chemical.swift
Normal file
164
LabWiseKit/Sources/LabWiseKit/Models/Chemical.swift
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Chemical: Codable, Identifiable, Sendable {
|
||||||
|
public let id: String
|
||||||
|
// Required
|
||||||
|
public var piFirstName: String
|
||||||
|
public var physicalState: String
|
||||||
|
public var chemicalName: String
|
||||||
|
public var bldgCode: String
|
||||||
|
public var lab: String
|
||||||
|
public var storageLocation: String
|
||||||
|
public var storageDevice: String
|
||||||
|
public var numberOfContainers: String
|
||||||
|
public var amountPerContainer: String
|
||||||
|
public var unitOfMeasure: String
|
||||||
|
public var casNumber: String
|
||||||
|
// Optional
|
||||||
|
public var chemicalFormula: String?
|
||||||
|
public var molecularWeight: String?
|
||||||
|
public var vendor: String?
|
||||||
|
public var catalogNumber: String?
|
||||||
|
public var lotNumber: String?
|
||||||
|
public var expirationDate: String?
|
||||||
|
public var concentration: String?
|
||||||
|
public var percentageFull: Double?
|
||||||
|
public var comments: String?
|
||||||
|
public var barcode: String?
|
||||||
|
public var contact: String?
|
||||||
|
public var scannedImage: String?
|
||||||
|
public var needsManualEntry: [String]?
|
||||||
|
public var createdAt: String?
|
||||||
|
public var updatedAt: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: String = "",
|
||||||
|
piFirstName: String = "",
|
||||||
|
physicalState: String = "",
|
||||||
|
chemicalName: String = "",
|
||||||
|
bldgCode: String = "",
|
||||||
|
lab: String = "",
|
||||||
|
storageLocation: String = "",
|
||||||
|
storageDevice: String = "",
|
||||||
|
numberOfContainers: String = "",
|
||||||
|
amountPerContainer: String = "",
|
||||||
|
unitOfMeasure: String = "",
|
||||||
|
casNumber: String = "",
|
||||||
|
chemicalFormula: String? = nil,
|
||||||
|
molecularWeight: String? = nil,
|
||||||
|
vendor: String? = nil,
|
||||||
|
catalogNumber: String? = nil,
|
||||||
|
lotNumber: String? = nil,
|
||||||
|
expirationDate: String? = nil,
|
||||||
|
concentration: String? = nil,
|
||||||
|
percentageFull: Double? = nil,
|
||||||
|
comments: String? = nil,
|
||||||
|
barcode: String? = nil,
|
||||||
|
contact: String? = nil,
|
||||||
|
scannedImage: String? = nil,
|
||||||
|
needsManualEntry: [String]? = nil,
|
||||||
|
createdAt: String? = nil,
|
||||||
|
updatedAt: String? = nil
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.piFirstName = piFirstName
|
||||||
|
self.physicalState = physicalState
|
||||||
|
self.chemicalName = chemicalName
|
||||||
|
self.bldgCode = bldgCode
|
||||||
|
self.lab = lab
|
||||||
|
self.storageLocation = storageLocation
|
||||||
|
self.storageDevice = storageDevice
|
||||||
|
self.numberOfContainers = numberOfContainers
|
||||||
|
self.amountPerContainer = amountPerContainer
|
||||||
|
self.unitOfMeasure = unitOfMeasure
|
||||||
|
self.casNumber = casNumber
|
||||||
|
self.chemicalFormula = chemicalFormula
|
||||||
|
self.molecularWeight = molecularWeight
|
||||||
|
self.vendor = vendor
|
||||||
|
self.catalogNumber = catalogNumber
|
||||||
|
self.lotNumber = lotNumber
|
||||||
|
self.expirationDate = expirationDate
|
||||||
|
self.concentration = concentration
|
||||||
|
self.percentageFull = percentageFull
|
||||||
|
self.comments = comments
|
||||||
|
self.barcode = barcode
|
||||||
|
self.contact = contact
|
||||||
|
self.scannedImage = scannedImage
|
||||||
|
self.needsManualEntry = needsManualEntry
|
||||||
|
self.createdAt = createdAt
|
||||||
|
self.updatedAt = updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ChemicalCreateBody: Codable, Sendable {
|
||||||
|
public var piFirstName: String
|
||||||
|
public var physicalState: String
|
||||||
|
public var chemicalName: String
|
||||||
|
public var bldgCode: String
|
||||||
|
public var lab: String
|
||||||
|
public var storageLocation: String
|
||||||
|
public var storageDevice: String
|
||||||
|
public var numberOfContainers: String
|
||||||
|
public var amountPerContainer: String
|
||||||
|
public var unitOfMeasure: String
|
||||||
|
public var casNumber: String
|
||||||
|
public var chemicalFormula: String?
|
||||||
|
public var molecularWeight: String?
|
||||||
|
public var vendor: String?
|
||||||
|
public var catalogNumber: String?
|
||||||
|
public var lotNumber: String?
|
||||||
|
public var expirationDate: String?
|
||||||
|
public var concentration: String?
|
||||||
|
public var percentageFull: Double?
|
||||||
|
public var comments: String?
|
||||||
|
public var barcode: String?
|
||||||
|
public var contact: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
piFirstName: String,
|
||||||
|
physicalState: String,
|
||||||
|
chemicalName: String,
|
||||||
|
bldgCode: String,
|
||||||
|
lab: String,
|
||||||
|
storageLocation: String,
|
||||||
|
storageDevice: String,
|
||||||
|
numberOfContainers: String,
|
||||||
|
amountPerContainer: String,
|
||||||
|
unitOfMeasure: String,
|
||||||
|
casNumber: String,
|
||||||
|
chemicalFormula: String? = nil,
|
||||||
|
molecularWeight: String? = nil,
|
||||||
|
vendor: String? = nil,
|
||||||
|
catalogNumber: String? = nil,
|
||||||
|
lotNumber: String? = nil,
|
||||||
|
expirationDate: String? = nil,
|
||||||
|
concentration: String? = nil,
|
||||||
|
percentageFull: Double? = nil,
|
||||||
|
comments: String? = nil,
|
||||||
|
barcode: String? = nil,
|
||||||
|
contact: String? = nil
|
||||||
|
) {
|
||||||
|
self.piFirstName = piFirstName
|
||||||
|
self.physicalState = physicalState
|
||||||
|
self.chemicalName = chemicalName
|
||||||
|
self.bldgCode = bldgCode
|
||||||
|
self.lab = lab
|
||||||
|
self.storageLocation = storageLocation
|
||||||
|
self.storageDevice = storageDevice
|
||||||
|
self.numberOfContainers = numberOfContainers
|
||||||
|
self.amountPerContainer = amountPerContainer
|
||||||
|
self.unitOfMeasure = unitOfMeasure
|
||||||
|
self.casNumber = casNumber
|
||||||
|
self.chemicalFormula = chemicalFormula
|
||||||
|
self.molecularWeight = molecularWeight
|
||||||
|
self.vendor = vendor
|
||||||
|
self.catalogNumber = catalogNumber
|
||||||
|
self.lotNumber = lotNumber
|
||||||
|
self.expirationDate = expirationDate
|
||||||
|
self.concentration = concentration
|
||||||
|
self.percentageFull = percentageFull
|
||||||
|
self.comments = comments
|
||||||
|
self.barcode = barcode
|
||||||
|
self.contact = contact
|
||||||
|
}
|
||||||
|
}
|
||||||
83
LabWiseKit/Sources/LabWiseKit/Models/LabProtocol.swift
Normal file
83
LabWiseKit/Sources/LabWiseKit/Models/LabProtocol.swift
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// A lab protocol document. Named `LabProtocol` to avoid collision with the Swift keyword `Protocol`.
|
||||||
|
public struct LabProtocol: Codable, Identifiable, Sendable {
|
||||||
|
public let id: String
|
||||||
|
public var title: String
|
||||||
|
public var content: String
|
||||||
|
public var fileUrl: String?
|
||||||
|
public var analysisResults: AnyCodable?
|
||||||
|
public var createdAt: String?
|
||||||
|
public var updatedAt: String?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case id, title, content, fileUrl, analysisResults, createdAt, updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: String = "",
|
||||||
|
title: String = "",
|
||||||
|
content: String = "",
|
||||||
|
fileUrl: String? = nil,
|
||||||
|
analysisResults: AnyCodable? = nil,
|
||||||
|
createdAt: String? = nil,
|
||||||
|
updatedAt: String? = nil
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.content = content
|
||||||
|
self.fileUrl = fileUrl
|
||||||
|
self.analysisResults = analysisResults
|
||||||
|
self.createdAt = createdAt
|
||||||
|
self.updatedAt = updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - AnyCodable helper
|
||||||
|
|
||||||
|
public struct AnyCodable: Codable, Sendable {
|
||||||
|
public let value: any Sendable
|
||||||
|
|
||||||
|
public init(_ value: any Sendable) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.singleValueContainer()
|
||||||
|
if let intVal = try? container.decode(Int.self) {
|
||||||
|
value = intVal
|
||||||
|
} else if let doubleVal = try? container.decode(Double.self) {
|
||||||
|
value = doubleVal
|
||||||
|
} else if let boolVal = try? container.decode(Bool.self) {
|
||||||
|
value = boolVal
|
||||||
|
} else if let stringVal = try? container.decode(String.self) {
|
||||||
|
value = stringVal
|
||||||
|
} else if let arrayVal = try? container.decode([AnyCodable].self) {
|
||||||
|
value = arrayVal
|
||||||
|
} else if let dictVal = try? container.decode([String: AnyCodable].self) {
|
||||||
|
value = dictVal
|
||||||
|
} else {
|
||||||
|
value = NSNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
switch value {
|
||||||
|
case let intVal as Int:
|
||||||
|
try container.encode(intVal)
|
||||||
|
case let doubleVal as Double:
|
||||||
|
try container.encode(doubleVal)
|
||||||
|
case let boolVal as Bool:
|
||||||
|
try container.encode(boolVal)
|
||||||
|
case let stringVal as String:
|
||||||
|
try container.encode(stringVal)
|
||||||
|
case let arrayVal as [AnyCodable]:
|
||||||
|
try container.encode(arrayVal)
|
||||||
|
case let dictVal as [String: AnyCodable]:
|
||||||
|
try container.encode(dictVal)
|
||||||
|
default:
|
||||||
|
try container.encodeNil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
LabWiseKit/Tests/LabWiseKitTests/LabWiseKitTests.swift
Normal file
6
LabWiseKit/Tests/LabWiseKitTests/LabWiseKitTests.swift
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import Testing
|
||||||
|
@testable import LabWiseKit
|
||||||
|
|
||||||
|
@Test func example() async throws {
|
||||||
|
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||||
|
}
|
||||||
8
LabWiseUITests/LabWiseKit/.gitignore
vendored
Normal file
8
LabWiseUITests/LabWiseKit/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm/configuration/registries.json
|
||||||
|
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||||
|
.netrc
|
||||||
26
LabWiseUITests/LabWiseKit/Package.swift
Normal file
26
LabWiseUITests/LabWiseKit/Package.swift
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// swift-tools-version: 6.2
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "LabWiseKit",
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||||
|
.library(
|
||||||
|
name: "LabWiseKit",
|
||||||
|
targets: ["LabWiseKit"]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package and products from dependencies.
|
||||||
|
.target(
|
||||||
|
name: "LabWiseKit"
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "LabWiseKitTests",
|
||||||
|
dependencies: ["LabWiseKit"]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// The Swift Programming Language
|
||||||
|
// https://docs.swift.org/swift-book
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import Testing
|
||||||
|
@testable import LabWiseKit
|
||||||
|
|
||||||
|
@Test func example() async throws {
|
||||||
|
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user