dwata icon indicating copy to clipboard operation
dwata copied to clipboard

Email Client Desktop App using egui

Open brainless opened this issue 3 months ago • 0 comments

Email Client Desktop App using egui

Overview

Create a desktop email client application using Rust and the egui GUI framework. This app will display email data from a SQLite3 database (generated separately in #173).

Depends on: #173 (Fake Data Generator)


Project Structure

This should be implemented as a separate binary crate in the Cargo workspace:

Binary name: dwata-app-egui

Workspace structure:

dwata/
├── Cargo.toml                 # Workspace manifest
├── dwata-db/                  # Shared library (from #173)
├── fake-data-generator/       # Binary from #173
└── dwata-app-egui/            # THIS BINARY (Issue #174)
    ├── Cargo.toml
    └── src/
        ├── main.rs
        ├── ui/
        └── worker/

Cargo.toml for dwata-app-egui:

[package]
name = "dwata-app-egui"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "dwata-app-egui"
path = "src/main.rs"

[dependencies]
dwata-db = { path = "../dwata-db" }  # Shared database library
eframe = "0.27"
egui = "0.27"
dirs = "5.0"

Usage:

# Build and run the egui email client
cargo run --bin dwata-app-egui

# Or build in release mode
cargo build --release --bin dwata-app-egui
./target/release/dwata-app-egui

Using the Shared dwata-db Library

IMPORTANT: This application should use the shared dwata-db library crate for ALL database operations. Do NOT create your own db/ module or duplicate any database code.

What dwata-db Provides

The dwata-db library includes:

  1. Database Models - All Rust structs:

    use dwata_db::{Account, Email, Contact, Folder, Label, Attachment};
    
  2. EmailRepository - All database query functions:

    use dwata_db::EmailRepository;
    
    let repo = EmailRepository::new(db_path)?;
    
    // Available methods:
    repo.get_accounts()?;
    repo.get_emails_by_folder(folder_id)?;
    repo.get_email_by_id(email_id)?;
    repo.search_emails_by_subject(query)?;
    repo.get_contacts(account_id)?;
    repo.get_labels(account_id)?;
    repo.get_folders(account_id)?;
    repo.get_attachments(email_id)?;
    repo.get_all_attachments()?;
    repo.mark_as_read(email_id)?;
    repo.toggle_star(email_id)?;
    
  3. Schema Creation (if needed):

    use dwata_db::create_tables;
    

Benefits

  • No code duplication - Database logic defined once
  • Type safety - Shared types across all binaries
  • Consistent behavior - Same queries used by all apps
  • Easy maintenance - Changes in one place

Code Structure (Simplified)

Since you're using the shared library, your code structure should be:

dwata-app-egui/src/
├── main.rs              # Entry point, egui setup, DB path check
├── ui/
│   ├── mod.rs
│   ├── app.rs           # Main app state and egui App trait
│   ├── navigation.rs    # Column 1: Navigation panel
│   ├── email_list.rs    # Column 2: Email list view
│   ├── email_detail.rs  # Column 3: Email detail view
│   ├── contacts.rs      # Contact list and detail views
│   ├── files.rs         # Files browser view
│   ├── search.rs        # Search bar and functionality
│   └── virtual_list.rs  # Reusable virtual scrolling component
└── worker/
    ├── mod.rs           # Worker thread logic
    └── messages.rs      # Request/Response types

Note: No db/ module needed - use dwata_db directly!


Database Connection

Database Location

The app will read from the SQLite database at:

  • Linux/macOS: ~/.config/dwata/db.sqlite3
  • Windows: %APPDATA%\dwata\db.sqlite3

Example path: /home/username/.config/dwata/db.sqlite3

Database Requirements

  • The app does NOT create the database - it must already exist (created by the generator from #173)
  • On startup: Check if database file exists at expected location
  • If database not found: Display error message and exit gracefully
    • Error message should show expected database path
    • Suggest running the fake data generator first

Example Implementation

use dwata_db::EmailRepository;
use std::path::PathBuf;

fn get_db_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
    let config_dir = dirs::config_dir()
        .ok_or("Could not determine config directory")?;
    
    let db_path = config_dir.join("dwata").join("db.sqlite3");
    
    if !db_path.exists() {
        return Err(format!(
            "Database not found at: {}\nPlease run the fake data generator first.",
            db_path.display()
        ).into());
    }
    
    Ok(db_path)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let db_path = get_db_path()?;
    let repository = EmailRepository::new(db_path)?;
    
    // Use repository in your app
    // ...
    
    Ok(())
}

Architecture

Threading Model

The application must use separate threads for UI and data processing to maintain responsiveness:

  • UI Thread: Handle rendering and user interaction with egui
  • Worker Thread(s): Database queries, search operations, data processing
  • Communication: Use message passing (`std::sync::mpsc` channels) between threads

Threading Flow

  1. User action in UI → UI sends request message to worker
  2. Worker receives request → queries database → sends result back to UI
  3. UI receives result → updates state → triggers re-render

Example: ``` User clicks "Inbox" folder → UI sends: FetchEmails { folder_id: 5 } → Worker queries DB → Worker sends: EmailsResult(Vec<Email>) → UI updates list and renders ```


User Interface Layout

3-Column Layout

Column 1: Navigation Panel (Left side, ~200-250px fixed width)

Account Selector:

  • Dropdown showing current account email
  • Click to show list of all accounts
  • Switch between accounts

Navigation Links:

  • "All Inboxes": Unified inbox view across all accounts
  • "Labels" section:
    • Section header (expandable/collapsible)
    • List of top 10 labels below header
    • Each label shows name and unread count badge
    • "View all labels" link if more than 10 labels exist
  • "Contacts": Navigate to contacts list
  • "Files": Browse all attachments

Visual Requirements:

  • Each navigation item shows unread count (if applicable)
  • Active/selected item highlighted
  • Hover states for interactive elements

Column 2: List View (Middle, flexible width)

Email List View (when folder/label selected):

  • Virtual scrolling list (render only visible items)

  • Each email item displays across two rows:

    First row:

    • Checkbox (for multi-select, unchecked by default)
    • Star icon (☆ for unstarred, ★ for starred)
    • Subject line (bold if unread, regular weight if read)

    Second row (indented to align with subject):

    • Sender name/email (gray text, smaller font)
    • Humanized timestamp (right-aligned, gray text, smaller font):
      • Time if today (e.g., "2:30 PM")
      • Short date if this year (e.g., "Jan 15")
      • Full date if older (e.g., "Jan 15, 2023")

    Visual separators:

    • 1px horizontal separator between each email item

    Optional future enhancements (not required in initial implementation):

    • Body snippet (first ~100 characters, plain text)
    • Paperclip icon (if has attachments)
    • Label tags/chips (if labeled)
  • Click on item → show in Column 3

  • Sort controls (optional for future):

    • Date (newest first / oldest first)
    • Sender (alphabetical)
    • Subject (alphabetical)

Contact List View (when Contacts selected):

  • Virtual scrolling list
  • Each contact item displays:
    • Contact name (or email if no name)
    • Email address
    • Last interaction date (optional)
  • Click on contact → show in Column 3
  • Search/filter input at top

Hidden when "Files" is selected (see Files View below)

Column 3: Detail View (Right, flexible width)

Email Detail View:

  • Header Section:

    • Subject (large, prominent text)
    • From: sender name and email
    • To: recipient list (expandable if many recipients)
    • Cc: (if any, expandable)
    • Date and time (full format)
    • Action buttons:
      • Reply
      • Reply All
      • Forward
      • Delete
      • Archive
      • Star/Unstar
      • Add Label
  • Body Section:

    • Email body (HTML rendering or plain text)
    • Scrollable if content is long
    • Preserve formatting
  • Attachments Section (if any):

    • List of attachments
    • Each shows: icon (based on file type), filename, size
    • Action: View/Download button

Contact Detail View:

  • Contact Info:

    • Name (large text)
    • Email address
    • Additional fields (if extended later: phone, address)
    • Action buttons: Compose Email, Edit, Delete
  • Recent Emails:

    • List of last 10 emails with this contact
    • Click to view email details

Hidden when "Files" is selected (see Files View below)

Files View (Special Layout)

When "Files" is selected from Column 1:

  • Columns 2 and 3 merge into single wide area
  • Display all attachments across all emails

Layout:

  • Virtual scrolling grid or list view
  • Each attachment item shows:
    • File type icon (PDF, image, doc, etc.)
    • Filename
    • File size
    • Source email subject (truncated)
    • Date received
  • Controls:
    • Filter by file type dropdown (All, Documents, Images, Archives, etc.)
    • Sort by: Name, Size, Date
    • Search by filename (optional for future)

Top Navigation Bar

Search Bar:

  • Prominent position (center or right side)
  • Search by email subject (Phase 1)
  • Shows results as-you-type or on Enter key
  • Results displayed in Column 2 (email list format)
  • Clear button to exit search

App Actions:

  • Settings icon/button
  • Sync/Refresh icon
  • Other utility buttons as needed

Virtual Scrolling Implementation

Performance Requirements

All long lists must use virtual scrolling to maintain 60fps:

What is Virtual Scrolling:

  • Only render items currently visible in viewport
  • Calculate which items should be visible based on scroll position
  • Maintain buffer of items above/below viewport for smooth scrolling

Implementation Strategy:

  1. Track scroll position (offset from top)
  2. Calculate visible range:
    • `first_visible_index = scroll_offset / item_height`
    • `last_visible_index = (scroll_offset + viewport_height) / item_height`
  3. Add buffer: render items `first - buffer` to `last + buffer`
  4. Use buffer of ~10-20 items beyond visible range

Target Lists:

  • Email list (Column 2) - ~50px per item
  • Contact list (Column 2) - ~40px per item
  • Files grid (Columns 2+3 merged) - variable item size

Virtual List Component

Create reusable virtual list component: ```rust struct VirtualList<T> { items: Vec<T>, item_height: f32, viewport_height: f32, scroll_offset: f32, buffer_items: usize, } ```


State Management

Application State

```rust struct AppState { // Current selections selected_account_id: Option, selected_folder_id: Option, selected_email_id: Option, selected_contact_id: Option,

// Current view
current_view: View, // Emails, Contacts, Files

// Data
accounts: Vec<Account>,
current_email_list: Vec<Email>,
current_email: Option<Email>,
labels: Vec<Label>,

// UI state
search_query: String,
is_loading: bool,
scroll_positions: HashMap<String, f32>,

// Communication
worker_tx: Sender<WorkerRequest>,
worker_rx: Receiver<WorkerResponse>,

} ```

Worker Communication

```rust enum WorkerRequest { FetchEmails { folder_id: i64 }, FetchEmailDetail { email_id: i64 }, SearchEmails { query: String }, FetchContacts { account_id: i64 }, MarkAsRead { email_id: i64 }, ToggleStar { email_id: i64 }, // ... other operations }

enum WorkerResponse { EmailsList(Vec<Email>), EmailDetail(Email), SearchResults(Vec<Email>), ContactsList(Vec<Contact>), OperationComplete, Error(String), } ```


Database Layer

Abstraction

  • No SQL in UI code: All database queries in separate module
  • Repository Pattern or similar abstraction
  • All queries return Rust structs (from models in #173)

Database Connection

  • Connect to database at startup using path from `get_db_path()`
  • Open in read-write mode (for marking emails read, starring, etc.)
  • Enable SQLite connection pooling if needed for worker threads

Example Repository Functions

```rust impl EmailRepository { fn new(db_path: PathBuf) -> Result<Self>; fn get_emails_by_folder(&self, folder_id: i64) -> Result<Vec<Email>>; fn get_email_by_id(&self, email_id: i64) -> Result<Email>; fn search_by_subject(&self, query: &str) -> Result<Vec<Email>>; fn mark_as_read(&self, email_id: i64) -> Result<()>; fn toggle_star(&self, email_id: i64) -> Result<()>; // ... more operations } ```

Use models from #173: Same Rust structs (Account, Email, Contact, etc.)


Features Checklist

Core Features

  • [ ] Database existence check on startup
  • [ ] Graceful error handling if database not found
  • [ ] Account switching (dropdown in Column 1)
  • [ ] Folder/label navigation
  • [ ] Email list with virtual scrolling (Column 2)
  • [ ] Email detail view (Column 3)
  • [ ] Contact list and detail view
  • [ ] Files/attachments browser (merged columns)
  • [ ] Search by email subject
  • [ ] Responsive layout (handle window resize)

Email Actions

  • [ ] Mark email as read/unread
  • [ ] Star/unstar emails
  • [ ] Select and view email
  • [ ] View attachments metadata

UI/UX

  • [ ] Virtual scrolling maintains 60fps
  • [ ] Loading indicators during data fetch
  • [ ] Smooth transitions between views
  • [ ] Keyboard navigation (optional: arrow keys, Enter to open)
  • [ ] Unread count badges
  • [ ] Smart timestamp formatting

Code Structure

``` src/ ├── main.rs # Entry point, egui setup, DB path check ├── db/ │ ├── mod.rs │ ├── models.rs # Rust structs (same as #173) │ └── repository.rs # Database operations ├── ui/ │ ├── mod.rs │ ├── app.rs # Main app state and egui App trait │ ├── navigation.rs # Column 1: Navigation panel │ ├── email_list.rs # Column 2: Email list view │ ├── email_detail.rs # Column 3: Email detail view │ ├── contacts.rs # Contact list and detail views │ ├── files.rs # Files browser view │ ├── search.rs # Search bar and functionality │ └── virtual_list.rs # Reusable virtual scrolling component └── worker/ ├── mod.rs # Worker thread logic └── messages.rs # Request/Response types ```


Implementation Phases

Phase 1: Foundation

  • [ ] Project setup with egui
  • [ ] Database path detection and existence check
  • [ ] Database connection with error handling
  • [ ] Basic repository layer
  • [ ] Worker thread with message passing

Phase 2: Basic UI

  • [ ] 3-column layout skeleton
  • [ ] Navigation panel (Column 1)
  • [ ] Email list (Column 2, simple version without virtual scrolling)
  • [ ] Email detail view (Column 3)
  • [ ] Account switching

Phase 3: Advanced Features

  • [ ] Virtual scrolling for email list
  • [ ] Contact views (list + detail)
  • [ ] Files browser
  • [ ] Search functionality
  • [ ] Email actions (mark read, star)

Phase 4: Polish

  • [ ] Loading states and error handling
  • [ ] Performance optimization
  • [ ] UI refinements (colors, spacing, icons)
  • [ ] Keyboard shortcuts (optional)

Performance Targets

  • Startup time: <2 seconds (including DB connection)
  • Email list rendering: 60fps while scrolling through 2000 items
  • Search response: <100ms for subject search
  • View switching: <50ms to switch between folders/views
  • Memory usage: <200MB for app with full dataset
  • Frame rate: Maintain 60fps during normal interaction

Success Criteria

  • [ ] App checks for database and handles missing DB gracefully
  • [ ] All features from checklist implemented
  • [ ] Virtual scrolling performs smoothly with 2000+ emails
  • [ ] UI remains responsive during all database operations
  • [ ] Search returns results quickly (<100ms)
  • [ ] App handles window resize gracefully
  • [ ] Clean separation between UI and database layers
  • [ ] Code is well-structured for future modifications

Technical Stack

  • Language: Rust (latest stable)
  • GUI Framework: egui
  • Database: SQLite3 (via rusqlite)
  • Threading: std::thread + mpsc channels
  • Additional Crates:
    • `eframe` - egui framework integration
    • `rusqlite` - SQLite interface
    • `dirs` - Platform-specific directory paths
    • `serde` / `serde_json` - Serialization
    • `chrono` - Date/time handling for timestamps
    • `anyhow` or `thiserror` - Error handling

Documentation

  • [ ] README with setup instructions
  • [ ] Instructions to run fake data generator first
  • [ ] Database location documentation
  • [ ] Code comments for complex logic
  • [ ] Architecture overview
  • [ ] Screenshots of the UI
  • [ ] Performance benchmarks

brainless avatar Oct 11 '25 03:10 brainless