Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): Dynamic theme selection #39

Merged
merged 7 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
cache::Cache as LedgerApiCache, mock::LedgerApiMock, Device, DeviceInfo, LedgerApiT,
},
},
screen::{OutgoingMessage, Screen, ScreenName},
screen::{resources::Resources, OutgoingMessage, Screen, ScreenName},
};

pub struct App {
Expand Down Expand Up @@ -140,8 +140,12 @@ impl App {
mut screen: Screen<L, C, M>,
terminal: &mut Terminal<B>,
) -> (StateRegistry, ApiRegistry<L, C, M>, OutgoingMessage) {
let resources = Resources::default();

loop {
terminal.draw(|frame| screen.render(frame)).unwrap();
terminal
.draw(|frame| screen.render(frame, &resources))
.unwrap();

let event = event::poll(Duration::ZERO)
.unwrap()
Expand Down
6 changes: 3 additions & 3 deletions app/src/screen/asset/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ratatui::{crossterm::event::Event, Frame};
use rust_decimal::Decimal;
use strum::EnumIter;

use super::{OutgoingMessage, ScreenT};
use super::{resources::Resources, OutgoingMessage, ScreenT};
use crate::{
api::{
blockchain_monitoring::{BlockchainMonitoringApiT, TransactionInfo, TransactionUid},
Expand Down Expand Up @@ -104,8 +104,8 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> ScreenT<L, C,
}
}

fn render(&self, frame: &mut Frame<'_>) {
view::render(self, frame);
fn render(&self, frame: &mut Frame<'_>, resources: &Resources) {
view::render(self, frame, resources);
}

fn tick(&mut self, event: Option<Event>) -> Option<OutgoingMessage> {
Expand Down
60 changes: 40 additions & 20 deletions app/src/screen/asset/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ use crate::{
common_types::{Account, Network},
ledger::LedgerApiT,
},
screen::common::{format_address, network_symbol, render_centered_text},
screen::{
common::{format_address, network_symbol, render_centered_text, BackgroundWidget},
resources::Resources,
},
};

use super::{Model, TimePeriod};
Expand All @@ -31,15 +34,23 @@ const TX_UID_MAX_LEN: usize = 16;
pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT>(
model: &Model<L, C, M>,
frame: &mut Frame<'_>,
resources: &Resources,
) {
let area = frame.size();

frame.render_widget(BackgroundWidget::new(resources.background_color), area);

let [price_chart_area, txs_list_area] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Fill(1); 2])
.areas(area);

let price_chart_block = Block::new().title("Price").borders(Borders::all());
let price_chart_block = Block::new()
.title("Price")
.borders(Borders::all())
.fg(resources.main_color)
.bg(resources.background_color);

let inner_price_chart_area = price_chart_block.inner(price_chart_area);
frame.render_widget(price_chart_block, price_chart_area);

Expand All @@ -49,15 +60,23 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
model.selected_time_period,
frame,
inner_price_chart_area,
resources,
);
} else {
render_price_chart_placeholder(model.selected_time_period, frame, inner_price_chart_area);
render_price_chart_placeholder(
model.selected_time_period,
frame,
inner_price_chart_area,
resources,
);
}

let txs_list_block = Block::new()
.title("Transactions")
.borders(Borders::all())
.padding(Padding::proportional(1));
.padding(Padding::proportional(1))
.fg(resources.main_color);

let inner_txs_list_area = txs_list_block.inner(txs_list_area);
frame.render_widget(txs_list_block, txs_list_area);

Expand All @@ -77,6 +96,7 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
&tx_list[..],
frame,
inner_txs_list_area,
resources,
);
}
None => {
Expand All @@ -90,8 +110,9 @@ fn render_price_chart(
selected_time_period: TimePeriod,
frame: &mut Frame<'_>,
area: Rect,
resources: &Resources,
) {
let legend = render_chart_legend(selected_time_period);
let legend = render_chart_legend(selected_time_period, resources);

let max_price = *prices.iter().max().expect("Empty `prices` vector provided");

Expand All @@ -105,23 +126,19 @@ fn render_price_chart(
.marker(symbols::Marker::Bar)
.name(legend)
.graph_type(GraphType::Line)
.style(Style::default().magenta())
.data(&price_data)];

let x_axis = Axis::default()
.style(Style::default().white())
.bounds([0.0, (price_data.len() - 1) as f64]);
let x_axis = Axis::default().bounds([0.0, (price_data.len() - 1) as f64]);

let y_axis = Axis::default()
.style(Style::default().white())
.bounds([0.0, max_price.try_into().unwrap()]);
let y_axis = Axis::default().bounds([0.0, max_price.try_into().unwrap()]);

let chart = Chart::new(datasets)
.x_axis(x_axis)
.y_axis(y_axis)
.legend_position(Some(ratatui::widgets::LegendPosition::BottomRight))
// Always show a legend(see `hidden_legend_constraints` docs).
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)));
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)))
.bg(resources.background_color);

frame.render_widget(chart, area);
}
Expand All @@ -130,21 +147,23 @@ fn render_price_chart_placeholder(
selected_time_period: TimePeriod,
frame: &mut Frame<'_>,
area: Rect,
resources: &Resources,
) {
let legend = render_chart_legend(selected_time_period);
let legend = render_chart_legend(selected_time_period, resources);

let chart = Chart::new(vec![Dataset::default().name(legend)])
.legend_position(Some(ratatui::widgets::LegendPosition::BottomRight))
// Always show a legend(see `hidden_legend_constraints` docs).
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)));
.hidden_legend_constraints((Constraint::Min(0), Constraint::Min(0)))
.bg(resources.background_color);

frame.render_widget(chart, area);

let text = Text::raw("Price is loading...");
render_centered_text(frame, area, text);
}

fn render_chart_legend(selected_time_period: TimePeriod) -> Line<'static> {
fn render_chart_legend(selected_time_period: TimePeriod, resources: &Resources) -> Line<'static> {
let legend = TimePeriod::iter().map(|period| {
let label = match period {
TimePeriod::Day => " d[ay]",
Expand All @@ -155,9 +174,9 @@ fn render_chart_legend(selected_time_period: TimePeriod) -> Line<'static> {
};

if period == selected_time_period {
Span::raw(label).red()
Span::raw(label).fg(resources.accent_color)
} else {
Span::raw(label).green()
Span::raw(label).fg(resources.main_color)
}
});

Expand All @@ -169,6 +188,7 @@ fn render_tx_list(
tx_list: &[(TransactionUid, TransactionInfo)],
frame: &mut Frame<'_>,
area: Rect,
resources: &Resources,
) {
let (selected_account_network, selected_account) = selected_account;

Expand All @@ -193,7 +213,7 @@ fn render_tx_list(
vec![
Span::raw(from),
Span::raw(" -> "),
Span::raw(to).green(),
Span::raw(to).fg(resources.accent_color),
Span::raw(format!(" for {}{}", amount, network_icon)),
]
}
Expand All @@ -202,7 +222,7 @@ fn render_tx_list(
let to = format_address(&to.get_info().pk, ADDRESSES_MAX_LEN);

vec![
Span::raw(from).green(),
Span::raw(from).fg(resources.accent_color),
Span::raw(" -> "),
Span::raw(to),
Span::raw(format!(" for {}{}", amount, network_icon)),
Expand Down
26 changes: 25 additions & 1 deletion app/src/screen/common.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use ratatui::{
layout::{Constraint, Flex, Layout, Rect},
text::Text,
style::{Color, Stylize},
text::{Line, Text},
widgets::Widget,
Frame,
};

Expand Down Expand Up @@ -48,6 +50,28 @@ pub fn format_address(address: &str, max_symbols: usize) -> String {
)
}

pub struct BackgroundWidget {
color: Color,
}

impl BackgroundWidget {
pub fn new(color: Color) -> Self {
Self { color }
}
}

impl Widget for BackgroundWidget {
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized,
{
let line = Line::raw(" ".repeat(area.width as usize)).bg(self.color);
for y in area.y..area.y + area.height {
buf.set_line(area.x, y, &line, area.width);
}
}
}

#[cfg(test)]
mod tests {
use itertools::Itertools;
Expand Down
6 changes: 3 additions & 3 deletions app/src/screen/deposit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::time::Instant;

use ratatui::{crossterm::event::Event, Frame};

use super::{OutgoingMessage, ScreenT};
use super::{resources::Resources, OutgoingMessage, ScreenT};
use crate::{
api::{
blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT,
Expand Down Expand Up @@ -33,8 +33,8 @@ impl<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT> ScreenT<L, C,
}
}

fn render(&self, frame: &mut Frame<'_>) {
view::render(self, frame);
fn render(&self, frame: &mut Frame<'_>, resources: &Resources) {
view::render(self, frame, resources);
}

fn tick(&mut self, event: Option<Event>) -> Option<OutgoingMessage> {
Expand Down
52 changes: 40 additions & 12 deletions app/src/screen/deposit/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ use qrcode::{Color as QrCodeColor, QrCode};
use ratatui::{
layout::{Alignment, Constraint, Flex, Layout, Rect},
prelude::Buffer,
style::Stylize,
style::{Color, Stylize},
text::Text,
widgets::{Block, BorderType, Borders, Padding, Widget},
Frame,
};

use crate::api::{
blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT, ledger::LedgerApiT,
use crate::{
api::{
blockchain_monitoring::BlockchainMonitoringApiT, coin_price::CoinPriceApiT,
ledger::LedgerApiT,
},
screen::{common::BackgroundWidget, resources::Resources},
};

use super::Model;
Expand All @@ -21,7 +25,12 @@ const DISPLAY_COPIED_TEXT_FOR: Duration = Duration::from_secs(2);
pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApiT>(
model: &Model<L, C, M>,
frame: &mut Frame<'_>,
resources: &Resources,
) {
let area = frame.size();

frame.render_widget(BackgroundWidget::new(resources.background_color), area);

let pubkey = model
.state
.selected_account
Expand All @@ -31,9 +40,9 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
.get_info()
.pk;

let area = frame.size();

let address_text = Text::raw(&pubkey).alignment(Alignment::Center);
let address_text = Text::raw(&pubkey)
.alignment(Alignment::Center)
.fg(resources.main_color);

let display_copied_text = if let Some(last_copy) = model.last_address_copy {
last_copy.elapsed() <= DISPLAY_COPIED_TEXT_FOR
Expand All @@ -42,9 +51,9 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
};

let description_text = if display_copied_text {
Text::raw("copied!").green()
Text::raw("copied!").fg(resources.accent_color)
} else {
Text::raw("press `c` to copy")
Text::raw("press `c` to copy").fg(resources.main_color)
}
.alignment(Alignment::Center);

Expand All @@ -58,7 +67,10 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
.flex(Flex::Center)
.areas(address_with_description_area);

let qr_code = QrCodeWidget::new(pubkey.clone()).with_size(QrCodeSize::Small);
let qr_code = QrCodeWidget::new(pubkey.clone())
.size(QrCodeSize::Small)
.dark_color(resources.qr_code_dark_color)
.light_color(resources.qr_code_light_color);

frame.render_widget(qr_code, qr_code_area);
frame.render_widget(address_text, address_area);
Expand All @@ -68,6 +80,9 @@ pub(super) fn render<L: LedgerApiT, C: CoinPriceApiT, M: BlockchainMonitoringApi
struct QrCodeWidget {
content: String,
size: QrCodeSize,

dark_color: Color,
light_color: Color,
}

enum QrCodeSize {
Expand Down Expand Up @@ -97,8 +112,8 @@ impl Widget for QrCodeWidget {
HORIZONTAL_BLOCK_PADDING,
VERTICAL_BLOCK_PADDING,
))
.black()
.on_white();
.bg(self.light_color)
.fg(self.dark_color);

let expected_block_height = code.height() as u16 + VERTICAL_BLOCK_PADDING * 2 + 2;
let expected_block_width = code.width() as u16 + HORIZONTAL_BLOCK_PADDING * 2 + 2;
Expand All @@ -122,14 +137,27 @@ impl QrCodeWidget {
QrCodeWidget {
content,
size: QrCodeSize::Big,

dark_color: Color::Black,
light_color: Color::White,
}
}

fn with_size(mut self, size: QrCodeSize) -> Self {
fn size(mut self, size: QrCodeSize) -> Self {
self.size = size;
self
}

fn dark_color(mut self, color: Color) -> Self {
self.dark_color = color;
self
}

fn light_color(mut self, color: Color) -> Self {
self.light_color = color;
self
}

fn render_big(&self, code: QrCode) -> String {
let width = code.width();
let colors = code.into_colors();
Expand Down
Loading