settings.rs (20130B)
1 use egui::{vec2, Button, Color32, ComboBox, Frame, Margin, RichText, ThemePreference}; 2 use notedeck::{tr, Images, LanguageIdentifier, Localization, NotedeckTextStyle, ThemeHandler}; 3 use notedeck_ui::NoteOptions; 4 use strum::Display; 5 6 use crate::{nav::RouterAction, Damus, Route}; 7 8 #[derive(Clone, Copy, PartialEq, Eq, Display)] 9 pub enum ShowNoteClientOptions { 10 Hide, 11 Top, 12 Bottom, 13 } 14 15 pub enum SettingsAction { 16 SetZoom(f32), 17 SetTheme(ThemePreference), 18 SetShowNoteClient(ShowNoteClientOptions), 19 SetLocale(LanguageIdentifier), 20 OpenRelays, 21 OpenCacheFolder, 22 ClearCacheFolder, 23 } 24 25 impl SettingsAction { 26 pub fn process_settings_action<'a>( 27 self, 28 app: &mut Damus, 29 theme_handler: &'a mut ThemeHandler, 30 i18n: &'a mut Localization, 31 img_cache: &mut Images, 32 ctx: &egui::Context, 33 ) -> Option<RouterAction> { 34 let mut route_action: Option<RouterAction> = None; 35 36 match self { 37 SettingsAction::OpenRelays => { 38 route_action = Some(RouterAction::route_to(Route::Relays)); 39 } 40 SettingsAction::SetZoom(zoom_level) => { 41 ctx.set_zoom_factor(zoom_level); 42 } 43 SettingsAction::SetShowNoteClient(newvalue) => match newvalue { 44 ShowNoteClientOptions::Hide => { 45 app.note_options.set(NoteOptions::ShowNoteClientTop, false); 46 app.note_options 47 .set(NoteOptions::ShowNoteClientBottom, false); 48 } 49 ShowNoteClientOptions::Bottom => { 50 app.note_options.set(NoteOptions::ShowNoteClientTop, false); 51 app.note_options 52 .set(NoteOptions::ShowNoteClientBottom, true); 53 } 54 ShowNoteClientOptions::Top => { 55 app.note_options.set(NoteOptions::ShowNoteClientTop, true); 56 app.note_options 57 .set(NoteOptions::ShowNoteClientBottom, false); 58 } 59 }, 60 SettingsAction::SetTheme(theme) => { 61 ctx.options_mut(|o| { 62 o.theme_preference = theme; 63 }); 64 theme_handler.save(theme); 65 } 66 SettingsAction::SetLocale(language) => { 67 _ = i18n.set_locale(language); 68 } 69 SettingsAction::OpenCacheFolder => { 70 use opener; 71 let _ = opener::open(img_cache.base_path.clone()); 72 } 73 SettingsAction::ClearCacheFolder => { 74 let _ = img_cache.clear_folder_contents(); 75 } 76 } 77 route_action 78 } 79 } 80 81 pub struct SettingsView<'a> { 82 theme: &'a mut String, 83 selected_language: &'a mut String, 84 show_note_client: &'a mut ShowNoteClientOptions, 85 i18n: &'a mut Localization, 86 img_cache: &'a mut Images, 87 } 88 89 impl<'a> SettingsView<'a> { 90 pub fn new( 91 img_cache: &'a mut Images, 92 selected_language: &'a mut String, 93 theme: &'a mut String, 94 show_note_client: &'a mut ShowNoteClientOptions, 95 i18n: &'a mut Localization, 96 ) -> Self { 97 Self { 98 show_note_client, 99 theme, 100 img_cache, 101 selected_language, 102 i18n, 103 } 104 } 105 106 pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> { 107 let id = ui.id(); 108 let mut action = None; 109 110 Frame::default() 111 .inner_margin(Margin::symmetric(10, 10)) 112 .show(ui, |ui| { 113 Frame::group(ui.style()) 114 .fill(ui.style().visuals.widgets.open.bg_fill) 115 .inner_margin(10.0) 116 .show(ui, |ui| { 117 ui.vertical(|ui| { 118 ui.label( 119 RichText::new(tr!( 120 self.i18n, 121 "Appearance", 122 "Label for appearance settings section" 123 )) 124 .text_style(NotedeckTextStyle::Body.text_style()), 125 ); 126 ui.separator(); 127 ui.spacing_mut().item_spacing = vec2(10.0, 10.0); 128 129 let current_zoom = ui.ctx().zoom_factor(); 130 131 ui.horizontal(|ui| { 132 ui.label( 133 RichText::new(tr!( 134 self.i18n, 135 "Zoom Level:", 136 "Label for zoom level, Appearance settings section" 137 )) 138 .text_style(NotedeckTextStyle::Small.text_style()), 139 ); 140 141 if ui 142 .button( 143 RichText::new("-") 144 .text_style(NotedeckTextStyle::Small.text_style()), 145 ) 146 .clicked() 147 { 148 let new_zoom = (current_zoom - 0.1).max(0.1); 149 action = Some(SettingsAction::SetZoom(new_zoom)); 150 }; 151 152 ui.label( 153 RichText::new(format!("{:.0}%", current_zoom * 100.0)) 154 .text_style(NotedeckTextStyle::Small.text_style()), 155 ); 156 157 if ui 158 .button( 159 RichText::new("+") 160 .text_style(NotedeckTextStyle::Small.text_style()), 161 ) 162 .clicked() 163 { 164 let new_zoom = (current_zoom + 0.1).min(10.0); 165 action = Some(SettingsAction::SetZoom(new_zoom)); 166 }; 167 168 if ui 169 .button( 170 RichText::new(tr!( 171 self.i18n, 172 "Reset", 173 "Label for reset zoom level, Appearance settings section" 174 )) 175 .text_style(NotedeckTextStyle::Small.text_style()), 176 ) 177 .clicked() 178 { 179 action = Some(SettingsAction::SetZoom(1.0)); 180 } 181 }); 182 183 ui.horizontal(|ui| { 184 ui.label( 185 RichText::new(tr!( 186 self.i18n, 187 "Language:", 188 "Label for language, Appearance settings section" 189 )) 190 .text_style(NotedeckTextStyle::Small.text_style()), 191 ); 192 ComboBox::from_label("") 193 .selected_text(self.selected_language.to_owned()) 194 .show_ui(ui, |ui| { 195 for lang in self.i18n.get_available_locales() { 196 if ui 197 .selectable_value( 198 self.selected_language, 199 lang.to_string(), 200 lang.to_string(), 201 ) 202 .clicked() 203 { 204 action = 205 Some(SettingsAction::SetLocale(lang.to_owned())) 206 } 207 } 208 }) 209 }); 210 211 ui.horizontal(|ui| { 212 ui.label( 213 RichText::new(tr!( 214 self.i18n, 215 "Theme:", 216 "Label for theme, Appearance settings section" 217 )) 218 .text_style(NotedeckTextStyle::Small.text_style()), 219 ); 220 if ui 221 .selectable_value( 222 self.theme, 223 "Light".into(), 224 RichText::new(tr!( 225 self.i18n, 226 "Light", 227 "Label for Theme Light, Appearance settings section" 228 )) 229 .text_style(NotedeckTextStyle::Small.text_style()), 230 ) 231 .clicked() 232 { 233 action = Some(SettingsAction::SetTheme(ThemePreference::Light)); 234 } 235 if ui 236 .selectable_value( 237 self.theme, 238 "Dark".into(), 239 RichText::new(tr!( 240 self.i18n, 241 "Dark", 242 "Label for Theme Dark, Appearance settings section" 243 )) 244 .text_style(NotedeckTextStyle::Small.text_style()), 245 ) 246 .clicked() 247 { 248 action = Some(SettingsAction::SetTheme(ThemePreference::Dark)); 249 } 250 }); 251 }); 252 }); 253 254 ui.add_space(5.0); 255 256 Frame::group(ui.style()) 257 .fill(ui.style().visuals.widgets.open.bg_fill) 258 .inner_margin(10.0) 259 .show(ui, |ui| { 260 ui.label( 261 RichText::new(tr!( 262 self.i18n, 263 "Storage", 264 "Label for storage settings section" 265 )) 266 .text_style(NotedeckTextStyle::Body.text_style()), 267 ); 268 ui.separator(); 269 270 ui.vertical(|ui| { 271 ui.spacing_mut().item_spacing = vec2(10.0, 10.0); 272 273 ui.horizontal_wrapped(|ui| { 274 let static_imgs_size = self 275 .img_cache 276 .static_imgs 277 .cache_size 278 .lock() 279 .unwrap(); 280 281 let gifs_size = self.img_cache.gifs.cache_size.lock().unwrap(); 282 283 ui.label( 284 RichText::new(format!("{} {}", 285 tr!( 286 self.i18n, 287 "Image cache size:", 288 "Label for Image cache size, Storage settings section" 289 ), 290 format_size( 291 [static_imgs_size, gifs_size] 292 .iter() 293 .fold(0_u64, |acc, cur| acc 294 + cur.unwrap_or_default()) 295 ) 296 )) 297 .text_style(NotedeckTextStyle::Small.text_style()), 298 ); 299 300 ui.end_row(); 301 302 if !notedeck::ui::is_compiled_as_mobile() && 303 ui.button(RichText::new(tr!(self.i18n, "View folder:", "Label for view folder button, Storage settings section")) 304 .text_style(NotedeckTextStyle::Small.text_style())).clicked() { 305 action = Some(SettingsAction::OpenCacheFolder); 306 } 307 308 let clearcache_resp = ui.button( 309 RichText::new(tr!( 310 self.i18n, 311 "Clear cache", 312 "Label for clear cache button, Storage settings section" 313 )) 314 .text_style(NotedeckTextStyle::Small.text_style()) 315 .color(Color32::LIGHT_RED), 316 ); 317 318 let id_clearcache = id.with("clear_cache"); 319 if clearcache_resp.clicked() { 320 ui.data_mut(|d| d.insert_temp(id_clearcache, true)); 321 } 322 323 if ui.data_mut(|d| *d.get_temp_mut_or_default(id_clearcache)) { 324 let mut confirm_pressed = false; 325 clearcache_resp.show_tooltip_ui(|ui| { 326 let confirm_resp = ui.button(tr!( 327 self.i18n, 328 "Confirm", 329 "Label for confirm clear cache, Storage settings section" 330 )); 331 if confirm_resp.clicked() { 332 confirm_pressed = true; 333 } 334 335 if confirm_resp.clicked() || ui.button(tr!( 336 self.i18n, 337 "Cancel", 338 "Label for cancel clear cache, Storage settings section" 339 )).clicked() { 340 ui.data_mut(|d| d.insert_temp(id_clearcache, false)); 341 } 342 }); 343 344 if confirm_pressed { 345 action = Some(SettingsAction::ClearCacheFolder); 346 } else if !confirm_pressed 347 && clearcache_resp.clicked_elsewhere() 348 { 349 ui.data_mut(|d| d.insert_temp(id_clearcache, false)); 350 } 351 }; 352 }); 353 }); 354 }); 355 356 ui.add_space(5.0); 357 358 Frame::group(ui.style()) 359 .fill(ui.style().visuals.widgets.open.bg_fill) 360 .inner_margin(10.0) 361 .show(ui, |ui| { 362 ui.label( 363 RichText::new(tr!( 364 self.i18n, 365 "Others", 366 "Label for others settings section" 367 )) 368 .text_style(NotedeckTextStyle::Body.text_style()), 369 ); 370 ui.separator(); 371 ui.vertical(|ui| { 372 ui.spacing_mut().item_spacing = vec2(10.0, 10.0); 373 374 ui.horizontal_wrapped(|ui| { 375 ui.label( 376 RichText::new( 377 tr!( 378 self.i18n, 379 "Show source client", 380 "Label for Show source client, others settings section" 381 )) 382 .text_style(NotedeckTextStyle::Small.text_style()), 383 ); 384 385 for option in [ 386 ShowNoteClientOptions::Hide, 387 ShowNoteClientOptions::Top, 388 ShowNoteClientOptions::Bottom, 389 ] { 390 let label = option.clone().to_string(); 391 392 if ui 393 .selectable_value( 394 self.show_note_client, 395 option, 396 RichText::new(label) 397 .text_style(NotedeckTextStyle::Small.text_style()), 398 ) 399 .changed() 400 { 401 action = Some(SettingsAction::SetShowNoteClient(option)); 402 } 403 } 404 }); 405 }); 406 }); 407 408 ui.add_space(10.0); 409 410 if ui 411 .add_sized( 412 [ui.available_width(), 30.0], 413 Button::new( 414 RichText::new(tr!( 415 self.i18n, 416 "Configure relays", 417 "Label for configure relays, settings section" 418 )) 419 .text_style(NotedeckTextStyle::Small.text_style()), 420 ), 421 ) 422 .clicked() 423 { 424 action = Some(SettingsAction::OpenRelays); 425 } 426 }); 427 428 action 429 } 430 } 431 432 pub fn format_size(size_bytes: u64) -> String { 433 const KB: f64 = 1024.0; 434 const MB: f64 = KB * 1024.0; 435 const GB: f64 = MB * 1024.0; 436 437 let size = size_bytes as f64; 438 439 if size < KB { 440 format!("{size:.0} Bytes") 441 } else if size < MB { 442 format!("{:.1} KB", size / KB) 443 } else if size < GB { 444 format!("{:.1} MB", size / MB) 445 } else { 446 format!("{:.2} GB", size / GB) 447 } 448 }