Files
Gotcha/crates/app/src/lib.rs
2026-08-15 17:25:25 +02:00

156 lines
3.8 KiB
Rust

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<ServerProvider> for gotcha_gitea::Provider {
fn from(provider: ServerProvider) -> Self {
match provider {
ServerProvider::Gitea => Self::Gitea,
ServerProvider::Forgejo => Self::Forgejo,
}
}
}
impl From<gotcha_gitea::Provider> 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<String> 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,
pub notifications_enabled: bool,
}
#[derive(uniffi::Object)]
pub struct GotchaCore {
state: Mutex<State>,
server_activity: tokio::sync::Mutex<Option<ServerActivitySession>>,
startup_error: Option<String>,
}
struct ServerActivitySession {
server_id: String,
next_page: u32,
pager: gotcha_gitea::ServerActivityPager,
}
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<i32, GotchaError> {
(page > 0)
.then(|| i32::try_from(page).ok())
.flatten()
.ok_or_else(|| "Invalid page number.".into())
}
impl GotchaCore {
fn server(&self) -> Result<Server, GotchaError> {
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<Server, GotchaError> {
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<RepositoryRow> {
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,
))
})
}
}