use std::sync::Mutex; use thiserror::Error; mod api; mod core; mod domain; mod presentation; mod storage; use api::*; use domain::*; pub use presentation::*; use storage::*; uniffi::setup_scaffolding!(); #[derive(Clone, Copy, Debug, uniffi::Enum)] pub enum RepositoryPane { Issues, Commits, Milestones, } #[derive(Clone, Copy, Debug, uniffi::Enum)] pub enum ServerProvider { Gitea, Forgejo, } impl From for gotcha_gitea::Provider { fn from(provider: ServerProvider) -> Self { match provider { ServerProvider::Gitea => Self::Gitea, ServerProvider::Forgejo => Self::Forgejo, } } } impl From for ServerProvider { fn from(provider: gotcha_gitea::Provider) -> Self { match provider { gotcha_gitea::Provider::Gitea => Self::Gitea, gotcha_gitea::Provider::Forgejo => Self::Forgejo, } } } impl RepositoryPane { const ALL: [Self; 3] = [Self::Issues, Self::Commits, Self::Milestones]; const fn key(self) -> &'static str { match self { Self::Issues => "issues", Self::Commits => "commits", Self::Milestones => "milestones", } } } #[derive(Debug, Error, uniffi::Error)] pub enum GotchaError { #[error("{message}")] Message { message: String }, } impl From for GotchaError { fn from(message: String) -> Self { Self::Message { message } } } impl From<&str> for GotchaError { fn from(message: &str) -> Self { message.to_string().into() } } #[derive(Clone, uniffi::Record)] pub struct Settings { pub issue_status: String, pub pull_status: String, pub appearance: u32, } #[derive(uniffi::Object)] pub struct GotchaCore { state: Mutex, startup_error: Option, } fn validate_repository<'a>( owner: &'a str, repository: &'a str, ) -> Result<(&'a str, &'a str), GotchaError> { let owner = owner.trim(); let repository = repository.trim(); if owner.is_empty() || repository.is_empty() { return Err("Select a repository first.".into()); } Ok((owner, repository)) } fn valid_page(page: u32) -> Result { (page > 0) .then(|| i32::try_from(page).ok()) .flatten() .ok_or_else(|| "Invalid page number.".into()) } impl GotchaCore { fn server(&self) -> Result { let state = self.state.lock().unwrap(); state .active_server .and_then(|index| state.preferences.servers.get(index)) .cloned() .ok_or_else(|| "Select a server first.".to_string().into()) } fn server_by_id(&self, id: &str) -> Result { self.state .lock() .unwrap() .preferences .servers .iter() .find(|server| server.credential_account == id) .cloned() .ok_or_else(|| "That server is no longer configured.".into()) } fn repository_rows(&self, state: &State, pane: RepositoryPane) -> Vec { let server_url = state .active_server .and_then(|index| state.preferences.servers.get(index)) .map(|server| server.url.as_str()) .unwrap_or_default(); repository_rows(&state.repositories, |repository| { state.preferences.favorites.contains(&favorite_key( pane, server_url, &repository.owner, &repository.name, )) }) } }