diff --git a/crates/notedeck/src/persist/settings_handler.rs b/crates/notedeck/src/persist/settings_handler.rs index 8584c12ca..3a7850caa 100644 --- a/crates/notedeck/src/persist/settings_handler.rs +++ b/crates/notedeck/src/persist/settings_handler.rs @@ -40,6 +40,7 @@ pub struct Settings { #[serde(default = "default_animate_nav_transitions")] pub animate_nav_transitions: bool, pub max_hashtags_per_note: usize, + pub load_media_by_default: bool, } fn default_animate_nav_transitions() -> bool { @@ -57,6 +58,7 @@ impl Default for Settings { note_body_font_size: DEFAULT_NOTE_BODY_FONT_SIZE, animate_nav_transitions: default_animate_nav_transitions(), max_hashtags_per_note: DEFAULT_MAX_HASHTAGS_PER_NOTE, + load_media_by_default: false, } } } @@ -75,7 +77,7 @@ impl SettingsHandler { } } - fn read_from_zomfactor_file(&self) -> Option { + fn read_from_zoomfactor_file(&self) -> Option { match self.directory.get_file(ZOOM_FACTOR_FILE.to_string()) { Ok(contents) => serde_json::from_str::(&contents).ok(), Err(_) => None, @@ -97,11 +99,11 @@ impl SettingsHandler { }; // if zoom_factor.txt exists migrate - if let Some(zom_factor) = self.read_from_zomfactor_file() { + if let Some(zoom_factor) = self.read_from_zoomfactor_file() { info!("migrating theme preference from zom_factor file"); _ = delete_file(&self.directory.file_path, ZOOM_FACTOR_FILE.to_string()); - settings.zoom_factor = zom_factor; + settings.zoom_factor = zoom_factor; migrated = true; } else { info!("zoom_factor.txt exists migrate file not found, using default zoom factor"); @@ -196,6 +198,11 @@ impl SettingsHandler { self.try_save_settings(); } + pub fn set_load_media_by_default(&mut self, value: bool) { + self.get_settings_mut().load_media_by_default = value; + self.try_save_settings(); + } + pub fn set_note_body_font_size(&mut self, value: f32) { self.get_settings_mut().note_body_font_size = value; self.try_save_settings(); @@ -264,6 +271,13 @@ impl SettingsHandler { self.current_settings.is_some() } + pub fn load_media_by_default(&self) -> bool { + self.current_settings + .as_ref() + .map(|s| s.load_media_by_default) + .unwrap_or(false) + } + pub fn note_body_font_size(&self) -> f32 { self.current_settings .as_ref() diff --git a/crates/notedeck/src/ui.rs b/crates/notedeck/src/ui.rs index 0f35b8dae..31c0bf69d 100644 --- a/crates/notedeck/src/ui.rs +++ b/crates/notedeck/src/ui.rs @@ -1,7 +1,48 @@ +use egui::{Color32, Stroke}; + use crate::NotedeckTextStyle; pub const NARROW_SCREEN_WIDTH: f32 = 550.0; +pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { + let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0); + let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click()); + if response.clicked() { + *on = !*on; + response.mark_changed(); + } + response.widget_info(|| { + egui::WidgetInfo::selected(egui::WidgetType::Checkbox, ui.is_enabled(), *on, "") + }); + + if ui.is_rect_visible(rect) { + let how_on = ui.ctx().animate_bool_responsive(response.id, *on); + let visuals = ui.style().interact_selectable(&response, *on); + let rect = rect.expand(visuals.expansion); + let radius = 0.5 * rect.height(); + let stroke_color = if ui.visuals().dark_mode { + Color32::WHITE + } else { + Color32::BLACK + }; + let stroke = Stroke::new(visuals.fg_stroke.width, stroke_color); + + ui.painter().rect( + rect, + radius, + visuals.bg_fill, + visuals.bg_stroke, + egui::StrokeKind::Inside, + ); + let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on); + let center = egui::pos2(circle_x, rect.center().y); + ui.painter() + .circle(center, 0.75 * radius, visuals.bg_fill, stroke); + } + + response +} + pub fn richtext_small(text: S) -> egui::RichText where S: Into, diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index 530d0306a..2e725122f 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -57,7 +57,6 @@ pub struct Damus { /// Were columns loaded from the commandline? If so disable persistence. pub options: AppOptions, pub note_options: NoteOptions, - pub unrecognized_args: BTreeSet, /// keep track of follow packs @@ -436,6 +435,11 @@ fn render_damus(damus: &mut Damus, app_ctx: &mut AppContext<'_>, ui: &mut egui:: .note_options .set(NoteOptions::Wide, is_narrow(ui.ctx())); + damus.note_options.set( + NoteOptions::LoadMediaByDefault, + app_ctx.settings.load_media_by_default(), + ); + let app_resp = if notedeck::ui::is_narrow(ui.ctx()) { render_damus_mobile(damus, app_ctx, ui) } else { @@ -662,6 +666,11 @@ impl Damus { fn get_note_options(args: ColumnsArgs, settings_handler: &mut SettingsHandler) -> NoteOptions { let mut note_options = NoteOptions::default(); + note_options.set( + NoteOptions::LoadMediaByDefault, + settings_handler.load_media_by_default(), + ); + note_options.set( NoteOptions::Textmode, args.is_flag_set(ColumnsFlag::Textmode), diff --git a/crates/notedeck_columns/src/ui/settings.rs b/crates/notedeck_columns/src/ui/settings.rs index 02852e950..7f4983a3b 100644 --- a/crates/notedeck_columns/src/ui/settings.rs +++ b/crates/notedeck_columns/src/ui/settings.rs @@ -6,9 +6,10 @@ use egui_extras::{Size, StripBuilder}; use enostr::NoteId; use nostrdb::Transaction; use notedeck::{ - tr, ui::richtext_small, Images, JobsCache, LanguageIdentifier, Localization, NoteContext, - NotedeckTextStyle, Settings, SettingsHandler, DEFAULT_MAX_HASHTAGS_PER_NOTE, - DEFAULT_NOTE_BODY_FONT_SIZE, + tr, + ui::{richtext_small, toggle_ui}, + Images, JobsCache, LanguageIdentifier, Localization, NoteContext, NotedeckTextStyle, Settings, + SettingsHandler, DEFAULT_MAX_HASHTAGS_PER_NOTE, DEFAULT_NOTE_BODY_FONT_SIZE, }; use notedeck_ui::{ app_images::{copy_to_clipboard_dark_image, copy_to_clipboard_image}, @@ -30,6 +31,7 @@ const RESET_ZOOM: f32 = 1.0; pub enum SettingsAction { SetZoomFactor(f32), + SetLoadMediaByDefault(bool), SetTheme(ThemePreference), SetLocale(LanguageIdentifier), SetRepliestNewestFirst(bool), @@ -100,6 +102,12 @@ impl SettingsAction { settings.set_max_hashtags_per_note(value); accounts.update_max_hashtags_per_note(value); } + Self::SetLoadMediaByDefault(load_media) => { + app.note_options + .set(NoteOptions::LoadMediaByDefault, load_media); + + settings.set_load_media_by_default(load_media); + } } route_action } @@ -468,18 +476,7 @@ impl<'a> SettingsView<'a> { "Label for Sort replies newest first, others settings section", ))); - if ui - .toggle_value( - &mut self.settings.show_replies_newest_first, - RichText::new(tr!( - self.note_context.i18n, - "On", - "Setting to turn on sorting replies so that the newest are shown first" - )) - .text_style(NotedeckTextStyle::Small.text_style()), - ) - .changed() - { + if toggle_ui(ui, &mut self.settings.show_replies_newest_first).changed() { action = Some(SettingsAction::SetRepliestNewestFirst( self.settings.show_replies_newest_first, )); @@ -733,6 +730,10 @@ impl<'a> SettingsView<'a> { ui.add_space(5.0); + self.media_section(ui); + + ui.add_space(5.0); + if let Some(new_action) = self.storage_section(ui) { action = Some(new_action); } @@ -759,6 +760,36 @@ impl<'a> SettingsView<'a> { BodyResponse::scroll(scroll_out) } + + fn media_section(&mut self, ui: &mut egui::Ui) -> Option { + let mut action = None; + + let title = tr!( + self.note_context.i18n, + "Media", + "label for media setting section" + ); + + settings_group(ui, title, |ui| { + ui.horizontal_wrapped(|ui| { + ui.label(richtext_small(tr!( + self.note_context.i18n, + "Load media by default", + "Label for load media by default, Media settings section", + ))); + + if toggle_ui(ui, &mut self.settings.load_media_by_default).changed() { + action = Some(SettingsAction::SetLoadMediaByDefault( + self.settings.load_media_by_default, + )); + } + }); + + ui.end_row(); + }); + + action + } } pub fn format_size(size_bytes: u64) -> String { diff --git a/crates/notedeck_ui/src/note/media.rs b/crates/notedeck_ui/src/note/media.rs index 3ccf13c24..c88a14cbf 100644 --- a/crates/notedeck_ui/src/note/media.rs +++ b/crates/notedeck_ui/src/note/media.rs @@ -62,13 +62,16 @@ pub fn image_carousel( let mut media_action: Option<(usize, MediaUIAction)> = None; for (i, media) in medias.iter().enumerate() { + let trusted_media = note_options.contains(NoteOptions::TrustMedia) + || note_options.contains(NoteOptions::LoadMediaByDefault); + let media_response = render_media( ui, img_cache, job_pool, jobs, media, - note_options.contains(NoteOptions::TrustMedia), + trusted_media, i18n, size, if note_options.contains(NoteOptions::NoAnimations) { diff --git a/crates/notedeck_ui/src/note/options.rs b/crates/notedeck_ui/src/note/options.rs index cb66c801c..7f49dbca2 100644 --- a/crates/notedeck_ui/src/note/options.rs +++ b/crates/notedeck_ui/src/note/options.rs @@ -47,6 +47,9 @@ bitflags! { /// There is enough trust to show media in this note const TrustMedia = 1 << 20; + + /// show media by default + const LoadMediaByDefault = 1 << 21; } }