initial hunting-ground

This commit is contained in:
rizary 2025-07-08 14:57:51 +07:00
parent e1326e529c
commit 8c49495865
Signed by untrusted user who does not match committer: rizary
GPG key ID: 2CE8D69D02F1CEB5
62 changed files with 5624 additions and 3109 deletions

453
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,453 @@
# Contributing to Ziya-Slint
Thank you for your interest in contributing to Ziya-Slint! This guide outlines our development approach, coding standards, and best practices for the desktop trading application.
## Project Overview
Ziya-Slint is a desktop trading application built with Rust and Slint UI framework, designed for cryptocurrency trading with real-time market data and portfolio management.
## Development Philosophy
We follow these core principles:
1. **KISS Principle** - Keep code simple and straightforward
2. **Explicit Error Handling** - Never use dummy types or unfinished code
3. **Real-world Ready** - All code should be production-ready
4. **Async-first** - Prefer tokio over std for async operations
5. **Type Safety** - Leverage Rust's type system for correctness
6. **Desktop UX** - Native desktop app behavior and performance
## Getting Started
### Prerequisites
- Rust 2024 Edition (see `rust-toolchain.toml`)
- Just command runner (for development tasks)
- Redis (for data caching)
- Git and Git-cliff (for development workflow)
### Initial Setup
1. Clone the repository
2. Copy configuration files:
```bash
cp Config.example.toml Config.toml
```
3. Install development dependencies:
```bash
just install-deps
```
4. Verify setup:
```bash
just test
just clippy
```
### Development Workflow
We use `just` for common development tasks:
```bash
# List all available commands
just
# Development with hot reloading
just dev
# Building
just build # Build with dev features
just build-prod # Build with prod features
# Quality assurance
just test # Run tests
just clippy # Run linter
just fmt # Format code
just check # Check code for errors
just clean # Clean build artifacts
```
## Coding Standards
### Error Handling
We use strict error handling patterns. **Never** use:
- Dummy types or results
- Placeholder implementations
- TODO comments in production code
**Always** use our error handling pattern:
```rust
use crate::err_with_loc;
use crate::error::Result;
// Import the Result type
use crate::error::Result;
// Use map_err before ? operator
let result = some_operation()
.map_err(|e| {
error!("operation_failed: {}", e);
err_with_loc!(AppError::OperationFailed(format!("operation_failed: {}", e)))
})?;
```
### Async Programming
- **Use tokio** instead of std for async operations
- Follow the actors pattern from [this guide](https://ryhl.io/blog/actors-with-tokio/)
- **Never** use `tokio::spawn` with UI components directly
- **Always** use `slint::invoke_from_event_loop` for UI updates from background tasks
### Code Formatting
We use `rustfmt` with specific configuration (see `rustfmt.toml`):
- Max width: 120 characters
- Use field init shorthand
- Reorder imports and impl items
- Group imports by StdExternalCrate
- Prefer same line braces
Run formatting:
```bash
just fmt
```
## Architecture: Feature-Sliced Design (FSD)
We follow **Feature-Sliced Design (FSD)** methodology for frontend architecture:
### FSD Layer Structure
```
ui/
├── app/ # Application layer - root, global setup
├── pages/ # Page layer - complete screens
├── widgets/ # Widget layer - composite UI blocks
├── entities/ # Entity layer - business entities
├── features/ # Feature layer - user interactions
└── shared/ # Shared layer - reusable resources
├── ui/ # UI components
├── types/ # Type definitions
└── design-system/ # Theme, tokens, etc.
```
### FSD Rules & Guidelines
1. **Import Rule**: Higher layers can only import from lower layers
- ✅ `app/``pages/``widgets/``entities/``features/``shared/`
- ❌ Never import from higher layers
2. **Layer Responsibilities**:
- **App**: Global providers, routing, application setup
- **Pages**: Complete screens, page-level logic
- **Widgets**: Composite UI blocks (navigation, forms, cards)
- **Entities**: Business entities (token, user, market data)
- **Features**: User interactions (login, trading, portfolio management)
- **Shared**: Reusable resources (UI kit, utilities, constants)
3. **Slicing by Features**: Each feature should be self-contained
```
features/
├── authentication/
│ ├── ui/ # Login components
│ ├── model/ # Auth state
│ └── api/ # Auth API calls
└── trading/
├── ui/ # Trading components
├── model/ # Trading state
└── api/ # Trading API calls
```
4. **Component Composition**: Build from bottom-up
```slint
// shared/ui/button/
export component Button { /* base button */ }
// widgets/navigation/
import { Button } from "../../shared/ui/button/";
export component NavigationWidget {
Button { /* composed navigation */ }
}
// pages/dashboard/
import { NavigationWidget } from "../../widgets/navigation/";
export component Dashboard {
NavigationWidget { /* page composition */ }
}
```
5. **State Management**: Follow unidirectional data flow
- Global state in `app/` layer
- Feature state in respective `features/` slices
- Pass data down, emit events up
6. **Design System**: Centralized in `shared/design-system/`
- Theme tokens and variables
- Consistent spacing, colors, typography
- Reusable UI components
## Slint UI Development
### Threading & State Management
**Critical Pattern for Slint + Tokio:**
```rust
// Background work in Rust
tokio::spawn(async move {
let result = do_background_work().await;
// Update UI from main thread following FSD data flow
let _ = slint::invoke_from_event_loop(move || {
if let Some(ui) = ui_weak.upgrade() {
// Update flows from app → pages → widgets → shared
ui.update_global_state(result);
}
});
});
```
### Component Development Guidelines
1. **Start with Shared Layer**: Build reusable components first
```slint
// shared/ui/loading/loading.slint
export component LoadingSpinner {
// Base loading component
}
```
2. **Compose in Widgets**: Create business-specific blocks
```slint
// widgets/token-card/index.slint
import { LoadingSpinner } from "../../shared/ui/loading/";
export component TokenCard {
LoadingSpinner { /* token-specific loading */ }
}
```
3. **Integrate in Pages**: Assemble complete screens
```slint
// pages/dashboard/index.slint
import { TokenCard } from "../../widgets/token-card/";
export component Dashboard {
TokenCard { /* dashboard context */ }
}
```
4. **Wire in App**: Handle global state and routing
```slint
// app/index.slint
import { Dashboard } from "../pages/dashboard/";
export component App {
if current-page == "dashboard": Dashboard { }
}
```
### File Naming Conventions
- Use `index.slint` for main component exports
- Use kebab-case for file names: `token-card.slint`
- Use PascalCase for component names: `TokenCard`
### Property & Callback Flow
- Properties flow down: `app → pages → widgets → shared`
- Callbacks flow up: `shared → widgets → pages → app`
- Keep callback interfaces simple and focused
## FSD Best Practices
### ✅ Do:
- Keep components focused on single responsibility
- Use semantic component names that reflect business domain
- Extract common patterns to `shared/` layer
- Implement loading and error states consistently
- Use TypeScript-like typing through Slint properties
- Follow consistent import paths relative to FSD structure
### ❌ Don't:
- Import from higher layers (breaks FSD hierarchy)
- Put business logic directly in UI components
- Create circular dependencies between features
- Hardcode values that should be in design system
- Mix concerns (UI logic with business logic)
- Create deep nesting beyond FSD layers
### Common Anti-patterns to Avoid:
```slint
// ❌ BAD: Widget importing from pages
// widgets/header/index.slint
import { Dashboard } from "../../pages/dashboard/"; // Wrong!
// ❌ BAD: Shared component with business logic
// shared/ui/button/index.slint
export component Button {
// Don't put trading logic here!
clicked => { execute_trade(); }
}
// ✅ GOOD: Proper callback delegation
// shared/ui/button/index.slint
export component Button {
callback clicked();
}
// ✅ GOOD: Business logic in appropriate layer
// features/trading/ui/trade-button.slint
import { Button } from "../../../shared/ui/button/";
export component TradeButton {
Button {
clicked => { handle_trade_click(); }
}
}
```
## Testing
### Unit Tests
```bash
just test
```
### Integration Tests
- Test UI components with mock data
- Test service layer integration
- Use feature flags for test environments
### Feature Testing
```bash
# Run with specific features
cargo test --features dev
cargo test --features prod
```
## UI/UX Principles
We follow comprehensive usability guidelines:
1. **10 Usability Heuristics** - Nielsen's principles
2. **Gestalt Principles** - Visual hierarchy and grouping
3. **Accessibility** - Support for different user needs
4. **Simplicity** - Minimize cognitive load
5. **Desktop Patterns** - Native desktop app behavior
### Key Requirements:
- Responsive design within window constraints (1080x800 - 1920x1080)
- Proper error messaging in plain language
- Loading states with progress indication
- Keyboard navigation support
- Native desktop interactions
## Submission Guidelines
### Branch Naming
Use descriptive names:
- `feat/shared/button-component` - New shared component
- `feat/pages/dashboard-redesign` - Page-level changes
- `feat/features/trading-flow` - Feature implementation
- `fix/widgets/token-card-loading` - Bug fixes
- `refactor/shared/design-system` - Code improvements
### Commit Messages
Be descriptive:
```
feat(shared): add loading spinner component
- Implement reusable loading spinner in shared/ui
- Add animation and theme support
- Export from shared layer for use in widgets
fix(pages): resolve dashboard layout overflow
- Fix token card grid overflow in dashboard
- Improve responsive behavior for small windows
- Add proper scrolling for token list
```
### Code Review Checklist
**General:**
- [ ] Follows error handling patterns
- [ ] Uses tokio for async operations
- [ ] Includes proper logging
- [ ] Formatted with rustfmt
- [ ] Passes all tests
- [ ] Updates documentation if needed
**FSD-Specific:**
- [ ] Components placed in correct FSD layer
- [ ] Import dependencies only from lower layers
- [ ] Properties flow down, callbacks flow up
- [ ] No direct business logic in UI components
- [ ] Reusable components in `shared/` layer
- [ ] Feature-specific logic in `features/` layer
**UI/UX:**
- [ ] Follows design system patterns
- [ ] Implements proper loading states
- [ ] Handles error states gracefully
- [ ] Supports keyboard navigation
- [ ] Responsive within window constraints
## Development Environment
### Recommended Tools
- **IDE**: VS Code with rust-analyzer and Slint extension
- **Debugging**: Use tracing for structured logging
- **File Watching**: `watchexec` for auto-reload during development
- **Hot Reloading**: `just dev` for development workflow
### Common Issues
#### Slint Compilation Errors
If you encounter field offset errors with watchexec:
```bash
cargo clean
# Then rebuild
```
#### Threading Issues
Remember: Slint UI components are not `Send`. Always use `slint::invoke_from_event_loop` for UI updates from background tasks.
#### Hot Reloading
For the best development experience:
```bash
just dev
```
This watches both `src/` and `ui/` directories and automatically rebuilds.
## Documentation
- **Component APIs**: Document with clear examples
- **Architecture**: Update this guide when patterns change
- **Configuration**: Document all config options
- **UI Components**: Maintain design system documentation
## Getting Help
1. Check existing documentation
2. Search closed issues on GitHub
3. Ask in project discussions
4. Check Slint documentation for UI issues
## Code of Conduct
- Be respectful and inclusive
- Focus on constructive feedback
- Help others learn and grow
- Maintain professional communication
---
By following these guidelines, you help maintain code quality and ensure smooth collaboration on the Ziya-Slint desktop application. Thank you for contributing! 🚀

596
Cargo.lock generated
View file

@ -434,6 +434,19 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-compat"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bab94bde396a3f7b4962e396fdad640e241ed797d4d8d77fc8c237d14c58fc0"
dependencies = [
"futures-core",
"futures-io",
"once_cell",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "async-executor" name = "async-executor"
version = "1.13.2" version = "1.13.2"
@ -678,6 +691,27 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bb8"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d8b8e1a22743d9241575c6ba822cf9c8fef34771c86ab7e477a4fbfd254e5"
dependencies = [
"futures-util",
"parking_lot",
"tokio",
]
[[package]]
name = "bb8-redis"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5143936af5e1eea1a881e3e3d21b6777da6315e5e307bc3d0c2301c44fa37da9"
dependencies = [
"bb8",
"redis",
]
[[package]] [[package]]
name = "bincode" name = "bincode"
version = "1.3.3" version = "1.3.3"
@ -1154,7 +1188,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core",
"memchr", "memchr",
"pin-project-lite",
"tokio",
"tokio-util",
] ]
[[package]] [[package]]
@ -1556,6 +1594,15 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "deranged"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [
"powerfmt",
]
[[package]] [[package]]
name = "derivation-path" name = "derivation-path"
version = "0.2.0" version = "0.2.0"
@ -1768,15 +1815,6 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "endi" name = "endi"
version = "1.1.0" version = "1.1.0"
@ -2023,6 +2061,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5" checksum = "2551bf44bc5f776c15044b9b94153a00198be06743e262afaaa61f11ac7523a5"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.2" version = "1.1.2"
@ -2429,25 +2473,6 @@ dependencies = [
"gl_generator", "gl_generator",
] ]
[[package]]
name = "h2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "half" name = "half"
version = "2.6.0" version = "2.6.0"
@ -2548,109 +2573,12 @@ dependencies = [
"hmac 0.8.1", "hmac 0.8.1",
] ]
[[package]]
name = "http"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.2.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
[[package]]
name = "hyper"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"libc",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "i-slint-backend-linuxkms" name = "i-slint-backend-linuxkms"
version = "1.12.1" version = "1.12.1"
@ -3152,12 +3080,6 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]] [[package]]
name = "is_terminal_polyfill" name = "is_terminal_polyfill"
version = "1.70.1" version = "1.70.1"
@ -3547,6 +3469,15 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]] [[package]]
name = "maybe-rayon" name = "maybe-rayon"
version = "0.1.1" version = "0.1.1"
@ -3590,12 +3521,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@ -3642,23 +3567,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "ndk" name = "ndk"
version = "0.9.0" version = "0.9.0"
@ -3724,6 +3632,16 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.6" version = "0.4.6"
@ -3734,6 +3652,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]] [[package]]
name = "num-derive" name = "num-derive"
version = "0.4.2" version = "0.4.2"
@ -4187,12 +4111,6 @@ dependencies = [
"syn 2.0.104", "syn 2.0.104",
] ]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.109" version = "0.9.109"
@ -4224,6 +4142,12 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "owned_ttf_parser" name = "owned_ttf_parser"
version = "0.25.0" version = "0.25.0"
@ -4292,6 +4216,19 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "petgraph"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca"
dependencies = [
"fixedbitset",
"hashbrown 0.15.4",
"indexmap",
"serde",
"serde_derive",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.3" version = "0.11.3"
@ -4455,6 +4392,12 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.21"
@ -4748,6 +4691,28 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "redis"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f6fd3fd5bb3a9a48819ae5cbad501968f208c1abd139649e7e2cf5d7f4b40b"
dependencies = [
"bytes",
"cfg-if",
"combine",
"futures-util",
"itoa",
"num-bigint",
"percent-encoding",
"pin-project-lite",
"ryu",
"sha1_smol",
"socket2",
"tokio",
"tokio-util",
"url",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.4.1" version = "0.4.1"
@ -4774,8 +4739,17 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata", "regex-automata 0.4.9",
"regex-syntax", "regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
] ]
[[package]] [[package]]
@ -4786,57 +4760,21 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax", "regex-syntax 0.8.5",
] ]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.5" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "reqwest"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
dependencies = [
"base64 0.22.1",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]] [[package]]
name = "resvg" name = "resvg"
version = "0.45.1" version = "0.45.1"
@ -4934,24 +4872,6 @@ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"zeroize",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.21"
@ -4991,15 +4911,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@ -5031,29 +4942,6 @@ dependencies = [
"tiny-skia", "tiny-skia",
] ]
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.1",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.26" version = "1.0.26"
@ -5130,18 +5018,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_with" name = "serde_with"
version = "3.13.0" version = "3.13.0"
@ -5165,6 +5041,12 @@ dependencies = [
"syn 2.0.104", "syn 2.0.104",
] ]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.9.9" version = "0.9.9"
@ -5199,6 +5081,15 @@ dependencies = [
"keccak", "keccak",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -6969,12 +6860,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "synstructure" name = "synstructure"
version = "0.13.2" version = "0.13.2"
@ -6995,27 +6880,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "system-deps" name = "system-deps"
version = "6.2.2" version = "6.2.2"
@ -7114,6 +6978,15 @@ dependencies = [
"syn 2.0.104", "syn 2.0.104",
] ]
[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "tiff" name = "tiff"
version = "0.9.1" version = "0.9.1"
@ -7125,6 +6998,37 @@ dependencies = [
"weezl", "weezl",
] ]
[[package]]
name = "time"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",
]
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.11.4" version = "0.11.4"
@ -7219,12 +7123,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tokio-native-tls" name = "tokio-stream"
version = "0.3.1" version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [ dependencies = [
"native-tls", "futures-core",
"pin-project-lite",
"tokio", "tokio",
] ]
@ -7291,12 +7196,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.41" version = "0.1.41"
@ -7309,6 +7208,18 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-appender"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror 1.0.69",
"time",
"tracing-subscriber",
]
[[package]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.30" version = "0.1.30"
@ -7327,13 +7238,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
] ]
[[package]] [[package]]
name = "try-lock" name = "tracing-log"
version = "0.2.5" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
@ -7526,6 +7461,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -7577,15 +7518,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.9.0+wasi-snapshot-preview1" version = "0.9.0+wasi-snapshot-preview1"
@ -8303,16 +8235,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winreg"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "wio" name = "wio"
version = "0.2.2" version = "0.2.2"
@ -8653,23 +8575,37 @@ dependencies = [
] ]
[[package]] [[package]]
name = "ziya-slint" name = "ziya"
version = "0.2.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-compat",
"bb8",
"bb8-redis",
"bs58",
"chrono", "chrono",
"chrono-tz", "chrono-tz",
"env_logger 0.11.8", "env_logger 0.11.8",
"futures-util",
"i-slint-backend-winit", "i-slint-backend-winit",
"image",
"log", "log",
"reqwest", "petgraph",
"redis",
"serde", "serde",
"serde_json", "serde_json",
"slint", "slint",
"slint-build", "slint-build",
"solana-pubkey",
"solana-sdk", "solana-sdk",
"thiserror 2.0.12",
"tokio", "tokio",
"tokio-stream",
"tokio-util",
"toml 0.8.23",
"tracing",
"tracing-appender",
"tracing-subscriber",
"winit",
] ]
[[package]] [[package]]

View file

@ -1,25 +1,182 @@
[package] [package]
name = "ziya-slint" name = "ziya"
version = "0.2.0" version = "0.1.0"
edition = "2021" edition = "2021"
description = "One stop shop for your trading habit - Slint version" description = "One stop shop for your trading habit - Slint version"
authors = ["rizary"] authors = ["rizary"]
license = "MIT" license = "MIT"
[lib]
name = "ziya"
[dependencies] [dependencies]
slint = "1.12.0" slint = "1.8.0"
i-slint-backend-winit = "1.12.0" i-slint-backend-winit = "1.12.0"
winit = "0.30"
tokio = { version = "1.0", features = ["full"] } tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] } tokio-util = "0.7.13"
serde_json = "1.0" async-compat = "0.2.4"
reqwest = { version = "0.12", features = ["json"] }
anyhow = "1.0" anyhow = "1.0"
log = "0.4" log = "0.4"
env_logger = "0.11" env_logger = "0.11"
solana-sdk = "2.3.0" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
chrono = { version = "0.4.39", features = ["serde"] } chrono = { version = "0.4.39", features = ["serde"] }
chrono-tz = "0.10.3" chrono-tz = "0.10.3"
image = { version = "0.25.6", default-features = false, features = [ "ico", "jpeg", "gif", "webp", "tiff" ] } redis = { version = "0.32.2", features = ["aio", "tokio-comp"] }
bb8 = "0.9"
bb8-redis = "0.24.0"
solana-sdk = "2.3.0"
solana-pubkey = "2.3.0"
thiserror = "2.0.12"
futures-util = "0.3"
tokio-stream = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
bs58 = "0.5"
petgraph = { version = "0.8.1", features = ["serde-1"] }
[build-dependencies] [build-dependencies]
slint-build = "1.12.0" slint-build = "1.12.0"
[features]
default = ["prod"]
dev = []
prod = []
deep-trace = []
# Config for 'git cliff'
# Run with `GITHUB_TOKEN=$(gh auth token) git cliff --bump -up CHANGELOG.md`
# https://git-cliff.org/docs/configuration
[workspace.metadata.git-cliff.bump]
features_always_bump_minor = false
breaking_always_bump_major = false
[workspace.metadata.git-cliff.remote.github]
owner = "rizilab"
repo = "ziya"
[workspace.metadata.git-cliff.changelog]
# changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{% if version -%}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else -%}
## [Unreleased]
{% endif -%}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{%- for commit in commits %}
- {% if commit.breaking %}**BREAKING** {% endif -%}
{% if commit.scope %}*({{ commit.scope }})* {% endif -%}
{{ commit.message | trim | upper_first }}\
{% if commit.github.username and commit.github.username != "rizary" %} by \
[@{{ commit.github.username }}](https://github.com/{{ commit.github.username }})\
{%- endif -%}
{% if commit.github.pr_number %} in \
[#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }})\
{%- endif -%}.
{%- set fixes = commit.footers | filter(attribute="token", value="Fixes") -%}
{%- set closes = commit.footers | filter(attribute="token", value="Closes") -%}
{% for footer in fixes | concat(with=closes) -%}
{%- set issue_number = footer.value | trim_start_matches(pat="#") %} \
([{{ footer.value }}]({{ self::remote_url() }}/issues/{{ issue_number }}))\
{%- endfor -%}
{% if commit.body %}
{%- for section in commit.body | trim | split(pat="\n\n") %}
{% raw %} {% endraw %}- {{ section | replace(from="\n", to=" ") }}
{%- endfor -%}
{%- endif -%}
{% endfor %}
{% endfor %}
{%- if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
### New Contributors
{%- endif -%}
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
- @{{ contributor.username }} made their first contribution
{%- if contributor.pr_number %} in \
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor %}\n
"""
# template for the changelog footer
footer = """
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{% for release in releases -%}
{% if release.version -%}
{% if release.previous.version -%}
[{{ release.version | trim_start_matches(pat="v") }}]: \
{{ self::remote_url() }}/compare/{{ release.previous.version }}...{{ release.version }}
{% else -%}
{#- compare against the initial commit for the first version -#}
[{{ release.version | trim_start_matches(pat="v") }}]: \
{{ self::remote_url() }}/compare/{{ release.commit_id }}...{{ release.version }}
{% endif -%}
{% else -%}
[Unreleased]: {{ self::remote_url() }}/compare/{{ release.previous.version }}...HEAD
{% endif -%}
{%- endfor -%}
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = []
[workspace.metadata.git-cliff.git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = []
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->Features" },
{ body = ".*security", group = "<!-- 1 -->Security" },
{ message = "^fix", group = "<!-- 2 -->Bug Fixes" },
{ message = "^perf", group = "<!-- 3 -->Performance" },
{ message = "^doc", group = "<!-- 4 -->Documentation" },
{ message = "^test", group = "<!-- 5 -->Tests" },
{ message = "^refactor", group = "<!-- 6 -->Refactor" },
{ message = "^style", group = "<!-- 7 -->Style" },
{ message = "^chore", group = "<!-- 8 -->Miscellaneous" },
{ message = "^ci", default_scope = "ci", group = "<!-- 8 -->Miscellaneous" },
{ message = "^release", skip = true },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers
filter_commits = false
# regex for matching git tags
tag_pattern = "v[0-9].*"
# regex for skipping tags
skip_tags = ""
# regex for ignoring tags
ignore_tags = ""
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

193
README.md
View file

@ -7,149 +7,118 @@
## Overview ## Overview
This is a modern rewrite of the Ziya trading platform using [Slint](https://slint.dev/) for the UI and Rust for the backend logic. Slint provides a declarative UI language that makes it easy to create beautiful, native desktop applications. Ziya is a modern, high-performance trading platform built with [Slint](https://slint.dev/) and Rust. Designed for cryptocurrency traders who demand speed, reliability, and a beautiful user interface.
## Features ## Features
- **Modern Native UI**: Built with Slint's declarative UI language - **⚡ Lightning Fast**: Native Rust performance with instant startup
- **Fast Performance**: Native Rust performance with efficient rendering - **🎨 Beautiful UI**: Modern interface built with Slint's declarative language
- **Clean Architecture**: Separated UI and business logic - **📊 Real-time Data**: Live token prices and market analysis
- **Async Operations**: Non-blocking token fetching and user management - **🔒 Secure**: Type-safe Rust backend with robust error handling
- **Modular Design**: Well-organized codebase with clear separation of concerns - **📱 Native Feel**: True desktop application, not a web wrapper
- **🚀 Lightweight**: ~5MB standalone binary vs ~100MB Electron apps
### Application States ## Installation
- **Loading**: Initial app startup and initialization ### Download Pre-built Binaries
- **Login**: User authentication interface
- **Dashboard**: Main trading dashboard with token search
- **Hunting Ground**: Advanced trading tools (placeholder)
- **Profile**: User profile and wallet information
## Project Structure Visit our [Releases](https://github.com/rizilab/ziya/releases) page to download the latest version for your platform:
``` - **Windows**: `ziya-x86_64-pc-windows-msvc.zip`
ziya-slint/ - **macOS**: `ziya-x86_64-apple-darwin.tar.xz` (Intel) or `ziya-aarch64-apple-darwin.tar.xz` (Apple Silicon)
├── src/ - **Linux**: `ziya-x86_64-unknown-linux-gnu.tar.xz`
│ ├── main.rs # Application entry point
│ ├── lib.rs # Module organization
│ ├── app_state.rs # Application state management
│ └── services/ # Business logic services
│ ├── mod.rs
│ ├── token_service.rs # Token data and search
│ └── user_service.rs # User authentication and profiles
├── ui/
│ └── app-window.slint # Main UI definition
├── Cargo.toml # Project dependencies
└── build.rs # Build configuration
```
## Getting Started ### Build from Source
### Prerequisites If you have Rust installed:
- Rust 1.70+ (recommended via [rustup](https://rustup.rs/))
- Git
### Installation
1. Clone the repository:
```bash
git clone <repository-url>
cd ziya-slint
```
2. Build and run:
```bash
cargo run
```
## Development
### Running in Development Mode
```bash ```bash
# Run with debug logging git clone https://github.com/rizilab/ziya.git
RUST_LOG=debug cargo run cd ziya-slint
cargo install --path .
``` ```
### Building for Release For detailed build instructions, see [CONTRIBUTING.md](CONTRIBUTING.md).
```bash ## Quick Start
cargo build --release
```
## Architecture 1. **Launch Ziya**: Run the application
2. **Login**: Enter your credentials or create a new account
3. **Dashboard**: Browse and search tokens
4. **Trade**: Access advanced tools in the Hunting Ground
### UI Layer (Slint) ## Application Features
The UI is defined in `ui/app-window.slint` using Slint's declarative language. Key features: ### Dashboard
- **Token Search**: Find tokens by name, symbol, or address
- **Market Overview**: Real-time price data and trends
- **Portfolio**: Track your holdings and performance
- **State-based rendering**: Different UI states for loading, login, dashboard, etc. ### Hunting Ground
- **Reactive properties**: Automatic UI updates when data changes - **Advanced Analysis**: Deep market insights and analytics
- **Component composition**: Reusable UI elements - **Trading Tools**: Professional-grade trading interface
- **Modern styling**: Clean, professional design - **Risk Management**: Built-in tools to manage trading risks
### Business Logic (Rust) ### Profile Management
- **Wallet Integration**: Connect and manage multiple wallets
- **Settings**: Customize your trading experience
- **Security**: Secure credential management
- **Services Pattern**: Separate services for different concerns ## Why Slint + Rust?
- **Async/Await**: Non-blocking operations for better UX
- **Error Handling**: Proper error propagation with `anyhow`
- **Type Safety**: Strong typing throughout the application
### Key Components | Aspect | Traditional (Electron) | Ziya (Slint + Rust) |
|--------|----------------------|-------------------|
| **Memory Usage** | ~50MB+ | ~10MB |
| **Startup Time** | 2-5 seconds | Instant |
| **Bundle Size** | ~100MB | ~5MB |
| **Performance** | JavaScript V8 | Native machine code |
| **Security** | Web vulnerabilities | Memory-safe Rust |
| **Updates** | Large downloads | Efficient delta updates |
1. **TokenService**: Handles token data fetching and search ## Support
2. **UserService**: Manages user authentication and profiles
3. **AppState**: Central application state management
4. **Main**: Coordinates UI and business logic
## Comparison with Vue/Electron Version - **Documentation**: [Full documentation](https://docs.ziya.trading)
- **Issues**: [GitHub Issues](https://github.com/rizilab/ziya/issues)
- **Discussions**: [GitHub Discussions](https://github.com/rizilab/ziya/discussions)
- **Community**: [Discord Server](https://discord.gg/bismillahdao)
| Aspect | Vue/Electron | Slint/Rust | ## Roadmap
|--------|--------------|------------|
| **Performance** | ~50MB memory, slower startup | ~10MB memory, instant startup | ### Current Version (v0.2.0)
| **Bundle Size** | ~100MB with Node.js | ~5MB standalone binary | - ✅ Core trading dashboard
| **Development** | Hot reload, web tools | Fast compilation, native debugging | - ✅ Token search and filtering
| **Deployment** | Requires Node.js runtime | Single self-contained binary | - ✅ User authentication
| **UI Development** | HTML/CSS/JS knowledge required | Slint DSL (QML-like, simpler) | - ✅ Basic portfolio tracking
| **Type Safety** | TypeScript (optional) | Full Rust type safety |
### Next Release (v0.3.0)
- 🚧 Real-time WebSocket data feeds
- 🚧 Advanced charting with TradingView
- 🚧 Multi-exchange support
- 🚧 Trading automation tools
### Future Releases
- 📋 Mobile companion app
- 📋 DeFi protocol integration
- 📋 Social trading features
- 📋 Advanced risk analytics
## Contributing ## Contributing
This is part of the bismillahDAO ecosystem. When contributing: We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for:
1. Follow Rust conventions and best practices - Development setup
2. Keep UI logic in Slint files, business logic in Rust - Code style guidelines
3. Write tests for business logic - Testing procedures
4. Update documentation for new features - Release process
## Migration Status
### ✅ Completed Features
- [x] Basic application structure
- [x] Loading, login, and dashboard states
- [x] Token list display and search
- [x] User profile management
- [x] Navigation between views
- [x] Mock data services
### 🚧 In Progress
- [ ] Real API integration
- [ ] Advanced token analysis
- [ ] Wallet integration
- [ ] Trading features
### 📋 Planned Features
- [ ] Real-time token price updates
- [ ] Advanced charting
- [ ] Portfolio tracking
- [ ] Trading automation
- [ ] Export/import functionality
## License ## License
MIT License - see LICENSE file for details. MIT License - see [LICENSE](LICENSE) file for details.
## About bismillahDAO ## About bismillahDAO
This project is created by bismillahDAO, focused on building ethical and innovative trading tools for the cryptocurrency community. Ziya is proudly developed by [bismillahDAO](https://bismillahdao.org), building ethical and innovative tools for the cryptocurrency community.
---
**⭐ Star this repository if you find Ziya useful!**

View file

@ -1,8 +1,6 @@
fn main() { fn main() {
// Use fluent-light as default, but we'll support dynamic switching // Use fluent-light as default, but we'll support dynamic switching
let config = slint_build::CompilerConfiguration::new() let config = slint_build::CompilerConfiguration::new().with_style("fluent".to_string());
.with_style("fluent-light".to_string());
slint_build::compile_with_config("ui/index.slint", config) slint_build::compile_with_config("ui/index.slint", config).expect("Slint build failed");
.expect("Slint build failed");
} }

59
justfile Normal file
View file

@ -0,0 +1,59 @@
# Default recipe to display available commands
default:
@just --list
# Run the application with dev features
run:
cargo run --features dev
# Run the application with prod features
run-prod:
cargo run --features prod
# Build the application with dev features
build:
cargo build --features dev
# Build the application with prod features
build-prod:
cargo build --features prod
# Run tests
test:
cargo test
# Run clippy linter
clippy:
cargo clippy --all-targets --all-features -- -D warnings
# Format code
fmt:
cargo fmt
# Check formatting
fmt-check:
cargo fmt --check
# Run development server with hot reloading using watchexec
dev:
watchexec --clear --stop-timeout=3s -i "./**/target" -w "src" -w "ui" -e "rs,toml,slint" --project-origin "." -r "just run"
# Generate changelog using git-cliff
changelog:
git-cliff -o CHANGELOG.md
# Generate unreleased changelog
changelog-unreleased:
git-cliff --unreleased --tag unreleased -o CHANGELOG.md
# Clean build artifacts
clean:
cargo clean
# Check for security vulnerabilities
audit:
cargo audit
# Install development dependencies
install-deps:
cargo install watchexec-cli git-cliff cargo-audit

37
src/config.rs Normal file
View file

@ -0,0 +1,37 @@
use crate::err_with_loc;
use crate::error::app::AppError;
use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageRedisConfig {
pub host: String,
pub port: u16,
pub pool_size: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
pub directory: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub storage_redis: StorageRedisConfig,
pub logging: LoggingConfig,
}
pub async fn load_config(path: impl AsRef<Path>) -> Result<Config> {
let config_str = std::fs::read_to_string(path).map_err(|e| {
err_with_loc!(AppError::Config(format!(
"failed_to_read_config_file: {}",
e
)))
})?;
let config: Config = toml::from_str(&config_str)
.map_err(|e| err_with_loc!(AppError::Config(format!("failed_to_parse_config: {}", e))))?;
Ok(config)
}

22
src/error/app.rs Normal file
View file

@ -0,0 +1,22 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Redis error: {0}")]
Redis(String),
#[error("Handler error: {0}")]
Handler(String),
#[error("Slint error: {0}")]
Slint(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("UI error: {0}")]
UiError(String),
}

13
src/error/mod.rs Normal file
View file

@ -0,0 +1,13 @@
pub mod app;
pub use anyhow::anyhow;
pub use anyhow::Context;
pub use anyhow::Error;
pub use anyhow::Result;
#[macro_export]
macro_rules! err_with_loc {
($err:expr) => {
anyhow::anyhow!($err).context(format!("at {}:{}", file!(), line!()))
};
}

19
src/handler/mod.rs Normal file
View file

@ -0,0 +1,19 @@
pub mod token;
pub mod ui;
use crate::model::token::{MaxDepthReachedData, NewTokenCreatedData, TokenCexUpdatedData};
// Messages that can be sent to the hunting ground handler
#[derive(Debug)]
pub enum TokenHandler {
NewToken { data: NewTokenCreatedData },
CexUpdated { data: TokenCexUpdatedData },
MaxDepthReached { data: MaxDepthReachedData },
}
#[derive(Debug)]
pub enum SlintHandler {
ClearNewTokens {},
ClearCexTokens {},
ClearAnalysisTokens {},
}

284
src/handler/token.rs Normal file
View file

@ -0,0 +1,284 @@
use chrono::TimeZone;
use chrono::Utc;
use chrono_tz::Asia::Jakarta;
use std::{collections::VecDeque, sync::Arc};
use tokio::sync::{mpsc, oneshot};
use tracing::{debug, error, info, warn};
use super::SlintHandler;
use super::TokenHandler;
use crate::error::Result;
use crate::model::token::{MaxDepthReachedData, NewTokenCreatedData, TokenCexUpdatedData};
use crate::slint_ui::{CexUpdatedUiData, MainWindow, MaxDepthReachedUiData, NewTokenUiData};
use crate::storage::StorageEngine;
use crate::task::shutdown::ShutdownSignal;
use slint::{Model, SharedString, Weak};
// Internal state for the hunting ground handler
pub struct TokenMetadataHandler {
pub receiver: mpsc::Receiver<TokenHandler>,
pub slint_tx: mpsc::Sender<SlintHandler>,
pub ui_weak: Weak<MainWindow>,
pub shutdown: ShutdownSignal,
}
impl TokenMetadataHandler {
pub fn new(
receiver: mpsc::Receiver<TokenHandler>,
slint_tx: mpsc::Sender<SlintHandler>,
ui_weak: Weak<MainWindow>,
shutdown: ShutdownSignal,
) -> Self {
Self {
receiver,
slint_tx,
ui_weak,
shutdown,
}
}
pub async fn process_new_token_to_slint(&self, token: NewTokenCreatedData) {
info!("processing_new_token_to_slint: mint={}", token.mint.clone());
let utc_timestamp = Utc.timestamp_millis_opt(token.created_at as i64).unwrap();
let jakarta_timestamp = utc_timestamp.with_timezone(&chrono_tz::Asia::Jakarta);
let created_at = jakarta_timestamp.format("%Y-%m-%d %H:%M:%S");
let new_token_ui_data = NewTokenUiData {
mint: SharedString::from(token.mint.to_string()),
bonding_curve: token
.bonding_curve
.map(|bc| SharedString::from(bc.to_string()))
.unwrap_or(SharedString::from("")),
name: SharedString::from(token.name),
symbol: SharedString::from(token.symbol),
uri: SharedString::from(token.uri),
creator: SharedString::from(token.creator.to_string()),
created_at: SharedString::from(created_at.to_string()),
};
// ✅ Update UI using proper Slint threading model
let ui_weak_clone = self.ui_weak.clone();
slint::invoke_from_event_loop(move || {
if let Some(ui) = ui_weak_clone.upgrade() {
// Convert ModelRc to Vec, add new item, and set back
let current_tokens = ui.get_new_tokens();
let mut tokens_vec: Vec<NewTokenUiData> = current_tokens.iter().collect();
tokens_vec.insert(0, new_token_ui_data);
ui.set_new_tokens(tokens_vec.as_slice().into());
info!(
"process_new_token_to_slint::ui_updated::total_tokens={}",
tokens_vec.len()
);
} else {
warn!("process_new_token_to_slint::ui_weak_reference_expired");
}
})
.unwrap_or_else(|e| {
error!(
"process_new_token_to_slint::failed_to_invoke_from_event_loop: {}",
e
);
});
}
pub async fn process_cex_updated_to_slint(&self, token: TokenCexUpdatedData) {
info!("processing_cex_updated_to_slint: mint={}", token.mint);
let utc_created_at = Utc.timestamp_millis_opt(token.created_at as i64).unwrap();
let jakarta_created_at = utc_created_at.with_timezone(&chrono_tz::Asia::Jakarta);
let created_at = jakarta_created_at.format("%Y-%m-%d %H:%M:%S");
let utc_updated_at = Utc.timestamp_millis_opt(token.updated_at as i64).unwrap();
let jakarta_updated_at = utc_updated_at.with_timezone(&chrono_tz::Asia::Jakarta);
let updated_at = jakarta_updated_at.format("%Y-%m-%d %H:%M:%S");
let cex_updated_ui_data = CexUpdatedUiData {
mint: SharedString::from(token.mint),
name: SharedString::from(token.name),
uri: SharedString::from(token.uri),
dev_name: SharedString::from(token.dev_name),
creator: SharedString::from(token.creator),
cex_name: SharedString::from(token.cex_name),
cex_address: SharedString::from(token.cex_address),
bonding_curve: SharedString::from(token.bonding_curve),
created_at: SharedString::from(created_at.to_string()),
updated_at: SharedString::from(updated_at.to_string()),
node_count: token.node_count as i32,
edge_count: token.edge_count as i32,
};
// ✅ Update UI using proper Slint threading model
let ui_weak_clone = self.ui_weak.clone();
slint::invoke_from_event_loop(move || {
if let Some(ui) = ui_weak_clone.upgrade() {
// Convert ModelRc to Vec, add new item, and set back
let current_tokens = ui.get_cex_tokens();
let mut tokens_vec: Vec<CexUpdatedUiData> = current_tokens.iter().collect();
tokens_vec.insert(0, cex_updated_ui_data);
ui.set_cex_tokens(tokens_vec.as_slice().into());
info!(
"process_cex_updated_to_slint::ui_updated::total_tokens={}",
tokens_vec.len()
);
} else {
warn!("process_cex_updated_to_slint::ui_weak_reference_expired");
}
})
.unwrap_or_else(|e| {
error!(
"process_cex_updated_to_slint::failed_to_invoke_from_event_loop: {}",
e
);
});
}
pub async fn process_max_depth_reached_to_slint(&self, token: MaxDepthReachedData) {
info!("processing_max_depth_reached_to_slint: mint={}", token.mint);
let utc_created_at = Utc.timestamp_millis_opt(token.created_at as i64).unwrap();
let jakarta_created_at = utc_created_at.with_timezone(&chrono_tz::Asia::Jakarta);
let created_at = jakarta_created_at.format("%Y-%m-%d %H:%M:%S");
let utc_updated_at = Utc.timestamp_millis_opt(token.updated_at as i64).unwrap();
let jakarta_updated_at = utc_updated_at.with_timezone(&chrono_tz::Asia::Jakarta);
let updated_at = jakarta_updated_at.format("%Y-%m-%d %H:%M:%S");
let max_depth_ui_data = MaxDepthReachedUiData {
mint: SharedString::from(token.mint),
name: SharedString::from(token.name),
uri: SharedString::from(token.uri),
dev_name: SharedString::from(token.dev_name),
creator: SharedString::from(token.creator),
bonding_curve: SharedString::from(token.bonding_curve),
created_at: SharedString::from(created_at.to_string()),
updated_at: SharedString::from(updated_at.to_string()),
node_count: token.node_count as i32,
edge_count: token.edge_count as i32,
};
// ✅ Update UI using proper Slint threading model
let ui_weak_clone = self.ui_weak.clone();
slint::invoke_from_event_loop(move || {
if let Some(ui) = ui_weak_clone.upgrade() {
// Convert ModelRc to Vec, add new item, and set back
let current_tokens = ui.get_analysis_tokens();
let mut tokens_vec: Vec<MaxDepthReachedUiData> = current_tokens.iter().collect();
tokens_vec.insert(0, max_depth_ui_data);
ui.set_analysis_tokens(tokens_vec.as_slice().into());
info!(
"process_max_depth_reached_to_slint::ui_updated::total_tokens={}",
tokens_vec.len()
);
} else {
warn!("process_max_depth_reached_to_slint::ui_weak_reference_expired");
}
})
.unwrap_or_else(|e| {
error!(
"process_max_depth_reached_to_slint::failed_to_invoke_from_event_loop: {}",
e
);
});
}
}
async fn run_token_handler(mut token_metadata_handler: TokenMetadataHandler) {
debug!("token_handler::started");
loop {
tokio::select! {
Some(msg) = token_metadata_handler.receiver.recv() => {
match msg {
TokenHandler::NewToken { data } => {
info!("received_new_token: mint={}", data.mint);
token_metadata_handler.process_new_token_to_slint(data).await;
},
TokenHandler::CexUpdated { data } => {
info!("received_cex_updated_token: mint={}", data.mint);
token_metadata_handler.process_cex_updated_to_slint(data).await;
},
TokenHandler::MaxDepthReached { data } => {
info!("received_max_depth_reached_token: mint={}", data.mint);
token_metadata_handler.process_max_depth_reached_to_slint(data).await;
},
}
},
else => {
// Channel closed, exit gracefully
debug!("hunting_ground_handler_token::channel_closed::exiting");
break;
}
}
}
}
// Main hunting ground handler following the muhafidh actor pattern
#[derive(Clone)]
pub struct TokenMetadataHandlerOperator {
pub db: Arc<StorageEngine>,
pub sender: mpsc::Sender<TokenHandler>,
pub shutdown: ShutdownSignal,
pub ui_weak: Weak<MainWindow>,
}
impl TokenMetadataHandlerOperator {
pub fn new(
db: Arc<StorageEngine>,
shutdown: ShutdownSignal,
receiver: mpsc::Receiver<TokenHandler>,
sender: mpsc::Sender<TokenHandler>,
slint_tx: mpsc::Sender<SlintHandler>,
ui_weak: Weak<MainWindow>,
) -> Self {
let token_metadata_handler =
TokenMetadataHandler::new(receiver, slint_tx, ui_weak.clone(), shutdown.clone());
// Spawn the actor
tokio::spawn(run_token_handler(token_metadata_handler));
Self {
db,
sender,
shutdown,
ui_weak,
}
}
pub async fn process_new_token(&self, token: NewTokenCreatedData) -> Result<()> {
info!("processing_new_token: mint={}", token.mint.clone());
if let Err(e) = self.sender.try_send(TokenHandler::NewToken {
data: token.clone(),
}) {
error!(
"failed_to_send_token_to_token_handler::mint::{}::error::{}",
token.mint, e
);
}
Ok(())
}
pub async fn process_cex_updated(&self, token: TokenCexUpdatedData) -> Result<()> {
info!("processing_cex_updated: mint={}", token.mint);
if let Err(e) = self.sender.try_send(TokenHandler::CexUpdated {
data: token.clone(),
}) {
error!(
"failed_to_send_cex_updated_to_token_handler::mint::{}::error::{}",
token.mint, e
);
}
Ok(())
}
pub async fn process_max_depth_reached(&self, token: MaxDepthReachedData) -> Result<()> {
info!("processing_max_depth_reached: mint={}", token.mint);
if let Err(e) = self.sender.try_send(TokenHandler::MaxDepthReached {
data: token.clone(),
}) {
error!(
"failed_to_send_max_depth_reached_to_token_handler::mint::{}::error::{}",
token.mint, e
);
}
Ok(())
}
}

99
src/handler/ui.rs Normal file
View file

@ -0,0 +1,99 @@
use std::sync::Arc;
use tokio::sync::mpsc;
use tracing::{debug, error, info};
use super::SlintHandler;
use super::TokenHandler;
use crate::storage::StorageEngine;
use crate::task::shutdown::ShutdownSignal;
// Internal state for the hunting ground handler
pub struct SlintHandlerUi {
pub receiver: mpsc::Receiver<SlintHandler>,
pub token_tx: mpsc::Sender<TokenHandler>,
pub shutdown: ShutdownSignal,
}
impl SlintHandlerUi {
pub fn new(
receiver: mpsc::Receiver<SlintHandler>,
token_tx: mpsc::Sender<TokenHandler>,
shutdown: ShutdownSignal,
) -> Self {
Self {
receiver,
token_tx,
shutdown,
}
}
}
async fn run_slint_handler_ui(mut slint_handler_ui: SlintHandlerUi) {
debug!("slint_handler_ui::started");
loop {
tokio::select! {
Some(msg) = slint_handler_ui.receiver.recv() => {
match msg {
SlintHandler::ClearNewTokens { } => {
info!("received_clear_new_tokens");
},
SlintHandler::ClearCexTokens { } => {
info!("received_clear_cex_tokens");
},
SlintHandler::ClearAnalysisTokens { } => {
info!("received_clear_analysis_tokens");
},
}
},
else => {
// Channel closed, exit gracefully
debug!("hunting_ground_handler_token::channel_closed::exiting");
break;
}
}
}
}
// Main hunting ground handler following the muhafidh actor pattern
#[derive(Clone)]
pub struct SlintHandlerUiOperator {
pub db: Arc<StorageEngine>,
pub sender: mpsc::Sender<SlintHandler>,
pub shutdown: ShutdownSignal,
}
impl SlintHandlerUiOperator {
pub fn new(
db: Arc<StorageEngine>,
shutdown: ShutdownSignal,
receiver: mpsc::Receiver<SlintHandler>,
sender: mpsc::Sender<SlintHandler>,
token_tx: mpsc::Sender<TokenHandler>,
) -> Self {
let slint_handler_ui = SlintHandlerUi::new(receiver, token_tx, shutdown.clone());
// Spawn the actor
tokio::spawn(run_slint_handler_ui(slint_handler_ui));
Self {
db,
sender,
shutdown,
}
}
pub fn clear_new_tokens(&self) {
if let Err(e) = self.sender.try_send(SlintHandler::ClearNewTokens {}) {
error!("failed_to_send_clear_new_tokens::error::{}", e);
}
}
pub fn clear_cex_tokens(&self) {
if let Err(e) = self.sender.try_send(SlintHandler::ClearCexTokens {}) {
error!("failed_to_send_clear_cex_tokens::error::{}", e);
}
}
pub fn clear_analysis_tokens(&self) {
if let Err(e) = self.sender.try_send(SlintHandler::ClearAnalysisTokens {}) {
error!("failed_to_send_clear_analysis_tokens::error::{}", e);
}
}
}

View file

@ -1,2 +1,363 @@
// This lib.rs is currently not used since we're building a binary application // This lib.rs is currently not used since we're building a binary application
// If needed in the future, add modules here // If needed in the future, add modules here
pub mod config;
pub mod error;
pub mod handler;
pub mod model;
pub mod storage;
pub mod task;
pub mod tracing;
pub mod slint_ui {
slint::include_modules!();
}
// Re-export commonly used types
pub use config::Config;
pub use error::app::AppError;
pub use error::Result;
pub use slint::*;
pub use slint_ui::*;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use handler::token::TokenMetadataHandlerOperator;
use handler::ui::SlintHandlerUiOperator;
use handler::SlintHandler;
use handler::TokenHandler;
use storage::StorageEngine;
use task::shutdown::ShutdownSignal;
use tracing::{error, info, warn};
// Application states
#[derive(Debug, Clone, PartialEq)]
pub enum AppState {
Loading,
Login,
Authenticated,
}
// User session
#[derive(Debug, Clone)]
pub struct UserSession {
pub email: String,
pub authenticated_at: SystemTime,
}
pub fn get_current_unix_timestamp() -> i32 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i32
}
// Simple email validation
pub fn is_valid_email(email: &str) -> bool {
email.contains('@') && email.contains('.') && email.len() > 5
}
// Health check function
pub async fn check_backend_health(db: &StorageEngine) -> bool {
// Check Redis connection
match db.redis.pool.get().await {
Ok(mut conn) => {
match redis::cmd("PING").query_async::<String>(&mut *conn).await {
Ok(_) => {
info!("Redis health check passed");
true
}
Err(e) => {
warn!("Redis ping failed: {}", e);
false
}
}
}
Err(e) => {
warn!("Redis health check failed: {}", e);
false
}
}
}
pub struct ZiyaApp {
pub db: Arc<StorageEngine>,
pub ui_weak: Weak<MainWindow>,
pub shutdown: ShutdownSignal,
pub cancellation_token: CancellationToken,
pub slint_handler: Arc<SlintHandlerUiOperator>,
pub token_handler: Arc<TokenMetadataHandlerOperator>,
pub app_state: AppState,
pub user_session: Option<UserSession>,
}
impl ZiyaApp {
pub async fn new(config: &Config, ui_weak: Weak<MainWindow>) -> Result<Self> {
info!("Creating storage engine");
let db = StorageEngine::new(config.clone()).await?;
let db = Arc::new(db);
let shutdown_signal = ShutdownSignal::new();
let cancellation_token = CancellationToken::new();
let (slint_tx, slint_rx) = mpsc::channel::<SlintHandler>(1000);
let (token_tx, token_rx) = mpsc::channel::<TokenHandler>(1000);
let slint_handler = Arc::new(SlintHandlerUiOperator::new(
db.clone(),
shutdown_signal.clone(),
slint_rx,
slint_tx.clone(),
token_tx.clone(),
));
let token_handler = Arc::new(TokenMetadataHandlerOperator::new(
db.clone(),
shutdown_signal.clone(),
token_rx,
token_tx,
slint_tx,
ui_weak.clone(),
));
Ok(Self {
db,
ui_weak,
shutdown: shutdown_signal,
cancellation_token,
slint_handler,
token_handler,
app_state: AppState::Loading,
user_session: None,
})
}
// Initialize the UI with health check
pub fn init_ui(&self) -> Result<()> {
if let Some(ui) = self.ui_weak.upgrade() {
// Set initial state
ui.set_app_state("loading".into());
ui.set_is_loading(true);
ui.set_has_connection_error(false);
ui.set_loading_status("Initializing your trading environment...".into());
// Set up health check callback
let db = self.db.clone();
let ui_weak = self.ui_weak.clone();
ui.on_retry_health_check(move || {
let db = db.clone();
let ui_weak_for_spawn = ui_weak.clone();
let ui_weak_for_immediate = ui_weak.clone();
// Immediately update UI for loading state
if let Some(ui) = ui_weak_for_immediate.upgrade() {
ui.set_is_loading(true);
ui.set_has_connection_error(false);
ui.set_loading_status("Checking backend connection...".into());
}
// Spawn background task for health check
tokio::spawn(async move {
// Simulate loading time
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let health_result = check_backend_health(&db).await;
// Update UI from event loop thread
let _ = slint::invoke_from_event_loop(move || {
if let Some(ui) = ui_weak_for_spawn.upgrade() {
if health_result {
info!("Health check passed, transitioning to login");
ui.set_app_state("login".into());
} else {
warn!("Health check failed");
ui.set_has_connection_error(true);
ui.set_is_loading(false);
ui.set_loading_status("Connection failed".into());
}
}
});
});
});
// Set up login callback
let ui_weak = self.ui_weak.clone();
ui.on_login_attempt(move |email, _password| {
let ui_weak = ui_weak.clone();
let email_str = email.to_string();
info!("Login attempt for email: {}", email_str);
if let Some(ui) = ui_weak.upgrade() {
if is_valid_email(&email_str) {
ui.set_user_email(email.into());
ui.set_app_state("authenticated".into());
ui.set_is_authenticated(true);
info!("User authenticated successfully");
} else {
warn!("Invalid email provided: {}", email_str);
// Handle login error in the UI
}
}
});
// Set up logout callback
let ui_weak = self.ui_weak.clone();
ui.on_logout_requested(move || {
let ui_weak = ui_weak.clone();
if let Some(ui) = ui_weak.upgrade() {
info!("User logout requested");
ui.set_user_email("".into());
ui.set_app_state("login".into());
ui.set_is_authenticated(false);
}
});
// Set up authenticate user callback (for demo mode)
let ui_weak = self.ui_weak.clone();
ui.on_authenticate_user(move |email| {
let ui_weak = ui_weak.clone();
let email_str = email.to_string();
if let Some(ui) = ui_weak.upgrade() {
info!("Demo authentication for: {}", email_str);
ui.set_user_email(email.into());
ui.set_app_state("authenticated".into());
ui.set_is_authenticated(true);
}
});
// Set up sidebar toggle
let ui_weak = self.ui_weak.clone();
ui.on_toggle_sidebar(move || {
let ui_weak = ui_weak.clone();
if let Some(ui) = ui_weak.upgrade() {
let current_state = ui.get_sidebar_state();
if current_state.as_str() == "full" {
ui.set_sidebar_state("icon-only".into());
} else {
ui.set_sidebar_state("full".into());
}
}
});
// Start initial health check
let db = self.db.clone();
let ui_weak = self.ui_weak.clone();
tokio::spawn(async move {
// Initial delay to show loading screen
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
let health_result = check_backend_health(&db).await;
// Update UI from event loop thread
let _ = slint::invoke_from_event_loop(move || {
if let Some(ui) = ui_weak.upgrade() {
ui.set_loading_status("Checking backend connection...".into());
if health_result {
info!("Initial health check passed");
ui.set_app_state("login".into());
} else {
warn!("Initial health check failed");
ui.set_has_connection_error(true);
ui.set_is_loading(false);
ui.set_loading_status("Connection failed".into());
}
}
});
});
Ok(())
} else {
Err(AppError::UiError("Failed to upgrade UI weak reference".into()).into())
}
}
pub async fn run(self) -> Result<()> {
info!("Starting Ziya Slint Application");
// Initialize UI callbacks
self.init_ui()?;
let slint_handler = self.slint_handler.clone();
let db = self.db.clone();
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
let ui_task = task::ui::spawn_ui_task(
slint_handler.clone(),
self.ui_weak.clone(),
self.cancellation_token.clone(),
shutdown_tx.clone(),
);
// ✅ Spawn all background subscriber tasks
let new_token_subscriber = task::subscriber::spawn_new_token_subscriber(
self.token_handler.clone(),
db.clone(),
self.cancellation_token.clone(),
);
let cex_updated_subscriber = task::subscriber::spawn_token_cex_updated_subscriber(
self.token_handler.clone(),
db.clone(),
self.cancellation_token.clone(),
);
let max_depth_subscriber = task::subscriber::spawn_max_depth_reached_subscriber(
self.token_handler.clone(),
db.clone(),
self.cancellation_token.clone(),
);
// ✅ Use the original tokio::select! pattern
tokio::select! {
result = ui_task => {
match result {
Ok(Ok(())) => info!("ui_task_completed_successfully"),
Ok(Err(e)) => error!("ui_task_error: {}", e),
Err(e) => error!("ui_task_join_error: {}", e),
}
},
result = new_token_subscriber => {
match result {
Ok(Ok(())) => info!("new_token_subscriber_completed_successfully"),
Ok(Err(e)) => error!("new_token_subscriber_error: {}", e),
Err(e) => error!("new_token_subscriber_join_error: {}", e),
}
},
result = cex_updated_subscriber => {
match result {
Ok(Ok(())) => info!("cex_updated_subscriber_completed_successfully"),
Ok(Err(e)) => error!("cex_updated_subscriber_error: {}", e),
Err(e) => error!("cex_updated_subscriber_join_error: {}", e),
}
},
result = max_depth_subscriber => {
match result {
Ok(Ok(())) => info!("max_depth_subscriber_completed_successfully"),
Ok(Err(e)) => error!("max_depth_subscriber_error: {}", e),
Err(e) => error!("max_depth_subscriber_join_error: {}", e),
}
},
_ = tokio::signal::ctrl_c() => {
info!("ctrl_c_received::initiating_shutdown");
let _ = shutdown_tx.send(()).await;
},
_ = shutdown_rx.recv() => {
info!("shutdown_signal_received");
self.cancellation_token.cancel();
},
}
info!("application_shutdown_complete");
Ok(())
}
}

View file

@ -1,119 +1,36 @@
// Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms. // Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use slint::*; use ziya::config::load_config;
use i_slint_backend_winit::WinitWindowAccessor; use ziya::error::Result;
use ziya::slint_ui::*;
use ziya::tracing::setup_tracing;
use ziya::ZiyaApp;
// Import the main window from our FSD-structured UI #[tokio::main]
slint::include_modules!(); async fn main() -> Result<()> {
// ✅ Create and show UI on main thread using Slint's built-in pattern
let ui = MainWindow::new()
.map_err(|e| ziya::error::app::AppError::Slint(format!("failed_to_create_ui: {}", e)))?;
fn main() -> Result<(), slint::PlatformError> { // ✅ Set up async tasks using spawn_local with async-compat for Tokio futures
// Create the main window let ui_weak = ui.as_weak();
let ui = MainWindow::new()?; // Load configuration
let config = load_config("Config.toml").await.unwrap();
// Clone handle for callbacks // Initialize tracing with config
let ui_handle = ui.as_weak(); setup_tracing(config.clone(), "ziya-slint").await.unwrap();
// Handle navigation changes // Create and run the app
ui.on_navigation_changed({ let app = ZiyaApp::new(&config, ui_weak).await.unwrap();
let ui_handle = ui_handle.clone(); slint::spawn_local(async_compat::Compat::new(async {
move |page| { app.run().await.unwrap();
println!("Navigation changed to: {}", page); }))
if let Some(ui) = ui_handle.upgrade() { .unwrap();
// Update current page state
ui.set_current_page(page.into());
}
}
});
// Handle theme toggle - using direct function call as per Slint discussion #5860 ui.run().map_err(|e| {
ui.on_theme_toggle_clicked({ ziya::error::app::AppError::Slint(format!("failed_to_run_event_loop: {}", e))
let ui_handle = ui_handle.clone(); })?;
move || {
println!("Theme toggle callback received from UI");
if let Some(ui) = ui_handle.upgrade() {
// Call the public theme toggle function directly
ui.invoke_toggle_theme();
println!("Theme toggle function invoked successfully");
}
}
});
// Handle logout Ok(())
ui.on_logout_clicked({
let _ui_handle = ui_handle.clone();
move || {
println!("Logout clicked");
// Implement logout logic here
}
});
// Handle buy action
ui.on_buy_clicked({
let _ui_handle = ui_handle.clone();
move || {
println!("Buy clicked");
// Implement buy logic here
}
});
// Handle sell action
ui.on_sell_clicked({
let _ui_handle = ui_handle.clone();
move || {
println!("Sell clicked");
// Implement sell logic here
}
});
// Window control handlers - simplified drag handling
ui.on_start_drag_window({
let ui_handle = ui_handle.clone();
move || {
if let Some(ui) = ui_handle.upgrade() {
let window = ui.window();
window.with_winit_window(|winit_window| {
if !window.is_maximized() {
let _ = winit_window.drag_window();
}
});
}
}
});
ui.on_minimize_window({
let ui_handle = ui_handle.clone();
move || {
if let Some(ui) = ui_handle.upgrade() {
let _ = ui.window().set_minimized(true);
}
}
});
ui.on_maximize_window({
let ui_handle = ui_handle.clone();
move || {
if let Some(ui) = ui_handle.upgrade() {
let window = ui.window();
let _ = window.set_maximized(!window.is_maximized());
}
}
});
ui.on_close_window({
let ui_handle = ui_handle.clone();
move || {
if let Some(ui) = ui_handle.upgrade() {
let _ = ui.window().hide();
let _ = slint::quit_event_loop();
}
}
});
// Set initial state
ui.set_current_page("Dashboard".into());
ui.set_user_initials("JD".into());
// Run the application
ui.run()
} }

612
src/model/cex.rs Normal file
View file

@ -0,0 +1,612 @@
use std::str::FromStr;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Cex {
pub name: CexName,
pub address: solana_pubkey::Pubkey,
}
impl Cex {
pub fn new(name: CexName, address: solana_pubkey::Pubkey) -> Self {
Self { name, address }
}
pub fn get_exchange_name(address: solana_pubkey::Pubkey) -> Option<CexName> {
match address.to_string().as_str() {
"FpwQQhQQoEaVu3WU2qZMfF1hx48YyfwsLoRgXG83E99Q" => Some(CexName::CoinbaseHW1),
"GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" => Some(CexName::CoinbaseHW2),
"D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" => Some(CexName::CoinbaseHW3),
"DPqsobysNf5iA9w7zrQM8HLzCKZEDMkZsWbiidsAt1xo" => Some(CexName::CoinbaseHW4),
"H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" => Some(CexName::Coinbase1),
"2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" => Some(CexName::Coinbase2),
"59L2oxymiQQ9Hvhh92nt8Y7nDYjsauFkdb3SybdnsG6h" => Some(CexName::Coinbase4),
"9obNtb5GyUegcs3a1CbBkLuc5hEWynWfJC6gjz5uWQkE" => Some(CexName::Coinbase5),
"3vxheE5C46XzK4XftziRhwAf8QAfipD7HXXWj25mgkom" => Some(CexName::CoinbasePrime),
"CKy3KzEMSL1PQV6Wppggoqi2nGA7teE4L7JipEK89yqj" => Some(CexName::CoinbaseCW1),
"G6zmnfSdG6QJaDWYwbGQ4dpCSUC4gvjfZxYQ4ZharV7C" => Some(CexName::CoinbaseCW2),
"VTvk7sG6QQ28iK3NEKRRD9fvPzk5pKpJL2iwgVqMFcL" => Some(CexName::CoinbaseCW3),
"85cPov8nuRCkJ88VNMcHaHZ26Ux85PbSrHW4jg7izW4h" => Some(CexName::CoinbaseCW4),
"D6gCBB3CZEMNbX1PDr3GtZAMhnebEumcgJ2yv8Etv5hF" => Some(CexName::CoinbaseCW5),
"3qP77PzrHxSrW1S8dH4Ss1dmpJDHpC6ATVgwy5FmXDEf" => Some(CexName::CoinbaseCW6),
"146yGthSmnTPuCo6Zfbmr56YbAyWZ3rzAhRcT7tTF5ha" => Some(CexName::CoinbaseCW7),
"GXTrXayxMJUujsRTxYjAbkdbNvs6u2KN89UpG8f6eMAg" => Some(CexName::CoinbaseCW8),
"AzAvbCQsXurd2PbGLYcB61tyvE8kLDaZShE1S5Bp3WeS" => Some(CexName::CoinbaseCW9),
"4pHKEisSmAr5CSump4dJnTJgG6eugmtieXcUxDBcQcG5" => Some(CexName::CoinbaseCW10),
"BmGyWBMEcjJD7JQD1jRJ5vEt7XX2LyVvtxwtTGV4N1bp" => Some(CexName::CoinbaseCW11),
"py5jDEUAynTufQHM7P6Tu9M8NUd8JYux7aMcLXcC51q" => Some(CexName::CoinbaseCW12),
"is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" => Some(CexName::OKXHW1),
"C68a6RCGLiPskbPYtAcsCjhG8tfTWYcoB4JjCrXFdqyo" => Some(CexName::OKXHW2),
"5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" => Some(CexName::OKX),
"9un5wqE3q4oCjyrDkwsdD48KteCJitQX5978Vh7KKxHo" => Some(CexName::OKX2),
"ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" => Some(CexName::MEXC1),
"5PAhQiYdLBd6SVdjzBQDxUAEFyDdF5ExNPQfcscnPRj5" => Some(CexName::MEXC2),
"FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" => Some(CexName::Kraken),
"9cNE6KBg2Xmf34FPMMvzDF8yUHMrgLRzBV3vD7b1JnUS" => Some(CexName::KrakenCW),
"F7RkX6Y1qTfBqoX5oHoZEgrG1Dpy55UZ3GfWwPbM58nQ" => Some(CexName::KrakenCW2),
"3yFwqXBfZY4jBVUafQ1YEXw189y2dN3V5KQq9uzBDy1E" => Some(CexName::Binance8),
"2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" => Some(CexName::Binance1),
"5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" => Some(CexName::Binance2),
"9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" => Some(CexName::Binance3),
"53unSgGWqEWANcPYRF35B2Bgf8BkszUtcccKiXwGGLyr" => Some(CexName::BinanceUSHW),
"3gd3dqgtJ4jWfBfLYTX67DALFetjc5iS72sCgRhCkW2u" => Some(CexName::Binance10),
"6QJzieMYfp7yr3EdrePaQoG3Ghxs2wM98xSLRu8Xh56U" => Some(CexName::Binance11),
"GBrURzmtWujJRTA3Bkvo7ZgWuZYLMMwPCwre7BejJXnK" => Some(CexName::BinanceCW),
"4S8C1yrRZmJYPzCqzEVjZYf6qCYWFoF7hWLRzssTCotX" => Some(CexName::BitgetCW),
"A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" => Some(CexName::BitgetExchange),
"u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" => Some(CexName::Gateio1),
"HiRpdAZifEsZGdzQ5Xo5wcnaH3D2Jj9SoNsUzcYNK78J" => Some(CexName::Gateio2),
"AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" => Some(CexName::BybitHW),
"42brAgAVNzMBP7aaktPvAmBSPEkehnFQejiZc53EpJFd" => Some(CexName::BybitCW),
"FxteHmLwG9nk1eL4pjNve3Eub2goGkkz6g6TbvdmW46a" => Some(CexName::BitfinexHW),
"FyJBKcfcEBzGN74uNxZ95GxnCxeuJJujQCELpPv14ZfN" => Some(CexName::BitfinexCW),
"57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" => Some(CexName::KuCoin1),
"BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" => Some(CexName::KuCoin2),
"HVh6wHNBAsG3pq1Bj5oCzRjoWKVogEDHwUHkRz3ekFgt" => Some(CexName::KuCoin3),
"DBmae92YTQKLsNzXcPscxiwPqMcz9stQr2prB5ZCAHPd" => Some(CexName::KuCoinCW),
"7Ci23i82UMa8RpfVbdMjTytiDi2VoZS8uLyHhZBV2Qy7" => Some(CexName::PoloniexHW),
"8s9j5qUtuE9PGA5s7QeAXEh5oc2UGr71pmJXgyiZMHkt" => Some(CexName::LBank),
"G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" => Some(CexName::StakecomHotWallet),
"2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" => Some(CexName::DeBridgeVault),
"Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" => Some(CexName::RevolutHotWallet),
"HBxZShcE86UMmF93KUM8eWJKqeEXi5cqWCLYLMMhqMYm" => Some(CexName::BitStampHotWallet),
_ => None,
}
}
pub fn get_exchange_address(name: CexName) -> Option<solana_pubkey::Pubkey> {
match name {
CexName::CoinbaseHW1 => Some(
solana_pubkey::Pubkey::from_str("FpwQQhQQoEaVu3WU2qZMfF1hx48YyfwsLoRgXG83E99Q")
.unwrap(),
),
CexName::CoinbaseHW2 => Some(
solana_pubkey::Pubkey::from_str("GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE")
.unwrap(),
),
CexName::CoinbaseHW3 => Some(
solana_pubkey::Pubkey::from_str("D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf")
.unwrap(),
),
CexName::CoinbaseHW4 => Some(
solana_pubkey::Pubkey::from_str("DPqsobysNf5iA9w7zrQM8HLzCKZEDMkZsWbiidsAt1xo")
.unwrap(),
),
CexName::Coinbase1 => Some(
solana_pubkey::Pubkey::from_str("H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS")
.unwrap(),
),
CexName::Coinbase2 => Some(
solana_pubkey::Pubkey::from_str("2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm")
.unwrap(),
),
CexName::Coinbase4 => Some(
solana_pubkey::Pubkey::from_str("59L2oxymiQQ9Hvhh92nt8Y7nDYjsauFkdb3SybdnsG6h")
.unwrap(),
),
CexName::Coinbase5 => Some(
solana_pubkey::Pubkey::from_str("9obNtb5GyUegcs3a1CbBkLuc5hEWynWfJC6gjz5uWQkE")
.unwrap(),
),
CexName::CoinbasePrime => Some(
solana_pubkey::Pubkey::from_str("3vxheE5C46XzK4XftziRhwAf8QAfipD7HXXWj25mgkom")
.unwrap(),
),
CexName::CoinbaseCW1 => Some(
solana_pubkey::Pubkey::from_str("CKy3KzEMSL1PQV6Wppggoqi2nGA7teE4L7JipEK89yqj")
.unwrap(),
),
CexName::CoinbaseCW2 => Some(
solana_pubkey::Pubkey::from_str("G6zmnfSdG6QJaDWYwbGQ4dpCSUC4gvjfZxYQ4ZharV7C")
.unwrap(),
),
CexName::CoinbaseCW3 => Some(
solana_pubkey::Pubkey::from_str("VTvk7sG6QQ28iK3NEKRRD9fvPzk5pKpJL2iwgVqMFcL")
.unwrap(),
),
CexName::CoinbaseCW4 => Some(
solana_pubkey::Pubkey::from_str("85cPov8nuRCkJ88VNMcHaHZ26Ux85PbSrHW4jg7izW4h")
.unwrap(),
),
CexName::CoinbaseCW5 => Some(
solana_pubkey::Pubkey::from_str("D6gCBB3CZEMNbX1PDr3GtZAMhnebEumcgJ2yv8Etv5hF")
.unwrap(),
),
CexName::CoinbaseCW6 => Some(
solana_pubkey::Pubkey::from_str("3qP77PzrHxSrW1S8dH4Ss1dmpJDHpC6ATVgwy5FmXDEf")
.unwrap(),
),
CexName::CoinbaseCW7 => Some(
solana_pubkey::Pubkey::from_str("146yGthSmnTPuCo6Zfbmr56YbAyWZ3rzAhRcT7tTF5ha")
.unwrap(),
),
CexName::CoinbaseCW8 => Some(
solana_pubkey::Pubkey::from_str("GXTrXayxMJUujsRTxYjAbkdbNvs6u2KN89UpG8f6eMAg")
.unwrap(),
),
CexName::CoinbaseCW9 => Some(
solana_pubkey::Pubkey::from_str("AzAvbCQsXurd2PbGLYcB61tyvE8kLDaZShE1S5Bp3WeS")
.unwrap(),
),
CexName::CoinbaseCW10 => Some(
solana_pubkey::Pubkey::from_str("4pHKEisSmAr5CSump4dJnTJgG6eugmtieXcUxDBcQcG5")
.unwrap(),
),
CexName::CoinbaseCW11 => Some(
solana_pubkey::Pubkey::from_str("BmGyWBMEcjJD7JQD1jRJ5vEt7XX2LyVvtxwtTGV4N1bp")
.unwrap(),
),
CexName::CoinbaseCW12 => Some(
solana_pubkey::Pubkey::from_str("py5jDEUAynTufQHM7P6Tu9M8NUd8JYux7aMcLXcC51q")
.unwrap(),
),
CexName::OKXHW1 => Some(
solana_pubkey::Pubkey::from_str("is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV")
.unwrap(),
),
CexName::OKXHW2 => Some(
solana_pubkey::Pubkey::from_str("C68a6RCGLiPskbPYtAcsCjhG8tfTWYcoB4JjCrXFdqyo")
.unwrap(),
),
CexName::OKX => Some(
solana_pubkey::Pubkey::from_str("5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD")
.unwrap(),
),
CexName::OKX2 => Some(
solana_pubkey::Pubkey::from_str("9un5wqE3q4oCjyrDkwsdD48KteCJitQX5978Vh7KKxHo")
.unwrap(),
),
CexName::MEXC1 => Some(
solana_pubkey::Pubkey::from_str("ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ")
.unwrap(),
),
CexName::MEXC2 => Some(
solana_pubkey::Pubkey::from_str("5PAhQiYdLBd6SVdjzBQDxUAEFyDdF5ExNPQfcscnPRj5")
.unwrap(),
),
CexName::Kraken => Some(
solana_pubkey::Pubkey::from_str("FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5")
.unwrap(),
),
CexName::KrakenCW => Some(
solana_pubkey::Pubkey::from_str("9cNE6KBg2Xmf34FPMMvzDF8yUHMrgLRzBV3vD7b1JnUS")
.unwrap(),
),
CexName::KrakenCW2 => Some(
solana_pubkey::Pubkey::from_str("F7RkX6Y1qTfBqoX5oHoZEgrG1Dpy55UZ3GfWwPbM58nQ")
.unwrap(),
),
CexName::Binance8 => Some(
solana_pubkey::Pubkey::from_str("3yFwqXBfZY4jBVUafQ1YEXw189y2dN3V5KQq9uzBDy1E")
.unwrap(),
),
CexName::Binance1 => Some(
solana_pubkey::Pubkey::from_str("2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S")
.unwrap(),
),
CexName::Binance2 => Some(
solana_pubkey::Pubkey::from_str("5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9")
.unwrap(),
),
CexName::Binance3 => Some(
solana_pubkey::Pubkey::from_str("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM")
.unwrap(),
),
CexName::BinanceUSHW => Some(
solana_pubkey::Pubkey::from_str("53unSgGWqEWANcPYRF35B2Bgf8BkszUtcccKiXwGGLyr")
.unwrap(),
),
CexName::Binance10 => Some(
solana_pubkey::Pubkey::from_str("3gd3dqgtJ4jWfBfLYTX67DALFetjc5iS72sCgRhCkW2u")
.unwrap(),
),
CexName::Binance11 => Some(
solana_pubkey::Pubkey::from_str("6QJzieMYfp7yr3EdrePaQoG3Ghxs2wM98xSLRu8Xh56U")
.unwrap(),
),
CexName::BinanceCW => Some(
solana_pubkey::Pubkey::from_str("GBrURzmtWujJRTA3Bkvo7ZgWuZYLMMwPCwre7BejJXnK")
.unwrap(),
),
CexName::BitgetCW => Some(
solana_pubkey::Pubkey::from_str("4S8C1yrRZmJYPzCqzEVjZYf6qCYWFoF7hWLRzssTCotX")
.unwrap(),
),
CexName::BitgetExchange => Some(
solana_pubkey::Pubkey::from_str("A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR")
.unwrap(),
),
CexName::Gateio1 => Some(
solana_pubkey::Pubkey::from_str("u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w")
.unwrap(),
),
CexName::Gateio2 => Some(
solana_pubkey::Pubkey::from_str("HiRpdAZifEsZGdzQ5Xo5wcnaH3D2Jj9SoNsUzcYNK78J")
.unwrap(),
),
CexName::BybitHW => Some(
solana_pubkey::Pubkey::from_str("AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2")
.unwrap(),
),
CexName::BybitCW => Some(
solana_pubkey::Pubkey::from_str("42brAgAVNzMBP7aaktPvAmBSPEkehnFQejiZc53EpJFd")
.unwrap(),
),
CexName::BitfinexHW => Some(
solana_pubkey::Pubkey::from_str("FxteHmLwG9nk1eL4pjNve3Eub2goGkkz6g6TbvdmW46a")
.unwrap(),
),
CexName::BitfinexCW => Some(
solana_pubkey::Pubkey::from_str("FyJBKcfcEBzGN74uNxZ95GxnCxeuJJujQCELpPv14ZfN")
.unwrap(),
),
CexName::KuCoin1 => Some(
solana_pubkey::Pubkey::from_str("57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs")
.unwrap(),
),
CexName::KuCoin2 => Some(
solana_pubkey::Pubkey::from_str("BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6")
.unwrap(),
),
CexName::KuCoin3 => Some(
solana_pubkey::Pubkey::from_str("HVh6wHNBAsG3pq1Bj5oCzRjoWKVogEDHwUHkRz3ekFgt")
.unwrap(),
),
CexName::KuCoinCW => Some(
solana_pubkey::Pubkey::from_str("DBmae92YTQKLsNzXcPscxiwPqMcz9stQr2prB5ZCAHPd")
.unwrap(),
),
CexName::PoloniexHW => Some(
solana_pubkey::Pubkey::from_str("7Ci23i82UMa8RpfVbdMjTytiDi2VoZS8uLyHhZBV2Qy7")
.unwrap(),
),
CexName::LBank => Some(
solana_pubkey::Pubkey::from_str("8s9j5qUtuE9PGA5s7QeAXEh5oc2UGr71pmJXgyiZMHkt")
.unwrap(),
),
CexName::StakecomHotWallet => Some(
solana_pubkey::Pubkey::from_str("G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE")
.unwrap(),
),
CexName::DeBridgeVault => Some(
solana_pubkey::Pubkey::from_str("2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS")
.unwrap(),
),
CexName::RevolutHotWallet => Some(
solana_pubkey::Pubkey::from_str("Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU")
.unwrap(),
),
CexName::BitStampHotWallet => Some(
solana_pubkey::Pubkey::from_str("HBxZShcE86UMmF93KUM8eWJKqeEXi5cqWCLYLMMhqMYm")
.unwrap(),
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CexName {
#[serde(rename = "coinbase_hw1")]
CoinbaseHW1,
#[serde(rename = "coinbase_hw2")]
CoinbaseHW2,
#[serde(rename = "coinbase_hw3")]
CoinbaseHW3,
#[serde(rename = "coinbase_hw4")]
CoinbaseHW4,
#[serde(rename = "coinbase_1")]
Coinbase1,
#[serde(rename = "coinbase_2")]
Coinbase2,
#[serde(rename = "coinbase_4")]
Coinbase4,
#[serde(rename = "coinbase_5")]
Coinbase5,
#[serde(rename = "coinbase_prime")]
CoinbasePrime,
#[serde(rename = "coinbase_cw1")]
CoinbaseCW1,
#[serde(rename = "coinbase_cw2")]
CoinbaseCW2,
#[serde(rename = "coinbase_cw3")]
CoinbaseCW3,
#[serde(rename = "coinbase_cw4")]
CoinbaseCW4,
#[serde(rename = "coinbase_cw5")]
CoinbaseCW5,
#[serde(rename = "coinbase_cw6")]
CoinbaseCW6,
#[serde(rename = "coinbase_cw7")]
CoinbaseCW7,
#[serde(rename = "coinbase_cw8")]
CoinbaseCW8,
#[serde(rename = "coinbase_cw9")]
CoinbaseCW9,
#[serde(rename = "coinbase_cw10")]
CoinbaseCW10,
#[serde(rename = "coinbase_cw11")]
CoinbaseCW11,
#[serde(rename = "coinbase_cw12")]
CoinbaseCW12,
#[serde(rename = "okx_hw1")]
OKXHW1,
#[serde(rename = "okx_hw2")]
OKXHW2,
#[serde(rename = "okx")]
OKX,
#[serde(rename = "okx_2")]
OKX2,
#[serde(rename = "mexc_1")]
MEXC1,
#[serde(rename = "mexc_2")]
MEXC2,
#[serde(rename = "kraken")]
Kraken,
#[serde(rename = "kraken_cw")]
KrakenCW,
#[serde(rename = "kraken_cw2")]
KrakenCW2,
#[serde(rename = "binance_8")]
Binance8,
#[serde(rename = "binance_1")]
Binance1,
#[serde(rename = "binance_2")]
Binance2,
#[serde(rename = "binance_3")]
Binance3,
#[serde(rename = "binance_us_hw")]
BinanceUSHW,
#[serde(rename = "binance_10")]
Binance10,
#[serde(rename = "binance_11")]
Binance11,
#[serde(rename = "binance_cw")]
BinanceCW,
#[serde(rename = "bitget_cw")]
BitgetCW,
#[serde(rename = "bitget_exchange")]
BitgetExchange,
#[serde(rename = "gateio_1")]
Gateio1,
#[serde(rename = "gateio_2")]
Gateio2,
#[serde(rename = "bybit_hw")]
BybitHW,
#[serde(rename = "bybit_cw")]
BybitCW,
#[serde(rename = "bitfinex_hw")]
BitfinexHW,
#[serde(rename = "bitfinex_cw")]
BitfinexCW,
#[serde(rename = "kucoin_1")]
KuCoin1,
#[serde(rename = "kucoin_2")]
KuCoin2,
#[serde(rename = "kucoin_3")]
KuCoin3,
#[serde(rename = "kucoin_cw")]
KuCoinCW,
#[serde(rename = "poloniex_hw")]
PoloniexHW,
#[serde(rename = "lbank")]
LBank,
#[serde(rename = "stakecom_hot_wallet")]
StakecomHotWallet,
#[serde(rename = "debridge_vault")]
DeBridgeVault,
#[serde(rename = "revolut_hot_wallet")]
RevolutHotWallet,
#[serde(rename = "bitstamp_hot_wallet")]
BitStampHotWallet,
}
impl std::fmt::Display for CexName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CexName::CoinbaseHW1 => write!(f, "coinbase_hw1"),
CexName::CoinbaseHW2 => write!(f, "coinbase_hw2"),
CexName::CoinbaseHW3 => write!(f, "coinbase_hw3"),
CexName::CoinbaseHW4 => write!(f, "coinbase_hw4"),
CexName::Coinbase1 => write!(f, "coinbase_1"),
CexName::Coinbase2 => write!(f, "coinbase_2"),
CexName::Coinbase4 => write!(f, "coinbase_4"),
CexName::Coinbase5 => write!(f, "coinbase_5"),
CexName::CoinbasePrime => write!(f, "coinbase_prime"),
CexName::CoinbaseCW1 => write!(f, "coinbase_cw1"),
CexName::CoinbaseCW2 => write!(f, "coinbase_cw2"),
CexName::CoinbaseCW3 => write!(f, "coinbase_cw3"),
CexName::CoinbaseCW4 => write!(f, "coinbase_cw4"),
CexName::CoinbaseCW5 => write!(f, "coinbase_cw5"),
CexName::CoinbaseCW6 => write!(f, "coinbase_cw6"),
CexName::CoinbaseCW7 => write!(f, "coinbase_cw7"),
CexName::CoinbaseCW8 => write!(f, "coinbase_cw8"),
CexName::CoinbaseCW9 => write!(f, "coinbase_cw9"),
CexName::CoinbaseCW10 => write!(f, "coinbase_cw10"),
CexName::CoinbaseCW11 => write!(f, "coinbase_cw11"),
CexName::CoinbaseCW12 => write!(f, "coinbase_cw12"),
CexName::OKXHW1 => write!(f, "okx_hw1"),
CexName::OKXHW2 => write!(f, "okx_hw2"),
CexName::OKX => write!(f, "okx"),
CexName::OKX2 => write!(f, "okx_2"),
CexName::MEXC1 => write!(f, "mexc_1"),
CexName::MEXC2 => write!(f, "mexc_2"),
CexName::Kraken => write!(f, "kraken"),
CexName::KrakenCW => write!(f, "kraken_cw"),
CexName::KrakenCW2 => write!(f, "kraken_cw2"),
CexName::Binance8 => write!(f, "binance_8"),
CexName::Binance1 => write!(f, "binance_1"),
CexName::Binance2 => write!(f, "binance_2"),
CexName::Binance3 => write!(f, "binance_3"),
CexName::BinanceUSHW => write!(f, "binance_us_hw"),
CexName::Binance10 => write!(f, "binance_10"),
CexName::Binance11 => write!(f, "binance_11"),
CexName::BinanceCW => write!(f, "binance_cw"),
CexName::BitgetCW => write!(f, "bitget_cw"),
CexName::BitgetExchange => write!(f, "bitget_exchange"),
CexName::Gateio1 => write!(f, "gateio_1"),
CexName::Gateio2 => write!(f, "gateio_2"),
CexName::BybitHW => write!(f, "bybit_hw"),
CexName::BybitCW => write!(f, "bybit_cw"),
CexName::BitfinexHW => write!(f, "bitfinex_hw"),
CexName::BitfinexCW => write!(f, "bitfinex_cw"),
CexName::KuCoin1 => write!(f, "kucoin_1"),
CexName::KuCoin2 => write!(f, "kucoin_2"),
CexName::KuCoin3 => write!(f, "kucoin_3"),
CexName::KuCoinCW => write!(f, "kucoin_cw"),
CexName::PoloniexHW => write!(f, "poloniex_hw"),
CexName::LBank => write!(f, "lbank"),
CexName::StakecomHotWallet => write!(f, "stakecom_hot_wallet"),
CexName::DeBridgeVault => write!(f, "debridge_vault"),
CexName::RevolutHotWallet => write!(f, "revolut_hot_wallet"),
CexName::BitStampHotWallet => write!(f, "bitstamp_hot_wallet"),
}
}
}
impl From<CexName> for String {
fn from(cex: CexName) -> Self {
match cex {
CexName::CoinbaseHW1 => "coinbase_hw1".to_string(),
CexName::CoinbaseHW2 => "coinbase_hw2".to_string(),
CexName::CoinbaseHW3 => "coinbase_hw3".to_string(),
CexName::CoinbaseHW4 => "coinbase_hw4".to_string(),
CexName::Coinbase1 => "coinbase_1".to_string(),
CexName::Coinbase2 => "coinbase_2".to_string(),
CexName::Coinbase4 => "coinbase_4".to_string(),
CexName::Coinbase5 => "coinbase_5".to_string(),
CexName::CoinbasePrime => "coinbase_prime".to_string(),
CexName::CoinbaseCW1 => "coinbase_cw1".to_string(),
CexName::CoinbaseCW2 => "coinbase_cw2".to_string(),
CexName::CoinbaseCW3 => "coinbase_cw3".to_string(),
CexName::CoinbaseCW4 => "coinbase_cw4".to_string(),
CexName::CoinbaseCW5 => "coinbase_cw5".to_string(),
CexName::CoinbaseCW6 => "coinbase_cw6".to_string(),
CexName::CoinbaseCW7 => "coinbase_cw7".to_string(),
CexName::CoinbaseCW8 => "coinbase_cw8".to_string(),
CexName::CoinbaseCW9 => "coinbase_cw9".to_string(),
CexName::CoinbaseCW10 => "coinbase_cw10".to_string(),
CexName::CoinbaseCW11 => "coinbase_cw11".to_string(),
CexName::CoinbaseCW12 => "coinbase_cw12".to_string(),
CexName::OKXHW1 => "okx_hw1".to_string(),
CexName::OKXHW2 => "okx_hw2".to_string(),
CexName::OKX => "okx".to_string(),
CexName::OKX2 => "okx_2".to_string(),
CexName::MEXC1 => "mexc_1".to_string(),
CexName::MEXC2 => "mexc_2".to_string(),
CexName::Kraken => "kraken".to_string(),
CexName::KrakenCW => "kraken_cw".to_string(),
CexName::KrakenCW2 => "kraken_cw2".to_string(),
CexName::Binance8 => "binance_8".to_string(),
CexName::Binance1 => "binance_1".to_string(),
CexName::Binance2 => "binance_2".to_string(),
CexName::Binance3 => "binance_3".to_string(),
CexName::BinanceUSHW => "binance_us_hw".to_string(),
CexName::Binance10 => "binance_10".to_string(),
CexName::Binance11 => "binance_11".to_string(),
CexName::BinanceCW => "binance_cw".to_string(),
CexName::BitgetCW => "bitget_cw".to_string(),
CexName::BitgetExchange => "bitget_exchange".to_string(),
CexName::Gateio1 => "gateio_1".to_string(),
CexName::Gateio2 => "gateio_2".to_string(),
CexName::BybitHW => "bybit_hw".to_string(),
CexName::BybitCW => "bybit_cw".to_string(),
CexName::BitfinexHW => "bitfinex_hw".to_string(),
CexName::BitfinexCW => "bitfinex_cw".to_string(),
CexName::KuCoin1 => "kucoin_1".to_string(),
CexName::KuCoin2 => "kucoin_2".to_string(),
CexName::KuCoin3 => "kucoin_3".to_string(),
CexName::KuCoinCW => "kucoin_cw".to_string(),
CexName::PoloniexHW => "poloniex_hw".to_string(),
CexName::LBank => "lbank".to_string(),
CexName::StakecomHotWallet => "stakecom_hot_wallet".to_string(),
CexName::DeBridgeVault => "debridge_vault".to_string(),
CexName::RevolutHotWallet => "revolut_hot_wallet".to_string(),
CexName::BitStampHotWallet => "bitstamp_hot_wallet".to_string(),
}
}
}
impl CexName {
pub fn as_str(&self) -> &'static str {
match self {
CexName::CoinbaseHW1 => "coinbase_hw1",
CexName::CoinbaseHW2 => "coinbase_hw2",
CexName::CoinbaseHW3 => "coinbase_hw3",
CexName::CoinbaseHW4 => "coinbase_hw4",
CexName::Coinbase1 => "coinbase_1",
CexName::Coinbase2 => "coinbase_2",
CexName::Coinbase4 => "coinbase_4",
CexName::Coinbase5 => "coinbase_5",
CexName::CoinbasePrime => "coinbase_prime",
CexName::CoinbaseCW1 => "coinbase_cw1",
CexName::CoinbaseCW2 => "coinbase_cw2",
CexName::CoinbaseCW3 => "coinbase_cw3",
CexName::CoinbaseCW4 => "coinbase_cw4",
CexName::CoinbaseCW5 => "coinbase_cw5",
CexName::CoinbaseCW6 => "coinbase_cw6",
CexName::CoinbaseCW7 => "coinbase_cw7",
CexName::CoinbaseCW8 => "coinbase_cw8",
CexName::CoinbaseCW9 => "coinbase_cw9",
CexName::CoinbaseCW10 => "coinbase_cw10",
CexName::CoinbaseCW11 => "coinbase_cw11",
CexName::CoinbaseCW12 => "coinbase_cw12",
CexName::OKXHW1 => "okx_hw1",
CexName::OKXHW2 => "okx_hw2",
CexName::OKX => "okx",
CexName::OKX2 => "okx_2",
CexName::MEXC1 => "mexc_1",
CexName::MEXC2 => "mexc_2",
CexName::Kraken => "kraken",
CexName::KrakenCW => "kraken_cw",
CexName::KrakenCW2 => "kraken_cw2",
CexName::Binance8 => "binance_8",
CexName::Binance1 => "binance_1",
CexName::Binance2 => "binance_2",
CexName::Binance3 => "binance_3",
CexName::BinanceUSHW => "binance_us_hw",
CexName::Binance10 => "binance_10",
CexName::Binance11 => "binance_11",
CexName::BinanceCW => "binance_cw",
CexName::BitgetCW => "bitget_cw",
CexName::BitgetExchange => "bitget_exchange",
CexName::Gateio1 => "gateio_1",
CexName::Gateio2 => "gateio_2",
CexName::BybitHW => "bybit_hw",
CexName::BybitCW => "bybit_cw",
CexName::BitfinexHW => "bitfinex_hw",
CexName::BitfinexCW => "bitfinex_cw",
CexName::KuCoin1 => "kucoin_1",
CexName::KuCoin2 => "kucoin_2",
CexName::KuCoin3 => "kucoin_3",
CexName::KuCoinCW => "kucoin_cw",
CexName::PoloniexHW => "poloniex_hw",
CexName::LBank => "lbank",
CexName::StakecomHotWallet => "stakecom_hot_wallet",
CexName::DeBridgeVault => "debridge_vault",
CexName::RevolutHotWallet => "revolut_hot_wallet",
CexName::BitStampHotWallet => "bitstamp_hot_wallet",
}
}
}

121
src/model/graph.rs Normal file
View file

@ -0,0 +1,121 @@
use std::collections::HashMap;
use std::sync::Arc;
use petgraph::prelude::*;
use petgraph::Graph;
use serde::Deserialize;
use serde::Serialize;
use solana_pubkey::Pubkey;
use tokio::sync::RwLock;
use crate::model::cex::Cex;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddressNode {
pub address: solana_pubkey::Pubkey,
pub total_received: f64,
pub total_balance: f64,
pub is_cex: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionEdge {
pub from: solana_pubkey::Pubkey,
pub to: solana_pubkey::Pubkey,
pub amount: f64,
pub timestamp: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CreatorConnectionGraph {
graph: Graph<AddressNode, TransactionEdge>,
#[serde(skip)]
node_indices: HashMap<Pubkey, NodeIndex>,
}
impl CreatorConnectionGraph {
pub fn new() -> Self {
Self {
graph: Graph::new(),
node_indices: HashMap::new(),
}
}
// Rebuild the node_indices HashMap from the graph (useful after deserialization)
pub fn rebuild_indices(&mut self) {
self.node_indices.clear();
for node_index in self.graph.node_indices() {
if let Some(node) = self.graph.node_weight(node_index) {
self.node_indices.insert(node.address, node_index);
}
}
}
// Ensure indices are available (rebuild if empty and graph has nodes)
fn ensure_indices(&mut self) {
if self.node_indices.is_empty() && self.graph.node_count() > 0 {
self.rebuild_indices();
}
}
pub fn get_node_count(&self) -> usize {
self.graph.node_count()
}
pub fn get_edge_count(&self) -> usize {
self.graph.edge_count()
}
// Get all nodes in the graph
pub fn get_nodes(&self) -> Vec<AddressNode> {
self.graph.node_weights().map(|node| node.clone()).collect()
}
// Get all edges in the graph
pub fn get_edges(&self) -> Vec<TransactionEdge> {
self.graph.edge_weights().map(|edge| edge.clone()).collect()
}
}
// Thread-safe wrapper for the graph
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SharedCreatorConnectionGraph {
#[serde(skip)]
inner: Arc<RwLock<CreatorConnectionGraph>>,
}
impl SharedCreatorConnectionGraph {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(CreatorConnectionGraph::new())),
}
}
pub async fn get_node_count(&self) -> usize {
self.inner.read().await.get_node_count()
}
pub async fn get_edge_count(&self) -> usize {
self.inner.read().await.get_edge_count()
}
pub async fn clone_graph(&self) -> CreatorConnectionGraph {
let mut graph = self.inner.read().await.clone();
// Ensure indices are rebuilt after cloning (since they're skipped in serialization)
graph.rebuild_indices();
graph
}
// Method to ensure indices are available (useful after deserialization)
pub async fn ensure_indices(&self) {
self.inner.write().await.ensure_indices();
}
}
impl From<CreatorConnectionGraph> for SharedCreatorConnectionGraph {
fn from(graph: CreatorConnectionGraph) -> Self {
Self {
inner: Arc::new(RwLock::new(graph)),
}
}
}

3
src/model/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod cex;
pub mod graph;
pub mod token;

48
src/model/token.rs Normal file
View file

@ -0,0 +1,48 @@
use super::graph::SharedCreatorConnectionGraph;
use serde::{Deserialize, Serialize};
// Type for new token created event (matches NewTokenCache from muhafidh)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewTokenCreatedData {
pub mint: solana_pubkey::Pubkey,
pub bonding_curve: Option<solana_pubkey::Pubkey>,
pub name: String,
pub symbol: String,
pub uri: String,
pub creator: solana_pubkey::Pubkey,
pub created_at: u64, // Unix timestamp in seconds
}
// Type for token CEX updated event (matches TokenAnalyzedCache from muhafidh)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenCexUpdatedData {
pub mint: String, // Mint address as string
pub name: String,
pub uri: String,
pub dev_name: String, // DevName from muhafidh
pub creator: String, // Creator address as string
pub cex_name: String,
pub cex_address: String,
pub bonding_curve: String, // Bonding curve address as string
pub created_at: u64, // Unix timestamp in seconds
pub updated_at: u64, // Unix timestamp in seconds
pub node_count: usize,
pub edge_count: usize,
pub graph: SharedCreatorConnectionGraph,
}
// Type for max depth reached event (same structure as TokenAnalyzedCache)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaxDepthReachedData {
pub mint: String, // Mint address as string
pub name: String,
pub uri: String,
pub dev_name: String, // DevName from muhafidh
pub creator: String, // Creator address as string
pub bonding_curve: String, // Bonding curve address as string
pub created_at: u64, // Unix timestamp in seconds
pub updated_at: u64, // Unix timestamp in seconds
pub node_count: usize,
pub edge_count: usize,
pub graph: SharedCreatorConnectionGraph,
}

26
src/storage/mod.rs Normal file
View file

@ -0,0 +1,26 @@
use std::sync::Arc;
pub mod redis;
use crate::config::Config;
use crate::error::Result;
use crate::storage::redis::RedisStorage;
use tracing::error;
#[derive(Clone)]
pub struct StorageEngine {
pub redis: Arc<RedisStorage>,
}
impl StorageEngine {
pub async fn new(config: Config) -> Result<Self> {
let redis_storage = Arc::new(RedisStorage::new(&config.storage_redis).await.map_err(
|e| {
error!("failed_to_create_redis_storage: {}", e);
e
},
)?);
Ok(Self {
redis: redis_storage,
})
}
}

73
src/storage/redis.rs Normal file
View file

@ -0,0 +1,73 @@
use bb8::Pool;
use bb8_redis::RedisConnectionManager;
use redis::aio::PubSub;
use tracing::{error, info, instrument};
use crate::config::StorageRedisConfig;
use crate::err_with_loc;
use crate::error::app::AppError;
use crate::error::Result;
pub type RedisPool = Pool<RedisConnectionManager>;
#[derive(Clone)]
pub struct RedisStorage {
pub pool: RedisPool,
redis_url: String, // Store the URL to create new connections
}
impl RedisStorage {
#[instrument(level = "debug")]
pub async fn new(config: &StorageRedisConfig) -> Result<Self> {
let redis_url = format!("redis://{}:{}/?protocol=resp3", config.host, config.port);
let manager = RedisConnectionManager::new(redis_url.clone()).map_err(|e| {
error!("failed_to_create_redis_manager: {}", e);
err_with_loc!(AppError::Redis(format!(
"failed_to_create_redis_manager: {}",
e
)))
})?;
let pool = bb8::Pool::builder()
.max_size(config.pool_size)
.build(manager)
.await
.map_err(|e| {
error!("failed_to_create_redis_pool: {}", e);
err_with_loc!(AppError::Redis(format!(
"failed_to_create_redis_pool: {}",
e
)))
})?;
info!("redis::connection_established");
Ok(Self {
pool,
redis_url,
})
}
/// Create a new independent PubSub connection for subscribers
/// Each subscriber should use its own connection to avoid blocking
pub async fn create_pubsub_connection(&self) -> Result<PubSub> {
let client = redis::Client::open(self.redis_url.clone()).map_err(|e| {
error!("failed_to_create_redis_client_for_pubsub: {}", e);
err_with_loc!(AppError::Redis(format!(
"failed_to_create_redis_client_for_pubsub: {}",
e
)))
})?;
let pubsub = client.get_async_pubsub().await.map_err(|e| {
error!("failed_to_get_new_pubsub_connection: {}", e);
err_with_loc!(AppError::Redis(format!(
"failed_to_get_new_pubsub_connection: {}",
e
)))
})?;
Ok(pubsub)
}
}

3
src/task/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod shutdown;
pub mod subscriber;
pub mod ui;

33
src/task/shutdown.rs Normal file
View file

@ -0,0 +1,33 @@
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use tokio::sync::Notify;
#[derive(Debug, Clone)]
pub struct ShutdownSignal {
pub signal: Arc<Notify>,
shutdown_triggered: Arc<AtomicBool>,
}
impl ShutdownSignal {
pub fn new() -> Self {
Self {
signal: Arc::new(Notify::new()),
shutdown_triggered: Arc::new(AtomicBool::new(false)),
}
}
pub fn shutdown(&self) {
self.shutdown_triggered.store(true, Ordering::SeqCst);
self.signal.notify_waiters();
}
pub fn is_shutdown(&self) -> bool {
self.shutdown_triggered.load(Ordering::SeqCst)
}
pub async fn wait_for_shutdown(&self) {
self.signal.notified().await;
}
}

186
src/task/subscriber.rs Normal file
View file

@ -0,0 +1,186 @@
use crate::err_with_loc;
use crate::error::app::AppError;
use crate::error::Result;
use crate::handler::token::TokenMetadataHandlerOperator;
use crate::model::token::{MaxDepthReachedData, NewTokenCreatedData, TokenCexUpdatedData};
use crate::slint_ui::{MainWindow, NewTokenUiData};
use crate::storage::StorageEngine;
use futures_util::StreamExt;
use slint::Weak;
use std::sync::Arc;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, warn};
// ✅ Simplified subscriber that creates its own PubSub connection
pub fn spawn_new_token_subscriber(
token_handler: Arc<TokenMetadataHandlerOperator>,
db: Arc<StorageEngine>,
cancellation_token: CancellationToken,
) -> JoinHandle<Result<()>> {
tokio::spawn(async move {
debug!("new_token_subscriber::starting");
// ✅ Create dedicated PubSub connection for this subscriber
let mut pubsub = db.redis.create_pubsub_connection().await?;
if let Err(e) = pubsub.subscribe("new_token_created").await {
error!("failed_to_subscribe_to_new_token_created: {}", e);
return Err(err_with_loc!(AppError::Redis(format!(
"failed_to_subscribe_to_new_token_created: {}",
e
))));
}
// Create a channel for buffering messages - with good capacity for performance
let (buffer_tx, mut buffer_rx) = mpsc::channel::<NewTokenCreatedData>(1000);
info!("new_token_subscriber::subscribed_successfully");
let mut msg_stream = pubsub.on_message();
let cancellation_token = cancellation_token.clone();
loop {
tokio::select! {
Some(token) = buffer_rx.recv() => {
// ✅ Just call the existing handler - no duplication!
if let Err(e) = token_handler.process_new_token(token.clone()).await {
error!("failed_to_send_token_to_token_handler::mint::{}::error::{}", token.mint.clone(), e);
}
},
Some(message) = msg_stream.next() => {
if let Ok(msg) = message.get_payload::<String>() {
if let Ok(token) = serde_json::from_str::<NewTokenCreatedData>(&msg) {
debug!("new_token_received::mint::{}::name::{}::creator::{}",
token.mint, token.name, token.creator);
if let Err(e) = buffer_tx.try_send(token.clone()) {
error!("failed_to_send_token_to_buffer::mint::{}::error::{}", token.mint, e);
}
}
}
},
_ = cancellation_token.cancelled() => {
warn!("new_token_subscriber::shutdown_signal_received");
break;
}
}
}
info!("new_token_subscriber::ended");
Ok(())
})
}
pub fn spawn_token_cex_updated_subscriber(
token_handler: Arc<TokenMetadataHandlerOperator>,
db: Arc<StorageEngine>,
cancellation_token: CancellationToken,
) -> JoinHandle<Result<()>> {
tokio::spawn(async move {
debug!("token_cex_updated_subscriber::starting");
// ✅ Create dedicated PubSub connection for this subscriber
let mut pubsub = db.redis.create_pubsub_connection().await?;
if let Err(e) = pubsub.subscribe("token_cex_updated").await {
error!("failed_to_subscribe_to_token_cex_updated: {}", e);
return Err(err_with_loc!(AppError::Redis(format!(
"failed_to_subscribe_to_token_cex_updated: {}",
e
))));
}
// Create a channel for buffering messages - with good capacity for performance
let (buffer_tx, mut buffer_rx) = mpsc::channel::<TokenCexUpdatedData>(1000);
info!("token_cex_updated_subscriber::subscribed_successfully");
let mut msg_stream = pubsub.on_message();
let cancellation_token = cancellation_token.clone();
loop {
tokio::select! {
Some(token) = buffer_rx.recv() => {
// ✅ Just call the existing handler - no duplication!
if let Err(e) = token_handler.process_cex_updated(token.clone()).await {
error!("failed_to_send_cex_updated_to_token_handler::mint::{}::error::{}", token.mint, e);
}
},
Some(message) = msg_stream.next() => {
if let Ok(msg) = message.get_payload::<String>() {
if let Ok(token) = serde_json::from_str::<TokenCexUpdatedData>(&msg) {
debug!("token_cex_updated_received::mint::{}::name::{}::cex::{}",
token.mint, token.name, token.cex_name);
if let Err(e) = buffer_tx.try_send(token.clone()) {
error!("failed_to_send_cex_updated_to_buffer::mint::{}::error::{}", token.mint, e);
}
}
}
},
_ = cancellation_token.cancelled() => {
warn!("token_cex_updated_subscriber::shutdown_signal_received");
break;
}
}
}
info!("token_cex_updated_subscriber::ended");
Ok(())
})
}
pub fn spawn_max_depth_reached_subscriber(
token_handler: Arc<TokenMetadataHandlerOperator>,
db: Arc<StorageEngine>,
cancellation_token: CancellationToken,
) -> JoinHandle<Result<()>> {
tokio::spawn(async move {
debug!("max_depth_reached_subscriber::starting");
// ✅ Create dedicated PubSub connection for this subscriber
let mut pubsub = db.redis.create_pubsub_connection().await?;
if let Err(e) = pubsub.subscribe("max_depth_reached").await {
error!("failed_to_subscribe_to_max_depth_reached: {}", e);
return Err(err_with_loc!(AppError::Redis(format!(
"failed_to_subscribe_to_max_depth_reached: {}",
e
))));
}
// Create a channel for buffering messages - with good capacity for performance
let (buffer_tx, mut buffer_rx) = mpsc::channel::<MaxDepthReachedData>(1000);
info!("max_depth_reached_subscriber::subscribed_successfully");
let mut msg_stream = pubsub.on_message();
let cancellation_token = cancellation_token.clone();
loop {
tokio::select! {
Some(token) = buffer_rx.recv() => {
// ✅ Just call the existing handler - no duplication!
if let Err(e) = token_handler.process_max_depth_reached(token.clone()).await {
error!("failed_to_send_max_depth_reached_to_token_handler::mint::{}::error::{}", token.mint, e);
}
},
Some(message) = msg_stream.next() => {
if let Ok(msg) = message.get_payload::<String>() {
if let Ok(token) = serde_json::from_str::<MaxDepthReachedData>(&msg) {
debug!("max_depth_reached_received::mint::{}::name::{}::nodes::{}::edges::{}",
token.mint, token.name, token.node_count, token.edge_count);
if let Err(e) = buffer_tx.try_send(token.clone()) {
error!("failed_to_send_max_depth_reached_to_buffer::mint::{}::error::{}", token.mint, e);
}
}
}
},
_ = cancellation_token.cancelled() => {
warn!("max_depth_reached_subscriber::shutdown_signal_received");
break;
}
}
}
info!("max_depth_reached_subscriber::ended");
Ok(())
})
}

147
src/task/ui.rs Normal file
View file

@ -0,0 +1,147 @@
use i_slint_backend_winit::WinitWindowAccessor;
use slint::Weak;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use crate::error::Result;
use crate::get_current_unix_timestamp;
use crate::handler::ui::SlintHandlerUiOperator;
use crate::slint_ui::*;
use crate::{err_with_loc, error::app::AppError};
use std::sync::Arc;
use tracing::{error, info};
pub fn spawn_ui_task(
slint_handler: Arc<SlintHandlerUiOperator>,
ui_weak: Weak<MainWindow>,
cancellation_token: CancellationToken,
shutdown_tx: tokio::sync::mpsc::Sender<()>,
) -> JoinHandle<Result<()>> {
tokio::spawn(async move {
ui_weak
.clone()
.upgrade_in_event_loop(move |ui| {
// Window dragging
ui.on_start_drag_window({
let ui_weak = ui.as_weak();
move || {
let _ = ui_weak.upgrade_in_event_loop(|ui| {
let _ = ui.window().with_winit_window(
|winit_window: &winit::window::Window| {
let _ = winit_window.drag_window();
},
);
});
}
});
// Window minimize
ui.on_minimize_window({
let ui_weak = ui_weak.clone();
move || {
let _ = ui_weak.upgrade_in_event_loop(|ui| {
let _ = ui.window().with_winit_window(
|winit_window: &winit::window::Window| {
winit_window.set_minimized(true);
},
);
});
}
});
// Window maximize/restore
ui.on_maximize_window({
let ui_weak = ui_weak.clone();
move || {
let _ = ui_weak.upgrade_in_event_loop(|ui| {
let _ = ui.window().with_winit_window(
|winit_window: &winit::window::Window| {
let is_maximized = winit_window.is_maximized();
winit_window.set_maximized(!is_maximized);
},
);
});
}
});
// Theme toggle
ui.on_theme_toggle_clicked({
let ui_weak = ui_weak.clone();
move || {
let _ = ui_weak.upgrade_in_event_loop(|ui| {
ui.invoke_toggle_theme();
info!("Theme toggled");
});
}
});
// Navigation callback
ui.on_navigation_changed({
move |page| {
info!("Navigation changed to: {}", page);
}
});
let new_tokens_handler = slint_handler.clone();
ui.on_clear_new_tokens(move || {
let handler = new_tokens_handler.clone();
handler.clear_new_tokens();
});
let cex_tokens_handler = slint_handler.clone();
ui.on_clear_cex_tokens(move || {
let handler = cex_tokens_handler.clone();
handler.clear_cex_tokens();
});
let analysis_tokens_handler = slint_handler.clone();
ui.on_clear_analysis_tokens(move || {
let handler = analysis_tokens_handler.clone();
handler.clear_analysis_tokens();
});
// Other callbacks (placeholder implementations)
ui.on_logout_clicked({
move || {
info!("Logout clicked");
}
});
// Window close - with proper task cleanup
ui.on_close_window({
let ui_weak = ui_weak.clone();
let shutdown_tx = shutdown_tx.clone();
let cancellation_token = cancellation_token.clone();
move || {
let _ = ui_weak.upgrade_in_event_loop({
let shutdown_tx = shutdown_tx.clone();
let cancellation_token = cancellation_token.clone();
move |ui| {
info!("close_window::shutting_down_all_tasks");
// Signal shutdown to all subscriber tasks
cancellation_token.cancel();
// Hide window immediately for responsive UI
let _ = ui.window().hide();
// Send shutdown signal
let _ = shutdown_tx.try_send(());
info!("close_window::all_tasks_cleaned_up");
// Quit the event loop after cleanup
let _ = slint::quit_event_loop();
}
});
}
});
})
.map_err(|e| {
error!("failed_to_setup_ui_callbacks: {}", e);
err_with_loc!(AppError::Slint(format!(
"failed_to_setup_ui_callbacks: {}",
e
)))
})
})
}

73
src/tracing/filter.rs Normal file
View file

@ -0,0 +1,73 @@
use tracing::Level;
use tracing::Metadata;
use tracing_subscriber::layer::Context;
use tracing_subscriber::layer::Filter;
use tracing_subscriber::registry::LookupSpan;
// Custom filter for exact debug level matching
pub struct DebugOnlyFilter;
impl<S> Filter<S> for DebugOnlyFilter
where
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
let target = meta.target();
meta.level() == &Level::DEBUG && target.starts_with("ziya")
}
}
// Custom filter for error and warn levels
pub struct ErrorWarnFilter;
impl<S> Filter<S> for ErrorWarnFilter
where
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
let target = meta.target();
(meta.level() == &Level::ERROR || meta.level() == &Level::WARN)
&& target.starts_with("ziya")
}
}
// Custom filter for info levels
#[cfg(feature = "dev")]
pub struct InfoOnlyFilter;
#[cfg(feature = "dev")]
impl<S> Filter<S> for InfoOnlyFilter
where
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
let target = meta.target();
meta.level() == &Level::INFO && target.starts_with("ziya")
}
}
// Custom filter for error levels
pub struct ErrorOnlyFilter;
impl<S> Filter<S> for ErrorOnlyFilter
where
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
let target = meta.target();
meta.level() == &Level::ERROR && target.starts_with("ziya")
}
}
// Custom filter for warn levels
pub struct WarnOnlyFilter;
impl<S> Filter<S> for WarnOnlyFilter
where
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
{
fn enabled(&self, meta: &Metadata<'_>, _ctx: &Context<'_, S>) -> bool {
let target = meta.target();
meta.level() == &Level::WARN && target.starts_with("ziya")
}
}

60
src/tracing/format.rs Normal file
View file

@ -0,0 +1,60 @@
use tracing::Event;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::FmtContext;
use tracing_subscriber::fmt::FormatEvent;
use tracing_subscriber::fmt::FormatFields;
use tracing_subscriber::registry::LookupSpan;
pub struct ZiyaFormat {
pub app_name: String,
}
// Implement Clone for ZiyaFormat
impl Clone for ZiyaFormat {
fn clone(&self) -> Self {
Self {
app_name: self.app_name.clone(),
}
}
}
impl<S, N> FormatEvent<S, N> for ZiyaFormat
where
S: tracing::Subscriber + for<'lookup> LookupSpan<'lookup>,
N: for<'writer> FormatFields<'writer> + 'static,
{
fn format_event(
&self,
ctx: &FmtContext<'_, S, N>,
mut writer: Writer<'_>,
event: &Event<'_>,
) -> std::fmt::Result {
// To get the message, we need to format the fields with a special visitor
let metadata = event.metadata();
let file = metadata.file().unwrap_or("unknown");
let line = metadata.line().unwrap_or(0);
if file == "unknown" && !cfg!(feature = "deep-trace") {
return Ok(());
}
let utc_timestamp = chrono::Utc::now();
let jakarta_timestamp = utc_timestamp.with_timezone(&chrono_tz::Asia::Jakarta);
let timestamp = jakarta_timestamp.format("%Y-%m-%d %H:%M:%S");
write!(
writer,
"{} {}::{}::{}::{}::",
metadata.level(),
timestamp,
self.app_name,
file,
line
)?;
// Format the actual message
ctx.field_format().format_fields(writer.by_ref(), event)?;
writeln!(writer)
}
}

187
src/tracing/mod.rs Normal file
View file

@ -0,0 +1,187 @@
pub mod filter;
pub mod format;
pub use tracing::*;
use std::path::Path;
use self::filter::DebugOnlyFilter;
use self::filter::ErrorOnlyFilter;
use self::filter::ErrorWarnFilter;
#[cfg(feature = "dev")]
use self::filter::InfoOnlyFilter;
use self::format::ZiyaFormat;
use tracing_appender::rolling::RollingFileAppender;
use tracing_appender::rolling::Rotation;
use tracing_subscriber::prelude::*;
use tracing_subscriber::Layer;
use crate::config::Config;
use crate::err_with_loc;
use crate::error::app::AppError;
use crate::error::Result;
pub async fn setup_tracing(config: Config, app_name: &str) -> Result<()> {
// Get logging config
let logging_config = config.logging;
// Base logs directory
let base_logs_dir = Path::new(&logging_config.directory);
// Create logs directories if they don't exist
let logs_dirs = [
base_logs_dir,
&base_logs_dir.join("debug"),
&base_logs_dir.join("error"),
];
for dir in &logs_dirs {
if !dir.exists() {
std::fs::create_dir_all(dir).map_err(|e| {
err_with_loc!(AppError::Config(format!(
"Failed to create logs directory {}: {}",
dir.display(),
e
)))
})?;
}
}
// Create file appenders for each log level
#[cfg(feature = "dev")]
let info_appender =
RollingFileAppender::new(Rotation::DAILY, base_logs_dir, format!("{}.log", app_name));
let debug_appender = RollingFileAppender::new(
Rotation::DAILY,
base_logs_dir.join("debug"),
format!("{}.log", app_name),
);
let error_appender = RollingFileAppender::new(
Rotation::DAILY,
base_logs_dir.join("error"),
format!("{}.log", app_name),
);
// Create non-blocking writers
#[cfg(feature = "dev")]
let (non_blocking_info, info_guard) = tracing_appender::non_blocking(info_appender);
let (non_blocking_debug, debug_guard) = tracing_appender::non_blocking(debug_appender);
let (non_blocking_error, error_guard) = tracing_appender::non_blocking(error_appender);
// Store the guards in statics to keep them alive
#[cfg(feature = "dev")]
static mut INFO_GUARD: Option<tracing_appender::non_blocking::WorkerGuard> = None;
static mut DEBUG_GUARD: Option<tracing_appender::non_blocking::WorkerGuard> = None;
static mut ERROR_GUARD: Option<tracing_appender::non_blocking::WorkerGuard> = None;
// Create the custom format for all outputs
let format = ZiyaFormat {
app_name: app_name.to_string(),
};
// Set up the registry with all outputs
let subscriber = tracing_subscriber::registry()
// DEBUG log file - debug only using custom filter
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(false)
.with_file(true)
.with_line_number(true)
.with_target(false)
.event_format(format.clone())
.with_writer(non_blocking_debug)
.with_filter(DebugOnlyFilter),
)
// ERROR log file - warn and error only
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(false)
.with_file(true)
.with_line_number(true)
.with_target(false)
.event_format(format.clone())
.with_writer(non_blocking_error)
.with_filter(ErrorWarnFilter),
);
#[cfg(feature = "prod")]
let subscriber = subscriber
// Terminal output with custom ZiyaFormat - Error only in production
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(true)
.with_file(true)
.with_line_number(true)
.with_target(false)
.event_format(format.clone())
.with_filter(ErrorOnlyFilter),
);
#[cfg(feature = "dev")]
let subscriber = subscriber
// Terminal output with custom ZiyaFormat - INFO and above in development
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(true)
.with_file(true)
.with_line_number(true)
.with_target(false)
.event_format(format.clone())
.with_filter(InfoOnlyFilter),
)
// INFO log file - info and above
.with(
tracing_subscriber::fmt::Layer::default()
.with_ansi(false)
.with_file(true)
.with_line_number(true)
.with_target(false)
.event_format(format.clone())
.with_writer(non_blocking_info)
.with_filter(InfoOnlyFilter),
);
// Set the subscriber as the global default
match tracing::subscriber::set_global_default(subscriber) {
Ok(_) => {
// Store the guards to keep the loggers alive
#[cfg(feature = "dev")]
unsafe {
INFO_GUARD = Some(info_guard);
}
unsafe {
DEBUG_GUARD = Some(debug_guard);
ERROR_GUARD = Some(error_guard);
}
tracing::info!(
"{}_logging_started::info_logs::{}\\{}.log",
app_name,
base_logs_dir.display(),
app_name
);
tracing::info!(
"{}_logging_started::debug_logs::{}\\debug\\{}.log",
app_name,
base_logs_dir.display(),
app_name
);
tracing::info!(
"{}_logging_started::error_logs::{}\\error\\{}.log",
app_name,
base_logs_dir.display(),
app_name
);
Ok(())
}
Err(e) => {
error!("failed_to_setup_tracing: {}", e);
Err(err_with_loc!(AppError::Config(format!(
"Failed to setup tracing: {}",
e
))))
}
}
}

View file

@ -1,325 +1,268 @@
// App Layer - Main Application Entry Point // App Layer - Main Application Entry Point
// This is the root of the application following Feature-Sliced Design // This is the root of the application following Feature-Sliced Design
// No std-widgets needed since we're using Layout components // Import std-widgets for Palette
import { Palette } from "std-widgets.slint";
// Import from shared layer (design system, UI kit) import { NewTokenUiData, CexUpdatedUiData, MaxDepthReachedUiData } from "../shared/types/token.slint";
import { Theme } from "../shared/design-system/tokens/theme.slint";
// Import widgets (standalone UI blocks) // Import widgets (standalone UI blocks)
import { NavigationWidget } from "../widgets/navigation/index.slint"; import { NavigationWidget } from "../widgets/navigation/index.slint";
import { TitleBar } from "../widgets/window-controls/ui/title-bar.slint";
// Import pages // Import pages
import { Dashboard } from "../pages/dashboard/index.slint"; import { Dashboard } from "../pages/dashboard/index.slint";
import { HuntingGroundPage } from "../pages/hunting-ground/index.slint";
import { TradingPage } from "../pages/trading/index.slint";
import { PortfolioPage } from "../pages/portfolio/index.slint";
import { MarketsPage } from "../pages/markets/index.slint";
import { LoginView } from "../pages/auth/index.slint";
import { LoadingView } from "../shared/ui/index.slint";
export component App { export component App inherits Window {
width: 100%; title: "Ziya - One Stop Shop for Your Trading Habit";
height: 100%; min-width: 1280px;
min-height: 720px;
// Application state // Disable default background
background: transparent;
// Theme state
in-out property <bool> is-dark-mode: false;
// Application state management
in-out property <string> app-state: "loading"; // "loading", "login", "authenticated"
in-out property <bool> is-authenticated: false;
in-out property <string> user-email: "";
in-out property <string> sidebar-state: "full"; // "full", "icon-only", or "hidden"
// Loading state
in-out property <bool> is-loading: true;
in-out property <bool> has-connection-error: false;
in-out property <string> loading-status: "Initializing your trading environment...";
// Navigation state
in-out property <string> current-page: "Dashboard"; in-out property <string> current-page: "Dashboard";
in-out property <string> user-initials: "JD"; in-out property <string> user-initials: "JD";
// Callbacks // Token data
callback navigation-changed(string); in-out property <[NewTokenUiData]> new-tokens: [];
callback theme-toggle-clicked(); in-out property <[CexUpdatedUiData]> cex-tokens: [];
callback logout-clicked(); in-out property <[MaxDepthReachedUiData]> analysis-tokens: [];
callback buy-clicked(); in-out property <int> current-time: 0;
callback sell-clicked();
// Callbacks for application state
callback health-check-completed(bool); // true if healthy, false if error
callback retry-health-check();
callback login-attempt(string, string);
callback logout-requested();
callback authenticate-user(string);
// Callbacks for window controls
callback start-drag-window(); callback start-drag-window();
callback minimize-window(); callback minimize-window();
callback maximize-window(); callback maximize-window();
callback close-window(); callback close-window();
callback theme-toggle-clicked();
// Global drag area - covers entire window but sits behind interactive elements // Callbacks for navigation
TouchArea { callback navigation-changed(string);
callback toggle-sidebar();
// Callbacks for hunting ground
callback refresh-hunting-ground();
callback clear-column(string);
// Other callbacks
callback logout-clicked();
callback buy-clicked();
callback sell-clicked();
callback clear-new-tokens();
callback clear-cex-tokens();
callback clear-analysis-tokens();
Rectangle {
background: Palette.background;
width: 100%; width: 100%;
height: 100%; height: 100%;
z: -1;
moved => { // Apply overflow hidden to prevent scrolling
if (self.pressed) { clip: true;
root.start-drag-window();
} // Loading Screen
if app-state == "loading": LoadingView {
is-loading: root.is-loading;
has-error: root.has-connection-error;
status-text: root.loading-status;
retry-connection => {
root.retry-health-check();
} }
} }
VerticalLayout { // Login Screen
if app-state == "login": VerticalLayout {
spacing: 0px; spacing: 0px;
// Title Bar - fixed height // Title Bar (fixed height)
Rectangle { TitleBar {
height: 32px; height: 40px;
background: Theme.surface; is-dark-theme: root.is-dark-mode;
minimize-window => {
HorizontalLayout { root.minimize-window();
padding: 4px; }
spacing: 8px; maximize-window => {
root.maximize-window();
// Draggable area (left side) - takes most of the space }
Rectangle { close-window => {
horizontal-stretch: 1; root.close-window();
}
Text { toggle-theme => {
text: "Ziya Trading Platform"; root.is-dark-mode = !root.is-dark-mode;
color: Theme.text-primary; if (root.is-dark-mode) {
font-size: 14px; Palette.color-scheme = ColorScheme.dark;
font-weight: 600; } else {
vertical-alignment: center; Palette.color-scheme = ColorScheme.light;
x: 8px; }
root.theme-toggle-clicked();
} }
} }
// Theme Toggle Button - fixed size LoginView {
Rectangle { login-clicked(email, password) => {
width: 40px; root.login-attempt(email, password);
height: 20px;
border-radius: 10px;
background: Theme.is-dark-mode ? Theme.primary : Theme.border;
Rectangle {
width: 16px;
height: 16px;
border-radius: 8px;
background: white;
x: Theme.is-dark-mode ? 22px : 2px;
y: 2px;
animate x { duration: 200ms; easing: ease-in-out; }
} }
TouchArea { navigate-to-dashboard => {
clicked => { // Demo mode - skip login
theme-toggle-clicked(); root.authenticate-user("demo@ziya.trading");
}
back-to-loading => {
root.app-state = "loading";
root.retry-health-check();
} }
} }
} }
// Window Controls - fixed size // Main Application (Authenticated State)
HorizontalLayout { if app-state == "authenticated": VerticalLayout {
spacing: 2px; spacing: 0px;
// Title Bar (fixed height)
// Minimize Button TitleBar {
Rectangle { height: 40px;
width: 28px; is-dark-theme: root.is-dark-mode;
height: 24px; minimize-window => {
border-radius: 2px; root.minimize-window();
background: minimize-area.pressed ? #30404040 : minimize-area.has-hover ? #20404040 : transparent;
Text {
text: "";
color: Theme.text-primary;
font-size: 14px;
font-weight: 400;
horizontal-alignment: center;
vertical-alignment: center;
} }
maximize-window => {
minimize-area := TouchArea { root.maximize-window();
clicked => { minimize-window(); } }
close-window => {
root.close-window();
}
toggle-theme => {
root.is-dark-mode = !root.is-dark-mode;
if (root.is-dark-mode) {
Palette.color-scheme = ColorScheme.dark;
} else {
Palette.color-scheme = ColorScheme.light;
}
root.theme-toggle-clicked();
} }
} }
// Maximize Button // Main Content Area (stretches to fill remaining space)
Rectangle {
width: 28px;
height: 24px;
border-radius: 2px;
background: maximize-area.pressed ? #30404040 : maximize-area.has-hover ? #20404040 : transparent;
Text {
text: "□";
color: Theme.text-primary;
font-size: 12px;
font-weight: 400;
horizontal-alignment: center;
vertical-alignment: center;
}
maximize-area := TouchArea {
clicked => { maximize-window(); }
}
}
// Close Button
Rectangle {
width: 28px;
height: 24px;
border-radius: 2px;
background: close-area.pressed ? #E53E3E : close-area.has-hover ? #C53030 : transparent;
Text {
text: "×";
color: close-area.has-hover ? white : Theme.text-primary;
font-size: 16px;
font-weight: 400;
horizontal-alignment: center;
vertical-alignment: center;
}
close-area := TouchArea {
clicked => { close-window(); }
}
}
}
}
}
// Main content area - stretches to fill remaining vertical space
HorizontalLayout { HorizontalLayout {
spacing: 0px; spacing: 0px;
// Navigation Widget (Sidebar) - fixed width // Navigation Sidebar (different states)
NavigationWidget { if sidebar-state == "full": NavigationWidget {
width: 280px; width: 280px;
current-page: root.current-page; current-page: root.current-page;
user-initials: root.user-initials; user-initials: root.user-initials;
sidebar-state: root.sidebar-state;
navigation-changed(page) => { navigation-changed(page) => {
root.current-page = page; root.current-page = page;
root.navigation-changed(page); root.navigation-changed(page);
} }
logout-clicked => {
logout-clicked => { root.logout-clicked(); } root.logout-requested();
}
toggle-sidebar => {
root.sidebar-state = "icon-only";
}
} }
// Main content area - stretches to fill remaining horizontal space if sidebar-state == "icon-only": NavigationWidget {
Rectangle { width: 80px;
background: Theme.background; current-page: root.current-page;
user-initials: root.user-initials;
sidebar-state: root.sidebar-state;
navigation-changed(page) => {
root.current-page = page;
root.navigation-changed(page);
}
logout-clicked => {
root.logout-requested();
}
toggle-sidebar => {
root.sidebar-state = "full";
}
}
// Page routing based on current-page // Page Content (stretches to fill remaining space)
Rectangle {
background: Palette.background;
// Content container with proper centering
VerticalLayout {
alignment: stretch;
// Route to different pages
if current-page == "Dashboard": Dashboard { if current-page == "Dashboard": Dashboard {
user-initials: root.user-initials; user-initials: root.user-initials;
logout => { root.logout-clicked(); } logout => {
trade-buy(asset, amount) => { root.buy-clicked(); } root.logout-requested();
trade-sell(asset, amount) => { root.sell-clicked(); } }
} }
if current-page == "Trading": Rectangle { if current-page == "Hunting Ground": HuntingGroundPage {
background: Theme.background; new-tokens: root.new-tokens;
cex-tokens: root.cex-tokens;
analysis-tokens: root.analysis-tokens;
current-time: root.current-time;
VerticalLayout { clear-new-tokens => {
padding: 24px; root.clear-new-tokens();
spacing: 16px;
alignment: center;
Text {
text: "🎯 Trading Interface";
font-size: 32px;
font-weight: 700;
color: Theme.text-primary;
horizontal-alignment: center;
} }
Text { clear-cex-tokens => {
text: "Advanced trading features coming soon..."; root.clear-cex-tokens();
font-size: 18px; }
color: Theme.text-secondary;
horizontal-alignment: center; clear-analysis-tokens => {
root.clear-analysis-tokens();
}
}
if current-page == "Trading": TradingPage {
buy-clicked => {
root.buy-clicked();
}
sell-clicked => {
root.sell-clicked();
}
}
if current-page == "Portfolio": PortfolioPage {
}
if current-page == "Markets": MarketsPage {
}
}
}
} }
} }
} }
if current-page == "Portfolio": Rectangle {
background: Theme.background;
VerticalLayout {
padding: 24px;
spacing: 16px;
alignment: center;
Text {
text: "💼 Portfolio Management";
font-size: 32px;
font-weight: 700;
color: Theme.text-primary;
horizontal-alignment: center;
}
Text {
text: "Portfolio tracking and management features coming soon...";
font-size: 18px;
color: Theme.text-secondary;
horizontal-alignment: center;
}
}
}
if current-page == "Markets": Rectangle {
background: Theme.background;
VerticalLayout {
padding: 24px;
spacing: 16px;
alignment: center;
Text {
text: "📈 Market Data";
font-size: 32px;
font-weight: 700;
color: Theme.text-primary;
horizontal-alignment: center;
}
Text {
text: "Real-time market data and analysis coming soon...";
font-size: 18px;
color: Theme.text-secondary;
horizontal-alignment: center;
}
}
}
if current-page == "HuntingGround": Rectangle {
background: Theme.background;
VerticalLayout {
padding: 24px;
spacing: 16px;
alignment: center;
Text {
text: "🎯 Token Hunting Ground";
font-size: 32px;
font-weight: 700;
color: Theme.text-primary;
horizontal-alignment: center;
}
Text {
text: "Discover and track new tokens coming soon...";
font-size: 18px;
color: Theme.text-secondary;
horizontal-alignment: center;
}
}
}
if current-page == "Profile": Rectangle {
background: Theme.background;
VerticalLayout {
padding: 24px;
spacing: 16px;
alignment: center;
Text {
text: "👤 User Profile";
font-size: 32px;
font-weight: 700;
color: Theme.text-primary;
horizontal-alignment: center;
}
Text {
text: "Profile management and settings coming soon...";
font-size: 18px;
color: Theme.text-secondary;
horizontal-alignment: center;
}
}
}
}
}
}
} }

View file

@ -3,7 +3,8 @@
// Following the pattern from moonlogs: index -> app -> pages/widgets/entities/shared // Following the pattern from moonlogs: index -> app -> pages/widgets/entities/shared
import { App } from "app/index.slint"; import { App } from "app/index.slint";
import { Theme } from "shared/design-system/index.slint"; import { Palette } from "std-widgets.slint";
import { NewTokenUiData, CexUpdatedUiData, MaxDepthReachedUiData } from "shared/types/token.slint";
export component MainWindow inherits Window { export component MainWindow inherits Window {
// Window properties // Window properties
@ -12,43 +13,141 @@ export component MainWindow inherits Window {
min-width: 1080px; min-width: 1080px;
min-height: 800px; min-height: 800px;
no-frame: true; no-frame: true;
background: Theme.background; background: Palette.background;
// Properties // Theme state
in-out property <bool> is-dark-mode: true;
// Application state management
in-out property <string> app-state: "loading"; // "loading", "login", "authenticated"
in-out property <bool> is-authenticated: false;
in-out property <string> user-email: "";
in-out property <string> sidebar-state: "full"; // "full", "icon-only", or "hidden"
// Loading state
in-out property <bool> is-loading: true;
in-out property <bool> has-connection-error: false;
in-out property <string> loading-status: "Initializing your trading environment...";
// Navigation state
in-out property <string> current-page: "Dashboard"; in-out property <string> current-page: "Dashboard";
in-out property <string> user-initials: "JD"; in-out property <string> user-initials: "JD";
// Callbacks that will be handled by main.rs // Hunting ground properties - using correct types
callback navigation-changed(string); in-out property <[NewTokenUiData]> new-tokens: [];
callback theme-toggle-clicked(); in-out property <[CexUpdatedUiData]> cex-tokens: [];
callback logout-clicked(); in-out property <[MaxDepthReachedUiData]> analysis-tokens: [];
callback buy-clicked(); in-out property <int> current-time: 0;
callback sell-clicked();
// Callbacks for application state
callback health-check-completed(bool); // true if healthy, false if error
callback retry-health-check();
callback login-attempt(string, string);
callback logout-requested();
callback authenticate-user(string);
// Callbacks for window controls
callback start-drag-window(); callback start-drag-window();
callback minimize-window(); callback minimize-window();
callback maximize-window(); callback maximize-window();
callback close-window(); callback close-window();
callback theme-toggle-clicked();
// Callbacks for navigation
callback navigation-changed(string);
callback toggle-sidebar();
// Callbacks for hunting ground
callback refresh-hunting-ground();
callback clear-column(string);
// Other callbacks
callback logout-clicked();
callback buy-clicked();
callback sell-clicked();
callback clear-new-tokens();
callback clear-cex-tokens();
callback clear-analysis-tokens();
// Public function that can be called from Rust to toggle theme // Public function that can be called from Rust to toggle theme
public function toggle_theme() { public function toggle_theme() {
Theme.is-dark-mode = !Theme.is-dark-mode; root.is-dark-mode = !root.is-dark-mode;
debug("Theme toggled from Rust. New state: " + (Theme.is-dark-mode ? "dark" : "light")); if (root.is-dark-mode) {
Palette.color-scheme = ColorScheme.dark;
} else {
Palette.color-scheme = ColorScheme.light;
}
debug("Theme toggled from Rust. New state: " + (root.is-dark-mode ? "dark" : "light"));
}
TouchArea {
width: 100%;
height: 100%;
moved => {
if (self.pressed) {
start-drag-window();
}
}
} }
// App component handles all the UI following FSD layers // App component handles all the UI following FSD layers
App { App {
width: 100%; width: 100%;
height: 100%; height: 100%;
// Theme state
is-dark-mode: root.is-dark-mode;
// Application state management
app-state: root.app-state;
is-authenticated: root.is-authenticated;
user-email: root.user-email;
sidebar-state: root.sidebar-state;
// Loading state
is-loading: root.is-loading;
has-connection-error: root.has-connection-error;
loading-status: root.loading-status;
// Navigation state
current-page: root.current-page; current-page: root.current-page;
user-initials: root.user-initials; user-initials: root.user-initials;
// Hunting ground properties
new-tokens: root.new-tokens;
cex-tokens: root.cex-tokens;
analysis-tokens: root.analysis-tokens;
current-time: root.current-time;
// Forward all callbacks to main.rs // Forward all callbacks to main.rs
health-check-completed(healthy) => {
root.health-check-completed(healthy);
}
retry-health-check => {
root.retry-health-check();
}
login-attempt(email, password) => {
root.login-attempt(email, password);
}
logout-requested => {
root.logout-requested();
}
authenticate-user(email) => {
root.authenticate-user(email);
}
navigation-changed(page) => { navigation-changed(page) => {
root.current-page = page; root.current-page = page;
root.navigation-changed(page); root.navigation-changed(page);
} }
toggle-sidebar => {
if (root.sidebar-state == "full") {
root.sidebar-state = "icon-only";
} else {
root.sidebar-state = "full";
}
}
theme-toggle-clicked => { theme-toggle-clicked => {
root.theme-toggle-clicked(); root.toggle_theme();
} }
logout-clicked => { logout-clicked => {
root.logout-clicked(); root.logout-clicked();
@ -71,5 +170,17 @@ export component MainWindow inherits Window {
close-window => { close-window => {
root.close-window(); root.close-window();
} }
refresh-hunting-ground => {
root.refresh-hunting-ground();
}
clear-new-tokens => {
root.clear-new-tokens();
}
clear-cex-tokens => {
root.clear-cex-tokens();
}
clear-analysis-tokens => {
root.clear-analysis-tokens();
}
} }
} }

View file

@ -1,153 +1,135 @@
import { Button, VerticalBox, HorizontalBox, LineEdit } from "std-widgets.slint"; import { Palette, Button, VerticalBox, HorizontalBox, LineEdit } from "std-widgets.slint";
import { TitleBar } from "../../../widgets/window-controls/ui/title-bar.slint";
// Login component with branded interface // Login component with branded interface
export component LoginView { export component LoginView {
in-out property <color> primary-color: #2563eb; width: 100%;
in-out property <color> background-color: #f8fafc; height: 100%;
in-out property <color> text-color: #1e293b; in-out property <bool> is-dark-mode: true;
in-out property <string> app-version: "0.2.0";
// Login form state // Define the input properties for passing data to parent
in-out property <string> email: "";
in-out property <string> password: "";
// Callbacks
callback login-clicked(string, string); callback login-clicked(string, string);
callback navigate-to-dashboard(); callback navigate-to-dashboard();
callback back-to-loading();
callback minimize-window();
callback maximize-window();
callback close-window();
callback theme-toggle-clicked();
Rectangle { VerticalLayout {
background: background-color;
VerticalBox {
alignment: center; alignment: center;
spacing: 30px; spacing: 32px;
padding: 40px; padding: 40px;
// Logo and branding section Rectangle {
VerticalBox { width: 100%;
alignment: center; height: 100%;
spacing: 10px; background: Palette.background;
// Center container
Rectangle {
width: 400px;
height: 500px;
background: Palette.alternate-background;
border-radius: 16px;
drop-shadow-blur: 24px;
drop-shadow-color: #000000.with-alpha(0.1);
border-width: 1px;
border-color: Palette.border;
VerticalLayout {
alignment: center;
spacing: 24px;
padding: 32px;
// Logo/Icon
Rectangle { Rectangle {
width: 80px; width: 80px;
height: 80px; height: 80px;
border-radius: 40px; border-radius: 40px;
background: primary-color.with-alpha(0.1); background: Palette.accent-background;
// Logo placeholder Text {
Rectangle { text: "🚀";
width: 40px; font-size: 40px;
height: 40px; horizontal-alignment: center;
background: primary-color; vertical-alignment: center;
border-radius: 20px;
} }
} }
// Title
Text { Text {
text: "Ziya"; text: "Ziya";
font-size: 48px; font-size: 32px;
color: text-color;
font-weight: 700; font-weight: 700;
color: Palette.alternate-foreground;
horizontal-alignment: center;
} }
// Subtitle
Text { Text {
text: "/dˤiˈjaːʔ/, \"zeeyah\" — Proper noun, meaning \"light\""; text: "Your one-stop trading platform";
font-size: 14px; font-size: 16px;
color: text-color; color: Palette.alternate-foreground;
opacity: 0.6; horizontal-alignment: center;
opacity: 0.7;
} }
Text { // Form container
text: "One stop shop trading solution"; VerticalLayout {
font-size: 20px; spacing: 16px;
color: text-color; width: 100%;
opacity: 0.8;
}
Text {
text: "A bismillahDAO creation";
font-size: 12px;
color: primary-color;
font-weight: 600;
}
}
// Login form section
VerticalBox {
alignment: center;
spacing: 20px;
width: 400px;
// Email field // Email field
VerticalBox {
spacing: 8px;
Text {
text: "Email";
color: text-color;
font-weight: 500;
}
LineEdit { LineEdit {
placeholder-text: "Enter your email"; placeholder-text: "Enter your email";
text <=> email; width: 100%;
height: 45px; height: 44px;
}
} }
// Password field // Password field
VerticalBox {
spacing: 8px;
Text {
text: "Password";
color: text-color;
font-weight: 500;
}
LineEdit { LineEdit {
placeholder-text: "Enter your password"; placeholder-text: "Enter your password";
text <=> password; input-type: password;
height: 45px; width: 100%;
} height: 44px;
} }
// Login button // Login button
Button { Button {
text: "Sign In"; text: "Sign In";
primary: true; width: 100%;
height: 50px; height: 44px;
clicked => { clicked => {
login-clicked(email, password); root.login-clicked("demo@ziya.trading", "password123");
} }
} }
// Quick access button // Demo button for development
HorizontalBox {
spacing: 10px;
alignment: center;
Text {
text: "Quick access:";
color: text-color;
opacity: 0.7;
font-size: 12px;
}
Button { Button {
text: "Demo Mode"; text: "Continue to Demo";
height: 35px; width: 100%;
height: 44px;
clicked => { clicked => {
navigate-to-dashboard(); root.navigate-to-dashboard();
} }
} }
} }
// Back to connection check
Text { Text {
text: "Version " + app-version; text: "← Back to Connection Check";
font-size: 10px; font-size: 14px;
color: text-color; color: Palette.accent-background;
opacity: 0.5; horizontal-alignment: center;
TouchArea {
clicked => {
root.back-to-loading();
}
}
}
} }
} }
} }

View file

@ -1,7 +1,6 @@
import { ScrollView, Button, LineEdit, ComboBox } from "std-widgets.slint"; import { ScrollView, Button, LineEdit, ComboBox, Palette } from "std-widgets.slint";
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
// Professional dashboard component with global theme support // Professional dashboard component with global palette support
export component Dashboard { export component Dashboard {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -12,429 +11,107 @@ export component Dashboard {
callback trade-buy(string, string); callback trade-buy(string, string);
callback trade-sell(string, string); callback trade-sell(string, string);
// Let the parent layout handle sizing Rectangle {
background: Palette.background;
VerticalLayout { VerticalLayout {
spacing: 0px; alignment: center;
spacing: 20px;
padding: 40px;
// Top navbar using HorizontalLayout for proper layout // Icon
Rectangle { Rectangle {
height: 64px; width: 80px;
background: Theme.surface; height: 80px;
border-width: 1px; border-radius: 40px;
border-color: Theme.border; background: Palette.accent-background.with-alpha(0.1);
HorizontalLayout {
padding: 16px;
alignment: stretch;
Text { Text {
text: "Ziya Dashboard"; text: "📊";
color: Theme.text-primary; font-size: 40px;
font-size: 20px;
font-weight: 700;
vertical-alignment: center;
}
// Spacer to push user profile to the right
Rectangle {
horizontal-stretch: 1;
}
// User profile button
Rectangle {
width: 40px;
height: 40px;
background: Theme.primary;
border-radius: 20px;
Text {
text: user-initials;
color: white;
font-size: 14px;
font-weight: 700;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
TouchArea {
clicked => { logout(); }
}
}
}
} }
// Dashboard content - no scrollbar, just layout // Title
VerticalLayout {
vertical-stretch: 1;
padding: 24px;
spacing: 24px;
// Stats cards row using HorizontalLayout with proper stretch
HorizontalLayout {
spacing: 24px;
alignment: stretch;
// Total Balance
Rectangle {
horizontal-stretch: 1;
background: Theme.card-background;
border-radius: 12px;
border-width: 1px;
border-color: Theme.card-border;
drop-shadow-blur: 8px;
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
VerticalLayout {
padding: 20px;
spacing: 8px;
Text { Text {
text: "Total Balance"; text: "Dashboard";
color: Theme.text-secondary; font-size: 48px;
font-size: 14px;
font-weight: 500;
horizontal-alignment: left;
}
Text {
text: "$25,600";
color: Theme.primary;
font-size: 32px;
font-weight: 700; font-weight: 700;
horizontal-alignment: left; color: Palette.foreground;
horizontal-alignment: center;
} }
// Coming soon message
Text { Text {
text: "↗︎ 12% (30d)"; text: "Coming Soon";
color: Theme.success; font-size: 24px;
font-size: 13px;
font-weight: 500; font-weight: 500;
horizontal-alignment: left; color: Palette.foreground;
} horizontal-alignment: center;
}
} }
// Active Positions // Description
Rectangle {
horizontal-stretch: 1;
background: Theme.card-background;
border-radius: 12px;
border-width: 1px;
border-color: Theme.card-border;
drop-shadow-blur: 8px;
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
VerticalLayout {
padding: 20px;
spacing: 8px;
Text { Text {
text: "Active Positions"; text: "We're working hard to bring you an amazing dashboard experience.\nCheck back soon for portfolio insights, trading analytics, and more!";
color: Theme.text-secondary;
font-size: 14px;
font-weight: 500;
horizontal-alignment: left;
}
Text {
text: "8";
color: Theme.warning;
font-size: 32px;
font-weight: 700;
horizontal-alignment: left;
}
Text {
text: "↗︎ 2 new today";
color: Theme.success;
font-size: 13px;
font-weight: 500;
horizontal-alignment: left;
}
}
}
// P&L Today
Rectangle {
horizontal-stretch: 1;
background: Theme.card-background;
border-radius: 12px;
border-width: 1px;
border-color: Theme.card-border;
drop-shadow-blur: 8px;
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
VerticalLayout {
padding: 20px;
spacing: 8px;
Text {
text: "P&L Today";
color: Theme.text-secondary;
font-size: 14px;
font-weight: 500;
horizontal-alignment: left;
}
Text {
text: "+$450";
color: Theme.success;
font-size: 32px;
font-weight: 700;
horizontal-alignment: left;
}
Text {
text: "↗︎ +2.1%";
color: Theme.success;
font-size: 13px;
font-weight: 500;
horizontal-alignment: left;
}
}
}
}
// Trading interface row using HorizontalLayout
HorizontalLayout {
spacing: 24px;
alignment: stretch;
// Quick Trade card
Rectangle {
horizontal-stretch: 2;
background: Theme.card-background;
border-radius: 16px;
border-width: 1px;
border-color: Theme.card-border;
drop-shadow-blur: 16px;
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
VerticalLayout {
padding: 24px;
spacing: 20px;
Text {
text: "Quick Trade";
color: Theme.text-primary;
font-size: 18px;
font-weight: 700;
horizontal-alignment: left;
}
// Token selector using HorizontalLayout
HorizontalLayout {
spacing: 12px;
alignment: start;
Text {
text: "Token:";
color: Theme.text-secondary;
font-size: 14px;
vertical-alignment: center;
}
ComboBox {
model: ["SOL", "BTC", "ETH", "USDC"];
current-value: "SOL";
}
}
// Amount input using HorizontalLayout
HorizontalLayout {
spacing: 12px;
alignment: start;
Text {
text: "Amount:";
color: Theme.text-secondary;
font-size: 14px;
vertical-alignment: center;
}
LineEdit {
placeholder-text: "0.00";
horizontal-stretch: 1;
}
}
// Trade buttons using HorizontalLayout
HorizontalLayout {
spacing: 12px;
alignment: stretch;
Button {
text: "Buy";
primary: true;
horizontal-stretch: 1;
clicked => {
trade-buy("SOL", "100");
}
}
Button {
text: "Sell";
horizontal-stretch: 1;
clicked => {
trade-sell("SOL", "50");
}
}
}
}
}
// Recent Activity card
Rectangle {
horizontal-stretch: 1;
background: Theme.card-background;
border-radius: 16px;
border-width: 1px;
border-color: Theme.card-border;
drop-shadow-blur: 16px;
drop-shadow-color: Theme.is-dark-mode ? #00000020 : #00000008;
VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: "Recent Activity";
color: Theme.text-primary;
font-size: 18px;
font-weight: 700;
horizontal-alignment: left;
}
// Activity items using VerticalLayout
VerticalLayout {
spacing: 12px;
// Activity item 1
HorizontalLayout {
spacing: 12px;
alignment: stretch;
Rectangle {
width: 8px;
height: 8px;
border-radius: 4px;
background: Theme.success;
}
VerticalLayout {
spacing: 2px;
horizontal-stretch: 1;
Text {
text: "Bought 2.5 SOL";
color: Theme.text-primary;
font-size: 14px;
font-weight: 500;
}
Text {
text: "2 minutes ago";
color: Theme.text-secondary;
font-size: 12px;
}
}
}
// Activity item 2
HorizontalLayout {
spacing: 12px;
alignment: stretch;
Rectangle {
width: 8px;
height: 8px;
border-radius: 4px;
background: Theme.error;
}
VerticalLayout {
spacing: 2px;
horizontal-stretch: 1;
Text {
text: "Sold 1.0 ETH";
color: Theme.text-primary;
font-size: 14px;
font-weight: 500;
}
Text {
text: "1 hour ago";
color: Theme.text-secondary;
font-size: 12px;
}
}
}
// Activity item 3
HorizontalLayout {
spacing: 12px;
alignment: stretch;
Rectangle {
width: 8px;
height: 8px;
border-radius: 4px;
background: Theme.warning;
}
VerticalLayout {
spacing: 2px;
horizontal-stretch: 1;
Text {
text: "Staked 100 USDC";
color: Theme.text-primary;
font-size: 14px;
font-weight: 500;
}
Text {
text: "3 hours ago";
color: Theme.text-secondary;
font-size: 12px;
}
}
}
}
}
}
}
// Theme demonstration section
Rectangle {
background: Theme.surface;
border-radius: 12px;
border-width: 1px;
border-color: Theme.border;
VerticalLayout {
padding: 20px;
spacing: 12px;
Text {
text: "Theme Status";
color: Theme.text-primary;
font-size: 16px; font-size: 16px;
color: Palette.foreground;
horizontal-alignment: center;
opacity: 0.8;
}
// Placeholder for future features
Rectangle {
width: 400px;
height: 200px;
background: Palette.alternate-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
VerticalLayout {
alignment: center;
spacing: 10px;
Text {
text: "✨ Coming Features:";
font-size: 18px;
font-weight: 600; font-weight: 600;
color: Palette.alternate-foreground;
horizontal-alignment: center;
} }
VerticalLayout {
spacing: 8px;
Text { Text {
text: "Current theme: " + (Theme.is-dark-mode ? "Dark Mode 🌙" : "Light Mode ☀️"); text: "• Real-time Portfolio Tracking";
color: Theme.text-secondary;
font-size: 14px; font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
} }
Text { Text {
text: "All components are now using the global theme system for consistent theming across the application."; text: "• Advanced Trading Analytics";
color: Theme.text-secondary; font-size: 14px;
font-size: 12px; color: Palette.alternate-foreground;
horizontal-alignment: center;
}
Text {
text: "• Performance Metrics";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
Text {
text: "• Custom Widgets";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
} }
} }
} }

View file

@ -1,29 +1,79 @@
import { VerticalBox } from "std-widgets.slint"; import { Palette, ScrollView } from "std-widgets.slint";
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
// Import proper token data structs from shared types
import { NewTokenUiData, CexUpdatedUiData, MaxDepthReachedUiData } from "../../../shared/types/token.slint";
// Import the new token column widgets
import { NewTokensColumn, CexTokensColumn, AnalysisCompleteColumn } from "../../../widgets/token-column/ui/mod.slint";
// Hunting Ground Page Component // Hunting Ground Page Component
export component HuntingGroundPage { export component HuntingGroundPage {
Rectangle {
background: Theme.background;
VerticalBox { // Properties for token data - using correct types
padding: 32px; in property <[NewTokenUiData]> new-tokens: [];
spacing: 24px; in property <[CexUpdatedUiData]> cex-tokens: [];
alignment: center; in property <[MaxDepthReachedUiData]> analysis-tokens: [];
in property <int> current-time: 0;
callback clear-new-tokens();
callback clear-cex-tokens();
callback clear-analysis-tokens();
Rectangle {
background: Palette.background;
VerticalLayout {
alignment: start; // Explicitly align to top
padding: 16px; // Reduced from 24px
spacing: 16px; // Reduced from 24px
// Header with refresh button
HorizontalLayout {
alignment: space-between;
Text { Text {
text: "🎯 Hunting Ground"; text: "🎯 Token Hunting Ground";
color: Theme.text-primary; font-size: 28px;
font-size: 32px;
font-weight: 700; font-weight: 700;
horizontal-alignment: center; color: Palette.foreground;
}
} }
Text { // Three columns layout with fixed widths to prevent binding loops
text: "Advanced token hunting and discovery features"; HorizontalLayout {
color: Theme.text-secondary; spacing: 16px;
font-size: 16px; padding: 8px;
horizontal-alignment: center; height: 600px; // Fixed height to prevent overflow
// Column 1: New Tokens (fixed width)
NewTokensColumn {
width: 350px; // Fixed width instead of calculated
height: 100%;
tokens: root.new-tokens;
clear-tokens => {
root.clear-new-tokens();
}
}
// Column 2: CEX Analyst (fixed width)
CexTokensColumn {
width: 350px; // Fixed width instead of calculated
height: 100%;
tokens: root.cex-tokens;
clear-tokens => {
root.clear-cex-tokens();
}
}
// Column 3: Analysis Complete (fixed width)
AnalysisCompleteColumn {
width: 350px; // Fixed width instead of calculated
height: 100%;
tokens: root.analysis-tokens;
clear-tokens => {
root.clear-analysis-tokens();
}
}
} }
} }
} }

View file

@ -1,30 +1,113 @@
import { VerticalBox } from "std-widgets.slint"; import { Palette } from "std-widgets.slint";
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
// Markets Page Component
export component MarketsPage { export component MarketsPage {
Rectangle { width: 100%;
background: Theme.background; height: 100%;
VerticalBox { Rectangle {
padding: 32px; background: Palette.background;
spacing: 24px;
VerticalLayout {
alignment: center; alignment: center;
spacing: 20px;
padding: 40px;
// Icon
Rectangle {
width: 80px;
height: 80px;
border-radius: 40px;
background: Palette.accent-background.with-alpha(0.1);
Text {
text: "📊";
font-size: 40px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Title
Text { Text {
text: "Markets"; text: "Markets";
color: Theme.text-primary; font-size: 48px;
font-size: 32px;
font-weight: 700; font-weight: 700;
color: Palette.foreground;
horizontal-alignment: center;
}
// Coming soon message
Text {
text: "Coming Soon";
font-size: 24px;
font-weight: 500;
color: Palette.foreground;
horizontal-alignment: center;
}
// Description
Text {
text: "Real-time market data and analysis tools are on the way.\nStay tuned for comprehensive market insights!";
font-size: 16px;
color: Palette.foreground;
horizontal-alignment: center;
opacity: 0.8;
}
// Placeholder for future features
Rectangle {
width: 400px;
height: 200px;
background: Palette.alternate-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
VerticalLayout {
alignment: center;
spacing: 10px;
Text {
text: "🌍 Coming Features:";
font-size: 18px;
font-weight: 600;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
VerticalLayout {
spacing: 8px;
Text {
text: "• Live Price Feeds";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center; horizontal-alignment: center;
} }
Text { Text {
text: "Market data and analysis will be displayed here"; text: "• Market Cap Rankings";
color: Theme.text-secondary; font-size: 14px;
font-size: 16px; color: Palette.alternate-foreground;
horizontal-alignment: center; horizontal-alignment: center;
} }
Text {
text: "• Technical Analysis Tools";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
Text {
text: "• News & Market Sentiment";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
}
}
}
} }
} }
} }

View file

@ -1,30 +1,114 @@
import { VerticalBox } from "std-widgets.slint"; import { Palette } from "std-widgets.slint";
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
// Portfolio Page Component // Portfolio Page Component
export component PortfolioPage { export component PortfolioPage {
Rectangle { width: 100%;
background: Theme.background; height: 100%;
VerticalBox { Rectangle {
padding: 32px; background: Palette.background;
spacing: 24px;
VerticalLayout {
alignment: center; alignment: center;
spacing: 20px;
padding: 40px;
// Icon
Rectangle {
width: 80px;
height: 80px;
border-radius: 40px;
background: Palette.accent-background.with-alpha(0.1);
Text {
text: "💼";
font-size: 40px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Title
Text { Text {
text: "Portfolio"; text: "Portfolio";
color: Theme.text-primary; font-size: 48px;
font-size: 32px;
font-weight: 700; font-weight: 700;
color: Palette.foreground;
horizontal-alignment: center;
}
// Coming soon message
Text {
text: "Coming Soon";
font-size: 24px;
font-weight: 500;
color: Palette.foreground;
horizontal-alignment: center;
}
// Description
Text {
text: "Track your investments and performance with detailed portfolio analytics.\nComprehensive portfolio management tools are coming soon!";
font-size: 16px;
color: Palette.foreground;
horizontal-alignment: center;
opacity: 0.8;
}
// Placeholder for future features
Rectangle {
width: 400px;
height: 200px;
background: Palette.alternate-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
VerticalLayout {
alignment: center;
spacing: 10px;
Text {
text: "📊 Coming Features:";
font-size: 18px;
font-weight: 600;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
VerticalLayout {
spacing: 8px;
Text {
text: "• Asset Allocation Overview";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center; horizontal-alignment: center;
} }
Text { Text {
text: "Your investment portfolio will be displayed here"; text: "• Performance Analytics";
color: Theme.text-secondary; font-size: 14px;
font-size: 16px; color: Palette.alternate-foreground;
horizontal-alignment: center; horizontal-alignment: center;
} }
Text {
text: "• Historical P&L Reports";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
Text {
text: "• Portfolio Rebalancing Tools";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
}
}
}
} }
} }
} }

View file

@ -1,75 +1,113 @@
import { VerticalBox, HorizontalBox } from "std-widgets.slint"; import { Palette } from "std-widgets.slint";
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
// Trading Page Component
export component TradingPage { export component TradingPage {
width: 100%;
height: 100%;
callback buy-clicked(); callback buy-clicked();
callback sell-clicked(); callback sell-clicked();
Rectangle { Rectangle {
background: Theme.background; background: Palette.background;
VerticalBox { VerticalLayout {
padding: 32px;
spacing: 24px;
alignment: center; alignment: center;
spacing: 20px;
padding: 40px;
// Icon
Rectangle {
width: 80px;
height: 80px;
border-radius: 40px;
background: Palette.accent-background.with-alpha(0.1);
Text { Text {
text: "Trading Page"; text: "📈";
color: Theme.text-primary; font-size: 40px;
font-size: 32px; horizontal-alignment: center;
vertical-alignment: center;
}
}
// Title
Text {
text: "Trading";
font-size: 48px;
font-weight: 700; font-weight: 700;
color: Palette.foreground;
horizontal-alignment: center; horizontal-alignment: center;
} }
// Coming soon message
Text { Text {
text: "Advanced trading features coming soon..."; text: "Coming Soon";
color: Theme.text-secondary; font-size: 24px;
font-weight: 500;
color: Palette.foreground;
horizontal-alignment: center;
}
// Description
Text {
text: "Advanced trading interface with real-time market data is coming.\nGet ready for seamless trading experience!";
font-size: 16px; font-size: 16px;
color: Palette.foreground;
horizontal-alignment: center; horizontal-alignment: center;
opacity: 0.8;
} }
HorizontalBox { // Placeholder for future features
spacing: 16px; Rectangle {
width: 400px;
height: 200px;
background: Palette.alternate-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
VerticalLayout {
alignment: center; alignment: center;
spacing: 10px;
Rectangle {
width: 120px;
height: 40px;
background: #10b981;
border-radius: 8px;
Text { Text {
text: "Buy"; text: "🚀 Coming Features:";
color: white; font-size: 18px;
font-size: 14px;
font-weight: 600; font-weight: 600;
color: Palette.alternate-foreground;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center;
} }
TouchArea { VerticalLayout {
clicked => { buy-clicked(); } spacing: 8px;
}
}
Rectangle {
width: 120px;
height: 40px;
background: #ef4444;
border-radius: 8px;
Text { Text {
text: "Sell"; text: "• One-Click Trading";
color: white;
font-size: 14px; font-size: 14px;
font-weight: 600; color: Palette.alternate-foreground;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center;
} }
TouchArea { Text {
clicked => { sell-clicked(); } text: "• Advanced Order Types";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
Text {
text: "• Portfolio Integration";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
Text {
text: "• Risk Management Tools";
font-size: 14px;
color: Palette.alternate-foreground;
horizontal-alignment: center;
}
} }
} }
} }

View file

@ -1,971 +0,0 @@
// Fluent 2 Design System for Ziya Trading Platform
// Based on Microsoft Fluent 2 Design Language
// https://fluent2.microsoft.design/
// Complete Fluent UI Color System - All Themes Support
// Theme mode enumeration
export enum ColorMode { light, dark, team-light, team-dark }
export global FluentColors {
// Current theme mode
in-out property <ColorMode> current-mode: ColorMode.light;
// Theme switching callback
callback switch-theme(ColorMode);
// ===== LIGHT THEME COLORS =====
// Neutral Colors - Light
out property <color> neutral-foreground-1-light: #242424;
out property <color> neutral-foreground-2-light: #424242;
out property <color> neutral-foreground-3-light: #616161;
out property <color> neutral-foreground-4-light: #757575;
out property <color> neutral-foreground-disabled-light: #bdbdbd;
out property <color> neutral-foreground-inverted-light: #ffffff;
out property <color> neutral-foreground-inverted-2-light: #f5f5f5;
out property <color> neutral-foreground-on-brand-light: #ffffff;
out property <color> neutral-foreground-static-inverted-light: #ffffff;
out property <color> neutral-foreground-inverted-disabled-light: #ffffff;
// Interactive states - Light theme
out property <color> neutral-foreground-1-hover-light: #424242;
out property <color> neutral-foreground-1-pressed-light: #242424;
out property <color> neutral-foreground-2-hover-light: #616161;
out property <color> neutral-foreground-2-pressed-light: #424242;
out property <color> neutral-foreground-2-brand-hover-light: #0078d4;
out property <color> neutral-foreground-2-brand-pressed-light: #005a9e;
out property <color> neutral-foreground-2-brand-selected-light: #0078d4;
// Link colors - Light theme
out property <color> neutral-foreground-link-light: #0078d4;
out property <color> neutral-foreground-link-hover-light: #106ebe;
out property <color> neutral-foreground-link-pressed-light: #005a9e;
out property <color> neutral-foreground-link-selected-light: #0078d4;
out property <color> neutral-background-1-light: #ffffff;
out property <color> neutral-background-2-light: #fafafa;
out property <color> neutral-background-3-light: #f5f5f5;
out property <color> neutral-background-4-light: #f0f0f0;
out property <color> neutral-background-5-light: #ebebeb;
out property <color> neutral-background-6-light: #e1e1e1;
out property <color> neutral-background-inverted-light: #292929;
out property <color> neutral-background-static-light: #f0f0f0;
out property <color> neutral-background-alpha-light: #ffffff;
out property <color> neutral-background-alpha-2-light: #ffffff;
// Background interactive states - Light theme
out property <color> neutral-background-1-hover-light: #f5f5f5;
out property <color> neutral-background-1-pressed-light: #f0f0f0;
out property <color> neutral-background-1-selected-light: #ebebeb;
out property <color> neutral-background-2-hover-light: #f0f0f0;
out property <color> neutral-background-2-pressed-light: #ebebeb;
out property <color> neutral-background-2-selected-light: #e1e1e1;
out property <color> neutral-background-3-hover-light: #ebebeb;
out property <color> neutral-background-3-pressed-light: #e1e1e1;
out property <color> neutral-background-3-selected-light: #c7c7c7;
out property <color> neutral-stroke-1-light: #e1e1e1;
out property <color> neutral-stroke-2-light: #c7c7c7;
out property <color> neutral-stroke-3-light: #b3b3b3;
out property <color> neutral-stroke-accessible-light: #616161;
out property <color> neutral-stroke-focus-1-light: #ffffff;
out property <color> neutral-stroke-focus-2-light: #000000;
out property <color> neutral-stroke-disabled-light: #e1e1e1;
// Stroke interactive states - Light theme
out property <color> neutral-stroke-1-hover-light: #c7c7c7;
out property <color> neutral-stroke-1-pressed-light: #b3b3b3;
out property <color> neutral-stroke-1-selected-light: #b3b3b3;
out property <color> neutral-stroke-2-hover-light: #b3b3b3;
out property <color> neutral-stroke-2-pressed-light: #9e9e9e;
// ===== DARK THEME COLORS =====
// Neutral Colors - Dark
out property <color> neutral-foreground-1-dark: #ffffff;
out property <color> neutral-foreground-2-dark: #f5f5f5;
out property <color> neutral-foreground-3-dark: #ebebeb;
out property <color> neutral-foreground-4-dark: #cccccc;
out property <color> neutral-foreground-disabled-dark: #858585;
out property <color> neutral-foreground-inverted-dark: #242424;
out property <color> neutral-foreground-inverted-2-dark: #292929;
out property <color> neutral-foreground-on-brand-dark: #ffffff;
out property <color> neutral-foreground-static-inverted-dark: #ffffff;
out property <color> neutral-foreground-inverted-disabled-dark: #858585;
// Interactive states - Dark theme
out property <color> neutral-foreground-1-hover-dark: #f5f5f5;
out property <color> neutral-foreground-1-pressed-dark: #ffffff;
out property <color> neutral-foreground-2-hover-dark: #ebebeb;
out property <color> neutral-foreground-2-pressed-dark: #f5f5f5;
out property <color> neutral-foreground-2-brand-hover-dark: #62abf5;
out property <color> neutral-foreground-2-brand-pressed-dark: #77b7f7;
out property <color> neutral-foreground-2-brand-selected-dark: #479ef5;
// Link colors - Dark theme
out property <color> neutral-foreground-link-dark: #479ef5;
out property <color> neutral-foreground-link-hover-dark: #62abf5;
out property <color> neutral-foreground-link-pressed-dark: #77b7f7;
out property <color> neutral-foreground-link-selected-dark: #479ef5;
out property <color> neutral-background-1-dark: #1c1c1c;
out property <color> neutral-background-2-dark: #242424;
out property <color> neutral-background-3-dark: #292929;
out property <color> neutral-background-4-dark: #333333;
out property <color> neutral-background-5-dark: #3d3d3d;
out property <color> neutral-background-6-dark: #474747;
out property <color> neutral-background-inverted-dark: #ffffff;
out property <color> neutral-background-static-dark: #333333;
out property <color> neutral-background-alpha-dark: #1c1c1c;
out property <color> neutral-background-alpha-2-dark: #1c1c1c;
// Background interactive states - Dark theme
out property <color> neutral-background-1-hover-dark: #242424;
out property <color> neutral-background-1-pressed-dark: #292929;
out property <color> neutral-background-1-selected-dark: #333333;
out property <color> neutral-background-2-hover-dark: #292929;
out property <color> neutral-background-2-pressed-dark: #333333;
out property <color> neutral-background-2-selected-dark: #3d3d3d;
out property <color> neutral-background-3-hover-dark: #333333;
out property <color> neutral-background-3-pressed-dark: #3d3d3d;
out property <color> neutral-background-3-selected-dark: #474747;
out property <color> neutral-stroke-1-dark: #3d3d3d;
out property <color> neutral-stroke-2-dark: #525252;
out property <color> neutral-stroke-3-dark: #666666;
out property <color> neutral-stroke-accessible-dark: #cccccc;
out property <color> neutral-stroke-focus-1-dark: #ffffff;
out property <color> neutral-stroke-focus-2-dark: #000000;
out property <color> neutral-stroke-disabled-dark: #3d3d3d;
// Stroke interactive states - Dark theme
out property <color> neutral-stroke-1-hover-dark: #525252;
out property <color> neutral-stroke-1-pressed-dark: #666666;
out property <color> neutral-stroke-1-selected-dark: #666666;
out property <color> neutral-stroke-2-hover-dark: #666666;
out property <color> neutral-stroke-2-pressed-dark: #757575;
// ===== TEAM LIGHT THEME COLORS =====
// Neutral Colors - Team Light
out property <color> neutral-foreground-1-team-light: #242424;
out property <color> neutral-foreground-2-team-light: #424242;
out property <color> neutral-foreground-3-team-light: #616161;
out property <color> neutral-foreground-4-team-light: #757575;
out property <color> neutral-foreground-disabled-team-light: #bdbdbd;
out property <color> neutral-foreground-inverted-team-light: #ffffff;
out property <color> neutral-foreground-inverted-2-team-light: #f5f5f5;
out property <color> neutral-foreground-on-brand-team-light: #ffffff;
out property <color> neutral-foreground-static-inverted-team-light: #ffffff;
out property <color> neutral-foreground-inverted-disabled-team-light: #ffffff;
// Interactive states - Team Light theme
out property <color> neutral-foreground-1-hover-team-light: #424242;
out property <color> neutral-foreground-1-pressed-team-light: #242424;
out property <color> neutral-foreground-2-hover-team-light: #616161;
out property <color> neutral-foreground-2-pressed-team-light: #424242;
out property <color> neutral-foreground-2-brand-hover-team-light: #585a96;
out property <color> neutral-foreground-2-brand-pressed-team-light: #4c4e85;
out property <color> neutral-foreground-2-brand-selected-team-light: #6264a7;
// Link colors - Team Light theme
out property <color> neutral-foreground-link-team-light: #6264a7;
out property <color> neutral-foreground-link-hover-team-light: #585a96;
out property <color> neutral-foreground-link-pressed-team-light: #4c4e85;
out property <color> neutral-foreground-link-selected-team-light: #6264a7;
out property <color> neutral-background-1-team-light: #ffffff;
out property <color> neutral-background-2-team-light: #f8f8f8;
out property <color> neutral-background-3-team-light: #f3f2f1;
out property <color> neutral-background-4-team-light: #edebe9;
out property <color> neutral-background-5-team-light: #e1dfdd;
out property <color> neutral-background-6-team-light: #d2d0ce;
out property <color> neutral-background-inverted-team-light: #292929;
out property <color> neutral-background-static-team-light: #edebe9;
out property <color> neutral-background-alpha-team-light: #ffffff;
out property <color> neutral-background-alpha-2-team-light: #ffffff;
// Background interactive states - Team Light theme
out property <color> neutral-background-1-hover-team-light: #f8f8f8;
out property <color> neutral-background-1-pressed-team-light: #f3f2f1;
out property <color> neutral-background-1-selected-team-light: #edebe9;
out property <color> neutral-background-2-hover-team-light: #f3f2f1;
out property <color> neutral-background-2-pressed-team-light: #edebe9;
out property <color> neutral-background-2-selected-team-light: #e1dfdd;
out property <color> neutral-background-3-hover-team-light: #edebe9;
out property <color> neutral-background-3-pressed-team-light: #e1dfdd;
out property <color> neutral-background-3-selected-team-light: #d2d0ce;
out property <color> neutral-stroke-1-team-light: #d2d0ce;
out property <color> neutral-stroke-2-team-light: #c8c6c4;
out property <color> neutral-stroke-3-team-light: #b3b0ad;
out property <color> neutral-stroke-accessible-team-light: #605e5c;
out property <color> neutral-stroke-focus-1-team-light: #ffffff;
out property <color> neutral-stroke-focus-2-team-light: #000000;
out property <color> neutral-stroke-disabled-team-light: #d2d0ce;
// Stroke interactive states - Team Light theme
out property <color> neutral-stroke-1-hover-team-light: #c8c6c4;
out property <color> neutral-stroke-1-pressed-team-light: #b3b0ad;
out property <color> neutral-stroke-1-selected-team-light: #b3b0ad;
out property <color> neutral-stroke-2-hover-team-light: #b3b0ad;
out property <color> neutral-stroke-2-pressed-team-light: #a19f9d;
// ===== TEAM DARK THEME COLORS =====
// Neutral Colors - Team Dark
out property <color> neutral-foreground-1-team-dark: #ffffff;
out property <color> neutral-foreground-2-team-dark: #f3f2f1;
out property <color> neutral-foreground-3-team-dark: #edebe9;
out property <color> neutral-foreground-4-team-dark: #d2d0ce;
out property <color> neutral-foreground-disabled-team-dark: #8a8886;
out property <color> neutral-foreground-inverted-team-dark: #242424;
out property <color> neutral-foreground-inverted-2-team-dark: #292929;
out property <color> neutral-foreground-on-brand-team-dark: #ffffff;
out property <color> neutral-foreground-static-inverted-team-dark: #ffffff;
out property <color> neutral-foreground-inverted-disabled-team-dark: #8a8886;
// Interactive states - Team Dark theme
out property <color> neutral-foreground-1-hover-team-dark: #f3f2f1;
out property <color> neutral-foreground-1-pressed-team-dark: #ffffff;
out property <color> neutral-foreground-2-hover-team-dark: #edebe9;
out property <color> neutral-foreground-2-pressed-team-dark: #f3f2f1;
out property <color> neutral-foreground-2-brand-hover-team-dark: #9a9bd2;
out property <color> neutral-foreground-2-brand-pressed-team-dark: #a8a9db;
out property <color> neutral-foreground-2-brand-selected-team-dark: #8b8cc8;
// Link colors - Team Dark theme
out property <color> neutral-foreground-link-team-dark: #8b8cc8;
out property <color> neutral-foreground-link-hover-team-dark: #9a9bd2;
out property <color> neutral-foreground-link-pressed-team-dark: #a8a9db;
out property <color> neutral-foreground-link-selected-team-dark: #8b8cc8;
out property <color> neutral-background-1-team-dark: #252423;
out property <color> neutral-background-2-team-dark: #2d2c2b;
out property <color> neutral-background-3-team-dark: #323130;
out property <color> neutral-background-4-team-dark: #3b3a39;
out property <color> neutral-background-5-team-dark: #484644;
out property <color> neutral-background-6-team-dark: #605e5c;
out property <color> neutral-background-inverted-team-dark: #ffffff;
out property <color> neutral-background-static-team-dark: #3b3a39;
out property <color> neutral-background-alpha-team-dark: #252423;
out property <color> neutral-background-alpha-2-team-dark: #252423;
// Background interactive states - Team Dark theme
out property <color> neutral-background-1-hover-team-dark: #2d2c2b;
out property <color> neutral-background-1-pressed-team-dark: #323130;
out property <color> neutral-background-1-selected-team-dark: #3b3a39;
out property <color> neutral-background-2-hover-team-dark: #323130;
out property <color> neutral-background-2-pressed-team-dark: #3b3a39;
out property <color> neutral-background-2-selected-team-dark: #484644;
out property <color> neutral-background-3-hover-team-dark: #3b3a39;
out property <color> neutral-background-3-pressed-team-dark: #484644;
out property <color> neutral-background-3-selected-team-dark: #605e5c;
out property <color> neutral-stroke-1-team-dark: #484644;
out property <color> neutral-stroke-2-team-dark: #605e5c;
out property <color> neutral-stroke-3-team-dark: #797775;
out property <color> neutral-stroke-accessible-team-dark: #d2d0ce;
out property <color> neutral-stroke-focus-1-team-dark: #ffffff;
out property <color> neutral-stroke-focus-2-team-dark: #000000;
out property <color> neutral-stroke-disabled-team-dark: #484644;
// Stroke interactive states - Team Dark theme
out property <color> neutral-stroke-1-hover-team-dark: #605e5c;
out property <color> neutral-stroke-1-pressed-team-dark: #797775;
out property <color> neutral-stroke-1-selected-team-dark: #797775;
out property <color> neutral-stroke-2-hover-team-dark: #797775;
out property <color> neutral-stroke-2-pressed-team-dark: #8a8886;
// ===== BRAND COLORS (All Themes) =====
// Brand Colors - Light
out property <color> brand-background-1-light: #0078d4;
out property <color> brand-background-2-light: #106ebe;
out property <color> brand-background-3-light: #005a9e;
out property <color> brand-foreground-1-light: #ffffff;
out property <color> brand-foreground-2-light: #f3f2f1;
out property <color> brand-stroke-1-light: #0078d4;
out property <color> brand-stroke-2-light: #106ebe;
// Brand interactive states - Light theme
out property <color> brand-background-1-hover-light: #106ebe;
out property <color> brand-background-1-pressed-light: #005a9e;
out property <color> brand-background-1-selected-light: #0078d4;
out property <color> brand-background-2-hover-light: #005a9e;
out property <color> brand-background-2-pressed-light: #004578;
out property <color> brand-stroke-1-hover-light: #106ebe;
out property <color> brand-stroke-1-pressed-light: #005a9e;
// Brand Colors - Dark
out property <color> brand-background-1-dark: #479ef5;
out property <color> brand-background-2-dark: #62abf5;
out property <color> brand-background-3-dark: #77b7f7;
out property <color> brand-foreground-1-dark: #ffffff;
out property <color> brand-foreground-2-dark: #f3f2f1;
out property <color> brand-stroke-1-dark: #479ef5;
out property <color> brand-stroke-2-dark: #62abf5;
// Brand interactive states - Dark theme
out property <color> brand-background-1-hover-dark: #62abf5;
out property <color> brand-background-1-pressed-dark: #77b7f7;
out property <color> brand-background-1-selected-dark: #479ef5;
out property <color> brand-background-2-hover-dark: #77b7f7;
out property <color> brand-background-2-pressed-dark: #8cc8f8;
out property <color> brand-stroke-1-hover-dark: #62abf5;
out property <color> brand-stroke-1-pressed-dark: #77b7f7;
// Brand Colors - Team Light
out property <color> brand-background-1-team-light: #6264a7;
out property <color> brand-background-2-team-light: #585a96;
out property <color> brand-background-3-team-light: #4c4e85;
out property <color> brand-foreground-1-team-light: #ffffff;
out property <color> brand-foreground-2-team-light: #f3f2f1;
out property <color> brand-stroke-1-team-light: #6264a7;
out property <color> brand-stroke-2-team-light: #585a96;
// Brand interactive states - Team Light theme
out property <color> brand-background-1-hover-team-light: #585a96;
out property <color> brand-background-1-pressed-team-light: #4c4e85;
out property <color> brand-background-1-selected-team-light: #6264a7;
out property <color> brand-background-2-hover-team-light: #4c4e85;
out property <color> brand-background-2-pressed-team-light: #414374;
out property <color> brand-stroke-1-hover-team-light: #585a96;
out property <color> brand-stroke-1-pressed-team-light: #4c4e85;
// Brand Colors - Team Dark
out property <color> brand-background-1-team-dark: #8b8cc8;
out property <color> brand-background-2-team-dark: #9a9bd2;
out property <color> brand-background-3-team-dark: #a8a9db;
out property <color> brand-foreground-1-team-dark: #ffffff;
out property <color> brand-foreground-2-team-dark: #f3f2f1;
out property <color> brand-stroke-1-team-dark: #8b8cc8;
out property <color> brand-stroke-2-team-dark: #9a9bd2;
// Brand interactive states - Team Dark theme
out property <color> brand-background-1-hover-team-dark: #9a9bd2;
out property <color> brand-background-1-pressed-team-dark: #a8a9db;
out property <color> brand-background-1-selected-team-dark: #8b8cc8;
out property <color> brand-background-2-hover-team-dark: #a8a9db;
out property <color> brand-background-2-pressed-team-dark: #b6b7e4;
out property <color> brand-stroke-1-hover-team-dark: #9a9bd2;
out property <color> brand-stroke-1-pressed-team-dark: #a8a9db;
// ===== SEMANTIC COLORS (All Themes) =====
// Success Colors - All Themes
out property <color> success-background-1-light: #107c10;
out property <color> success-background-2-light: #0e6e0e;
out property <color> success-foreground-1-light: #ffffff;
out property <color> success-stroke-1-light: #107c10;
out property <color> success-background-1-dark: #54b054;
out property <color> success-background-2-dark: #6bb26b;
out property <color> success-foreground-1-dark: #ffffff;
out property <color> success-stroke-1-dark: #54b054;
out property <color> success-background-1-team-light: #237b4b;
out property <color> success-background-2-team-light: #1e6f42;
out property <color> success-foreground-1-team-light: #ffffff;
out property <color> success-stroke-1-team-light: #237b4b;
out property <color> success-background-1-team-dark: #5bb85b;
out property <color> success-background-2-team-dark: #6fc46f;
out property <color> success-foreground-1-team-dark: #ffffff;
out property <color> success-stroke-1-team-dark: #5bb85b;
// Warning Colors - All Themes
out property <color> warning-background-1-light: #fde047;
out property <color> warning-background-2-light: #facc15;
out property <color> warning-foreground-1-light: #323130;
out property <color> warning-stroke-1-light: #f7c52d;
out property <color> warning-background-1-dark: #ffb900;
out property <color> warning-background-2-dark: #ffc328;
out property <color> warning-foreground-1-dark: #323130;
out property <color> warning-stroke-1-dark: #ffb900;
out property <color> warning-background-1-team-light: #c19c00;
out property <color> warning-background-2-team-light: #a18600;
out property <color> warning-foreground-1-team-light: #ffffff;
out property <color> warning-stroke-1-team-light: #c19c00;
out property <color> warning-background-1-team-dark: #ffd43a;
out property <color> warning-background-2-team-dark: #ffda56;
out property <color> warning-foreground-1-team-dark: #323130;
out property <color> warning-stroke-1-team-dark: #ffd43a;
// Critical/Error Colors - All Themes
out property <color> critical-background-1-light: #d13438;
out property <color> critical-background-2-light: #b91c1c;
out property <color> critical-foreground-1-light: #ffffff;
out property <color> critical-stroke-1-light: #d13438;
out property <color> critical-background-1-dark: #ff6b6b;
out property <color> critical-background-2-dark: #ff8080;
out property <color> critical-foreground-1-dark: #ffffff;
out property <color> critical-stroke-1-dark: #ff6b6b;
out property <color> critical-background-1-team-light: #c50e1f;
out property <color> critical-background-2-team-light: #a80e1b;
out property <color> critical-foreground-1-team-light: #ffffff;
out property <color> critical-stroke-1-team-light: #c50e1f;
out property <color> critical-background-1-team-dark: #ff8080;
out property <color> critical-background-2-team-dark: #ff9494;
out property <color> critical-foreground-1-team-dark: #ffffff;
out property <color> critical-stroke-1-team-dark: #ff8080;
// ===== CURRENT THEME ACCESSORS =====
// These automatically switch based on current-mode
// Current Neutral Colors
out property <color> neutral-foreground-1: current-mode == ColorMode.light ? neutral-foreground-1-light :
current-mode == ColorMode.dark ? neutral-foreground-1-dark :
current-mode == ColorMode.team-light ? neutral-foreground-1-team-light : neutral-foreground-1-team-dark;
out property <color> neutral-foreground-2: current-mode == ColorMode.light ? neutral-foreground-2-light :
current-mode == ColorMode.dark ? neutral-foreground-2-dark :
current-mode == ColorMode.team-light ? neutral-foreground-2-team-light : neutral-foreground-2-team-dark;
out property <color> neutral-foreground-3: current-mode == ColorMode.light ? neutral-foreground-3-light :
current-mode == ColorMode.dark ? neutral-foreground-3-dark :
current-mode == ColorMode.team-light ? neutral-foreground-3-team-light : neutral-foreground-3-team-dark;
out property <color> neutral-foreground-disabled: current-mode == ColorMode.light ? neutral-foreground-disabled-light :
current-mode == ColorMode.dark ? neutral-foreground-disabled-dark :
current-mode == ColorMode.team-light ? neutral-foreground-disabled-team-light : neutral-foreground-disabled-team-dark;
out property <color> neutral-background-1: current-mode == ColorMode.light ? neutral-background-1-light :
current-mode == ColorMode.dark ? neutral-background-1-dark :
current-mode == ColorMode.team-light ? neutral-background-1-team-light : neutral-background-1-team-dark;
out property <color> neutral-background-2: current-mode == ColorMode.light ? neutral-background-2-light :
current-mode == ColorMode.dark ? neutral-background-2-dark :
current-mode == ColorMode.team-light ? neutral-background-2-team-light : neutral-background-2-team-dark;
out property <color> neutral-background-3: current-mode == ColorMode.light ? neutral-background-3-light :
current-mode == ColorMode.dark ? neutral-background-3-dark :
current-mode == ColorMode.team-light ? neutral-background-3-team-light : neutral-background-3-team-dark;
out property <color> neutral-background-4: current-mode == ColorMode.light ? neutral-background-4-light :
current-mode == ColorMode.dark ? neutral-background-4-dark :
current-mode == ColorMode.team-light ? neutral-background-4-team-light : neutral-background-4-team-dark;
out property <color> neutral-stroke-1: current-mode == ColorMode.light ? neutral-stroke-1-light :
current-mode == ColorMode.dark ? neutral-stroke-1-dark :
current-mode == ColorMode.team-light ? neutral-stroke-1-team-light : neutral-stroke-1-team-dark;
out property <color> neutral-stroke-2: current-mode == ColorMode.light ? neutral-stroke-2-light :
current-mode == ColorMode.dark ? neutral-stroke-2-dark :
current-mode == ColorMode.team-light ? neutral-stroke-2-team-light : neutral-stroke-2-team-dark;
out property <color> neutral-stroke-accessible: current-mode == ColorMode.light ? neutral-stroke-accessible-light :
current-mode == ColorMode.dark ? neutral-stroke-accessible-dark :
current-mode == ColorMode.team-light ? neutral-stroke-accessible-team-light : neutral-stroke-accessible-team-dark;
// Current Interactive States
out property <color> neutral-foreground-1-hover: current-mode == ColorMode.light ? neutral-foreground-1-hover-light :
current-mode == ColorMode.dark ? neutral-foreground-1-hover-dark :
current-mode == ColorMode.team-light ? neutral-foreground-1-hover-team-light : neutral-foreground-1-hover-team-dark;
out property <color> neutral-foreground-2-brand-hover: current-mode == ColorMode.light ? neutral-foreground-2-brand-hover-light :
current-mode == ColorMode.dark ? neutral-foreground-2-brand-hover-dark :
current-mode == ColorMode.team-light ? neutral-foreground-2-brand-hover-team-light : neutral-foreground-2-brand-hover-team-dark;
// Current Link Colors
out property <color> neutral-foreground-link: current-mode == ColorMode.light ? neutral-foreground-link-light :
current-mode == ColorMode.dark ? neutral-foreground-link-dark :
current-mode == ColorMode.team-light ? neutral-foreground-link-team-light : neutral-foreground-link-team-dark;
out property <color> neutral-foreground-link-hover: current-mode == ColorMode.light ? neutral-foreground-link-hover-light :
current-mode == ColorMode.dark ? neutral-foreground-link-hover-dark :
current-mode == ColorMode.team-light ? neutral-foreground-link-hover-team-light : neutral-foreground-link-hover-team-dark;
out property <color> neutral-foreground-link-pressed: current-mode == ColorMode.light ? neutral-foreground-link-pressed-light :
current-mode == ColorMode.dark ? neutral-foreground-link-pressed-dark :
current-mode == ColorMode.team-light ? neutral-foreground-link-pressed-team-light : neutral-foreground-link-pressed-team-dark;
// Current Background Interactive States
out property <color> neutral-background-1-hover: current-mode == ColorMode.light ? neutral-background-1-hover-light :
current-mode == ColorMode.dark ? neutral-background-1-hover-dark :
current-mode == ColorMode.team-light ? neutral-background-1-hover-team-light : neutral-background-1-hover-team-dark;
out property <color> neutral-background-1-pressed: current-mode == ColorMode.light ? neutral-background-1-pressed-light :
current-mode == ColorMode.dark ? neutral-background-1-pressed-dark :
current-mode == ColorMode.team-light ? neutral-background-1-pressed-team-light : neutral-background-1-pressed-team-dark;
// Current Stroke Interactive States
out property <color> neutral-stroke-1-hover: current-mode == ColorMode.light ? neutral-stroke-1-hover-light :
current-mode == ColorMode.dark ? neutral-stroke-1-hover-dark :
current-mode == ColorMode.team-light ? neutral-stroke-1-hover-team-light : neutral-stroke-1-hover-team-dark;
out property <color> neutral-stroke-1-pressed: current-mode == ColorMode.light ? neutral-stroke-1-pressed-light :
current-mode == ColorMode.dark ? neutral-stroke-1-pressed-dark :
current-mode == ColorMode.team-light ? neutral-stroke-1-pressed-team-light : neutral-stroke-1-pressed-team-dark;
// Current Brand Colors
out property <color> brand-background-1: current-mode == ColorMode.light ? brand-background-1-light :
current-mode == ColorMode.dark ? brand-background-1-dark :
current-mode == ColorMode.team-light ? brand-background-1-team-light : brand-background-1-team-dark;
out property <color> brand-background-2: current-mode == ColorMode.light ? brand-background-2-light :
current-mode == ColorMode.dark ? brand-background-2-dark :
current-mode == ColorMode.team-light ? brand-background-2-team-light : brand-background-2-team-dark;
out property <color> brand-foreground-1: current-mode == ColorMode.light ? brand-foreground-1-light :
current-mode == ColorMode.dark ? brand-foreground-1-dark :
current-mode == ColorMode.team-light ? brand-foreground-1-team-light : brand-foreground-1-team-dark;
// Current Brand Interactive States
out property <color> brand-background-1-hover: current-mode == ColorMode.light ? brand-background-1-hover-light :
current-mode == ColorMode.dark ? brand-background-1-hover-dark :
current-mode == ColorMode.team-light ? brand-background-1-hover-team-light : brand-background-1-hover-team-dark;
out property <color> brand-background-1-pressed: current-mode == ColorMode.light ? brand-background-1-pressed-light :
current-mode == ColorMode.dark ? brand-background-1-pressed-dark :
current-mode == ColorMode.team-light ? brand-background-1-pressed-team-light : brand-background-1-pressed-team-dark;
// Current Semantic Colors
out property <color> success-background-1: current-mode == ColorMode.light ? success-background-1-light :
current-mode == ColorMode.dark ? success-background-1-dark :
current-mode == ColorMode.team-light ? success-background-1-team-light : success-background-1-team-dark;
out property <color> warning-background-1: current-mode == ColorMode.light ? warning-background-1-light :
current-mode == ColorMode.dark ? warning-background-1-dark :
current-mode == ColorMode.team-light ? warning-background-1-team-light : warning-background-1-team-dark;
out property <color> critical-background-1: current-mode == ColorMode.light ? critical-background-1-light :
current-mode == ColorMode.dark ? critical-background-1-dark :
current-mode == ColorMode.team-light ? critical-background-1-team-light : critical-background-1-team-dark;
// Legacy compatibility and Trading-specific colors
out property <color> neutral-foreground-rest: neutral-foreground-1;
out property <color> profit-green: success-background-1;
out property <color> loss-red: critical-background-1;
out property <color> neutral-gray: neutral-foreground-3;
// Accent colors for trading UI
out property <color> accent-purple: #f000b8;
out property <color> accent-teal: #37cdbe;
out property <color> accent-blue: brand-background-1;
}
// Typography System - Official Fluent 2 Typography Tokens
// Based on: https://fluent2.microsoft.design/typography
export global FluentTypography {
// ===== FLUENT FONT STACKS =====
// Segoe - unmistakably Microsoft (Primary Web/Windows font stack)
out property <string> font-family-base: "Segoe UI Variable, Segoe UI, system-ui, sans-serif";
out property <string> font-family-web: "Segoe UI Variable, Segoe UI, system-ui, sans-serif";
out property <string> font-family-windows: "Segoe UI Variable, Segoe UI, system-ui, sans-serif";
// Platform-specific native font stacks for cross-platform consistency
out property <string> font-family-macos: "SF Pro Display, SF Pro Text, system-ui, sans-serif";
out property <string> font-family-ios: "SF Pro Display, SF Pro Text, system-ui, sans-serif";
out property <string> font-family-android: "Roboto, system-ui, sans-serif";
// Specialized font families
out property <string> font-family-monospace: "Cascadia Code, Cascadia Mono, Consolas, Courier New, monospace";
out property <string> font-family-numeric: "Bahnschrift, Segoe UI Variable, Segoe UI, system-ui, sans-serif";
// ===== FONT WEIGHTS - Fluent 2 Weight Scale =====
out property <int> font-weight-regular: 400; // Regular
out property <int> font-weight-medium: 500; // Medium (Android)
out property <int> font-weight-semibold: 600; // Semibold
out property <int> font-weight-bold: 700; // Bold
// ===== WEB TYPE RAMP - Official Fluent 2 Web Typography =====
// Caption styles
out property <length> font-size-caption-2: 10px; // Caption 2
out property <length> line-height-caption-2: 14px;
out property <int> font-weight-caption-2: font-weight-regular;
out property <int> font-weight-caption-2-strong: font-weight-semibold;
out property <length> font-size-caption-1: 12px; // Caption 1
out property <length> line-height-caption-1: 16px;
out property <int> font-weight-caption-1: font-weight-regular;
out property <int> font-weight-caption-1-strong: font-weight-semibold;
out property <int> font-weight-caption-1-stronger: font-weight-bold;
// Body styles
out property <length> font-size-body-1: 14px; // Body 1 (Default)
out property <length> line-height-body-1: 20px;
out property <int> font-weight-body-1: font-weight-regular;
out property <int> font-weight-body-1-strong: font-weight-semibold;
out property <int> font-weight-body-1-stronger: font-weight-bold;
// Subtitle styles
out property <length> font-size-subtitle-2: 16px; // Subtitle 2
out property <length> line-height-subtitle-2: 22px;
out property <int> font-weight-subtitle-2: font-weight-semibold;
out property <int> font-weight-subtitle-2-stronger: font-weight-bold;
out property <length> font-size-subtitle-1: 20px; // Subtitle 1
out property <length> line-height-subtitle-1: 26px;
out property <int> font-weight-subtitle-1: font-weight-semibold;
// Title styles
out property <length> font-size-title-3: 24px; // Title 3
out property <length> line-height-title-3: 32px;
out property <int> font-weight-title-3: font-weight-semibold;
out property <length> font-size-title-2: 28px; // Title 2
out property <length> line-height-title-2: 36px;
out property <int> font-weight-title-2: font-weight-semibold;
out property <length> font-size-title-1: 32px; // Title 1
out property <length> line-height-title-1: 40px;
out property <int> font-weight-title-1: font-weight-semibold;
// Large title and display
out property <length> font-size-large-title: 40px; // Large Title
out property <length> line-height-large-title: 52px;
out property <int> font-weight-large-title: font-weight-semibold;
out property <length> font-size-display: 68px; // Display
out property <length> line-height-display: 92px;
out property <int> font-weight-display: font-weight-semibold;
// ===== WINDOWS TYPE RAMP - Official Fluent 2 Windows Typography =====
// Windows-specific variants using Segoe UI Variable
out property <length> font-size-windows-caption: 12px; // Caption
out property <length> line-height-windows-caption: 16px;
out property <int> font-weight-windows-caption: font-weight-regular;
out property <length> font-size-windows-body: 14px; // Body
out property <length> line-height-windows-body: 20px;
out property <int> font-weight-windows-body: font-weight-regular;
out property <int> font-weight-windows-body-strong: font-weight-semibold;
out property <length> font-size-windows-body-large: 18px; // Body Large
out property <length> line-height-windows-body-large: 24px;
out property <int> font-weight-windows-body-large: font-weight-regular;
out property <length> font-size-windows-subtitle: 20px; // Subtitle
out property <length> line-height-windows-subtitle: 28px;
out property <int> font-weight-windows-subtitle: font-weight-semibold;
out property <length> font-size-windows-title: 28px; // Title
out property <length> line-height-windows-title: 36px;
out property <int> font-weight-windows-title: font-weight-semibold;
out property <length> font-size-windows-large-title: 40px; // Large Title
out property <length> line-height-windows-large-title: 52px;
out property <int> font-weight-windows-large-title: font-weight-semibold;
out property <length> font-size-windows-display: 68px; // Display
out property <length> line-height-windows-display: 92px;
out property <int> font-weight-windows-display: font-weight-semibold;
// ===== LEGACY NUMERIC TOKENS - For backward compatibility =====
out property <length> font-size-100: font-size-caption-2; // 10px
out property <length> font-size-200: font-size-caption-1; // 12px
out property <length> font-size-300: font-size-body-1; // 14px
out property <length> font-size-400: font-size-subtitle-2; // 16px
out property <length> font-size-500: 18px; // Custom
out property <length> font-size-600: font-size-subtitle-1; // 20px
out property <length> font-size-700: font-size-title-3; // 24px
out property <length> font-size-800: font-size-title-2; // 28px
out property <length> font-size-900: font-size-title-1; // 32px
out property <length> font-size-1000: font-size-large-title; // 40px
out property <length> font-size-1100: 48px; // Custom Large
out property <length> font-size-1200: font-size-display; // 68px
out property <length> line-height-100: line-height-caption-2; // 14px
out property <length> line-height-200: line-height-caption-1; // 16px
out property <length> line-height-300: line-height-body-1; // 20px
out property <length> line-height-400: line-height-subtitle-2; // 22px
out property <length> line-height-500: 24px; // Custom
out property <length> line-height-600: line-height-subtitle-1; // 26px
out property <length> line-height-700: line-height-title-3; // 32px
out property <length> line-height-800: line-height-title-2; // 36px
out property <length> line-height-900: line-height-title-1; // 40px
out property <length> line-height-1000: line-height-large-title; // 52px
out property <length> line-height-1100: 60px; // Custom
out property <length> line-height-1200: line-height-display; // 92px
// ===== TEXT STYLING UTILITIES =====
// Letter spacing for fine typography control
out property <length> letter-spacing-tight: -0.16px; // For large text
out property <length> letter-spacing-normal: 0px; // Standard
out property <length> letter-spacing-wide: 0.32px; // For small text
// Text decoration
out property <string> text-decoration-none: "none";
out property <string> text-decoration-underline: "underline";
out property <string> text-decoration-line-through: "line-through";
// Text transform (following Fluent guidance for sentence case)
out property <string> text-transform-none: "none";
out property <string> text-transform-uppercase: "uppercase";
out property <string> text-transform-lowercase: "lowercase";
out property <string> text-transform-capitalize: "capitalize";
// ===== USAGE EXAMPLES =====
// Caption 2: font-size: FluentTypography.font-size-caption-2; line-height: FluentTypography.line-height-caption-2; font-weight: FluentTypography.font-weight-caption-2;
// Body 1: font-size: FluentTypography.font-size-body-1; line-height: FluentTypography.line-height-body-1; font-weight: FluentTypography.font-weight-body-1;
// Title 1: font-size: FluentTypography.font-size-title-1; line-height: FluentTypography.line-height-title-1; font-weight: FluentTypography.font-weight-title-1;
// Display: font-size: FluentTypography.font-size-display; line-height: FluentTypography.line-height-display; font-weight: FluentTypography.font-weight-display;
}
// Spacing System - Fluent 2 Spacing Scale
export global FluentSpacing {
out property <length> space-2: 2px;
out property <length> space-4: 4px;
out property <length> space-6: 6px;
out property <length> space-8: 8px;
out property <length> space-10: 10px;
out property <length> space-12: 12px;
out property <length> space-16: 16px;
out property <length> space-20: 20px;
out property <length> space-24: 24px;
out property <length> space-28: 28px;
out property <length> space-32: 32px;
out property <length> space-36: 36px;
out property <length> space-40: 40px;
out property <length> space-48: 48px;
out property <length> space-64: 64px;
out property <length> space-80: 80px;
out property <length> space-96: 96px;
out property <length> space-120: 120px;
}
// Border Radius - Complete Fluent UI Border Radius System
export global FluentRadius {
// Corner Radius Scale
out property <length> border-radius-none: 0px;
out property <length> border-radius-small: 2px;
out property <length> border-radius-medium: 4px;
out property <length> border-radius-large: 6px;
out property <length> border-radius-x-large: 8px;
out property <length> border-radius-circular: 10000px;
// Legacy support
out property <length> radius-none: 0px;
out property <length> radius-small: 2px;
out property <length> radius-medium: 4px;
out property <length> radius-large: 6px;
out property <length> radius-xlarge: 8px;
out property <length> radius-circular: 10000px;
}
// Shadows - Complete Fluent UI Shadow System
// Based on: https://react.fluentui.dev/?path=/docs/theme-shadows--docs
export global FluentShadows {
// ===== SHADOW BRAND TOKENS =====
// These are the primary shadow tokens used throughout Fluent UI
// Shadow 2 - Subtle elevation (tooltips, dropdowns)
out property <length> shadow-2-x: 0px;
out property <length> shadow-2-y: 1px;
out property <length> shadow-2-blur: 2px;
out property <length> shadow-2-spread: 0px;
out property <color> shadow-2-color: #00000014; // 8% opacity black
// Shadow 4 - Low elevation (cards, buttons)
out property <length> shadow-4-x: 0px;
out property <length> shadow-4-y: 2px;
out property <length> shadow-4-blur: 4px;
out property <length> shadow-4-spread: 0px;
out property <color> shadow-4-color: #0000001f; // 12% opacity black
// Shadow 8 - Medium elevation (dialogs, menus)
out property <length> shadow-8-x: 0px;
out property <length> shadow-8-y: 4px;
out property <length> shadow-8-blur: 8px;
out property <length> shadow-8-spread: 0px;
out property <color> shadow-8-color: #0000001f; // 12% opacity black
// Shadow 16 - High elevation (modals, flyouts)
out property <length> shadow-16-x: 0px;
out property <length> shadow-16-y: 8px;
out property <length> shadow-16-blur: 16px;
out property <length> shadow-16-spread: 0px;
out property <color> shadow-16-color: #00000024; // 14% opacity black
// Shadow 28 - Very high elevation (teaching callouts)
out property <length> shadow-28-x: 0px;
out property <length> shadow-28-y: 14px;
out property <length> shadow-28-blur: 28px;
out property <length> shadow-28-spread: 0px;
out property <color> shadow-28-color: #00000024; // 14% opacity black
// Shadow 64 - Maximum elevation (panels, navigation)
out property <length> shadow-64-x: 0px;
out property <length> shadow-64-y: 32px;
out property <length> shadow-64-blur: 64px;
out property <length> shadow-64-spread: 0px;
out property <color> shadow-64-color: #00000033; // 20% opacity black
// ===== THEME-AWARE SHADOW COLORS =====
// Light theme shadow colors
out property <color> shadow-light-ambient: #0000000f; // 6% opacity black
out property <color> shadow-light-key: #00000014; // 8% opacity black
out property <color> shadow-light-ambient-darker: #00000014; // 8% opacity black
out property <color> shadow-light-key-darker: #0000001f; // 12% opacity black
out property <color> shadow-light-ambient-darkest: #0000001f; // 12% opacity black
out property <color> shadow-light-key-darkest: #00000024; // 14% opacity black
// Dark theme shadow colors
out property <color> shadow-dark-ambient: #0000001f; // 12% opacity black
out property <color> shadow-dark-key: #00000024; // 14% opacity black
out property <color> shadow-dark-ambient-darker: #00000024; // 14% opacity black
out property <color> shadow-dark-key-darker: #00000033; // 20% opacity black
out property <color> shadow-dark-ambient-darkest: #00000033; // 20% opacity black
out property <color> shadow-dark-key-darkest: #00000047; // 28% opacity black
// ===== SPECIALIZED SHADOW TOKENS =====
// Brand shadow (with brand color tint)
out property <length> shadow-brand-x: 0px;
out property <length> shadow-brand-y: 2px;
out property <length> shadow-brand-blur: 8px;
out property <length> shadow-brand-spread: 0px;
out property <color> shadow-brand-color: #0078d414; // Brand blue with 8% opacity
// Inset shadow (for pressed states)
out property <length> shadow-inset-x: 0px;
out property <length> shadow-inset-y: 1px;
out property <length> shadow-inset-blur: 2px;
out property <length> shadow-inset-spread: 0px;
out property <color> shadow-inset-color: #0000001f; // 12% opacity black
// Focus shadow (for accessibility)
out property <length> shadow-focus-x: 0px;
out property <length> shadow-focus-y: 0px;
out property <length> shadow-focus-blur: 0px;
out property <length> shadow-focus-spread: 2px;
out property <color> shadow-focus-color: #0078d4; // Brand blue
// ===== COMPONENT-SPECIFIC SHADOW TOKENS =====
// Button shadows
out property <color> shadow-button-rest: shadow-2-color;
out property <color> shadow-button-hover: shadow-4-color;
out property <color> shadow-button-pressed: shadow-inset-color;
// Card shadows
out property <color> shadow-card-rest: shadow-4-color;
out property <color> shadow-card-hover: shadow-8-color;
// Dialog shadows
out property <color> shadow-dialog: shadow-64-color;
// Flyout shadows
out property <color> shadow-flyout: shadow-16-color;
// Tooltip shadows
out property <color> shadow-tooltip: shadow-8-color;
// ===== LEGACY SHADOW SUPPORT =====
// Note: shadow-*-blur properties are already defined above in the main shadow tokens
// They can be used directly for backward compatibility
// ===== SHADOW UTILITY FUNCTIONS =====
// Helper properties for common shadow combinations
// Subtle shadow (for subtle elevation)
out property <length> subtle-shadow-x: shadow-2-x;
out property <length> subtle-shadow-y: shadow-2-y;
out property <length> subtle-shadow-blur: shadow-2-blur;
out property <color> subtle-shadow-color: shadow-2-color;
// Default shadow (for standard elevation)
out property <length> default-shadow-x: shadow-4-x;
out property <length> default-shadow-y: shadow-4-y;
out property <length> default-shadow-blur: shadow-4-blur;
out property <color> default-shadow-color: shadow-4-color;
// Deep shadow (for high elevation)
out property <length> deep-shadow-x: shadow-16-x;
out property <length> deep-shadow-y: shadow-16-y;
out property <length> deep-shadow-blur: shadow-16-blur;
out property <color> deep-shadow-color: shadow-16-color;
// ===== USAGE NOTES =====
// To apply shadows in components, use the individual tokens:
// Example: Rectangle { drop-shadow-offset-x: FluentShadows.shadow-4-x; drop-shadow-offset-y: FluentShadows.shadow-4-y; drop-shadow-blur: FluentShadows.shadow-4-blur; drop-shadow-color: FluentShadows.shadow-4-color; }
// For focus states: Rectangle { border-width: FluentShadows.shadow-focus-spread; border-color: FluentShadows.shadow-focus-color; }
}
// Component Tokens - Fluent 2 Component Specifications
export global FluentComponents {
// Button
out property <length> button-height-small: 24px;
out property <length> button-height-medium: 32px;
out property <length> button-height-large: 40px;
out property <length> button-padding-horizontal: 16px;
out property <length> button-border-radius: FluentRadius.border-radius-medium;
// Input
out property <length> input-height: 32px;
out property <length> input-padding-horizontal: 12px;
out property <length> input-border-radius: FluentRadius.border-radius-medium;
out property <length> input-border-width: 1px;
// Card
out property <length> card-padding: FluentSpacing.space-16;
out property <length> card-border-radius: FluentRadius.border-radius-large;
out property <length> card-border-width: 1px;
// Navigation
out property <length> nav-item-height: 40px;
out property <length> nav-item-padding: FluentSpacing.space-12;
out property <length> nav-item-border-radius: FluentRadius.border-radius-medium;
// Title Bar
out property <length> titlebar-height: 32px;
out property <length> titlebar-button-width: 46px;
// Sidebar
out property <length> sidebar-width: 280px;
out property <length> sidebar-collapsed-width: 48px;
}
// Animation & Motion - Fluent 2 Motion System
export global FluentMotion {
out property <duration> duration-ultra-fast: 50ms;
out property <duration> duration-faster: 100ms;
out property <duration> duration-fast: 150ms;
out property <duration> duration-normal: 200ms;
out property <duration> duration-slow: 300ms;
out property <duration> duration-slower: 400ms;
out property <duration> duration-ultra-slow: 500ms;
// Easing curves (represented as animation properties)
out property <easing> ease-accelerate: ease-in;
out property <easing> ease-decelerate: ease-out;
out property <easing> ease-standard: ease-in-out;
out property <easing> ease-max: ease;
}
// Layout Grid - Fluent 2 Layout System
export global FluentLayout {
// Breakpoints
out property <length> breakpoint-small: 480px;
out property <length> breakpoint-medium: 768px;
out property <length> breakpoint-large: 1024px;
out property <length> breakpoint-xlarge: 1440px;
// Container max widths
out property <length> container-small: 640px;
out property <length> container-medium: 768px;
out property <length> container-large: 1024px;
out property <length> container-xlarge: 1280px;
// Grid
out property <length> grid-gutter: FluentSpacing.space-24;
out property <int> grid-columns: 12;
}
// Z-Index Scale
export global FluentLayers {
out property <int> layer-base: 0;
out property <int> layer-dropdown: 1000;
out property <int> layer-sticky: 1020;
out property <int> layer-banner: 1030;
out property <int> layer-overlay: 1040;
out property <int> layer-modal: 1050;
out property <int> layer-popover: 1060;
out property <int> layer-tooltip: 1070;
out property <int> layer-toast: 1080;
}

View file

@ -1,8 +0,0 @@
// Design System Public API
// This file exports all design tokens and components from the design system
// Export design tokens
export { Theme } from "tokens/theme.slint";
// Export design system components
export * from "components/design-system.slint";

View file

@ -1,29 +0,0 @@
// Global theme management system
export global Theme {
in-out property <bool> is-dark-mode: false;
// Color scheme properties that automatically update based on theme
out property <color> background: is-dark-mode ? #1a1a1a : #ffffff;
out property <color> surface: is-dark-mode ? #2a2a2a : #f5f5f5;
out property <color> primary: #3b82f6;
out property <color> primary-variant: is-dark-mode ? #60a5fa : #2563eb;
out property <color> secondary: is-dark-mode ? #64748b : #475569;
out property <color> text-primary: is-dark-mode ? #ffffff : #1f2937;
out property <color> text-secondary: is-dark-mode ? #d1d5db : #6b7280;
out property <color> border: is-dark-mode ? #374151 : #e5e7eb;
out property <color> accent: is-dark-mode ? #22c55e : #16a34a;
out property <color> error: #ef4444;
out property <color> warning: #f59e0b;
out property <color> success: #10b981;
// Card and container colors
out property <color> card-background: is-dark-mode ? #2a2a2a : #ffffff;
out property <color> card-border: is-dark-mode ? #404040 : #e5e7eb;
// Navigation colors
out property <color> nav-background: is-dark-mode ? #1f1f1f : #f8fafc;
out property <color> nav-active: primary;
out property <color> nav-hover: is-dark-mode ? #374151 : #f1f5f9;
// Theme state is directly modified by components and Rust code
}

View file

@ -5,7 +5,7 @@
export { Theme, FluentColors, FluentTypography, FluentSpacing, FluentRadius, FluentShadows, FluentComponents, FluentMotion, FluentLayout, FluentLayers, ColorMode } from "design-system/index.slint"; export { Theme, FluentColors, FluentTypography, FluentSpacing, FluentRadius, FluentShadows, FluentComponents, FluentMotion, FluentLayout, FluentLayers, ColorMode } from "design-system/index.slint";
// Export UI components // Export UI components
export { Button, Card, Input, LoadingView, Container, Grid } from "ui/index.slint"; export { LoadingView } from "ui/index.slint";
// Export utilities and config - currently commented out in their index files // Export utilities and config - currently commented out in their index files
// export * from "lib/index.slint"; // export * from "lib/index.slint";

View file

@ -0,0 +1,43 @@
// Shared token data structures for Slint UI - matching muhafidh Redis models
// For new tokens from NewTokenCache
export struct NewTokenUiData {
mint: string,
bonding_curve: string, // Optional converted to string, empty if None
name: string,
symbol: string,
uri: string,
creator: string,
created_at: string,
}
// For analyzed tokens from TokenAnalyzedCache (covers both CEX and analysis complete)
export struct CexUpdatedUiData {
mint: string,
name: string,
uri: string,
dev_name: string,
creator: string,
cex_name: string,
cex_address: string,
bonding_curve: string,
created_at: string,
updated_at: string,
node_count: int,
edge_count: int,
// Note: graph field omitted as it's complex and not needed for UI display
}
export struct MaxDepthReachedUiData {
mint: string,
name: string,
uri: string,
dev_name: string,
creator: string,
bonding_curve: string,
created_at: string,
updated_at: string,
node_count: int,
edge_count: int,
// Note: graph field omitted as it's complex and not needed for UI display
}

View file

@ -1,50 +0,0 @@
import { VerticalBox, HorizontalBox } from "std-widgets.slint";
// Basic Button Component
export component Button {
in property <string> text;
in property <bool> enabled: true;
in property <color> background-color: #2563eb;
in property <color> text-color: white;
in property <color> hover-color: #1d4ed8;
in property <color> disabled-color: #94a3b8;
in property <length> border-radius: 6px;
in property <length> padding-horizontal: 16px;
in property <length> padding-vertical: 8px;
callback clicked();
min-width: 80px;
min-height: 36px;
Rectangle {
background: enabled ? (touch-area.has-hover ? hover-color : background-color) : disabled-color;
border-radius: border-radius;
HorizontalBox {
padding-left: padding-horizontal;
padding-right: padding-horizontal;
padding-top: padding-vertical;
padding-bottom: padding-vertical;
alignment: center;
Text {
text: root.text;
color: text-color;
font-size: 14px;
font-weight: 500;
horizontal-alignment: center;
vertical-alignment: center;
}
}
touch-area := TouchArea {
enabled: root.enabled;
clicked => {
if (root.enabled) {
root.clicked();
}
}
}
}
}

View file

@ -1,2 +0,0 @@
// Button Component Public API
export { Button } from "button.slint";

View file

@ -1,27 +0,0 @@
import { VerticalBox } from "std-widgets.slint";
// Basic Card Component
export component Card {
in property <color> background-color: white;
in property <color> border-color: #e2e8f0;
in property <length> border-radius: 8px;
in property <length> border-width: 1px;
in property <length> card-padding: 16px;
in property <length> shadow-blur: 4px;
in property <color> shadow-color: #00000010;
Rectangle {
background: background-color;
border-radius: border-radius;
border-width: border-width;
border-color: border-color;
drop-shadow-blur: shadow-blur;
drop-shadow-color: shadow-color;
drop-shadow-offset-y: 2px;
VerticalBox {
padding: root.card-padding;
@children
}
}
}

View file

@ -1,2 +0,0 @@
// Card Component Public API
export { Card } from "card.slint";

View file

@ -1,9 +1,5 @@
// UI Kit Public API // UI Kit Public API
// This file exports all shared UI components // This file exports all shared UI components
// Export basic components // Export shared components
export { Button } from "button/index.slint"; export { LoadingView } from "loading/loading.slint";
export { Card } from "card/index.slint";
export { Input } from "input/index.slint";
export { LoadingView } from "loading/index.slint";
export { Container, Grid } from "layout/index.slint";

View file

@ -1,2 +0,0 @@
// Input Component Public API
export { Input } from "input.slint";

View file

@ -1,20 +0,0 @@
import { LineEdit } from "std-widgets.slint";
// Basic Input Component
export component Input {
in-out property <string> text;
in property <string> placeholder;
in property <bool> enabled: true;
callback edited(string);
LineEdit {
text: root.text;
placeholder-text: root.placeholder;
enabled: root.enabled;
edited(t) => {
root.text = t;
root.edited(t);
}
}
}

View file

@ -1,2 +0,0 @@
// Layout Components Public API
export { Container, Grid } from "layout.slint";

View file

@ -1,32 +0,0 @@
import { VerticalBox, HorizontalBox } from "std-widgets.slint";
// Container Component
export component Container {
in property <length> container-max-width: 1200px;
in property <length> container-padding: 16px;
HorizontalBox {
alignment: center;
Rectangle {
max-width: root.container-max-width;
VerticalBox {
padding: root.container-padding;
@children
}
}
}
}
// Grid Component
export component Grid {
in property <length> gap: 16px;
in property <int> columns: 2;
// Basic grid implementation - can be enhanced
VerticalBox {
spacing: root.gap;
@children
}
}

View file

@ -1,2 +0,0 @@
// Loading Component Public API
export { LoadingView } from "loading.slint";

View file

@ -1,53 +1,177 @@
import { VerticalBox } from "std-widgets.slint"; import { ProgressIndicator, Button, Palette } from "std-widgets.slint";
// Loading component for app initialization // Loading component for app initialization with health check
export component LoadingView { export component LoadingView {
in-out property <color> primary-color: #2563eb; // Health check state
in-out property <color> background-color: #f8fafc; in-out property <bool> is-loading: true;
in-out property <color> text-color: #1e293b; in-out property <bool> has-error: false;
in-out property <string> error-message: "Connection problem";
in-out property <string> status-text: "Initializing your trading environment...";
in-out property <string> app-version: "0.2.0"; in-out property <string> app-version: "0.2.0";
Rectangle { // Callbacks
background: background-color; callback retry-connection();
callback health-check-complete();
VerticalBox { Rectangle {
background: Palette.background;
VerticalLayout {
alignment: center; alignment: center;
spacing: 20px; spacing: 32px;
padding: 40px;
// Logo section
VerticalLayout {
alignment: center;
spacing: 16px;
// Loading spinner
Rectangle { Rectangle {
width: 60px; width: 120px;
height: 60px; height: 120px;
border-radius: 30px; border-radius: 60px;
background: primary-color; background: Palette.accent-background;
// Pulsing animation effect states [
animate background { error when has-error: {
duration: 1s; background: #dc2626;
iteration-count: -1; in {
easing: ease-in-out; animate background { duration: 300ms; }
} }
} }
]
Text {
text: "🚀";
font-size: 48px;
horizontal-alignment: center;
vertical-alignment: center;
}
}
Text {
text: "Ziya";
font-size: 48px;
color: Palette.foreground;
font-weight: 700;
horizontal-alignment: center;
}
Text {
text: "Your one-stop trading platform";
font-size: 16px;
color: Palette.foreground;
opacity: 0.7;
horizontal-alignment: center;
}
}
// Status section
VerticalLayout {
alignment: center;
spacing: 24px;
if is-loading && !has-error: VerticalLayout {
alignment: center;
spacing: 16px;
Text { Text {
text: "Loading Ziya"; text: "Loading Ziya";
font-size: 24px; font-size: 24px;
color: text-color; color: Palette.foreground;
font-weight: 600; font-weight: 600;
horizontal-alignment: center;
}
// Using std ProgressIndicator
ProgressIndicator {
width: 200px;
height: 8px;
indeterminate: true;
} }
Text { Text {
text: "Initializing your trading environment..."; text: status-text;
font-size: 14px; font-size: 14px;
color: text-color; color: Palette.foreground;
opacity: 0.7; opacity: 0.7;
horizontal-alignment: center;
}
} }
if has-error: VerticalLayout {
alignment: center;
spacing: 20px;
Text {
text: "⚠️ Connection Failed";
font-size: 24px;
color: #dc2626;
font-weight: 600;
horizontal-alignment: center;
}
Text {
text: error-message;
font-size: 16px;
color: #dc2626;
horizontal-alignment: center;
}
Text {
text: "Please check your internet connection and try again";
font-size: 12px;
color: Palette.foreground;
opacity: 0.6;
horizontal-alignment: center;
}
Rectangle {
height: 45px;
width: 200px;
border-radius: 8px;
background: Palette.accent-background;
states [
pressed when retry-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when retry-touch-area.has-hover: {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
Text {
text: "🔄 Retry Connection";
color: Palette.accent-foreground;
font-size: 14px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
retry-touch-area := TouchArea {
clicked => {
retry-connection();
}
}
}
}
}
// Version info at bottom
Text { Text {
text: "Version " + app-version; text: "Version " + app-version;
font-size: 10px; font-size: 10px;
color: text-color; color: Palette.foreground;
opacity: 0.5; opacity: 0.4;
horizontal-alignment: center;
} }
} }
} }

View file

@ -1,97 +1,210 @@
import { Theme } from "../../../shared/design-system/tokens/theme.slint"; import { Palette, ScrollView } from "std-widgets.slint";
// Navigation Widget (Sidebar) // Navigation Widget (Sidebar)
export component NavigationWidget { export component NavigationWidget {
width: 280px;
height: 100%;
in-out property <string> current-page: "Dashboard"; in-out property <string> current-page: "Dashboard";
in property <string> user-initials: "JD"; in-out property <string> user-initials: "JD";
in-out property <string> sidebar-state: "full";
callback navigation-changed(string); callback navigation-changed(string);
callback logout-clicked(); callback logout-clicked();
callback toggle-sidebar();
Rectangle { Rectangle {
width: 100%; background: Palette.alternate-background;
// Right border using a separate rectangle
Rectangle {
width: 1px;
height: 100%; height: 100%;
background: Theme.nav-background; x: parent.width - 1px;
background: Palette.border;
VerticalLayout { }
padding: 16px;
spacing: 20px;
alignment: space-between;
// Top section with logo and navigation
VerticalLayout {
spacing: 20px;
// Logo Section
HorizontalLayout {
spacing: 12px;
alignment: start;
padding-top: 16px;
// Toggle button positioned in the middle of the sidebar edge
Rectangle { Rectangle {
width: 40px; width: 24px;
height: 40px; height: 80px;
border-radius: 20px; x: parent.width - 12px; // Half of the button extends outside the sidebar
background: Theme.primary; y: (parent.height - 80px) / 2; // Center vertically
z: 10; // Ensure it's above other elements
border-radius: 12px;
background: Palette.alternate-background;
border-width: 1px;
border-color: Palette.border;
states [
pressed when toggle-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when toggle-touch-area.has-hover: {
background: Palette.accent-background;
x: parent.width - 18px; // Slide out slightly on hover
in {
animate background { duration: 150ms; }
animate x { duration: 150ms; }
}
}
]
Text { Text {
text: "Z"; text: sidebar-state == "full" ? "" : "";
color: white; color: Palette.alternate-foreground;
font-size: 20px; font-size: 16px;
font-weight: 700; font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
toggle-touch-area := TouchArea {
clicked => {
root.toggle-sidebar();
}
}
}
VerticalLayout {
spacing: 20px;
padding: 20px;
padding-bottom: 40px; // Extra padding to prevent logout cut-off
// User Profile Section - Only show in full mode
if sidebar-state == "full": Rectangle {
height: 80px;
background: Palette.control-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
HorizontalLayout {
spacing: 12px;
padding: 16px;
alignment: start;
// User Avatar
Rectangle {
width: 48px;
height: 48px;
border-radius: 24px;
background: Palette.accent-background;
Text {
text: user-initials;
color: Palette.accent-foreground;
font-size: 18px;
font-weight: 600;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
} }
VerticalLayout { // User Info
spacing: 2px;
Text {
text: "Ziya";
color: Theme.text-primary;
font-size: 18px;
font-weight: 700;
}
Text {
text: "Trading Platform";
color: Theme.text-secondary;
font-size: 12px;
}
}
}
// Navigation Menu
VerticalLayout { VerticalLayout {
spacing: 4px; spacing: 4px;
alignment: center;
Text {
text: "Trading Account";
color: Palette.control-foreground;
font-size: 14px;
font-weight: 600;
horizontal-alignment: left;
vertical-alignment: center;
}
Text {
text: "Active Status";
color: Palette.control-foreground;
font-size: 12px;
opacity: 0.7;
horizontal-alignment: left;
vertical-alignment: center;
}
}
}
}
// User Profile Section - Icon only mode
if sidebar-state == "icon-only": Rectangle {
height: 48px;
width: 48px;
background: Palette.control-background;
border-radius: 24px;
border-width: 1px;
border-color: Palette.border;
Text {
text: user-initials;
color: Palette.accent-foreground;
font-size: 18px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Navigation Items
ScrollView {
VerticalLayout {
spacing: 8px;
// Dashboard // Dashboard
Rectangle { Rectangle {
height: 44px; height: 48px;
background: current-page == "Dashboard" ? Palette.selection-background : transparent;
border-radius: 8px; border-radius: 8px;
background: current-page == "Dashboard" ? Theme.nav-active : transparent; border-width: current-page == "Dashboard" ? 1px : 0px;
border-color: current-page == "Dashboard" ? Palette.border : transparent;
HorizontalLayout { states [
padding: 12px; pressed when dashboard-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when dashboard-touch-area.has-hover && current-page != "Dashboard": {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
if sidebar-state == "full": HorizontalLayout {
spacing: 12px; spacing: 12px;
padding: 12px;
alignment: start; alignment: start;
Text { Text {
text: "📊"; text: "📊";
font-size: 16px; font-size: 20px;
vertical-alignment: center; vertical-alignment: center;
} }
Text { Text {
text: "Dashboard"; text: "Dashboard";
color: current-page == "Dashboard" ? white : Theme.text-primary; color: current-page == "Dashboard" ? Palette.selection-foreground : Palette.alternate-foreground;
font-size: 14px; font-size: 14px;
font-weight: current-page == "Dashboard" ? 600 : 400; font-weight: current-page == "Dashboard" ? 600 : 400;
vertical-alignment: center; vertical-alignment: center;
} }
} }
TouchArea { if sidebar-state == "icon-only": Text {
text: "📊";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
}
dashboard-touch-area := TouchArea {
clicked => { clicked => {
root.current-page = "Dashboard"; root.current-page = "Dashboard";
root.navigation-changed("Dashboard"); root.navigation-changed("Dashboard");
@ -99,33 +212,115 @@ export component NavigationWidget {
} }
} }
// Trading // Hunting Ground
Rectangle { Rectangle {
height: 44px; height: 48px;
background: current-page == "Hunting Ground" ? Palette.selection-background : transparent;
border-radius: 8px; border-radius: 8px;
background: current-page == "Trading" ? Theme.nav-active : transparent; border-width: current-page == "Hunting Ground" ? 1px : 0px;
border-color: current-page == "Hunting Ground" ? Palette.border : transparent;
HorizontalLayout { states [
padding: 12px; pressed when hunting-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when hunting-touch-area.has-hover && current-page != "Hunting Ground": {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
if sidebar-state == "full": HorizontalLayout {
spacing: 12px; spacing: 12px;
padding: 12px;
alignment: start; alignment: start;
Text { Text {
text: "💹"; text: "🎯";
font-size: 16px; font-size: 20px;
vertical-alignment: center;
}
Text {
text: "Hunting Ground";
color: current-page == "Hunting Ground" ? Palette.selection-foreground : Palette.alternate-foreground;
font-size: 14px;
font-weight: current-page == "Hunting Ground" ? 600 : 400;
vertical-alignment: center;
}
}
if sidebar-state == "icon-only": Text {
text: "🎯";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
}
hunting-touch-area := TouchArea {
clicked => {
root.current-page = "Hunting Ground";
root.navigation-changed("Hunting Ground");
}
}
}
// Trading
Rectangle {
height: 48px;
background: current-page == "Trading" ? Palette.selection-background : transparent;
border-radius: 8px;
border-width: current-page == "Trading" ? 1px : 0px;
border-color: current-page == "Trading" ? Palette.border : transparent;
states [
pressed when trading-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when trading-touch-area.has-hover && current-page != "Trading": {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
if sidebar-state == "full": HorizontalLayout {
spacing: 12px;
padding: 12px;
alignment: start;
Text {
text: "⚡";
font-size: 20px;
vertical-alignment: center; vertical-alignment: center;
} }
Text { Text {
text: "Trading"; text: "Trading";
color: current-page == "Trading" ? white : Theme.text-primary; color: current-page == "Trading" ? Palette.selection-foreground : Palette.alternate-foreground;
font-size: 14px; font-size: 14px;
font-weight: current-page == "Trading" ? 600 : 400; font-weight: current-page == "Trading" ? 600 : 400;
vertical-alignment: center; vertical-alignment: center;
} }
} }
TouchArea { if sidebar-state == "icon-only": Text {
text: "⚡";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
}
trading-touch-area := TouchArea {
clicked => { clicked => {
root.current-page = "Trading"; root.current-page = "Trading";
root.navigation-changed("Trading"); root.navigation-changed("Trading");
@ -135,31 +330,55 @@ export component NavigationWidget {
// Portfolio // Portfolio
Rectangle { Rectangle {
height: 44px; height: 48px;
background: current-page == "Portfolio" ? Palette.selection-background : transparent;
border-radius: 8px; border-radius: 8px;
background: current-page == "Portfolio" ? Theme.nav-active : transparent; border-width: current-page == "Portfolio" ? 1px : 0px;
border-color: current-page == "Portfolio" ? Palette.border : transparent;
HorizontalLayout { states [
padding: 12px; pressed when portfolio-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when portfolio-touch-area.has-hover && current-page != "Portfolio": {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
if sidebar-state == "full": HorizontalLayout {
spacing: 12px; spacing: 12px;
padding: 12px;
alignment: start; alignment: start;
Text { Text {
text: "💼"; text: "💼";
font-size: 16px; font-size: 20px;
vertical-alignment: center; vertical-alignment: center;
} }
Text { Text {
text: "Portfolio"; text: "Portfolio";
color: current-page == "Portfolio" ? white : Theme.text-primary; color: current-page == "Portfolio" ? Palette.selection-foreground : Palette.alternate-foreground;
font-size: 14px; font-size: 14px;
font-weight: current-page == "Portfolio" ? 600 : 400; font-weight: current-page == "Portfolio" ? 600 : 400;
vertical-alignment: center; vertical-alignment: center;
} }
} }
TouchArea { if sidebar-state == "icon-only": Text {
text: "💼";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
}
portfolio-touch-area := TouchArea {
clicked => { clicked => {
root.current-page = "Portfolio"; root.current-page = "Portfolio";
root.navigation-changed("Portfolio"); root.navigation-changed("Portfolio");
@ -169,121 +388,118 @@ export component NavigationWidget {
// Markets // Markets
Rectangle { Rectangle {
height: 44px; height: 48px;
background: current-page == "Markets" ? Palette.selection-background : transparent;
border-radius: 8px; border-radius: 8px;
background: current-page == "Markets" ? Theme.nav-active : transparent; border-width: current-page == "Markets" ? 1px : 0px;
border-color: current-page == "Markets" ? Palette.border : transparent;
HorizontalLayout { states [
padding: 12px; pressed when markets-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when markets-touch-area.has-hover && current-page != "Markets": {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
if sidebar-state == "full": HorizontalLayout {
spacing: 12px; spacing: 12px;
padding: 12px;
alignment: start; alignment: start;
Text { Text {
text: "📈"; text: "📈";
font-size: 16px; font-size: 20px;
vertical-alignment: center; vertical-alignment: center;
} }
Text { Text {
text: "Markets"; text: "Markets";
color: current-page == "Markets" ? white : Theme.text-primary; color: current-page == "Markets" ? Palette.selection-foreground : Palette.alternate-foreground;
font-size: 14px; font-size: 14px;
font-weight: current-page == "Markets" ? 600 : 400; font-weight: current-page == "Markets" ? 600 : 400;
vertical-alignment: center; vertical-alignment: center;
} }
} }
TouchArea { if sidebar-state == "icon-only": Text {
text: "📈";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
}
markets-touch-area := TouchArea {
clicked => { clicked => {
root.current-page = "Markets"; root.current-page = "Markets";
root.navigation-changed("Markets"); root.navigation-changed("Markets");
} }
} }
} }
}
}
// Hunting Ground // Logout Section
Rectangle { Rectangle {
height: 44px; height: 48px;
background: transparent;
border-radius: 8px; border-radius: 8px;
background: current-page == "HuntingGround" ? Theme.nav-active : transparent;
HorizontalLayout { states [
padding: 12px; pressed when logout-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when logout-touch-area.has-hover: {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
if sidebar-state == "full": HorizontalLayout {
spacing: 12px; spacing: 12px;
padding: 12px;
alignment: start; alignment: start;
Text { Text {
text: "🎯"; text: "🚪";
font-size: 16px; font-size: 20px;
vertical-alignment: center; vertical-alignment: center;
} }
Text {
text: "Hunting Ground";
color: current-page == "HuntingGround" ? white : Theme.text-primary;
font-size: 14px;
font-weight: current-page == "HuntingGround" ? 600 : 400;
vertical-alignment: center;
}
}
TouchArea {
clicked => {
root.current-page = "HuntingGround";
root.navigation-changed("HuntingGround");
}
}
}
}
}
// User Profile Section (bottom)
Rectangle {
height: 60px;
border-radius: 8px;
background: Theme.surface;
HorizontalLayout {
padding: 12px;
spacing: 12px;
alignment: start;
Rectangle {
width: 36px;
height: 36px;
border-radius: 18px;
background: Theme.primary;
Text {
text: user-initials;
color: white;
font-size: 14px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
}
VerticalLayout {
spacing: 2px;
Text {
text: "User";
color: Theme.text-primary;
font-size: 14px;
font-weight: 600;
}
Text { Text {
text: "Logout"; text: "Logout";
color: Theme.text-secondary; color: Palette.alternate-foreground;
font-size: 12px; font-size: 14px;
} font-weight: 400;
vertical-alignment: center;
opacity: 0.8;
} }
} }
TouchArea { if sidebar-state == "icon-only": Text {
clicked => { root.logout-clicked(); } text: "🚪";
font-size: 20px;
horizontal-alignment: center;
vertical-alignment: center;
opacity: 0.8;
}
logout-touch-area := TouchArea {
clicked => {
root.logout-clicked();
}
} }
} }
} }

View file

@ -1,178 +0,0 @@
import { VerticalBox, HorizontalBox, ScrollView } from "std-widgets.slint";
// Navigation menu item data structure
export struct MenuItem {
name: string,
icon: string,
active: bool,
}
// Professional sidebar navigation with direct styling
export component Sidebar {
in property <[MenuItem]> menu-items: [
{ name: "Dashboard", icon: "📊", active: true },
{ name: "Profile", icon: "👤", active: false },
{ name: "Trading", icon: "💹", active: false },
{ name: "Portfolio", icon: "💼", active: false },
{ name: "Markets", icon: "📈", active: false },
{ name: "Hunting Ground", icon: "🎯", active: false },
{ name: "Analytics", icon: "📊", active: false },
];
callback menu-item-clicked(string);
width: 256px;
Rectangle {
background: #f2f2f2;
border-width: 0px;
border-color: #e5e6e6;
ScrollView {
viewport-height: root.height;
VerticalBox {
padding: 16px;
spacing: 8px;
// Sidebar Header
Rectangle {
height: 60px;
VerticalBox {
alignment: center;
spacing: 8px;
// Logo area
Rectangle {
height: 32px;
HorizontalBox {
alignment: center;
spacing: 8px;
// App icon
Rectangle {
width: 24px;
height: 24px;
background: #570df8;
border-radius: 8px;
}
Text {
text: "Ziya";
color: #1f2937;
font-size: 18px;
font-weight: 700;
}
}
}
// Divider
Rectangle {
height: 1px;
background: #e5e6e6;
}
}
}
// Menu Items
for item[index] in menu-items: Rectangle {
height: 44px;
border-radius: 8px;
// Active/hover state styling
background: item.active ? #570df8 : transparent;
states [
active when item.active: {
background: #570df8;
}
hover when menu-area.has-hover && !item.active: {
background: #e5e6e6;
}
]
animate background { duration: 150ms; }
HorizontalBox {
padding-left: 16px;
padding-right: 16px;
alignment: start;
spacing: 12px;
// Icon
Text {
text: item.icon;
font-size: 16px;
vertical-alignment: center;
width: 20px;
}
// Menu item text
Text {
text: item.name;
color: item.active ? #ffffff : #1f2937;
font-size: 14px;
font-weight: item.active ? 600 : 500;
vertical-alignment: center;
}
// Active indicator
if item.active: Rectangle {
width: 4px;
height: 20px;
background: #ffffff;
border-radius: 2px;
}
}
menu-area := TouchArea {
clicked => { menu-item-clicked(item.name); }
}
}
// Spacer to push footer to bottom
Rectangle {
height: 1px;
}
// Sidebar Footer
Rectangle {
height: 60px;
VerticalBox {
alignment: center;
spacing: 8px;
// Divider
Rectangle {
height: 1px;
background: #e5e6e6;
}
// Status section
HorizontalBox {
alignment: center;
spacing: 8px;
// Status indicator
Rectangle {
width: 8px;
height: 8px;
background: #36d399;
border-radius: 4px;
}
Text {
text: "Connected";
color: #6b7280;
font-size: 12px;
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,193 @@
import { Palette, ScrollView } from "std-widgets.slint";
import { MaxDepthReachedUiData } from "../../../shared/types/token.slint";
export component AnalysisCompleteColumn {
width: 100%; // Will be constrained by parent
height: 100%;
in property <[MaxDepthReachedUiData]> tokens: [];
callback clear-tokens();
Rectangle {
background: Palette.alternate-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
width: 100%;
height: 100%;
VerticalLayout {
spacing: 0px;
// Column Header
Rectangle {
height: 48px;
background: Palette.control-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
HorizontalLayout {
alignment: space-between;
padding: 12px;
Text {
text: "✅ Analysis Complete (" + tokens.length + ")";
color: Palette.control-foreground;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
Rectangle {
width: 20px;
height: 20px;
background: #f56565;
border-radius: 10px;
Text {
text: "×";
color: white;
font-size: 12px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
TouchArea {
clicked => { root.clear-tokens(); }
}
}
}
}
// Scrollable content area
ScrollView {
height: parent.height - 48px;
VerticalLayout {
spacing: 8px;
padding: 8px;
padding-bottom: 16px;
// Analysis token cards
for token[i] in tokens: Rectangle {
min-height: 80px;
background: Palette.background;
border-width: 1px;
border-color: Palette.border;
border-radius: 8px;
states [
pressed when touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when touch-area.has-hover: {
background: Palette.control-background;
border-color: #10b981;
in {
animate background { duration: 150ms; }
animate border-color { duration: 150ms; }
}
}
]
// Left border accent (green for complete)
Rectangle {
width: 3px;
height: 100%;
background: #10b981;
x: 0px;
border-radius: 3px;
}
HorizontalLayout {
padding: 12px;
spacing: 12px;
// Token avatar
Rectangle {
width: 40px;
height: 40px;
border-radius: 10px;
background: #10b981;
Text {
text: "✓";
color: white;
font-size: 12px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Token info
VerticalLayout {
spacing: 6px;
alignment: start;
// Name and completion status
HorizontalLayout {
spacing: 8px;
alignment: start;
Text {
text: token.name;
color: Palette.foreground;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
Rectangle {
height: 18px;
border-radius: 9px;
background: #10b981;
padding-left: 8px;
padding-right: 8px;
Text {
text: "Complete";
color: white;
font-size: 10px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
// Mint address
Text {
text: token.mint;
color: Palette.foreground;
font-size: 11px;
wrap: word-wrap;
opacity: 0.7;
vertical-alignment: center;
}
// Icons and timestamp
Text {
text: "";
color: Palette.foreground;
font-size: 11px;
opacity: 0.7;
vertical-alignment: center;
}
}
}
touch-area := TouchArea {
// Add click functionality here if needed
}
}
}
}
}
}
}

View file

@ -0,0 +1,193 @@
import { Palette, ScrollView } from "std-widgets.slint";
import { CexUpdatedUiData } from "../../../shared/types/token.slint";
export component CexTokensColumn {
width: 100%; // Will be constrained by parent
height: 100%;
in property <[CexUpdatedUiData]> tokens: [];
callback clear-tokens();
Rectangle {
background: Palette.alternate-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
width: 100%;
height: 100%;
VerticalLayout {
spacing: 0px;
// Column Header
Rectangle {
height: 48px;
background: Palette.control-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
HorizontalLayout {
alignment: space-between;
padding: 12px;
Text {
text: "📊 CEX Analyst (" + tokens.length + ")";
color: Palette.control-foreground;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
Rectangle {
width: 20px;
height: 20px;
background: #f56565;
border-radius: 10px;
Text {
text: "×";
color: white;
font-size: 12px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
TouchArea {
clicked => { root.clear-tokens(); }
}
}
}
}
// Scrollable content area
ScrollView {
height: parent.height - 48px;
VerticalLayout {
spacing: 8px;
padding: 8px;
padding-bottom: 16px;
// CEX token cards
for token[i] in tokens: Rectangle {
min-height: 80px;
background: Palette.background;
border-width: 1px;
border-color: Palette.border;
border-radius: 8px;
states [
pressed when touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when touch-area.has-hover: {
background: Palette.control-background;
border-color: #f59e0b;
in {
animate background { duration: 150ms; }
animate border-color { duration: 150ms; }
}
}
]
// Left border accent (orange for CEX)
Rectangle {
width: 3px;
height: 100%;
background: #f59e0b;
x: 0px;
border-radius: 3px;
}
HorizontalLayout {
padding: 12px;
spacing: 12px;
// Token avatar
Rectangle {
width: 40px;
height: 40px;
border-radius: 10px;
background: #f59e0b;
Text {
text: "C";
color: white;
font-size: 12px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Token info
VerticalLayout {
spacing: 6px;
alignment: start;
// Name and CEX info
HorizontalLayout {
spacing: 8px;
alignment: start;
Text {
text: token.name;
color: Palette.foreground;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
Rectangle {
height: 18px;
border-radius: 9px;
background: #f59e0b;
padding-left: 8px;
padding-right: 8px;
Text {
text: token.cex-name;
color: white;
font-size: 10px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
// Mint address
Text {
text: token.mint;
color: Palette.foreground;
font-size: 11px;
wrap: word-wrap;
opacity: 0.7;
vertical-alignment: center;
}
// Icons and timestamp
Text {
text: "";
color: Palette.foreground;
font-size: 11px;
opacity: 0.7;
vertical-alignment: center;
}
}
}
touch-area := TouchArea {
// Add click functionality here if needed
}
}
}
}
}
}
}

View file

@ -0,0 +1,6 @@
// Token Column Widgets Module
// Following Feature-Sliced Design - widgets layer
export { NewTokensColumn } from "new-tokens-column.slint";
export { CexTokensColumn } from "cex-tokens-column.slint";
export { AnalysisCompleteColumn } from "analysis-complete-column.slint";

View file

@ -0,0 +1,193 @@
import { Palette, ScrollView } from "std-widgets.slint";
import { NewTokenUiData } from "../../../shared/types/token.slint";
export component NewTokensColumn {
width: 100%; // Will be constrained by parent
height: 100%;
in property <[NewTokenUiData]> tokens: [];
callback clear-tokens();
Rectangle {
background: Palette.alternate-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
width: 100%;
height: 100%;
VerticalLayout {
spacing: 0px;
// Column Header
Rectangle {
height: 48px;
background: Palette.control-background;
border-radius: 12px;
border-width: 1px;
border-color: Palette.border;
HorizontalLayout {
alignment: space-between;
padding: 12px;
Text {
text: "🆕 New Tokens (" + tokens.length + ")";
color: Palette.control-foreground;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
Rectangle {
width: 20px;
height: 20px;
background: #f56565;
border-radius: 10px;
Text {
text: "×";
color: white;
font-size: 12px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
TouchArea {
clicked => { root.clear-tokens(); }
}
}
}
}
// Scrollable content area
ScrollView {
height: parent.height - 48px;
VerticalLayout {
spacing: 8px;
padding: 8px;
padding-bottom: 16px;
// New token cards
for token[i] in tokens: Rectangle {
min-height: 80px;
background: Palette.background;
border-width: 1px;
border-color: Palette.border;
border-radius: 8px;
states [
pressed when touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when touch-area.has-hover: {
background: Palette.control-background;
border-color: Palette.accent-background;
in {
animate background { duration: 150ms; }
animate border-color { duration: 150ms; }
}
}
]
// Left border accent
Rectangle {
width: 3px;
height: 100%;
background: Palette.accent-background;
x: 0px;
border-radius: 3px;
}
HorizontalLayout {
padding: 12px;
spacing: 12px;
// Token avatar
Rectangle {
width: 40px;
height: 40px;
border-radius: 10px;
background: Palette.accent-background;
Text {
text: token.symbol != "" ? token.symbol : "?";
color: Palette.accent-foreground;
font-size: 12px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
// Token info
VerticalLayout {
spacing: 6px;
alignment: start;
// Name and symbol
HorizontalLayout {
spacing: 8px;
alignment: start;
Text {
text: token.name;
color: Palette.foreground;
font-size: 14px;
font-weight: 600;
vertical-alignment: center;
}
if token.symbol != "": Rectangle {
height: 18px;
border-radius: 9px;
background: Palette.accent-background;
padding-left: 8px;
padding-right: 8px;
Text {
text: token.symbol;
color: Palette.accent-foreground;
font-size: 10px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
// Mint address
Text {
text: token.mint;
color: Palette.foreground;
font-size: 11px;
wrap: word-wrap;
opacity: 0.7;
vertical-alignment: center;
}
// Icons and timestamp
Text {
text: "";
color: Palette.foreground;
font-size: 11px;
opacity: 0.7;
vertical-alignment: center;
}
}
}
touch-area := TouchArea {
// Add click functionality here if needed
}
}
}
}
}
}
}

View file

@ -1,2 +1,2 @@
// Window Controls Widget Public API // Window Controls Widget Public API
export { WindowControlsWidget } from "ui/window-controls-widget.slint"; export { TitleBar } from "ui/title-bar.slint";

View file

@ -1,108 +1,106 @@
import { Button, HorizontalBox } from "std-widgets.slint"; import { Palette } from "std-widgets.slint";
import { Theme } from "../../../shared/design-system/tokens/theme.slint";
// Professional title bar component with theme support // Professional title bar component with theme support
export component TitleBar { export component TitleBar {
// Window state // Window state
in-out property <bool> is-maximized: false; in-out property <bool> is-maximized: false;
in-out property <bool> is-dark-theme: true;
// Callbacks for window controls // Callbacks for window controls
callback minimize-window(); callback minimize-window();
callback maximize-window(); callback maximize-window();
callback close-window(); callback close-window();
callback toggle-theme(); callback toggle-theme();
callback start-drag-window();
height: 32px; height: 40px;
Rectangle { Rectangle {
background: Theme.surface; background: Palette.alternate-background;
border-width: 0px;
border-color: Theme.border;
// Drag area for the title bar // Bottom border using a separate rectangle
drag-area := TouchArea { Rectangle {
width: 100%; width: 100%;
height: 100%; height: 1px;
y: parent.height - 1px;
moved => { background: Palette.border;
if (self.pressed) {
start-drag-window();
}
}
} }
HorizontalBox { HorizontalLayout {
padding: 16px; spacing: 0px;
alignment: stretch; alignment: space-between;
padding-left: 16px;
padding-right: 8px;
padding-top: 8px;
padding-bottom: 8px;
// Left side - App branding // Left side - App title and drag area
HorizontalBox { HorizontalLayout {
spacing: 12px;
alignment: start; alignment: start;
spacing: 8px;
// App icon // App icon
Rectangle { Rectangle {
width: 20px; width: 24px;
height: 20px; height: 24px;
border-radius: 6px;
background: Palette.accent-background;
// Ziya logo (layered rectangles)
Rectangle {
width: 16px;
height: 16px;
background: #570df8;
border-radius: 3px;
Rectangle {
x: 2px;
y: 2px;
width: 12px;
height: 12px;
background: #4506cb;
border-radius: 2px;
}
}
}
// App name
Text { Text {
text: "Ziya"; text: "Z";
color: Theme.text-primary; color: Palette.accent-foreground;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
} }
// Right side - Controls // App title
HorizontalBox { Text {
alignment: end; text: "Ziya - Trading Platform";
spacing: 4px; color: Palette.alternate-foreground;
font-size: 14px;
font-weight: 500;
vertical-alignment: center;
}
}
// Theme switcher // Right side - Theme toggle and window controls
HorizontalLayout {
spacing: 4px;
alignment: end;
// Theme toggle button
Rectangle { Rectangle {
width: 32px; width: 32px;
height: 32px; height: 24px;
border-radius: 4px; border-radius: 6px;
states [ states [
hover when theme-area.has-hover: { pressed when theme-touch-area.pressed: {
background: Theme.nav-hover; background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when theme-touch-area.has-hover: {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
} }
] ]
// Theme icon - shows current theme state
Text { Text {
text: Theme.is-dark-mode ? "🌙" : "☀️"; text: is-dark-theme ? "☀️" : "🌙";
font-size: 14px; font-size: 14px;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
theme-area := TouchArea { theme-touch-area := TouchArea {
clicked => { clicked => {
Theme.toggle-theme(); root.toggle-theme();
toggle-theme();
} }
} }
} }
@ -110,78 +108,110 @@ export component TitleBar {
// Minimize button // Minimize button
Rectangle { Rectangle {
width: 32px; width: 32px;
height: 32px; height: 24px;
border-radius: 4px; border-radius: 6px;
states [ states [
hover when min-area.has-hover: { pressed when minimize-touch-area.pressed: {
background: Theme.nav-hover; background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when minimize-touch-area.has-hover: {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
} }
] ]
// Minimize icon
Text { Text {
text: ""; text: "";
color: Theme.text-secondary; color: Palette.alternate-foreground;
font-size: 16px;
font-weight: 300;
horizontal-alignment: center;
vertical-alignment: center;
}
minimize-touch-area := TouchArea {
clicked => {
root.minimize-window();
}
}
}
// Maximize button
Rectangle {
width: 32px;
height: 24px;
border-radius: 6px;
states [
pressed when maximize-touch-area.pressed: {
background: Palette.selection-background;
in {
animate background { duration: 100ms; }
}
}
hover when maximize-touch-area.has-hover: {
background: Palette.control-background;
in {
animate background { duration: 150ms; }
}
}
]
Text {
text: "□";
color: Palette.alternate-foreground;
font-size: 14px; font-size: 14px;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
min-area := TouchArea { maximize-touch-area := TouchArea {
clicked => { minimize-window(); } clicked => {
root.maximize-window();
} }
} }
// Maximize/Restore button
Rectangle {
width: 32px;
height: 32px;
border-radius: 4px;
states [
hover when max-area.has-hover: {
background: Theme.nav-hover;
}
]
// Maximize icon
Text {
text: is-maximized ? "❐" : "□";
color: Theme.text-secondary;
font-size: 12px;
horizontal-alignment: center;
vertical-alignment: center;
}
max-area := TouchArea {
clicked => { maximize-window(); }
}
} }
// Close button // Close button
Rectangle { Rectangle {
width: 32px; width: 32px;
height: 32px; height: 24px;
border-radius: 4px; border-radius: 6px;
states [ states [
hover when close-area.has-hover: { pressed when close-touch-area.pressed: {
background: #ef4444; background: #d53f3f;
in {
animate background { duration: 100ms; }
}
}
hover when close-touch-area.has-hover: {
background: #f56565;
in {
animate background { duration: 150ms; }
}
} }
] ]
// Close icon
Text { Text {
text: "✕"; text: "×";
color: close-area.has-hover ? #ffffff : Theme.text-secondary; color: close-touch-area.has-hover ? white : Palette.alternate-foreground;
font-size: 10px; font-size: 16px;
font-weight: 400;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
close-area := TouchArea { close-touch-area := TouchArea {
clicked => { close-window(); } clicked => {
root.close-window();
}
} }
} }
} }

View file

@ -1,18 +0,0 @@
import { TitleBar } from "title-bar.slint";
// Window Controls Widget
export component WindowControlsWidget {
callback theme-toggle-clicked();
callback minimize-clicked();
callback maximize-clicked();
callback close-clicked();
callback start-drag-window();
TitleBar {
toggle-theme => { root.theme-toggle-clicked(); }
minimize-window => { root.minimize-clicked(); }
maximize-window => { root.maximize-clicked(); }
close-window => { root.close-clicked(); }
start-drag-window => { root.start-drag-window(); }
}
}