# Project export: Remedi

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 10.0
- Tagline: We implemented a medical mobile app that is catered towards the elderly and is highly user-friendly.
- Devpost: https://devpost.com/software/remedi-qtmyxv
- GitHub: https://github.com/joshuadchuang/Remedi
- Team: 4 GitHub contributor(s) — Joshua Chuang (15 commits), Joshua Chuang (9 commits), Albert Su (7 commits), Daniel (4 commits)

## Devpost submission (written by the team)

### Inspiration

Elderly people struggle to use mobile apps, so we sought to create an accessible and simple app that does most of the work for them.

### What it does

Stores a list of medications and sends notifications when it is time to take a dose.

### How we built it

Xcode swift and vapor

### Challenges we ran into

UNFINISHED This project is not finished, we ran into issues connecting the frontend and backend.

### Accomplishments we're proud of

Coding in a new language and creating a complete frontend and backend for our first iOS app

### What we learned

Learning Swift and SwiftUI

### What's next

Finishing the connection between the frontend and backend

## README (from the GitHub repository)

# Remedi

Remedi Version 1.0 10/29/2023

Remedi is a CalHacks collaboration between **Joshua Chuang**, **Zachary Chuang**, **Daniel Li**, and **Albert Su**. This mobile app aims to help elderly folk struggling with dementia to remind them when to take their medications and make their lives easier when it comes to manually inputting medication in. Key features include:

- Using Swift OCR Image to Text to collect needed inputs from taking picture of medication bottle label
- Utilizing Vapor backened to create Medication model and parse inputs to create array of hashmaps of Dates and Times for scheduling Medications.

We built this app to practice Frontend, Backend, and a CRUD API structure.

This app is not being launched officially, but we are open to expanding on the project in the future if we see fit.

## Detected evidence (automated analysis)

Indexed codebase: 31 recognized source files, 54 KB.
- Swift (language) — detected in the code

## Codebase structure (from repository index)

### Files (67 of 67)

```
.DS_Store
ImageSelectTestView.swift
imageTest.swift
LICENSE
README.md
RemediApp.xcodeproj/project.pbxproj
RemediApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
RemediApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
RemediApp.xcodeproj/project.xcworkspace/xcuserdata/albertsu.xcuserdatad/IDEFindNavigatorScopes.plist
RemediApp.xcodeproj/project.xcworkspace/xcuserdata/albertsu.xcuserdatad/UserInterfaceState.xcuserstate
RemediApp.xcodeproj/project.xcworkspace/xcuserdata/joshuachuang.xcuserdatad/UserInterfaceState.xcuserstate
RemediApp.xcodeproj/project.xcworkspace/xcuserdata/trdanli.xcuserdatad/UserInterfaceState.xcuserstate
RemediApp.xcodeproj/xcuserdata/albertsu.xcuserdatad/xcschemes/xcschememanagement.plist
RemediApp.xcodeproj/xcuserdata/joshuachuang.xcuserdatad/xcschemes/xcschememanagement.plist
RemediApp.xcodeproj/xcuserdata/trdanli.xcuserdatad/xcschemes/xcschememanagement.plist
RemediApp/.DS_Store
RemediApp/Assets.xcassets/AccentColor.colorset/Contents.json
RemediApp/Assets.xcassets/AppIcon.appiconset/Contents.json
RemediApp/Assets.xcassets/Colors/Black2.colorset/Contents.json
RemediApp/Assets.xcassets/Colors/Contents.json
RemediApp/Assets.xcassets/Colors/DarkAqua.colorset/Contents.json
RemediApp/Assets.xcassets/Colors/DarkBlue.colorset/Contents.json
RemediApp/Assets.xcassets/Colors/GrassGreen.colorset/Contents.json
RemediApp/Assets.xcassets/Colors/Green.colorset/Contents.json
RemediApp/Assets.xcassets/Colors/Mint.colorset/Contents.json
RemediApp/Assets.xcassets/Colors/OceanBlue.colorset/Contents.json
RemediApp/Assets.xcassets/Colors/SoftRed.colorset/Contents.json
RemediApp/Assets.xcassets/Contents.json
RemediApp/Medication.swift
RemediApp/Preview Content/Preview Assets.xcassets/Contents.json
RemediApp/RemediAppApp.swift
RemediApp/User Interface/.DS_Store
RemediApp/User Interface/Caretaker Views/CaretakerHomeView.swift
RemediApp/User Interface/ContentView.swift
RemediApp/User Interface/User Views/AddMedicationBlock.swift
RemediApp/User Interface/User Views/AddMedicationView.swift
RemediApp/User Interface/User Views/AppViewModel.swift
RemediApp/User Interface/User Views/CamInputForm.swift
RemediApp/User Interface/User Views/ListMedicationView.swift
RemediApp/User Interface/User Views/Medication Input Form.swift
RemediApp/User Interface/User Views/MedicationInfoView.swift
RemediApp/User Interface/User Views/MedicationView.swift
RemediApp/User Interface/User Views/ProfileView.swift
RemediApp/User Interface/User Views/UserHomeView.swift
RemediApp/User.swift
UserViewTest.swift
VaporBackend/.dockerignore
VaporBackend/.gitignore
VaporBackend/docker-compose.yml
VaporBackend/Dockerfile
VaporBackend/Package.resolved
VaporBackend/Package.swift
VaporBackend/Public/.gitkeep
VaporBackend/Resources/Views/index.leaf
VaporBackend/Sources/App/configure.swift
VaporBackend/Sources/App/Controllers/.gitkeep
VaporBackend/Sources/App/Controllers/TodoController.swift
VaporBackend/Sources/App/entrypoint.swift
VaporBackend/Sources/App/Migrations/CreateTodo.swift
VaporBackend/Sources/App/Models/Todo.swift
VaporBackend/Sources/App/routes.swift
VaporBackend/Sources/App/Storage.swift
VaporBackend/Sources/App/User.swift
VaporBackend/Sources/App/UserController.swift
VaporBackend/Tests/AppTests/AppTests.swift
VaporBackend/Users/123456.json
VaporBackend/Users/1234567.json
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- updated readme
- Merge branch 'main' of github.com:joshuadchuang/Remedi
- readme/project summary
- userViewTest to connect frontend to backend
- fixed med struct errors
- too many commits
- merge fix
- added listmedicationview and medication
- PUUUOLLLLL
- updated routes
- Polished VariousPages
- Remove submodule and add VaporBackend as regular folder
- Include Vapor Backend in Remedi repo
- finished backend
- Polished ContentView
- pushing backend
- added ui styling
- finished vapor backend
- ocr text partial start
- added vapor backend

## Key source files (fetched from GitHub, selected and truncated for size)

### VaporBackend/docker-compose.yml

```yaml
# Docker Compose file for Vapor
#
# Install Docker on your system to run and test
# your Vapor app in a production-like environment.
#
# Note: This file is intended for testing and does not
# implement best practices for a production deployment.
#
# Learn more: https://docs.docker.com/compose/reference/
#
#   Build images: docker-compose build
#      Start app: docker-compose up app
# Start database: docker-compose up db
# Run migrations: docker-compose run migrate
#       Stop all: docker-compose down (add -v to wipe db)
#
version: '3.7'

volumes:
  db_data:

x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  DATABASE_URL: mongodb://db:27017/vapor_database
  
services:
  app:
    image: vapor-backend:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    depends_on:
      - db
    ports:
      - '8080:8080'
    # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
    command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
  migrate:
    image: vapor-backend:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    depends_on:
      - db
    command: ["migrate", "--yes"]
    deploy:
      replicas: 0
  revert:
    image: vapor-backend:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    depends_on:
      - db
    command: ["migrate", "--revert", "--yes"]
    deploy:
      replicas: 0
  db:
    image: mongo:latest
    volumes:
      - db_data:/data/db
    restart: unless-stopped
    ports:
      - '27017:27017'

```

### VaporBackend/Dockerfile

```
# ================================
# Build image
# ================================
FROM swift:5.9-jammy as build

# Install OS updates
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y\
    && rm -rf /var/lib/apt/lists/*

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve --skip-update \
        $([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)

# Copy entire repo into container
COPY . .

# Build everything, with optimizations
RUN swift build -c release --static-swift-stdlib \
    # Workaround for https://github.com/apple/swift/pull/68669
    # This can be removed as soon as 5.9.1 is released, but is harmless if left in.
    -Xlinker -u -Xlinker _swift_backtrace_isThunkFunction

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/App" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM swift:5.9-jammy-slim

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      # libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      # libxml2 \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Provide configuration needed by the built-in crash reporter and some sensible default behaviors.
ENV SWIFT_ROOT=/usr SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./App"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]

```

### ImageSelectTestView.swift

```swift
//
//  ImageSelectTestView.swift
//  RemediApp
//
//  Created by Albert Su on 10/28/23.
//

import SwiftUI

struct ImageSelectTestView: View {
    @State private var selectedImage: Image?
    @State private var isImagePickerPresented = false

    var body: some View {
        VStack {
            Button(action: {
                isImagePickerPresented = true
            }) {
                Text("Select Image")
                    .font(.title)
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            selectedImage?
                .resizable()
                .aspectRatio(contentMode: .fit)
        }
        .sheet(isPresented: $isImagePickerPresented) {
            ImagePicker(selectedImage: $selectedImage)
        }
    }
}

struct ImageSelectTestView_Previews: PreviewProvider {
    static var previews: some View {
        ImageSelectTestView()
    }
}


```

### imageTest.swift

```swift
//
//  imageTest.swift
//  RemediApp
//
//  Created by Albert Su on 10/28/23.
//

import Foundation
import SwiftUI
import UIKit

struct ImagePicker: UIViewControllerRepresentable {
    @Binding var selectedImage: Image?
    @Environment(\.presentationMode) private var presentationMode

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        @Binding var selectedImage: Image?
        @Environment(\.presentationMode) private var presentationMode

        init(selectedImage: Binding<Image?>, presentationMode: Binding<Bool>) {
            _selectedImage = selectedImage
            _presentationMode = presentationMode
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let uiImage = info[.originalImage] as? UIImage {
                selectedImage = Image(uiImage: uiImage)
            }
            presentationMode.wrappedValue.dismiss()
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            presentationMode.wrappedValue.dismiss()
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(selectedImage: $selectedImage, presentationMode: $presentationMode)
    }

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePicker = UIImagePickerController()
        imagePicker.delegate = context.coordinator
        return imagePicker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        // No updates needed here
    }
}



```

### UserViewTest.swift

```swift
//
//  UserViewTest.swift
//  RemediApp
//
//  Created by Albert Su on 10/29/23.
//

import SwiftUI

struct UserViewTest: View {
    @State private var uniqueCode: String = ""
    @State private var userName: String = ""

    var body: some View {
        VStack {
            TextField("Enter User ID", text: $uniqueCode)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()

            Button("Get Unique Code") {
                // Send a GET request to the Vapor backend
                fetchUserName(uniqueCode: uniqueCode)
            }
            .padding()

            Text("User's Unique Code: \(userName)")
                .padding()
            
            Button("Update Unique Code") {
                // Send a GET request to the Vapor backend
                updateUser()
            }
            .padding()
        }
    }

    func fetchUserName(uniqueCode: String) {
        let baseUrl = "http://localhost:8080"
        
        guard let url = URL(string: "\(baseUrl)/user/\(uniqueCode)") else {
            // Handle URL creation error
            return
        }
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                // Handle network error
                print("Error: \(error)")
            } else if let data = data {
                do {
                    print("start decode")
                    print(print(String(data: data, encoding: .utf8)))
                    let user = try JSONDecoder().decode(User.self, from: data)
                    // Assuming you receive a User object from the server
                    print(userName)
                    userName = user.name
                } catch {
                    // Handle decoding error
                    print("Decoding Error: \(error)")
                }
            }
        }.resume()
    }
    
    func updateUser() {
        let baseUrl = "http://localhost:8080"
        
        guard let url = URL(string: "\(baseUrl)/user/") else {
            // Handle invalid URL
            return
        }
            
            // Prepare the PUT request
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            
            // Create a dictionary with the data you want to send in the request body
        let user = User(uniqueCode: "1234567", name: "old man 2", medications: [])
        do {
            let encoder = JSONEncoder()
            request.httpBody = try encoder.encode(user)
        } catch {
            // Handle JSON encoding error
            print("Error encoding user as JSON: \(error)")
            return
        }
        
            // Set the request headers if needed
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            
            let task = URLSession.shared.dataTask(with: request) { data, response, error in
                if let error = error {
                    // Handle network error
                    print("Error: \(error)")
                    return
                }
                
                if let response = response as? HTTPURLResponse, response.statusCode == 200 {
                    // Successful update
                    print("User updated successfully.")
                } else {
                    // Handle non-200 response codes
                    if let data = data, let errorMessage = String(data: data, encoding: .utf8) {
                        print("Error message: \(errorMessage)")
                    } else {
                        print("Unknown error")
                    }
                }
            }
            
            task.resume()
        }
}

struct UserViewTest_Previews: PreviewProvider {
    static var previews: some View {
        UserViewTest()
    }
}




```

### RemediApp/User.swift

```swift
//
//  User.swift
//  RemediApp
//
//  Created by Albert Su on 10/29/23.
//

import Foundation
import SwiftUI

struct User: Codable {
    var uniqueCode: String
    var name: String
    var medications: [Medication]
}

```

### RemediApp/RemediAppApp.swift

```swift
//
//  RemediAppApp.swift
//  RemediApp
//
//  Created by Joshua Chuang on 10/27/23.
//

import SwiftUI

@main
struct RemediAppApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

```

### RemediApp/Medication.swift

```swift
//
//  Medication.swift
//  RemediApp
//
//  Created by Joshua Chuang on 10/28/23.
//

import Foundation
import SwiftUI

struct Medication: Identifiable, Codable {
    var id = UUID()
    var name: String
    var time: Date
    var dosage: String
    var startDate: Date
    var frequencyPerDay: Int
    var duration: Int // in days
}

```

### VaporBackend/Package.swift

```swift
// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "VaporBackend",
    platforms: [
       .macOS(.v13)
    ],
    dependencies: [
        // 💧 A server-side Swift web framework.
        .package(url: "https://github.com/vapor/vapor.git", from: "4.83.1"),
        // 🗄 An ORM for SQL and NoSQL databases.
        .package(url: "https://github.com/vapor/fluent.git", from: "4.8.0"),
        // 🌱 Fluent driver for Mongo.
        .package(url: "https://github.com/vapor/fluent-mongo-driver.git", from: "1.0.0"),
        // 🍃 An expressive, performant, and extensible templating language built for Swift.
        .package(url: "https://github.com/vapor/leaf.git", from: "4.2.4"),
    ],
    targets: [
        .executableTarget(
            name: "App",
            dependencies: [
                .product(name: "Fluent", package: "fluent"),
                .product(name: "FluentMongoDriver", package: "fluent-mongo-driver"),
                .product(name: "Leaf", package: "leaf"),
                .product(name: "Vapor", package: "vapor"),
            ]
        ),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),

            // Workaround for https://github.com/apple/swift-package-manager/issues/6940
            .product(name: "Vapor", package: "vapor"),
            .product(name: "Fluent", package: "Fluent"),
            .product(name: "FluentMongoDriver", package: "fluent-mongo-driver"),
            .product(name: "Leaf", package: "leaf"),
        ])
    ]
)

```

### RemediApp/User Interface/ContentView.swift

```swift
//
//  ContentView.swift
//  RemediApp
//
//  Created by Joshua Chuang on 10/27/23.
//

import SwiftUI

struct ContentView: View {
    @StateObject var viewModel = AppViewModel()

    var body: some View {
        NavigationView {
            ZStack {
                BackgroundView()
                VStack(spacing: 10) {
                    TitleTextView(title: "Welcome to Remedi")
                    
                    Text("Your personal medication reminder assistant.")
                        .foregroundColor(.white)
                        .padding()
                    
                    VStack {
                        Image(systemName: "pills.circle")
                            .symbolRenderingMode(.palette)
                            .resizable()
                            .foregroundStyle(LinearGradient(gradient:
                                Gradient(colors: [Color("OceanBlue"), Color("GrassGreen")]),
                                startPoint: .topLeading,
                                endPoint:.bottomTrailing))
                            .aspectRatio(contentMode: .fit)
                            .frame(width: 220, height: 220)
                            .padding(.top, 70)
                    }
                    
                    Spacer()
                    
                    NavigationLink(destination: UserHomeView().environmentObject(viewModel)) {
                        Text("I am the User")
                            .frame(width: 280, height: 50)
                            .background(LinearGradient(gradient:
                                    Gradient(colors: [Color("OceanBlue"), Color("GrassGreen")]),
                                    startPoint: .topLeading,
                                    endPoint:.bottomTrailing))
                            .foregroundStyle(Color(.white))
                            .font(.system(size: 20, weight: .medium, design: .default))
                            .cornerRadius(10)
                            .padding()
                            //.foregroundColor(.white)
                            //.frame(maxWidth: .infinity, maxHeight: 50)
                            //.cornerRadius(10)
                    }
                    
                    NavigationLink(destination: CaretakerHomeView()) {
                        Text("I am the Caretaker")
                            .frame(width: 280, height: 50)
                            .background(LinearGradient(gradient:
                                    Gradient(colors: [Color("OceanBlue"), Color("GrassGreen")]),
                                    startPoint: .topLeading,
                                    endPoint:.bottomTrailing))
                            .foregroundStyle(Color(.white))
                            .font(.system(size: 20, weight: .medium, design: .default))
                            .cornerRadius(10)
//                            .foregroundColor(.white)
//                            .frame(maxWidth: .infinity, maxHeight: 50)
//                            .background(Color.green)
//                            .cornerRadius(10)
//                            .padding(.horizontal)
                    }
                    Spacer()
                }
            }
        }
    }
}

struct BackgroundView: View {
    
    var body: some View {
        LinearGradient(gradient: Gradient(colors: [Color("Black2"), Color("DarkAqua")]),
                       startPoint: .topLeading,
                       endPoint:.bottomTrailing)
            .ignoresSafeArea()
    }
}

struct TitleTextView: View {
    
    var title: String
    
    var body: some View {
        Text(title)
            .font(.system(size: 32, weight: .medium, design: .default))
            .foregroundColor(.white)
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

```

[21 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]