Skip to content

Cross Platform Development

Chizy edited this page Apr 16, 2025 · 2 revisions

Cross-Platform Development

This guide details the approach, techniques, and best practices for developing QuietDrop across desktop and mobile platforms using a unified codebase.

Overview

QuietDrop follows a "write once, deploy everywhere" approach using Rust and Tauri 2.0. This strategy allows us to:

  • Maintain a single codebase for all platforms
  • Share cryptographic and business logic
  • Provide consistent security across devices
  • Adapt the UI for optimal experience on each platform
graph TD
    subgraph "Unified Codebase"
        Core[QuietDrop Core]
        Frontend[Yew Frontend]
        Backend[Tauri Backend]
    end
    
    Core --> Backend
    Frontend --> Backend
    
    subgraph "Desktop Platforms"
        Backend --> Windows
        Backend --> macOS
        Backend --> Linux
    end
    
    subgraph "Mobile Platforms"
        Backend --> Android
        Backend --> iOS
    end
Loading

Platform Support Matrix - Target Capabilities

The following matrix outlines the capabilities we're targeting across different platforms. This represents our development roadmap rather than current functionality. As QuietDrop evolves, we'll prioritize implementing these features while maintaining consistent security and user experience across all supported platforms.

Feature Windows macOS Linux Android iOS
Encryption
Messaging
File Transfer
Multiple Windows
System Tray
Notifications
Background Service ⚠️ ⚠️
Biometric Auth
Secure Storage
Camera Access

✅ = Fully supported ⚠️ = Limited support ❌ = Not supported/Not applicable

Development Environment Setup

Unified Development Setup

To develop for all platforms, you'll need:

# Install Rust and core tools
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
cargo install trunk
cargo install tauri-cli

# Clone the repository
git clone https://github.com/chizy7/QuietDrop.git
cd QuietDrop

Then follow the platform-specific setups as needed.

Platform-Specific Setup

Refer to the comprehensive setup instructions in the Tauri Integration page for detailed platform requirements.

Code Organization for Cross-Platform

QuietDrop uses several strategies to manage cross-platform code:

1. Core Shared Logic

The quietdrop-core crate contains platform-independent code:

quietdrop-core/
├── src/
│   ├── authentication.rs  # Authentication logic
│   ├── encryption.rs      # Cryptography operations
│   ├── message.rs         # Message handling
│   ├── client.rs          # Client communication
│   ├── server.rs          # Server operations
│   └── lib.rs             # Public API
└── Cargo.toml

This core library is used by all platforms and contains essential encryption and messaging logic.

2. Conditional Compilation

We use Rust's conditional compilation to handle platform-specific code:

// Platform detection
#[cfg(desktop)]
fn desktop_specific_function() {
    // Code that only compiles for desktop targets
}

#[cfg(target_os = "android")]
fn android_specific_function() {
    // Android-specific code
}

#[cfg(target_os = "ios")]
fn ios_specific_function() {
    // iOS-specific code
}

// For features available on both mobile platforms
#[cfg(any(target_os = "android", target_os = "ios"))]
fn mobile_specific_function() {
    // Code for both Android and iOS
}

3. Abstraction Layers

Platform-specific implementations behind common interfaces:

// Define a common trait
trait SecureStorage {
    fn save_key(&self, key_name: &str, data: &[u8]) -> Result<(), Error>;
    fn load_key(&self, key_name: &str) -> Result<Vec<u8>, Error>;
}

// Desktop implementation
#[cfg(desktop)]
struct DesktopSecureStorage;

#[cfg(desktop)]
impl SecureStorage for DesktopSecureStorage {
    fn save_key(&self, key_name: &str, data: &[u8]) -> Result<(), Error> {
        // Desktop-specific implementation
    }
    
    fn load_key(&self, key_name: &str) -> Result<Vec<u8>, Error> {
        // Desktop-specific implementation
    }
}

// Android implementation
#[cfg(target_os = "android")]
struct AndroidSecureStorage;

#[cfg(target_os = "android")]
impl SecureStorage for AndroidSecureStorage {
    fn save_key(&self, key_name: &str, data: &[u8]) -> Result<(), Error> {
        // Android-specific implementation
    }
    
    fn load_key(&self, key_name: &str) -> Result<Vec<u8>, Error> {
        // Android-specific implementation
    }
}

// Factory function to create the appropriate implementation
fn create_secure_storage() -> Box<dyn SecureStorage> {
    #[cfg(desktop)]
    return Box::new(DesktopSecureStorage);
    
    #[cfg(target_os = "android")]
    return Box::new(AndroidSecureStorage);
    
    #[cfg(target_os = "ios")]
    return Box::new(IOSSecureStorage);
}

Cross-Platform UI Development

Adaptive UI Approach

QuietDrop uses a responsive design approach with Yew components:

#[function_component(AdaptiveLayout)]
fn adaptive_layout() -> Html {
    let platform = use_platform_detection();
    
    html! {
        <div class={classes!(
            "container", 
            match platform {
                Platform::Desktop => "desktop-container",
                Platform::Mobile => "mobile-container",
                Platform::Tablet => "tablet-container"
            }
        )}>
            {
                match platform {
                    Platform::Desktop => html! { <DesktopLayout /> },
                    Platform::Mobile => html! { <MobileLayout /> },
                    Platform::Tablet => html! { <TabletLayout /> }
                }
            }
        </div>
    }
}

// Platform detection hook
#[hook]
fn use_platform_detection() -> Platform {
    // Implementation that detects the current platform
    // This can use window size, user agent, or other factors
}

Responsive CSS

In addition to conditional rendering, use responsive CSS:

/* Base styles for all platforms */
.message-container {
    display: flex;
    flex-direction: column;
}

/* Desktop-specific styles */
@media (min-width: 768px) {
    .message-container {
        max-width: 800px;
        margin: 0 auto;
    }
    
    .message-input {
        display: flex;
        gap: 1rem;
    }
}

/* Mobile-specific styles */
@media (max-width: 767px) {
    .message-container {
        width: 100%;
        padding: 0 1rem;
    }
    
    .message-input {
        display: flex;
        flex-direction: column;
    }
}

Input Method Handling

Adapt to different input methods based on platform:

#[function_component(MessageComposer)]
fn message_composer() -> Html {
    let platform = use_platform_detection();
    
    html! {
        <div class="composer">
            {
                if let Platform::Desktop = platform {
                    // Desktop-friendly input with keyboard shortcuts
                    html! { <DesktopComposer /> }
                } else {
                    // Touch-friendly input for mobile
                    html! { <MobileComposer /> }
                }
            }
        </div>
    }
}

Data Storage Strategies

QuietDrop uses different storage strategies depending on platform constraints:

1. Encryption Keys

#[tauri::command]
fn store_encryption_keys(
    public_key: Vec<u8>,
    private_key: Vec<u8>
) -> Result<(), String> {
    #[cfg(desktop)]
    {
        // Use platform keychain/secure storage on desktop
        #[cfg(target_os = "windows")]
        windows_dpapi_store(&public_key, &private_key)?;
        
        #[cfg(target_os = "macos")]
        keychain_store(&public_key, &private_key)?;
        
        #[cfg(target_os = "linux")]
        secret_service_store(&public_key, &private_key)?;
    }
    
    #[cfg(target_os = "android")]
    {
        // Use Android Keystore
        android_keystore_store(&public_key, &private_key)?;
    }
    
    #[cfg(target_os = "ios")]
    {
        // Use iOS Keychain
        ios_keychain_store(&public_key, &private_key)?;
    }
    
    Ok(())
}

2. Message History

// Initialize database with platform-specific path
fn initialize_database() -> Result<Connection, Error> {
    let db_path = get_platform_database_path()?;
    let connection = Connection::open(db_path)?;
    
    // Create tables and indices
    connection.execute(
        "CREATE TABLE IF NOT EXISTS messages (
            id TEXT PRIMARY KEY,
            sender TEXT NOT NULL,
            recipient TEXT NOT NULL,
            content BLOB NOT NULL,
            timestamp TEXT NOT NULL
        )",
        [],
    )?;
    
    Ok(connection)
}

fn get_platform_database_path() -> Result<PathBuf, Error> {
    #[cfg(desktop)]
    {
        // Desktop path with fewer restrictions
        let mut path = dirs::data_dir().ok_or(Error::DirectoryNotFound)?;
        path.push("QuietDrop");
        path.push("messages.db");
        std::fs::create_dir_all(path.parent().unwrap())?;
        Ok(path)
    }
    
    #[cfg(mobile)]
    {
        // Mobile path within app sandbox
        let app_dir = tauri::api::path::app_dir(&Context::default())
            .ok_or(Error::DirectoryNotFound)?;
        let mut path = app_dir;
        path.push("messages.db");
        Ok(path)
    }
}

Platform-Specific Features

Desktop-Specific Features

System Tray

#[cfg(desktop)]
fn setup_system_tray(app: &mut App) -> Result<(), Error> {
    use tauri::{CustomMenuItem, SystemTray, SystemTrayMenu};
    
    let quit = CustomMenuItem::new("quit".to_string(), "Quit");
    let hide = CustomMenuItem::new("hide".to_string(), "Hide Window");
    let tray_menu = SystemTrayMenu::new()
        .add_item(quit)
        .add_item(hide);
    
    let tray = SystemTray::new().with_menu(tray_menu);
    app.system_tray(tray);
    
    Ok(())
}

Multiple Windows

#[cfg(desktop)]
#[tauri::command]
fn open_settings_window(app: tauri::AppHandle) -> Result<(), String> {
    tauri::WindowBuilder::new(
        &app,
        "settings",
        tauri::WindowUrl::App("settings.html".into())
    )
    .title("QuietDrop Settings")
    .inner_size(600.0, 400.0)
    .build()
    .map_err(|e| e.to_string())?;
    
    Ok(())
}

Mobile-Specific Features

Push Notifications

#[cfg(mobile)]
#[tauri::command]
async fn register_for_push_notifications(app: tauri::AppHandle) -> Result<(), String> {
    #[cfg(target_os = "android")]
    {
        // Android FCM registration
        let push_manager = app.push_notification_manager();
        let token = push_manager.register().await?;
        
        // Send token to server for message delivery
        send_token_to_server(&token).await?;
    }
    
    #[cfg(target_os = "ios")]
    {
        // iOS APNS registration
        let push_manager = app.push_notification_manager();
        let token = push_manager.register().await?;
        
        // Send token to server for message delivery
        send_token_to_server(&token).await?;
    }
    
    Ok(())
}

Biometric Authentication

#[cfg(mobile)]
#[tauri::command]
async fn authenticate_with_biometrics(app: tauri::AppHandle) -> Result<bool, String> {
    #[cfg(target_os = "android")]
    {
        // Android biometric authentication
        let biometric = app.biometric();
        let result = biometric
            .authenticate("Verify your identity to access messages")
            .await?;
        
        Ok(result.authenticated)
    }
    
    #[cfg(target_os = "ios")]
    {
        // iOS biometric authentication (Touch ID/Face ID)
        let biometric = app.biometric();
        let result = biometric
            .authenticate("Verify your identity to access messages")
            .await?;
        
        Ok(result.authenticated)
    }
    
    #[cfg(not(any(target_os = "android", target_os = "ios")))]
    {
        Err("Biometric authentication not supported on this platform".to_string())
    }
}

Testing Cross-Platform Code

Unit Testing

Use conditional compilation in tests:

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_core_encryption() {
        // Test encryption/decryption logic that works on all platforms
    }
    
    #[test]
    #[cfg(desktop)]
    fn test_desktop_specific_feature() {
        // Test desktop-only functionality
    }
    
    #[test]
    #[cfg(mobile)]
    fn test_mobile_specific_feature() {
        // Test mobile-only functionality
    }
}

Platform-Specific Testing

Set up automated testing for each target platform:

# .github/workflows/cross-platform-tests.yml
name: Cross-Platform Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test-desktop:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    
    steps:
      - uses: actions/checkout@v2
      - name: Setup Rust
        uses: actions-rs/toolchain@v1
        with:
          profile: minimal
          toolchain: stable
      - name: Run tests
        run: cargo test --workspace
  
  test-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      # Additional setup for Android testing
      # ...
  
  test-ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v2
      # Additional setup for iOS testing
      # ...

Performance Optimization

Mobile-Specific Optimizations

// Optimize for mobile devices
#[cfg(mobile)]
fn configure_performance_settings(app: &mut App) {
    // Reduce memory usage
    app.config_mut().memory_cache_size = 10 * 1024 * 1024; // 10MB
    
    // Implement lazy loading for message history
    app.config_mut().message_page_size = 20;
    
    // Reduce image quality for thumbnails
    app.config_mut().image_thumbnail_quality = 60;
}

Desktop-Specific Optimizations

// Optimize for desktop capabilities
#[cfg(desktop)]
fn configure_performance_settings(app: &mut App) {
    // Allow more memory usage
    app.config_mut().memory_cache_size = 100 * 1024 * 1024; // 100MB
    
    // Pre-fetch more message history
    app.config_mut().message_page_size = 100;
    
    // Higher quality thumbnails
    app.config_mut().image_thumbnail_quality = 80;
}

Deployment

Building for All Platforms

# Build for Windows, macOS, and Linux
cd quietdrop-tauri
cargo tauri build

# Build for Android
cd quietdrop-tauri
cargo tauri android build

# Build for iOS
cd quietdrop-tauri
cargo tauri ios build

Platform-Specific Distribution

Desktop Distribution

  • Windows: MSI installer and portable EXE
  • macOS: DMG installer and App Store
  • Linux: AppImage, DEB, and RPM packages

Mobile Distribution

  • Android: APK file and Google Play Store
  • iOS: App Store distribution

For detailed deployment instructions, see:

# Generate Windows MSI installer
cargo tauri build --target x86_64-pc-windows-msvc

# Generate macOS DMG
cargo tauri build --target x86_64-apple-darwin

# Generate Linux AppImage
cargo tauri build --target x86_64-unknown-linux-gnu

# Android APK
cargo tauri android build --release

# iOS IPA
cargo tauri ios build --release

Common Challenges and Solutions

Challenge: Different Navigation Patterns

Problem: Desktop uses multi-window navigation, while mobile uses tab-based navigation.

Solution: Implement platform-specific navigation controllers:

#[function_component(Navigation)]
fn navigation() -> Html {
    let platform = use_platform_detection();
    
    match platform {
        Platform::Desktop => html! {
            <DesktopNavigation>
                <MainWindow />
                <SettingsButton on_click={open_settings_window} />
                <ContactsButton on_click={open_contacts_window} />
            </DesktopNavigation>
        },
        Platform::Mobile => html! {
            <MobileNavigation>
                <TabBar>
                    <Tab id="messages" icon="message" title="Messages" />
                    <Tab id="contacts" icon="people" title="Contacts" />
                    <Tab id="settings" icon="settings" title="Settings" />
                </TabBar>
                <TabContent />
            </MobileNavigation>
        }
    }
}

Challenge: Different Input Methods

Problem: Desktop uses keyboard/mouse, while mobile uses touch input.

Solution: Adjust UI element sizes and interaction patterns:

/* Touch-friendly buttons on mobile */
.action-button {
    padding: 8px 16px;
}

@media (max-width: 767px) {
    .action-button {
        padding: 12px 20px; /* Larger touch target */
        margin-bottom: 12px; /* More space between buttons */
    }
}

Challenge: Storage Limitations

Problem: Mobile platforms have stricter storage limitations and sandbox restrictions.

Solution: Implement adaptive storage strategies:

fn get_message_storage_settings() -> MessageStorageSettings {
    #[cfg(desktop)]
    {
        MessageStorageSettings {
            max_storage_size: 1024 * 1024 * 1024, // 1GB
            keep_messages_days: 365, // Keep for a year
            auto_download_attachments: true,
        }
    }
    
    #[cfg(mobile)]
    {
        MessageStorageSettings {
            max_storage_size: 100 * 1024 * 1024, // 100MB
            keep_messages_days: 30, // Keep for a month
            auto_download_attachments: false, // Manual download to save data
        }
    }
}

Best Practices

  1. Design for the Most Constrained Platform First: Start with mobile constraints, then enhance for desktop
  2. Use Responsive Design Patterns: Create flexible layouts that adapt to different screen sizes
  3. Abstract Platform Differences: Use traits/interfaces to hide implementation details
  4. Minimize Platform-Specific Code: Keep as much logic as possible in the shared core
  5. Test on All Target Platforms: Regularly test on all supported platforms
  6. Consider Platform UX Expectations: Follow platform-specific UX guidelines
  7. Optimize Asset Sizes: Provide platform-appropriate assets (icons, images, etc.)
  8. Handle Offline States: Design for intermittent connectivity, especially on mobile

Related Resources

Clone this wiki locally