# Project export: Yap Bubble

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: TreeHacks 2024
- Tagline: Geospatial chat. Frictionlessly engage with your environment.
- Devpost: https://devpost.com/software/yap-bubble
- GitHub: https://github.com/shreyfirst/YapBubble/
- Team: 1 GitHub contributor(s) — Shrey Gupta (2 commits)

## Devpost submission (written by the team)

### Overview

DEMO URL: https://www.loom.com/share/5779cb53c9f74cb79ecc619e2c612dab?sid=59d56d62-281f-4073-8481-5b3d9b664d16

### Inspiration

Face to face connection is fantastic, but sometimes unrealistic for some conversations. If you're confused about what your professor is teaching, you can vibe check with people on Yap Bubble to see if they're in the same boat.

### What it does

Chat with people within 100 feet of you.

### How we built it

Convex. Such an amazing platform to handle websocket connections real time database polling/updates reactive functional queries (godsend for geohashing)

### Challenges we ran into

Tom, Michael, and Wayne were super helpful from Convex to help me talk through all my ideas an how it should be implemented ideally with the platform.

### Accomplishments we're proud of

I'm super excited that I was able to build this in the first place! It was super fun to use Convex in Swift, which isn't technically support and I had to hack around with the Rust library and some open source stuff.

### What we learned

geohashing is hard!

### What's next

Getting it viral!! Have some UI and experience things I need to work on first tho...

## README (from the GitHub repository)

# Yap Bubble

Geospatial chat!


## Detected evidence (automated analysis)

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

## Codebase structure (from repository index)

### Files (21 of 21)

```
.DS_Store
.gitattributes
ConvexChatApp/Assets.xcassets/AccentColor.colorset/Contents.json
ConvexChatApp/Assets.xcassets/AppIcon.appiconset/Contents.json
ConvexChatApp/Assets.xcassets/Contents.json
ConvexChatApp/ConvexChatApp.entitlements
ConvexChatApp/ConvexChatAppApp.swift
ConvexChatApp/Info.plist
ConvexChatApp/LocationManager.swift
ConvexChatApp/MessagesView.swift
ConvexChatApp/Preview Content/Preview Assets.xcassets/Contents.json
ConvexChatApp/Secret.plist
README.md
YapBubble.xcodeproj/project.pbxproj
YapBubble.xcodeproj/project.xcworkspace/contents.xcworkspacedata
YapBubble.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
YapBubble.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
YapBubble.xcodeproj/project.xcworkspace/xcuserdata/shrey.xcuserdatad/UserInterfaceState.xcuserstate
YapBubble.xcodeproj/xcshareddata/xcschemes/ConvexChatApp.xcscheme
YapBubble.xcodeproj/xcuserdata/mtr.xcuserdatad/xcschemes/xcschememanagement.plist
YapBubble.xcodeproj/xcuserdata/shrey.xcuserdatad/xcschemes/xcschememanagement.plist
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Update README.md
- Initial commit

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

### ConvexChatApp/ConvexChatAppApp.swift

```swift
//
//  ConvexChatAppApp.swift
//  ConvexChatApp
//
//  Created by Mathieu Tricoire on 2023-04-03.
//

import Convex
import SwiftUI

 let path = Bundle.main.path(forResource: "Secret", ofType: "plist")!
 let secret = NSDictionary(contentsOfFile: path)!
 let CONVEX_URL = secret["CONVEX_URL"] as! String

extension ConvexQueries {
    var getMessagesLive: ConvexQueryDescription {
        ConvexQueryDescription(path: "myFunctions:getMessagesLive")
    }
}

@main
@MainActor
struct ConvexChatAppApp: App {
    private var client = Client(deploymentUrl: CONVEX_URL)
    @StateObject var locationManager = LocationManager()

    var body: some Scene {
        WindowGroup {
            MessagesView()
                .convexClient(client)
                .environmentObject(locationManager)
                .task {
                    await client.connect()
                }
                .onAppear() {
                    locationManager.manager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "sdfd")

                }

        }
    }
}

```

### ConvexChatApp/LocationManager.swift

```swift
import SwiftUI
import CoreLocation
import MapKit

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    let manager = CLLocationManager()
    @Published var degrees: Double = 0
    @Published var locations: [CLLocation]?
    @Published var newDist: String?
    let radius: CLLocationDistance = 222.638
        
    override init() {
        super.init()
        manager.delegate = self
        manager.startUpdatingHeading()
        manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        manager.requestWhenInUseAuthorization()
        print("\(manager.accuracyAuthorization)")
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
        degrees = newHeading.trueHeading
    }
    
    func requestLocation() {
        manager.requestLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        self.locations = locations
        if let locations = self.locations {
            if locations.count >= 2 {
                newDist = ("\(locations[locations.count-1].distance(from: locations[locations.count-2]))")
            }
        }
        print("Locations: \(locations)")
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
    
    func checkLocationAuthorization() -> CLAuthorizationStatus {
        return manager.authorizationStatus
    }
    
    func createGridRegion(centerCoordinate: CLLocationCoordinate2D, spanDegrees: CLLocationDegrees) -> MKCoordinateRegion {
        let region = MKCoordinateRegion(center: centerCoordinate, latitudinalMeters: spanDegrees * 111000, longitudinalMeters: spanDegrees * 111000)
        return region
    }
}

```

### ConvexChatApp/MessagesView.swift

```swift
//
//  MessagesView.swift
//  ConvexChatApp
//
//  Created by Mathieu Tricoire on 2023-05-03.
//

import Convex
import SwiftUI
import CoreLocation
import CoreLocationUI

struct MessagesView: View {
    @Environment(\.convexClient) private var client
    @EnvironmentObject var locationManager: LocationManager
    private let timerInterval: TimeInterval = 1.0
    @State private var username = "HardcodedUsername"
    
    
    @State private var showingLocationModal = false
    @State private var currentLocation: CLLocation?
    
    
    @State private var lat: Double = 0.0
    @State private var long: Double = 0.0
    @ConvexQuery(\.getMessagesLive, args: ["lat": Value(floatLiteral: 0.0), "long": Value(floatLiteral: 0.0)]) private var messages
    
    private let dateFormatter = {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .long
        dateFormatter.timeStyle = .short
        return dateFormatter
    }()
    
    func sendMessage(_ body: String) {
        Task {
            
            try? await client?.mutation(path: "myFunctions:sendMessage", args: ["display_name": Value(stringLiteral: username), "message": Value(stringLiteral: body), "lat": Value(floatLiteral: lat), "long": Value(floatLiteral: long)])
        }
    }
    
    
    var body: some View {
        NavigationStack {
            VStack(spacing: 0) {
                if case let .array(messages) = messages {
//                    Button("Show Current Location") {
//                                    currentLocation = locationManager.locations?.last
//                                    showingLocationModal = true
//                                }
//                                .sheet(isPresented: $showingLocationModal) {
//                                    if let location = currentLocation {
//                                        // Displaying location details in a modal
//                                        Text("Current Location:\nLatitude: \(location.coordinate.latitude)\nLongitude: \(location.coordinate.longitude)")
//                                            .padding()
//                                    } else {
//                                        Text("No location data available")
//                                    }
//                                }
//                    LocationButton {
//                        locationManager.requestLocation()
//                        print(locationManager.locations)
//                    }
                    List {
                        ForEach(messages.reversed(), id: \.[dynamicMember: "_id"]) { message in
                            VStack(alignment: .leading) {
                                Text("**\(message.display_name?.description ?? "")**: \(message.message?.description ?? "")")
                                if case let .some(.float(creationTime)) = message._creationTime {
                                    Text(Date(timeIntervalSince1970: creationTime / 1000).description)
                                        .font(.caption)
                                        .foregroundColor(Color.gray)
                                }
                            }
                            .listRowSeparator(.hidden)
                        }
                    }
                    .listStyle(.plain)
                    .animation(.easeIn, value: messages)
                } else {
                    VStack {
                        Spacer()
                        Text("~ no messages ~")
                        Spacer()
                    }
                }
                
                CustomTextField { message in
                    sendMessage(message)
                }
                .background(.ultraThickMaterial)
            }
            .onTapGesture {
                hideKeyboard()
            }
            .onAppear {
                locationManager.requestLocation()
            }
            .onChange(of: locationManager.locations) { newLocations in
                if let location = newLocations?.last {
                    updateSubscription(with: location)
                }
            }
        }
    }
    func updateSubscription(with location: CLLocation) {
        let lat = location.coordinate.latitude
        let long = location.coordinate.longitude
        
        // Cancel previous subscription if necessary or manage subscriptions appropriately here
        
        Task {
            do {
                try await client?.subscribe(path: "myFunctions:getMessagesLive", args: ["lat": Value(floatLiteral: lat), "long": Value(floatLiteral: long)], resultHandler: { value in
                    print("Received value: \(value)")
                })
            } catch {
                print("Subscription failed with error: \(error)")
            }
        }
    }
}
struct MessagesView_Previews: PreviewProvider {
    static var previews: some View {
        MessagesView()
    }
}

// From: https://medium.com/@ckinetandrii/i-have-created-an-auto-resizing-textfield-using-swiftui-5839bb075a64
struct CustomTextField: View {
    @State var message: String = ""
    var action: (String) async -> Void
    
    var body: some View {
        HStack(alignment: .bottom) {
            HStack(spacing: 8) {
                withAnimation(.easeInOut) {
                    TextField("", text: $message, axis: .vertical)
                        .placeholder(when: message.isEmpty) {
                            Text("Message...")
                                .foregroundColor(.secondary)
                        }
                        .lineLimit(...7)
                }
            }
            .padding(.vertical, 8)
            .padding(.horizontal, 12)
            .background(.background)
            .cornerRadius(10)
            
            Button {
                Task {
                    await action(message)
                    message = ""
                }
            } label: {
          
[truncated — 900 more characters]
```