feat(mobile): improve layout and behavior on narrow screens

This commit is contained in:
Fernando López Guevara
2025-07-08 18:10:58 -03:00
committed by William Casarin
parent f25735f89e
commit ec25413433
6 changed files with 91 additions and 39 deletions

View File

@@ -2,7 +2,7 @@
//#[cfg(target_arch = "wasm32")] //#[cfg(target_arch = "wasm32")]
//use wasm_bindgen::prelude::*; //use wasm_bindgen::prelude::*;
use crate::app::NotedeckApp; use crate::app::NotedeckApp;
use egui::{vec2, Button, Label, Layout, Rect, RichText, ThemePreference, Widget}; use egui::{vec2, Button, Color32, Label, Layout, Rect, RichText, ThemePreference, Widget};
use egui_extras::{Size, StripBuilder}; use egui_extras::{Size, StripBuilder};
use nostrdb::{ProfileRecord, Transaction}; use nostrdb::{ProfileRecord, Transaction};
use notedeck::{App, AppAction, AppContext, NotedeckTextStyle, UserAccount, WalletType}; use notedeck::{App, AppAction, AppContext, NotedeckTextStyle, UserAccount, WalletType};
@@ -207,8 +207,8 @@ impl Chrome {
.size(Size::exact(amt_open)) // collapsible sidebar .size(Size::exact(amt_open)) // collapsible sidebar
.size(Size::remainder()) // the main app contents .size(Size::remainder()) // the main app contents
.clip(true) .clip(true)
.horizontal(|mut strip| { .horizontal(|mut hstrip| {
strip.cell(|ui| { hstrip.cell(|ui| {
let rect = ui.available_rect_before_wrap(); let rect = ui.available_rect_before_wrap();
if !ui.visuals().dark_mode { if !ui.visuals().dark_mode {
let rect = ui.available_rect_before_wrap(); let rect = ui.available_rect_before_wrap();
@@ -216,20 +216,28 @@ impl Chrome {
rect, rect,
0, 0,
notedeck_ui::colors::ALMOST_WHITE, notedeck_ui::colors::ALMOST_WHITE,
egui::Stroke::new(0.0, egui::Color32::TRANSPARENT), egui::Stroke::new(0.0, Color32::TRANSPARENT),
egui::StrokeKind::Inside, egui::StrokeKind::Inside,
); );
} }
ui.with_layout(Layout::top_down(egui::Align::Center), |ui| { StripBuilder::new(ui)
.size(Size::remainder())
.size(Size::remainder())
.vertical(|mut vstrip| {
vstrip.cell(|ui| {
_ = ui.vertical_centered(|ui| {
self.topdown_sidebar(ui); self.topdown_sidebar(ui);
})
}); });
vstrip.cell(|ui| {
ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| { ui.with_layout(Layout::bottom_up(egui::Align::Center), |ui| {
if let Some(action) = bottomup_sidebar(self, app_ctx, ui) { if let Some(action) = bottomup_sidebar(self, app_ctx, ui) {
got_action = Some(action); got_action = Some(action);
} }
}); });
});
});
// vertical sidebar line // vertical sidebar line
ui.painter().vline( ui.painter().vline(
@@ -239,7 +247,7 @@ impl Chrome {
); );
}); });
strip.cell(|ui| { hstrip.cell(|ui| {
/* /*
let rect = ui.available_rect_before_wrap(); let rect = ui.available_rect_before_wrap();
ui.painter().rect( ui.painter().rect(
@@ -286,7 +294,6 @@ impl Chrome {
.vertical(|mut strip| { .vertical(|mut strip| {
strip.strip(|builder| { strip.strip(|builder| {
// the chrome panel is nested above the toolbar // the chrome panel is nested above the toolbar
got_action = self.panel(ctx, builder, amt_open); got_action = self.panel(ctx, builder, amt_open);
}); });
@@ -303,6 +310,23 @@ impl Chrome {
fn toolbar(&mut self, ui: &mut egui::Ui) -> Option<ToolbarAction> { fn toolbar(&mut self, ui: &mut egui::Ui) -> Option<ToolbarAction> {
use egui_tabs::{TabColor, Tabs}; use egui_tabs::{TabColor, Tabs};
let rect = ui.available_rect_before_wrap();
ui.painter().hline(
rect.x_range(),
rect.top(),
ui.visuals().widgets.noninteractive.bg_stroke,
);
if !ui.visuals().dark_mode {
ui.painter().rect(
rect,
0,
notedeck_ui::colors::ALMOST_WHITE,
egui::Stroke::new(0.0, Color32::TRANSPARENT),
egui::StrokeKind::Inside,
);
}
let rs = Tabs::new(3) let rs = Tabs::new(3)
.selected(self.tab_selected) .selected(self.tab_selected)
.hover_bg(TabColor::none()) .hover_bg(TabColor::none())
@@ -496,8 +520,8 @@ fn notifications_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button( expanding_button(
"notifications-button", "notifications-button",
24.0, 24.0,
app_images::notifications_button_image(), app_images::notifications_light_image(),
app_images::notifications_button_image(), app_images::notifications_dark_image(),
ui, ui,
) )
} }
@@ -506,8 +530,8 @@ fn home_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button( expanding_button(
"home-button", "home-button",
24.0, 24.0,
app_images::home_button_image(), app_images::home_light_image(),
app_images::home_button_image(), app_images::home_dark_image(),
ui, ui,
) )
} }

View File

@@ -14,7 +14,9 @@ use crate::{
Result, Result,
}; };
use notedeck::{Accounts, AppAction, AppContext, DataPath, DataPathType, FilterState, UnknownIds}; use notedeck::{
ui::is_narrow, Accounts, AppAction, AppContext, DataPath, DataPathType, FilterState, UnknownIds,
};
use notedeck_ui::{jobs::JobsCache, NoteOptions}; use notedeck_ui::{jobs::JobsCache, NoteOptions};
use enostr::{ClientMessage, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPool}; use enostr::{ClientMessage, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPool};
@@ -561,14 +563,22 @@ fn render_damus_mobile(
} }
} }
rect.min.x = rect.max.x - 100.0; rect.min.x = rect.max.x - if is_narrow(ui.ctx()) { 60.0 } else { 100.0 };
rect.min.y = rect.max.y - 100.0; rect.min.y = rect.max.y - 100.0;
let interactive = true; let is_interactive = app_ctx
.accounts
.get_selected_account()
.key
.secret_key
.is_some();
let darkmode = ui.ctx().style().visuals.dark_mode; let darkmode = ui.ctx().style().visuals.dark_mode;
if ui if ui
.put(rect, ui::post::compose_note_button(interactive, darkmode)) .put(
rect,
ui::post::compose_note_button(is_interactive, darkmode),
)
.clicked() .clicked()
&& !app.columns(app_ctx.accounts).columns().is_empty() && !app.columns(app_ctx.accounts).columns().is_empty()
{ {

View File

@@ -187,7 +187,7 @@ impl<'a> AddColumnView<'a> {
ScrollArea::vertical() ScrollArea::vertical()
.show(ui, |ui| { .show(ui, |ui| {
let mut selected_option: Option<AddColumnResponse> = None; let mut selected_option: Option<AddColumnResponse> = None;
for column_option_data in self.get_base_options() { for column_option_data in self.get_base_options(ui) {
let option = column_option_data.option.clone(); let option = column_option_data.option.clone();
if self.column_option_ui(ui, column_option_data).clicked() { if self.column_option_ui(ui, column_option_data).clicked() {
selected_option = Some(option.take_as_response(self.cur_account)); selected_option = Some(option.take_as_response(self.cur_account));
@@ -203,7 +203,7 @@ impl<'a> AddColumnView<'a> {
fn notifications_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { fn notifications_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> {
let mut selected_option: Option<AddColumnResponse> = None; let mut selected_option: Option<AddColumnResponse> = None;
for column_option_data in self.get_notifications_options() { for column_option_data in self.get_notifications_options(ui) {
let option = column_option_data.option.clone(); let option = column_option_data.option.clone();
if self.column_option_ui(ui, column_option_data).clicked() { if self.column_option_ui(ui, column_option_data).clicked() {
selected_option = Some(option.take_as_response(self.cur_account)); selected_option = Some(option.take_as_response(self.cur_account));
@@ -441,7 +441,7 @@ impl<'a> AddColumnView<'a> {
helper.take_animation_response() helper.take_animation_response()
} }
fn get_base_options(&self) -> Vec<ColumnOptionData> { fn get_base_options(&self, ui: &mut Ui) -> Vec<ColumnOptionData> {
let mut vec = Vec::new(); let mut vec = Vec::new();
vec.push(ColumnOptionData { vec.push(ColumnOptionData {
title: "Universe", title: "Universe",
@@ -465,7 +465,7 @@ impl<'a> AddColumnView<'a> {
vec.push(ColumnOptionData { vec.push(ColumnOptionData {
title: "Notifications", title: "Notifications",
description: "Stay up to date with notifications and mentions", description: "Stay up to date with notifications and mentions",
icon: app_images::notifications_image(), icon: app_images::notifications_image(ui.visuals().dark_mode),
option: AddColumnOption::UndecidedNotification, option: AddColumnOption::UndecidedNotification,
}); });
vec.push(ColumnOptionData { vec.push(ColumnOptionData {
@@ -490,7 +490,7 @@ impl<'a> AddColumnView<'a> {
vec vec
} }
fn get_notifications_options(&self) -> Vec<ColumnOptionData> { fn get_notifications_options(&self, ui: &mut Ui) -> Vec<ColumnOptionData> {
let mut vec = Vec::new(); let mut vec = Vec::new();
let source = if self.cur_account.key.secret_key.is_some() { let source = if self.cur_account.key.secret_key.is_some() {
@@ -502,14 +502,14 @@ impl<'a> AddColumnView<'a> {
vec.push(ColumnOptionData { vec.push(ColumnOptionData {
title: "Your Notifications", title: "Your Notifications",
description: "Stay up to date with your notifications and mentions", description: "Stay up to date with your notifications and mentions",
icon: app_images::notifications_image(), icon: app_images::notifications_image(ui.visuals().dark_mode),
option: AddColumnOption::Notification(source), option: AddColumnOption::Notification(source),
}); });
vec.push(ColumnOptionData { vec.push(ColumnOptionData {
title: "Someone else's Notifications", title: "Someone else's Notifications",
description: "Stay up to date with someone else's notifications and mentions", description: "Stay up to date with someone else's notifications and mentions",
icon: app_images::notifications_image(), icon: app_images::notifications_image(ui.visuals().dark_mode),
option: AddColumnOption::ExternalNotification, option: AddColumnOption::ExternalNotification,
}); });

View File

@@ -3,6 +3,7 @@ use egui::{vec2, Direction, Layout, Pos2, Stroke};
use egui_tabs::TabColor; use egui_tabs::TabColor;
use enostr::KeypairUnowned; use enostr::KeypairUnowned;
use nostrdb::Transaction; use nostrdb::Transaction;
use notedeck::ui::is_narrow;
use notedeck_ui::jobs::JobsCache; use notedeck_ui::jobs::JobsCache;
use std::f32::consts::PI; use std::f32::consts::PI;
use tracing::{error, warn}; use tracing::{error, warn};
@@ -114,7 +115,9 @@ fn timeline_ui(
.unwrap_or(false); .unwrap_or(false);
let goto_top_resp = if show_top_button { let goto_top_resp = if show_top_button {
let top_button_pos = ui.available_rect_before_wrap().right_top() - vec2(48.0, -24.0); let top_button_pos_x = if is_narrow(ui.ctx()) { 28.0 } else { 48.0 };
let top_button_pos =
ui.available_rect_before_wrap().right_top() - vec2(top_button_pos_x, -24.0);
egui::Area::new(ui.id().with("foreground_area")) egui::Area::new(ui.id().with("foreground_area"))
.order(egui::Order::Middle) .order(egui::Order::Middle)
.fixed_pos(top_button_pos) .fixed_pos(top_button_pos)

View File

@@ -113,10 +113,14 @@ pub fn help_light_image() -> Image<'static> {
)) ))
} }
pub fn home_button_image() -> Image<'static> { pub fn home_dark_image() -> Image<'static> {
Image::new(include_image!("../../../assets/icons/home-toolbar.png")) Image::new(include_image!("../../../assets/icons/home-toolbar.png"))
} }
pub fn home_light_image() -> Image<'static> {
home_dark_image().tint(Color32::BLACK)
}
pub fn home_image() -> Image<'static> { pub fn home_image() -> Image<'static> {
Image::new(include_image!( Image::new(include_image!(
"../../../assets/icons/home_icon_dark_4x.png" "../../../assets/icons/home_icon_dark_4x.png"
@@ -141,16 +145,22 @@ pub fn new_deck_image() -> Image<'static> {
)) ))
} }
pub fn notifications_button_image() -> Image<'static> { pub fn notifications_image(dark_mode: bool) -> Image<'static> {
if dark_mode {
crate::app_images::notifications_dark_image()
} else {
crate::app_images::notifications_light_image()
}
}
pub fn notifications_dark_image() -> Image<'static> {
Image::new(include_image!( Image::new(include_image!(
"../../../assets/icons/notifications_dark_4x.png" "../../../assets/icons/notifications_dark_4x.png"
)) ))
} }
pub fn notifications_image() -> Image<'static> { pub fn notifications_light_image() -> Image<'static> {
Image::new(include_image!( notifications_dark_image().tint(Color32::BLACK)
"../../../assets/icons/notifications_icon_dark_4x.png"
))
} }
pub fn repost_dark_image() -> Image<'static> { pub fn repost_dark_image() -> Image<'static> {
Image::new(include_image!("../../../assets/icons/repost.svg")) Image::new(include_image!("../../../assets/icons/repost.svg"))

View File

@@ -15,6 +15,7 @@ pub use contents::{render_note_contents, render_note_preview, NoteContents};
pub use context::NoteContextButton; pub use context::NoteContextButton;
use notedeck::note::MediaAction; use notedeck::note::MediaAction;
use notedeck::note::ZapTargetAmount; use notedeck::note::ZapTargetAmount;
use notedeck::ui::is_narrow;
use notedeck::Images; use notedeck::Images;
pub use options::NoteOptions; pub use options::NoteOptions;
pub use reply_description::reply_desc; pub use reply_description::reply_desc;
@@ -253,6 +254,10 @@ impl<'a, 'd> NoteView<'a, 'd> {
ui.spacing_mut().item_spacing.x = 4.0; ui.spacing_mut().item_spacing.x = 4.0;
} }
if is_narrow(ui.ctx()) {
ui.spacing_mut().item_spacing.x = 1.0
}
let pfp_size = self.options().pfp_size(); let pfp_size = self.options().pfp_size();
match profile match profile
@@ -363,7 +368,7 @@ impl<'a, 'd> NoteView<'a, 'd> {
let horiz_resp = ui let horiz_resp = ui
.horizontal(|ui| { .horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 2.0; ui.spacing_mut().item_spacing.x = if is_narrow(ui.ctx()) { 1.0 } else { 2.0 };
ui.add(Username::new(profile.as_ref().ok(), note.pubkey()).abbreviated(20)); ui.add(Username::new(profile.as_ref().ok(), note.pubkey()).abbreviated(20));
let cached_note = note_cache.cached_note_or_insert_mut(note_key, note); let cached_note = note_cache.cached_note_or_insert_mut(note_key, note);
@@ -507,7 +512,7 @@ impl<'a, 'd> NoteView<'a, 'd> {
self.show_unread_indicator, self.show_unread_indicator,
); );
ui.horizontal(|ui| 's: { ui.horizontal(|ui| 's: {
ui.spacing_mut().item_spacing.x = 2.0; ui.spacing_mut().item_spacing.x = if is_narrow(ui.ctx()) { 1.0 } else { 2.0 };
let note_reply = self let note_reply = self
.note_context .note_context