# Project export: Power to the People

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: CruzHacks 2024
- Tagline: Every year over 20 million Americans are pulled over by police. What if we could make all those stops safe?
- Devpost: https://devpost.com/software/power-to-the-people-lbhxyd
- GitHub: https://github.com/brendan-ch/PowerToThePeople/
- Demo: https://www.figma.com/file/llRQYC9BYY0OCg5XZjDb35/CruzHacks-2024%3A-Power-to-the-People?type=design&node-id=0%3A1&mode=design&t=chQAGNqvoEeunZCP-1
- Video: https://www.youtube.com/embed/Un8JWXkpcW4?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (President's Pick)
- Team: 1 GitHub contributor(s) — Brendan Chen (106 commits)

## Devpost submission (written by the team)

### Inspiration

According to the Stanford Open Policing Project, police in the United States pull over more than 50,000 drivers in a typical day. Black and Hispanic drivers are stopped and searched more often than white drivers; for instance, in 2020 black drivers were stopped at a rate of 0.10 per capita by state patrols, compared to 0.07 for white drivers. Such disparities highlight the critical need for a tool that empowers drivers with knowledge about their rights and offers a measure of accountability in police interactions. Our app, "Power to the People," is designed to meet this essential need, providing reliable information and support to drivers during these often stressful and uncertain encounters​.

### What it does

"Power to the People" is a streamlined mobile app designed for use during police traffic stops. With a simple press and hold of the "I'm Being Pulled Over" button, the app displays legal guidelines, starts recording the interaction using both front and rear cameras, and securely saves the footage to the device for future review. We were also working on an emergency contacts feature that would notify contacts by text when the "I'm Being Pulled Over" button is activated. Given more time, we would've implemented this feature along with cloud backups, video sharing, and location tagging. We were also planning on implementing a database with varying laws for different states.

### How we built it

The app uses Swift and SwiftUI as well as a variety of iOS frameworks including AVFoundation and SwiftData. We also utilized a Python script using the smtplib library as an experimental way to send messages to the emergency contacts.

### Challenges we ran into

For the native app, we spent a lot of time figuring out how to use AVFoundation to record video. One core feature of the app is that it will record using the front and back cameras simultaneously, which we had to account for. Other than that, it was a matter of managing recordings stored in the app container and defining our data types through SwiftData. For the emergency contact messaging, we ran into problems running the Python script through serverless Applications like Azure and Cloudflare Workers. Our current solution (currently not connected to the app) is to call a website that runs the Python script that sends it to predetermined users since we were unable to connect the emergency contacts to the message script.

### Accomplishments we're proud of

We're proud of the Figma design and Swift-based interface for the app. We're also proud of the native technologies we learned while making the app, which we previously didn't have experience with.

### What we learned

We learned how to work with iOS native frameworks including AVFoundation and SwiftData. We also experimented with different ways of creating a backend for our app, although we didn't get to integrating it with the app.

### What's next

We hope to implement more accessibility-related features such as audio recordings containing basic information and answers to frequently asked questions in cases where users are not able to speak due to the stressful nature of the situation. More implementations include real-time storing and processing of recorded videos into databases, and automatic delivery of said recordings to the emergency contacts at the completion of the recording.

## README (from the GitHub repository)

# PowerToThePeople

A [CruzHacks 2024](https://cruzhacks.com) project created by [Brendan](https://github.com/brendan-ch), [David](https://github.com/dvsn0), [Jacob](https://github.com/JacobZCodes), and [Patrick](https://github.com/thisguyblink). This repository contains code for the native iOS app.

## Running the code

To build and run the project, you'll need a Mac with Xcode 15.x installed. To use the video recording feature, you'll need a physical iPhone running iOS 17.x.

Clone the repository and open the `.xcodeproj` file. You'll most likely be asked to select a development team, which you can do under "Signing and Capabilities" under the project settings.

If you haven't already, [enable development mode](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device) on your iPhone.

Connect your iPhone and select from the device menu (located on the top); then, run `Cmd` + `R` to install and run the app.


## Detected evidence (automated analysis)

Indexed codebase: 17 recognized source files, 36 KB.
- Swift (language) — detected in the code
- Python (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (26 of 26)

```
PowerToThePeople.xcodeproj/project.pbxproj
PowerToThePeople.xcodeproj/project.xcworkspace/contents.xcworkspacedata
PowerToThePeople.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
PowerToThePeople.xcodeproj/xcuserdata/bchendev.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
PowerToThePeople.xcodeproj/xcuserdata/bchendev.xcuserdatad/xcschemes/xcschememanagement.plist
PowerToThePeople/Assets.xcassets/AccentColor.colorset/Contents.json
PowerToThePeople/Assets.xcassets/AppIcon.appiconset/Contents.json
PowerToThePeople/Assets.xcassets/Contents.json
PowerToThePeople/Models/EmergencyContact.swift
PowerToThePeople/Models/Recording.swift
PowerToThePeople/Models/VideoCaptureService.swift
PowerToThePeople/PowerToThePeopleApp.swift
PowerToThePeople/Preview Content/Preview Assets.xcassets/Contents.json
PowerToThePeople/ViewModels/VideoCaptureModel.swift
PowerToThePeople/Views/ContactsSetup.swift
PowerToThePeople/Views/ContactsView.swift
PowerToThePeople/Views/ContactView.swift
PowerToThePeople/Views/HomeView.swift
PowerToThePeople/Views/IntroductionView.swift
PowerToThePeople/Views/PermissionsView.swift
PowerToThePeople/Views/RecordingsView.swift
PowerToThePeople/Views/RecordingView.swift
PowerToThePeople/Views/RightsDisplay.swift
PowerToThePeople/Views/SetupButton.swift
PowerToThePeople/Views/VideoView.swift
README.md
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Create README.md
- add share sheet menu
- disable the button if permission not granted
- add permissions logic
- update background depending on disabled
- add .disabled support
- remove back button from home
- navigate to contacts setup
- add Contacts setup page
- move NavigationStack into App
- refactor with SetupButton
- wrap everything in a scroll view
- add permissions setup screen
- display initial view based on setup completed key
- add setup view
- update minHeight to avoid layout shift
- update time formatting for recorded string
- show time + update view formatting
- add round borders to images
- force video to load in onAppear

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

### PowerToThePeople/PowerToThePeopleApp.swift

```swift
//
//  PowerToThePeopleApp.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.19.
//

import SwiftUI
import SwiftData

@main
struct PowerToThePeopleApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Recording.self,
            EmergencyContact.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            NavigationStack {
                let setupCompleted = UserDefaults.standard.bool(forKey: "setupCompleted")
                
                if setupCompleted {
                    HomeView()
                } else {
                    IntroductionView()
                }
            }
        }
        .modelContainer(sharedModelContainer)
    }
}

```

### PowerToThePeople/Models/EmergencyContact.swift

```swift
//
//  EmergencyContact.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftData

@Model
final class EmergencyContact {
    var name: String
    var phoneNumber: String
    var message: String
    
    init(name: String, phoneNumber: String, message: String) {
        self.name = name
        self.phoneNumber = phoneNumber
        self.message = message
    }
}

```

### PowerToThePeople/Views/SetupButton.swift

```swift
//
//  SetupButton.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftUI

struct SetupButton: View {
    var text: String
    var action: () -> Void
    var disabled: Bool?
    
    var body: some View {
        Button(action: action) {
            Text(text)
                .fontWeight(.bold)
                .frame(minWidth: 0, maxWidth: .infinity)
                .padding()
                .foregroundColor(.white)
                .background((disabled ?? false) ? Color.secondary : Color.blue)
                .cornerRadius(10)
        }
        .disabled(disabled ?? false)
    }
}

```

### PowerToThePeople/Views/ContactsSetup.swift

```swift
//
//  File.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftUI

struct ContactsSetup: View {
    @State private var homePageDisplayed = false
    
    var body: some View {
        VStack(spacing: 8) {
            ContactsView()
            
            SetupButton(text: "Next") {
                UserDefaults.standard.setValue(true, forKey: "setupCompleted")
                homePageDisplayed = true
            }
                .padding()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
        .navigationDestination(isPresented: $homePageDisplayed) {
            HomeView()
        }
    }
}


#Preview {
    ContactsSetup()
}

```

### PowerToThePeople/Views/VideoView.swift

```swift
//
//  VideoView.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftUI
import AVFoundation
import AVKit

/// View which displays a video player.
struct VideoView: View {
    let videoUrl: URL
    @State private var videoPlayer: AVPlayer?
    
    var body: some View {
        VStack {
            if videoPlayer != nil {
                VideoPlayer(player: videoPlayer)
            }
        }
        .onAppear {
            let playerItem = AVPlayerItem(url: videoUrl)
            videoPlayer = AVPlayer(playerItem: playerItem)
        }
        .ignoresSafeArea()
        .backgroundStyle(.black)
    }
}

#Preview {
    VideoView(videoUrl: Bundle.main.url(forResource: "back", withExtension: "mov")!)
}

```

### PowerToThePeople/Views/ContactView.swift

```swift
//
//  ContactView.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftUI

struct ContactView: View {
    @Bindable var contact: EmergencyContact
    
    var body: some View {
        Form {
            Section(header: Text("Contact")) {
                TextField("Name", text: $contact.name)
                TextField("Phone number", text: $contact.phoneNumber)
                    .textContentType(.telephoneNumber)
                    .keyboardType(.phonePad)
                TextField("Message", text: $contact.message, axis: .vertical)
                    .lineLimit(4, reservesSpace: true)
            }
        }
    }
}

#Preview {
    ContactView(contact: EmergencyContact(name: "Test Contact", phoneNumber: "", message: "Test Message"))
}

```

### PowerToThePeople/Views/IntroductionView.swift

```swift
//
//  IntroductionView.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftUI

struct IntroductionView: View {
    @State private var permissionsViewPresented = false
    
    var body: some View {
        VStack(spacing: 8) {
            Spacer()
            Text("Power to the People")
                .font(.largeTitle)
                .fontWeight(.bold)
            
            Text("A CruzHacks 2024 project by Brendan, David, Jacob, and Patrick")
                .multilineTextAlignment(.center)
            Spacer()
            
            SetupButton(text: "Continue") {
                print("Button pressed")
                permissionsViewPresented = true
            }
            .padding()
        }
        .navigationDestination(isPresented: $permissionsViewPresented) {
            PermissionsView()
        }
    }
}

#Preview {
    IntroductionView()
}

```

### PowerToThePeople/Views/RecordingsView.swift

```swift
//
//  RecordingsView.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftUI
import SwiftData

/// View which displays all past recordings.
struct RecordingsView: View {
    @Query var recordings: [Recording]
    @Environment(\.modelContext) var modelContext
    
    var body: some View {
        List {
            ForEach(recordings) { recording in
                NavigationLink {
                    RecordingView(recording: recording)
                } label: {
                    Text(recording.timestampEndedString)
                }
            }
            .onDelete(perform: deleteRecording)
        }
        .navigationTitle("Past Recordings")
        .navigationBarTitleDisplayMode(.large)
    }
    
    func deleteRecording(_ indexSet: IndexSet) {
        for index in indexSet {
            let recording = recordings[index]
            
            defer {
                // Otherwise, fail silently
                modelContext.delete(recording)
            }
            
            // Attempt to delete the recordings from the filesystem
            do {
                try FileManager.default.removeItem(at: recording.backCameraFile)
                try FileManager.default.removeItem(at: recording.frontCameraFile)
            } catch {
                print("Unable to delete recording: \(error)")
            }
        }
    }
}

#Preview {
    let schema = Schema([
        Recording.self,
    ])
    let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)

    let modelContainer = try! ModelContainer(for: schema, configurations: [modelConfiguration])
    
    return RecordingsView()
        .modelContainer(modelContainer)
}


```

### PowerToThePeople/Models/Recording.swift

```swift
//
//  Recording.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.19.
//

import Foundation
import SwiftData
import CoreLocation

@Model
final class Recording {
    /// Location of the video recording from the back camera (facing away from the user).
    let backCameraFile: URL
    
    /// Location of the video recording from the front camera.
    let frontCameraFile: URL
    
    /// The timestamp that the recording was started.
    let timestampStarted: Date
    
    /// The timestamp that the recording was ended.
    let timestampEnded: Date
    
    var timestampEndedString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "YYYY.MM.DD HH:mm:ss"
        
        return dateFormatter.string(from: timestampEnded)
    }
    
    /// Latitude of the location that the recording was taken.
    let locationLatitude: Int32?
    
    /// Longitude of the location that the recording was taken.
    let locationLongitude: Int32?
    
    /// Any notes added by the user.
    let notes: String
    
    init(backCameraFile: URL, frontCameraFile: URL, timestampStarted: Date, timestampEnded: Date, notes: String) {
        self.backCameraFile = backCameraFile
        self.frontCameraFile = frontCameraFile
        self.timestampStarted = timestampStarted
        self.timestampEnded = timestampEnded
        self.notes = notes
    }
    
    init(backCameraFile: URL, frontCameraFile: URL, timestampStarted: Date, timestampEnded: Date, locationLatitude: Int32, locationLongitude: Int32, notes: String) {
        self.backCameraFile = backCameraFile
        self.frontCameraFile = frontCameraFile
        self.timestampStarted = timestampStarted
        self.timestampEnded = timestampEnded
        self.locationLatitude = locationLatitude
        self.locationLongitude = locationLongitude
        self.notes = notes
    }
}

```

### PowerToThePeople/Views/ContactsView.swift

```swift
//
//  ContactsView.swift
//  PowerToThePeople
//
//  Created by Brendan Chen on 2024.01.20.
//

import Foundation
import SwiftData
import SwiftUI

/// View for editing emergency contacts.
struct ContactsView: View {
    let maximumNumberOfContacts = 5

    @Query var contacts: [EmergencyContact]
    @Environment(\.modelContext) var modelContext
    
    @State private var contactViewPresented = false
    
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            List {
                ForEach(contacts) { contact in
                    NavigationLink {
                        ContactView(contact: contact)
                    } label: {
                        contact.name == "" ? Text("New Contact") : Text(contact.name)
                    }
                }
                .onDelete(perform: deleteContacts)
                
                Button(action: addContact) {
                    Text("Add Emergency Contact (\(maximumNumberOfContacts - contacts.count)/\(maximumNumberOfContacts) remaining)")
                }
                .disabled(contacts.count >= maximumNumberOfContacts)
            }
            
        }
        .navigationTitle("Emergency Contacts")
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .navigationDestination(isPresented: $contactViewPresented) {
            if contacts.count > 0 {
                ContactView(contact: contacts[contacts.count - 1])
            }
        }
    }
    
    func addContact() {
        withAnimation {
            let newContact = EmergencyContact(name: "", phoneNumber: "", message: "")
            
            modelContext.insert(newContact)
            contactViewPresented = true
        }
    }
    
    func deleteContacts(_ indexSet: IndexSet) {
        for index in indexSet {
            let contact = contacts[index]
            modelContext.delete(contact)
        }
    }
}

#Preview {
    let schema = Schema([
        EmergencyContact.self,
    ])
    let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)

    let modelContainer = try! ModelContainer(for: schema, configurations: [modelConfiguration])
    
    return ContactsView()
        .modelContainer(modelContainer)
}

```

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