origo

license MIT

overview

desenvolvemos o origo como parte de um desafio focado em solucionar problemas de profissões específicas. a ideia era substituir as pranchetas e papéis usados em sítios arqueológicos por uma solução digital robusta. o aplicativo permite o cadastro detalhado de artefatos, incluindo dados técnicos como materiais, técnica de produção e estado de conservação, além de informações do sítio e número do processo.

mais do que um simples formulário, o origo atua como um companheiro de campo multimídia, permitindo anexar fotos e gravações de áudio a cada ficha, consolidando todas as informações da escavação em um único dispositivo.

do ponto de vista técnico, o origo foi um mergulho profundo no SwiftData para persistência local, lidando com modelos complexos que conectam artefatos a mídias variadas. a integração com o Google Maps SDK foi essencial para capturar coordenadas precisas, que convertemos para o formato UTM (padrão na arqueologia).

outro destaque foi a implementação da API de Speech, que transcreve automaticamente as notas de áudio gravadas pelo usuário em texto, economizando tempo valioso de digitação. a interface, construída 100% em SwiftUI, gerencia fluxos de entrada de dados densos sem sacrificar a clareza e a usabilidade.

design choices

palette

bedrock #472500
rust #C54302
dust #F6D2B2
river #436187

typography

Brasilero 2018 Free / onboarding e logotipo
Zebras jogam xadrez com o velho faquir
SF Pro Display / interface primária
Zebras jogam xadrez com o velho faquir

some fonts used in this project are proprietary and may not display correctly if they are not installed on your system.

rationale

origo é um aplicativo feito para ajudar arqueólogos com o trabalho de registro e catalogação de artefatos achados em campo.

considerando o propósito do app, a paleta de cores foi inspirada em tons terrosos e naturais, remetendo à terra e aos artefatos encontrados durante escavações.

as cores escolhidas transmitem uma sensação de conexão com a natureza e o passado histórico.

a tipografia busca inspiração na cultura popular brasileira para o onboarding e a escrita do nome do aplicativo, por meio da fonte Brasilêro, criada a partir da análise de milhares de placas e sinais escritos à mão em comunidades urbanas e rurais por todo o Brasil.

escolhemos essa fonte por remeter à cultura nacional e trazer um toque humano e artesanal ao design.

para o restante do aplicativo, utilizamos a fonte SF Pro Display, que é moderna, limpa e altamente legível em telas digitais, garantindo uma experiência prática e nativa de leitura para os usuários.

além disso, o aplicativo conta com mais um detalhe artesanal que remete ao trabalho em campo:

escaneamos diversas folhas e pequenas plantas, cujas silhuetas foram vetorizadas e utilizadas como ornamentos e detalhes visuais no onboarding e no logotipo do app, reforçando a conexão com a natureza e o ambiente arqueológico.

tech stack

SwiftUI
framework de UI
SwiftData
persistência local
Google Maps SDK
serviços de localização
AVFoundation
manuseio de áudio

code snippets

Gerenciamento de Estado de Rascunho

swift
class RecordDraft: ObservableObject {
    @Published var id: UUID = UUID()
    @Published var name: String = ""
    @Published var createdAt: Date = Date()
    @Published var location: LocationInfoModel? = nil
    @Published var artifactData: ArtifactDataModel? = nil
    @Published var artifactDetails: ArtifactDetailsModel? = nil
    @Published var audios: [AudioModel] = []
    @Published var photos: [CapturedImageModel] = []
    @Published var geolocation: MapMarkerModel? = nil
    var asRecordModel: RecordModel {
        let artifact = ArtifactModel(
            location: location,
            artifactData: artifactData,
            artifactDetails: artifactDetails
        )
        return RecordModel(
            id: id,
            name: name,
            createdAt: createdAt,
            artifact: artifact,
            audios: audios,
            photos: photos,
            geolocation: geolocation
        )
    }
}

Transcrição de Fala

swift
class AudioTranscriber: ObservableObject {
    @Published var transcription: String = ""
    private let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "pt-BR"))
    private var request: SFSpeechRecognitionRequest?
    func transcribeAudio(url: URL) {
        SFSpeechRecognizer.requestAuthorization { [weak self] authStatus in
            guard authStatus == .authorized else {
                return
            }
            self?.request = SFSpeechURLRecognitionRequest(url: url)
            self?.request?.requiresOnDeviceRecognition = true
            guard let recognizer = self?.recognizer, recognizer.isAvailable else {
                return
            }
            guard let request = self?.request else { return }
            recognizer.recognitionTask(with: request) { result, error in
                DispatchQueue.main.async {
                    if let result = result {
                        if result.isFinal {
                            self?.transcription = result.bestTranscription.formattedString
                        }
                    } else if let error = error {
                        self?.transcription = ""
                    }
                }
            }
        }
    }
}

Integração com Google Maps

swift
class MapsViewModel: NSObject, ObservableObject, GMSMapViewDelegate {
    @Published var cameraPositionLatitude: Double = 0
    @Published var cameraPositionLongitude: Double = 0
    @Published var utmString: String = ""
    @Published var mapView: GMSMapView!
    @Published var camera = GMSCameraPosition.camera(withTarget: .init(latitude: 0, longitude: 0), zoom: 15.0)
    private var locationManager: LocationViewModel = .init()
    private var formatter: LocationCoordinateFormatter!
    override init() {
        super.init()
        formatter = LocationCoordinateFormatter()
        formatter.format = .utm
        mapView = GMSMapView(frame: .zero, camera: camera)
        mapView.mapType = .hybrid
        mapView.isMyLocationEnabled = true
        mapView.delegate = self
    }
    func updateCurrentLocation() {
        self.cameraPositionLatitude = locationManager.latitude
        self.cameraPositionLongitude = locationManager.longitude
        camera = GMSCameraPosition.camera(withTarget: .init(latitude: self.cameraPositionLatitude, longitude: self.cameraPositionLongitude), zoom: camera.zoom)
    }
    func updateUTMCoordinates() {
        self.cameraPositionLatitude = camera.target.latitude
        self.cameraPositionLongitude = camera.target.longitude
        self.utmString = self.formatter.string(from: CLLocationCoordinate2D(latitude: self.cameraPositionLatitude, longitude: self.cameraPositionLongitude)) ?? ""
    }
    func mapView(_ mapView: GMSMapView, idleAt position: GMSCameraPosition) {
        camera = position
        updateUTMCoordinates()
    }
}

credits