Skip to main content

aven_core/
local_state.rs

1use std::collections::BTreeSet;
2
3use anyhow::Result;
4
5use crate::db::{Database, begin_immediate, get_meta, set_meta};
6
7const ONBOARDING_META_KEY: &str = "tui_onboarding_version";
8const FIRST_LAUNCH_ONBOARDING_VERSION: u32 = 1;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum OnboardingStatus {
12    Due,
13    Complete,
14    Established,
15}
16
17impl Database {
18    pub async fn onboarding_status(&self) -> Result<OnboardingStatus> {
19        let mut conn = self.acquire_reader().await?;
20        let marker = get_meta(&mut conn, ONBOARDING_META_KEY).await?;
21        if marker_version(marker.as_deref())
22            .is_some_and(|version| version >= FIRST_LAUNCH_ONBOARDING_VERSION)
23        {
24            return Ok(OnboardingStatus::Complete);
25        }
26
27        let has_tasks: bool = sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM tasks)")
28            .fetch_one(&mut *conn)
29            .await?;
30        Ok(if has_tasks {
31            OnboardingStatus::Established
32        } else {
33            OnboardingStatus::Due
34        })
35    }
36
37    pub async fn complete_onboarding(&self) -> Result<()> {
38        let mut conn = self.acquire_writer().await?;
39        let mut tx = begin_immediate(&mut conn).await?;
40        let marker = get_meta(&mut tx, ONBOARDING_META_KEY).await?;
41        if marker_version(marker.as_deref())
42            .is_none_or(|version| version < FIRST_LAUNCH_ONBOARDING_VERSION)
43        {
44            set_meta(
45                &mut tx,
46                ONBOARDING_META_KEY,
47                &FIRST_LAUNCH_ONBOARDING_VERSION.to_string(),
48            )
49            .await?;
50        }
51        tx.commit().await?;
52        Ok(())
53    }
54}
55
56fn marker_version(value: Option<&str>) -> Option<u32> {
57    value?.trim().parse().ok()
58}
59
60/// Database-local sidebar preferences shared across workspaces and projects.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
62pub enum SidebarSection {
63    Views,
64    Scope,
65    Projects,
66}
67
68impl SidebarSection {
69    fn meta_key(self) -> &'static str {
70        match self {
71            Self::Views => "tui_sidebar_views_collapsed",
72            Self::Scope => "tui_sidebar_scope_collapsed",
73            Self::Projects => "tui_sidebar_projects_collapsed",
74        }
75    }
76}
77
78impl Database {
79    pub async fn collapsed_sidebar_sections(&self) -> Result<BTreeSet<SidebarSection>> {
80        let mut conn = self.acquire_reader().await?;
81        let mut sections = BTreeSet::new();
82        for section in [
83            SidebarSection::Views,
84            SidebarSection::Scope,
85            SidebarSection::Projects,
86        ] {
87            if get_meta(&mut conn, section.meta_key())
88                .await?
89                .as_deref()
90                .map(str::trim)
91                == Some("true")
92            {
93                sections.insert(section);
94            }
95        }
96        Ok(sections)
97    }
98
99    pub async fn set_sidebar_section_collapsed(
100        &self,
101        section: SidebarSection,
102        collapsed: bool,
103    ) -> Result<()> {
104        let mut conn = self.acquire_writer().await?;
105        let mut tx = begin_immediate(&mut conn).await?;
106        set_meta(
107            &mut tx,
108            section.meta_key(),
109            if collapsed { "true" } else { "false" },
110        )
111        .await?;
112        tx.commit().await?;
113        Ok(())
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[tokio::test]
122    async fn sidebar_preferences_are_local_and_invalid_values_default_to_expanded() {
123        let database = Database::open(std::path::Path::new(":memory:"))
124            .await
125            .unwrap();
126        let before: i64 = {
127            let mut conn = database.acquire_reader().await.unwrap();
128            sqlx::query_scalar("SELECT COUNT(*) FROM changes")
129                .fetch_one(&mut *conn)
130                .await
131                .unwrap()
132        };
133        database
134            .set_sidebar_section_collapsed(SidebarSection::Scope, true)
135            .await
136            .unwrap();
137        {
138            let mut conn = database.acquire_writer().await.unwrap();
139            set_meta(&mut conn, SidebarSection::Views.meta_key(), "invalid")
140                .await
141                .unwrap();
142        }
143        assert_eq!(
144            database.collapsed_sidebar_sections().await.unwrap(),
145            BTreeSet::from([SidebarSection::Scope])
146        );
147        let mut conn = database.acquire_reader().await.unwrap();
148        let after: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM changes")
149            .fetch_one(&mut *conn)
150            .await
151            .unwrap();
152        assert_eq!(before, after);
153    }
154}