mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-18 09:04:21 +01:00
Merge remote-tracking branch 'fernando/feat/persist_settings'
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
use crate::account::FALLBACK_PUBKEY;
|
||||
use crate::i18n::Localization;
|
||||
use crate::persist::{AppSizeHandler, ZoomHandler};
|
||||
use crate::persist::{AppSizeHandler, SettingsHandler};
|
||||
use crate::wallet::GlobalWallet;
|
||||
use crate::zaps::Zaps;
|
||||
use crate::JobPool;
|
||||
use crate::{
|
||||
frame_history::FrameHistory, AccountStorage, Accounts, AppContext, Args, DataPath,
|
||||
DataPathType, Directory, Images, NoteAction, NoteCache, RelayDebugView, UnknownIds,
|
||||
};
|
||||
use crate::{JobPool, SettingsHandler};
|
||||
use egui::Margin;
|
||||
use egui::ThemePreference;
|
||||
use egui_winit::clipboard::Clipboard;
|
||||
@@ -40,9 +40,8 @@ pub struct Notedeck {
|
||||
global_wallet: GlobalWallet,
|
||||
path: DataPath,
|
||||
args: Args,
|
||||
settings_handler: SettingsHandler,
|
||||
settings: SettingsHandler,
|
||||
app: Option<Rc<RefCell<dyn App>>>,
|
||||
zoom: ZoomHandler,
|
||||
app_size: AppSizeHandler,
|
||||
unrecognized_args: BTreeSet<String>,
|
||||
clipboard: Clipboard,
|
||||
@@ -99,7 +98,15 @@ impl eframe::App for Notedeck {
|
||||
|
||||
render_notedeck(self, ctx);
|
||||
|
||||
self.zoom.try_save_zoom_factor(ctx);
|
||||
self.settings.update_batch(|settings| {
|
||||
settings.zoom_factor = ctx.zoom_factor();
|
||||
settings.locale = self.i18n.get_current_locale().to_string();
|
||||
settings.theme = if ctx.style().visuals.dark_mode {
|
||||
ThemePreference::Dark
|
||||
} else {
|
||||
ThemePreference::Light
|
||||
};
|
||||
});
|
||||
self.app_size.try_save_app_size(ctx);
|
||||
|
||||
if self.args.relay_debug {
|
||||
@@ -159,9 +166,7 @@ impl Notedeck {
|
||||
1024usize * 1024usize * 1024usize * 1024usize
|
||||
};
|
||||
|
||||
let mut settings_handler = SettingsHandler::new(&path);
|
||||
|
||||
settings_handler.load();
|
||||
let settings = SettingsHandler::new(&path).load();
|
||||
|
||||
let config = Config::new().set_ingester_threads(2).set_mapsize(map_size);
|
||||
|
||||
@@ -216,12 +221,8 @@ impl Notedeck {
|
||||
|
||||
let img_cache = Images::new(img_cache_dir);
|
||||
let note_cache = NoteCache::default();
|
||||
let zoom = ZoomHandler::new(&path);
|
||||
let app_size = AppSizeHandler::new(&path);
|
||||
|
||||
if let Some(z) = zoom.get_zoom_factor() {
|
||||
ctx.set_zoom_factor(z);
|
||||
}
|
||||
let app_size = AppSizeHandler::new(&path);
|
||||
|
||||
// migrate
|
||||
if let Err(e) = img_cache.migrate_v0() {
|
||||
@@ -236,7 +237,7 @@ impl Notedeck {
|
||||
let mut i18n = Localization::new();
|
||||
|
||||
let setting_locale: Result<LanguageIdentifier, LanguageIdentifierError> =
|
||||
settings_handler.locale().parse();
|
||||
settings.locale().parse();
|
||||
|
||||
if setting_locale.is_ok() {
|
||||
if let Err(err) = i18n.set_locale(setting_locale.unwrap()) {
|
||||
@@ -263,9 +264,8 @@ impl Notedeck {
|
||||
global_wallet,
|
||||
path: path.clone(),
|
||||
args: parsed_args,
|
||||
settings_handler,
|
||||
settings,
|
||||
app: None,
|
||||
zoom,
|
||||
app_size,
|
||||
unrecognized_args,
|
||||
frame_history: FrameHistory::default(),
|
||||
@@ -292,7 +292,7 @@ impl Notedeck {
|
||||
global_wallet: &mut self.global_wallet,
|
||||
path: &self.path,
|
||||
args: &self.args,
|
||||
settings_handler: &mut self.settings_handler,
|
||||
settings: &mut self.settings,
|
||||
clipboard: &mut self.clipboard,
|
||||
zaps: &mut self.zaps,
|
||||
frame_history: &mut self.frame_history,
|
||||
@@ -310,7 +310,15 @@ impl Notedeck {
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> ThemePreference {
|
||||
self.settings_handler.theme()
|
||||
self.settings.theme()
|
||||
}
|
||||
|
||||
pub fn note_body_font_size(&self) -> f32 {
|
||||
self.settings.note_body_font_size()
|
||||
}
|
||||
|
||||
pub fn zoom_factor(&self) -> f32 {
|
||||
self.settings.zoom_factor()
|
||||
}
|
||||
|
||||
pub fn unrecognized_args(&self) -> &BTreeSet<String> {
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct AppContext<'a> {
|
||||
pub global_wallet: &'a mut GlobalWallet,
|
||||
pub path: &'a DataPath,
|
||||
pub args: &'a Args,
|
||||
pub settings_handler: &'a mut SettingsHandler,
|
||||
pub settings: &'a mut SettingsHandler,
|
||||
pub clipboard: &'a mut Clipboard,
|
||||
pub zaps: &'a mut Zaps,
|
||||
pub frame_history: &'a mut FrameHistory,
|
||||
|
||||
@@ -31,6 +31,7 @@ pub fn desktop_font_size(text_style: &NotedeckTextStyle) -> f32 {
|
||||
NotedeckTextStyle::Button => 13.0,
|
||||
NotedeckTextStyle::Small => 12.0,
|
||||
NotedeckTextStyle::Tiny => 10.0,
|
||||
NotedeckTextStyle::NoteBody => 16.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +47,7 @@ pub fn mobile_font_size(text_style: &NotedeckTextStyle) -> f32 {
|
||||
NotedeckTextStyle::Button => 13.0,
|
||||
NotedeckTextStyle::Small => 12.0,
|
||||
NotedeckTextStyle::Tiny => 10.0,
|
||||
NotedeckTextStyle::NoteBody => 13.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
mod app_size;
|
||||
mod settings_handler;
|
||||
mod theme_handler;
|
||||
mod token_handler;
|
||||
mod zoom;
|
||||
|
||||
pub use app_size::AppSizeHandler;
|
||||
pub use settings_handler::Settings;
|
||||
pub use settings_handler::SettingsHandler;
|
||||
pub use theme_handler::ThemeHandler;
|
||||
pub use settings_handler::DEFAULT_NOTE_BODY_FONT_SIZE;
|
||||
pub use token_handler::TokenHandler;
|
||||
pub use zoom::ZoomHandler;
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
use crate::{
|
||||
storage::{self, delete_file},
|
||||
DataPath, DataPathType, Directory,
|
||||
storage::delete_file, timed_serializer::TimedSerializer, DataPath, DataPathType, Directory,
|
||||
};
|
||||
use egui::ThemePreference;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{error, info};
|
||||
|
||||
const THEME_FILE: &str = "theme.txt";
|
||||
const ZOOM_FACTOR_FILE: &str = "zoom_level.json";
|
||||
const SETTINGS_FILE: &str = "settings.json";
|
||||
|
||||
const DEFAULT_THEME: ThemePreference = ThemePreference::Dark;
|
||||
const DEFAULT_LOCALE: &str = "es-US";
|
||||
const DEFAULT_LOCALE: &str = "en-US";
|
||||
const DEFAULT_ZOOM_FACTOR: f32 = 1.0;
|
||||
const DEFAULT_SHOW_SOURCE_CLIENT: &str = "hide";
|
||||
const DEFAULT_SHOW_REPLIES_NEWEST_FIRST: bool = false;
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
pub const DEFAULT_NOTE_BODY_FONT_SIZE: f32 = 13.0;
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub const DEFAULT_NOTE_BODY_FONT_SIZE: f32 = 16.0;
|
||||
|
||||
fn deserialize_theme(serialized_theme: &str) -> Option<ThemePreference> {
|
||||
match serialized_theme {
|
||||
@@ -23,70 +28,97 @@ fn deserialize_theme(serialized_theme: &str) -> Option<ThemePreference> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Clone)]
|
||||
pub struct Settings {
|
||||
pub theme: ThemePreference,
|
||||
pub locale: String,
|
||||
pub zoom_factor: f32,
|
||||
pub show_source_client: String,
|
||||
pub show_replies_newest_first: bool,
|
||||
pub note_body_font_size: f32,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
// Use the same fallback theme as before
|
||||
Self {
|
||||
theme: DEFAULT_THEME,
|
||||
locale: DEFAULT_LOCALE.to_string(),
|
||||
zoom_factor: DEFAULT_ZOOM_FACTOR,
|
||||
show_source_client: "Hide".to_string(),
|
||||
show_source_client: DEFAULT_SHOW_SOURCE_CLIENT.to_string(),
|
||||
show_replies_newest_first: DEFAULT_SHOW_REPLIES_NEWEST_FIRST,
|
||||
note_body_font_size: DEFAULT_NOTE_BODY_FONT_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SettingsHandler {
|
||||
directory: Directory,
|
||||
serializer: TimedSerializer<Settings>,
|
||||
current_settings: Option<Settings>,
|
||||
}
|
||||
|
||||
impl SettingsHandler {
|
||||
fn read_legacy_theme(&self) -> Option<ThemePreference> {
|
||||
fn read_from_theme_file(&self) -> Option<ThemePreference> {
|
||||
match self.directory.get_file(THEME_FILE.to_string()) {
|
||||
Ok(contents) => deserialize_theme(contents.trim()),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate_to_settings_file(&mut self) -> Result<(), ()> {
|
||||
fn read_from_zomfactor_file(&self) -> Option<f32> {
|
||||
match self.directory.get_file(ZOOM_FACTOR_FILE.to_string()) {
|
||||
Ok(contents) => serde_json::from_str::<f32>(&contents).ok(),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn migrate_to_settings_file(&mut self) -> bool {
|
||||
let mut settings = Settings::default();
|
||||
let mut migrated = false;
|
||||
// if theme.txt exists migrate
|
||||
if let Some(theme_from_file) = self.read_legacy_theme() {
|
||||
if let Some(theme_from_file) = self.read_from_theme_file() {
|
||||
info!("migrating theme preference from theme.txt file");
|
||||
_ = delete_file(&self.directory.file_path, THEME_FILE.to_string());
|
||||
|
||||
self.current_settings = Some(Settings {
|
||||
theme: theme_from_file,
|
||||
..Settings::default()
|
||||
});
|
||||
|
||||
self.save();
|
||||
|
||||
Ok(())
|
||||
settings.theme = theme_from_file;
|
||||
migrated = true;
|
||||
} else {
|
||||
Err(())
|
||||
info!("theme.txt file not found, using default theme");
|
||||
};
|
||||
|
||||
// if zoom_factor.txt exists migrate
|
||||
if let Some(zom_factor) = self.read_from_zomfactor_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;
|
||||
migrated = true;
|
||||
} else {
|
||||
info!("zoom_factor.txt exists migrate file not found, using default zoom factor");
|
||||
};
|
||||
|
||||
if migrated {
|
||||
self.current_settings = Some(settings);
|
||||
self.try_save_settings();
|
||||
}
|
||||
migrated
|
||||
}
|
||||
|
||||
pub fn new(path: &DataPath) -> Self {
|
||||
let directory = Directory::new(path.path(DataPathType::Setting));
|
||||
let current_settings: Option<Settings> = None;
|
||||
let serializer =
|
||||
TimedSerializer::new(path, DataPathType::Setting, "settings.json".to_owned());
|
||||
|
||||
Self {
|
||||
directory,
|
||||
current_settings,
|
||||
serializer,
|
||||
current_settings: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&mut self) {
|
||||
if self.migrate_to_settings_file().is_ok() {
|
||||
return;
|
||||
pub fn load(mut self) -> Self {
|
||||
if self.migrate_to_settings_file() {
|
||||
return self;
|
||||
}
|
||||
|
||||
match self.directory.get_file(SETTINGS_FILE.to_string()) {
|
||||
@@ -107,27 +139,16 @@ impl SettingsHandler {
|
||||
self.current_settings = Some(Settings::default());
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
let settings = self.current_settings.as_ref().unwrap();
|
||||
match serde_json::to_string(settings) {
|
||||
Ok(serialized) => {
|
||||
if let Err(e) = storage::write_file(
|
||||
&self.directory.file_path,
|
||||
SETTINGS_FILE.to_string(),
|
||||
&serialized,
|
||||
) {
|
||||
error!("Could not save settings: {}", e);
|
||||
} else {
|
||||
info!("Settings saved successfully");
|
||||
}
|
||||
}
|
||||
Err(e) => error!("Failed to serialize settings: {}", e),
|
||||
};
|
||||
pub(crate) fn try_save_settings(&mut self) {
|
||||
let settings = self.get_settings_mut().clone();
|
||||
self.serializer.try_save(settings);
|
||||
}
|
||||
|
||||
fn get_settings_mut(&mut self) -> &mut Settings {
|
||||
pub fn get_settings_mut(&mut self) -> &mut Settings {
|
||||
if self.current_settings.is_none() {
|
||||
self.current_settings = Some(Settings::default());
|
||||
}
|
||||
@@ -136,7 +157,7 @@ impl SettingsHandler {
|
||||
|
||||
pub fn set_theme(&mut self, theme: ThemePreference) {
|
||||
self.get_settings_mut().theme = theme;
|
||||
self.save();
|
||||
self.try_save_settings();
|
||||
}
|
||||
|
||||
pub fn set_locale<S>(&mut self, locale: S)
|
||||
@@ -144,12 +165,12 @@ impl SettingsHandler {
|
||||
S: Into<String>,
|
||||
{
|
||||
self.get_settings_mut().locale = locale.into();
|
||||
self.save();
|
||||
self.try_save_settings();
|
||||
}
|
||||
|
||||
pub fn set_zoom_factor(&mut self, zoom_factor: f32) {
|
||||
self.get_settings_mut().zoom_factor = zoom_factor;
|
||||
self.save();
|
||||
self.try_save_settings();
|
||||
}
|
||||
|
||||
pub fn set_show_source_client<S>(&mut self, option: S)
|
||||
@@ -157,7 +178,17 @@ impl SettingsHandler {
|
||||
S: Into<String>,
|
||||
{
|
||||
self.get_settings_mut().show_source_client = option.into();
|
||||
self.save();
|
||||
self.try_save_settings();
|
||||
}
|
||||
|
||||
pub fn set_show_replies_newest_first(&mut self, value: bool) {
|
||||
self.get_settings_mut().show_replies_newest_first = 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();
|
||||
}
|
||||
|
||||
pub fn update_batch<F>(&mut self, update_fn: F)
|
||||
@@ -166,12 +197,12 @@ impl SettingsHandler {
|
||||
{
|
||||
let settings = self.get_settings_mut();
|
||||
update_fn(settings);
|
||||
self.save();
|
||||
self.try_save_settings();
|
||||
}
|
||||
|
||||
pub fn update_settings(&mut self, new_settings: Settings) {
|
||||
self.current_settings = Some(new_settings);
|
||||
self.save();
|
||||
self.try_save_settings();
|
||||
}
|
||||
|
||||
pub fn theme(&self) -> ThemePreference {
|
||||
@@ -202,7 +233,21 @@ impl SettingsHandler {
|
||||
.unwrap_or(DEFAULT_SHOW_SOURCE_CLIENT.to_string())
|
||||
}
|
||||
|
||||
pub fn show_replies_newest_first(&self) -> bool {
|
||||
self.current_settings
|
||||
.as_ref()
|
||||
.map(|s| s.show_replies_newest_first)
|
||||
.unwrap_or(DEFAULT_SHOW_REPLIES_NEWEST_FIRST)
|
||||
}
|
||||
|
||||
pub fn is_loaded(&self) -> bool {
|
||||
self.current_settings.is_some()
|
||||
}
|
||||
|
||||
pub fn note_body_font_size(&self) -> f32 {
|
||||
self.current_settings
|
||||
.as_ref()
|
||||
.map(|s| s.note_body_font_size)
|
||||
.unwrap_or(DEFAULT_NOTE_BODY_FONT_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
use egui::ThemePreference;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{storage, DataPath, DataPathType, Directory};
|
||||
|
||||
pub struct ThemeHandler {
|
||||
directory: Directory,
|
||||
fallback_theme: ThemePreference,
|
||||
}
|
||||
|
||||
const THEME_FILE: &str = "theme.txt";
|
||||
|
||||
impl ThemeHandler {
|
||||
pub fn new(path: &DataPath) -> Self {
|
||||
let directory = Directory::new(path.path(DataPathType::Setting));
|
||||
let fallback_theme = ThemePreference::Dark;
|
||||
Self {
|
||||
directory,
|
||||
fallback_theme,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(&self) -> ThemePreference {
|
||||
match self.directory.get_file(THEME_FILE.to_owned()) {
|
||||
Ok(contents) => match deserialize_theme(contents) {
|
||||
Some(theme) => theme,
|
||||
None => {
|
||||
error!(
|
||||
"Could not deserialize theme. Using fallback {:?} instead",
|
||||
self.fallback_theme
|
||||
);
|
||||
self.fallback_theme
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Could not read {} file: {:?}\nUsing fallback {:?} instead",
|
||||
THEME_FILE, e, self.fallback_theme
|
||||
);
|
||||
self.fallback_theme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self, theme: ThemePreference) {
|
||||
match storage::write_file(
|
||||
&self.directory.file_path,
|
||||
THEME_FILE.to_owned(),
|
||||
&theme_to_serialized(&theme),
|
||||
) {
|
||||
Ok(_) => info!(
|
||||
"Successfully saved {:?} theme change to {}",
|
||||
theme, THEME_FILE
|
||||
),
|
||||
Err(_) => error!("Could not save {:?} theme change to {}", theme, THEME_FILE),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn theme_to_serialized(theme: &ThemePreference) -> String {
|
||||
match theme {
|
||||
ThemePreference::Dark => "dark",
|
||||
ThemePreference::Light => "light",
|
||||
ThemePreference::System => "system",
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn deserialize_theme(serialized_theme: String) -> Option<ThemePreference> {
|
||||
match serialized_theme.as_str() {
|
||||
"dark" => Some(ThemePreference::Dark),
|
||||
"light" => Some(ThemePreference::Light),
|
||||
"system" => Some(ThemePreference::System),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use crate::{DataPath, DataPathType};
|
||||
use egui::Context;
|
||||
|
||||
use crate::timed_serializer::TimedSerializer;
|
||||
|
||||
pub struct ZoomHandler {
|
||||
serializer: TimedSerializer<f32>,
|
||||
}
|
||||
|
||||
impl ZoomHandler {
|
||||
pub fn new(path: &DataPath) -> Self {
|
||||
let serializer =
|
||||
TimedSerializer::new(path, DataPathType::Setting, "zoom_level.json".to_owned());
|
||||
|
||||
Self { serializer }
|
||||
}
|
||||
|
||||
pub fn try_save_zoom_factor(&mut self, ctx: &Context) {
|
||||
let cur_zoom_level = ctx.zoom_factor();
|
||||
self.serializer.try_save(cur_zoom_level);
|
||||
}
|
||||
|
||||
pub fn get_zoom_factor(&self) -> Option<f32> {
|
||||
self.serializer.get_item()
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ pub enum NotedeckTextStyle {
|
||||
Button,
|
||||
Small,
|
||||
Tiny,
|
||||
NoteBody,
|
||||
}
|
||||
|
||||
impl NotedeckTextStyle {
|
||||
@@ -29,6 +30,7 @@ impl NotedeckTextStyle {
|
||||
Self::Button => TextStyle::Button,
|
||||
Self::Small => TextStyle::Small,
|
||||
Self::Tiny => TextStyle::Name("Tiny".into()),
|
||||
Self::NoteBody => TextStyle::Name("NoteBody".into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +45,7 @@ impl NotedeckTextStyle {
|
||||
Self::Button => FontFamily::Proportional,
|
||||
Self::Small => FontFamily::Proportional,
|
||||
Self::Tiny => FontFamily::Proportional,
|
||||
Self::NoteBody => FontFamily::Proportional,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,16 @@ use crate::debouncer::Debouncer;
|
||||
use crate::{storage, DataPath, DataPathType, Directory};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use tracing::info; // Adjust this import path as needed
|
||||
use tracing::info;
|
||||
|
||||
pub struct TimedSerializer<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> {
|
||||
pub struct TimedSerializer<T: PartialEq + Clone + Serialize + for<'de> Deserialize<'de>> {
|
||||
directory: Directory,
|
||||
file_name: String,
|
||||
debouncer: Debouncer,
|
||||
saved_item: Option<T>,
|
||||
}
|
||||
|
||||
impl<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> TimedSerializer<T> {
|
||||
impl<T: PartialEq + Clone + Serialize + for<'de> Deserialize<'de>> TimedSerializer<T> {
|
||||
pub fn new(path: &DataPath, path_type: DataPathType, file_name: String) -> Self {
|
||||
let directory = Directory::new(path.path(path_type));
|
||||
let delay = Duration::from_millis(1000);
|
||||
@@ -30,11 +30,11 @@ impl<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> TimedSerialize
|
||||
self
|
||||
}
|
||||
|
||||
// returns whether successful
|
||||
/// Returns whether it actually wrote the new value
|
||||
pub fn try_save(&mut self, cur_item: T) -> bool {
|
||||
if self.debouncer.should_act() {
|
||||
if let Some(saved_item) = self.saved_item {
|
||||
if saved_item != cur_item {
|
||||
if let Some(ref saved_item) = self.saved_item {
|
||||
if *saved_item != cur_item {
|
||||
return self.save(cur_item);
|
||||
}
|
||||
} else {
|
||||
@@ -45,8 +45,8 @@ impl<T: PartialEq + Copy + Serialize + for<'de> Deserialize<'de>> TimedSerialize
|
||||
}
|
||||
|
||||
pub fn get_item(&self) -> Option<T> {
|
||||
if self.saved_item.is_some() {
|
||||
return self.saved_item;
|
||||
if let Some(ref item) = self.saved_item {
|
||||
return Some(item.clone());
|
||||
}
|
||||
if let Ok(file_contents) = self.directory.get_file(self.file_name.clone()) {
|
||||
if let Ok(item) = serde_json::from_str::<T>(&file_contents) {
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
use crate::NotedeckTextStyle;
|
||||
|
||||
pub const NARROW_SCREEN_WIDTH: f32 = 550.0;
|
||||
/// Determine if the screen is narrow. This is useful for detecting mobile
|
||||
/// contexts, but with the nuance that we may also have a wide android tablet.
|
||||
|
||||
pub fn richtext_small<S>(text: S) -> egui::RichText
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
egui::RichText::new(text).text_style(NotedeckTextStyle::Small.text_style())
|
||||
}
|
||||
|
||||
pub fn is_narrow(ctx: &egui::Context) -> bool {
|
||||
let screen_size = ctx.input(|c| c.screen_rect().size());
|
||||
screen_size.x < 550.0
|
||||
screen_size.x < NARROW_SCREEN_WIDTH
|
||||
}
|
||||
|
||||
pub fn is_oled() -> bool {
|
||||
|
||||
@@ -69,7 +69,13 @@ pub async fn android_main(app: AndroidApp) {
|
||||
Box::new(move |cc| {
|
||||
let ctx = &cc.egui_ctx;
|
||||
let mut notedeck = Notedeck::new(ctx, path, &app_args);
|
||||
setup_chrome(ctx, ¬edeck.args(), notedeck.theme());
|
||||
setup_chrome(
|
||||
ctx,
|
||||
¬edeck.args(),
|
||||
notedeck.theme(),
|
||||
notedeck.note_body_font_size(),
|
||||
notedeck.zoom_factor(),
|
||||
);
|
||||
|
||||
let context = &mut notedeck.app_context();
|
||||
let dave = Dave::new(cc.wgpu_render_state.as_ref());
|
||||
|
||||
@@ -112,11 +112,8 @@ impl ChromePanelAction {
|
||||
fn process(&self, ctx: &mut AppContext, chrome: &mut Chrome, ui: &mut egui::Ui) {
|
||||
match self {
|
||||
Self::SaveTheme(theme) => {
|
||||
ui.ctx().options_mut(|o| {
|
||||
o.theme_preference = *theme;
|
||||
});
|
||||
ctx.settings_handler.set_theme(*theme);
|
||||
ctx.settings_handler.save();
|
||||
ui.ctx().set_theme(*theme);
|
||||
ctx.settings.set_theme(*theme);
|
||||
}
|
||||
|
||||
Self::Toolbar(toolbar_action) => match toolbar_action {
|
||||
|
||||
@@ -98,7 +98,13 @@ async fn main() {
|
||||
let columns = Damus::new(&mut notedeck.app_context(), &args);
|
||||
let dave = Dave::new(cc.wgpu_render_state.as_ref());
|
||||
|
||||
setup_chrome(ctx, notedeck.args(), notedeck.theme());
|
||||
setup_chrome(
|
||||
ctx,
|
||||
notedeck.args(),
|
||||
notedeck.theme(),
|
||||
notedeck.note_body_font_size(),
|
||||
notedeck.zoom_factor(),
|
||||
);
|
||||
|
||||
// ensure we recognized all the arguments
|
||||
let completely_unrecognized: Vec<String> = notedeck
|
||||
|
||||
@@ -38,7 +38,13 @@ impl PreviewRunner {
|
||||
"unrecognized args: {:?}",
|
||||
notedeck.unrecognized_args()
|
||||
);
|
||||
setup_chrome(ctx, notedeck.args(), notedeck.theme());
|
||||
setup_chrome(
|
||||
ctx,
|
||||
notedeck.args(),
|
||||
notedeck.theme(),
|
||||
notedeck.note_body_font_size(),
|
||||
notedeck.zoom_factor(),
|
||||
);
|
||||
|
||||
notedeck.set_app(PreviewApp::new(preview));
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
use crate::{fonts, theme};
|
||||
|
||||
use eframe::NativeOptions;
|
||||
use egui::ThemePreference;
|
||||
use notedeck::{AppSizeHandler, DataPath};
|
||||
use egui::{FontId, ThemePreference};
|
||||
use notedeck::{AppSizeHandler, DataPath, NotedeckTextStyle};
|
||||
use notedeck_ui::app_images;
|
||||
use tracing::info;
|
||||
|
||||
pub fn setup_chrome(ctx: &egui::Context, args: ¬edeck::Args, theme: ThemePreference) {
|
||||
pub fn setup_chrome(
|
||||
ctx: &egui::Context,
|
||||
args: ¬edeck::Args,
|
||||
theme: ThemePreference,
|
||||
note_body_font_size: f32,
|
||||
zoom_factor: f32,
|
||||
) {
|
||||
let is_mobile = args
|
||||
.is_mobile
|
||||
.unwrap_or(notedeck::ui::is_compiled_as_mobile());
|
||||
@@ -31,6 +37,15 @@ pub fn setup_chrome(ctx: &egui::Context, args: ¬edeck::Args, theme: ThemePref
|
||||
ctx.set_visuals_of(egui::Theme::Light, theme::light_mode());
|
||||
|
||||
setup_cc(ctx, is_mobile);
|
||||
|
||||
ctx.set_zoom_factor(zoom_factor);
|
||||
|
||||
let mut style = (*ctx.style()).clone();
|
||||
style.text_styles.insert(
|
||||
NotedeckTextStyle::NoteBody.text_style(),
|
||||
FontId::proportional(note_body_font_size),
|
||||
);
|
||||
ctx.set_style(style);
|
||||
}
|
||||
|
||||
pub fn setup_cc(ctx: &egui::Context, is_mobile: bool) {
|
||||
@@ -39,7 +54,6 @@ pub fn setup_cc(ctx: &egui::Context, is_mobile: bool) {
|
||||
if notedeck::ui::is_compiled_as_mobile() {
|
||||
ctx.set_pixels_per_point(ctx.pixels_per_point() + 0.2);
|
||||
}
|
||||
|
||||
//ctx.set_pixels_per_point(1.0);
|
||||
//
|
||||
//
|
||||
|
||||
@@ -10,19 +10,16 @@ use crate::{
|
||||
subscriptions::{SubKind, Subscriptions},
|
||||
support::Support,
|
||||
timeline::{self, kind::ListKind, thread::Threads, TimelineCache, TimelineKind},
|
||||
ui::{self, DesktopSidePanel, SidePanelAction},
|
||||
ui::{self, DesktopSidePanel, ShowSourceClientOption, SidePanelAction},
|
||||
view_state::ViewState,
|
||||
Result,
|
||||
};
|
||||
|
||||
use crate::ui::settings::ShowNoteClientOption;
|
||||
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
use enostr::{ClientMessage, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPool};
|
||||
use nostrdb::Transaction;
|
||||
use notedeck::{
|
||||
tr, ui::is_narrow, Accounts, AppAction, AppContext, DataPath, DataPathType, FilterState,
|
||||
Images, JobsCache, Localization, UnknownIds,
|
||||
Images, JobsCache, Localization, SettingsHandler, UnknownIds,
|
||||
};
|
||||
use notedeck_ui::{
|
||||
media::{MediaViewer, MediaViewerFlags, MediaViewerState},
|
||||
@@ -445,6 +442,11 @@ impl Damus {
|
||||
let mut options = AppOptions::default();
|
||||
let tmp_columns = !parsed_args.columns.is_empty();
|
||||
options.set(AppOptions::TmpColumns, tmp_columns);
|
||||
options.set(AppOptions::Debug, app_context.args.debug);
|
||||
options.set(
|
||||
AppOptions::SinceOptimize,
|
||||
parsed_args.is_flag_set(ColumnsFlag::SinceOptimize),
|
||||
);
|
||||
|
||||
let decks_cache = if tmp_columns {
|
||||
info!("DecksCache: loading from command line arguments");
|
||||
@@ -489,37 +491,11 @@ impl Damus {
|
||||
// cache.add_deck_default(*pk);
|
||||
//}
|
||||
};
|
||||
let settings = &app_context.settings;
|
||||
|
||||
let support = Support::new(app_context.path);
|
||||
let mut note_options = NoteOptions::default();
|
||||
note_options.set(
|
||||
NoteOptions::Textmode,
|
||||
parsed_args.is_flag_set(ColumnsFlag::Textmode),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::ScrambleText,
|
||||
parsed_args.is_flag_set(ColumnsFlag::Scramble),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::HideMedia,
|
||||
parsed_args.is_flag_set(ColumnsFlag::NoMedia),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::ShowNoteClientTop,
|
||||
ShowNoteClientOption::Top == app_context.settings_handler.show_source_client().into()
|
||||
|| parsed_args.is_flag_set(ColumnsFlag::ShowNoteClientTop),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::ShowNoteClientBottom,
|
||||
ShowNoteClientOption::Bottom
|
||||
== app_context.settings_handler.show_source_client().into()
|
||||
|| parsed_args.is_flag_set(ColumnsFlag::ShowNoteClientBottom),
|
||||
);
|
||||
options.set(AppOptions::Debug, app_context.args.debug);
|
||||
options.set(
|
||||
AppOptions::SinceOptimize,
|
||||
parsed_args.is_flag_set(ColumnsFlag::SinceOptimize),
|
||||
);
|
||||
|
||||
let note_options = get_note_options(parsed_args, settings);
|
||||
|
||||
let jobs = JobsCache::default();
|
||||
|
||||
@@ -601,6 +577,39 @@ impl Damus {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_note_options(args: ColumnsArgs, settings_handler: &&mut SettingsHandler) -> NoteOptions {
|
||||
let mut note_options = NoteOptions::default();
|
||||
|
||||
note_options.set(
|
||||
NoteOptions::Textmode,
|
||||
args.is_flag_set(ColumnsFlag::Textmode),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::ScrambleText,
|
||||
args.is_flag_set(ColumnsFlag::Scramble),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::HideMedia,
|
||||
args.is_flag_set(ColumnsFlag::NoMedia),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::ShowNoteClientTop,
|
||||
ShowSourceClientOption::Top == settings_handler.show_source_client().into()
|
||||
|| args.is_flag_set(ColumnsFlag::ShowNoteClientTop),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::ShowNoteClientBottom,
|
||||
ShowSourceClientOption::Bottom == settings_handler.show_source_client().into()
|
||||
|| args.is_flag_set(ColumnsFlag::ShowNoteClientBottom),
|
||||
);
|
||||
|
||||
note_options.set(
|
||||
NoteOptions::RepliesNewestFirst,
|
||||
settings_handler.show_replies_newest_first(),
|
||||
);
|
||||
note_options
|
||||
}
|
||||
|
||||
/*
|
||||
fn circle_icon(ui: &mut egui::Ui, openness: f32, response: &egui::Response) {
|
||||
let stroke = ui.style().interact(&response).fg_stroke;
|
||||
@@ -622,6 +631,7 @@ fn render_damus_mobile(
|
||||
let mut app_action: Option<AppAction> = None;
|
||||
|
||||
let active_col = app.columns_mut(app_ctx.i18n, app_ctx.accounts).selected as usize;
|
||||
|
||||
if !app.columns(app_ctx.accounts).columns().is_empty() {
|
||||
let r = nav::render_nav(
|
||||
active_col,
|
||||
|
||||
@@ -30,8 +30,6 @@ use crate::{
|
||||
Damus,
|
||||
};
|
||||
|
||||
use crate::ui::settings::ShowNoteClientOption;
|
||||
|
||||
use egui_nav::{Nav, NavAction, NavResponse, NavUiType, Percent, PopupResponse, PopupSheet};
|
||||
use enostr::ProfileState;
|
||||
use nostrdb::{Filter, Ndb, Transaction};
|
||||
@@ -487,13 +485,9 @@ fn process_render_nav_action(
|
||||
.process_relay_action(ui.ctx(), ctx.pool, action);
|
||||
None
|
||||
}
|
||||
RenderNavAction::SettingsAction(action) => action.process_settings_action(
|
||||
app,
|
||||
ctx.settings_handler,
|
||||
ctx.i18n,
|
||||
ctx.img_cache,
|
||||
ui.ctx(),
|
||||
),
|
||||
RenderNavAction::SettingsAction(action) => {
|
||||
action.process_settings_action(app, ctx.settings, ctx.i18n, ctx.img_cache, ui.ctx())
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(action) = router_action {
|
||||
@@ -587,28 +581,14 @@ fn render_nav_body(
|
||||
.ui(ui)
|
||||
.map(RenderNavAction::RelayAction),
|
||||
|
||||
Route::Settings => {
|
||||
let mut show_note_client: ShowNoteClientOption = app.note_options.into();
|
||||
|
||||
let mut theme: String = (if ui.visuals().dark_mode {
|
||||
"Dark"
|
||||
} else {
|
||||
"Light"
|
||||
})
|
||||
.into();
|
||||
|
||||
let mut selected_language: String = ctx.i18n.get_current_locale().to_string();
|
||||
|
||||
SettingsView::new(
|
||||
ctx.img_cache,
|
||||
&mut selected_language,
|
||||
&mut theme,
|
||||
&mut show_note_client,
|
||||
ctx.i18n,
|
||||
)
|
||||
.ui(ui)
|
||||
.map(RenderNavAction::SettingsAction)
|
||||
}
|
||||
Route::Settings => SettingsView::new(
|
||||
&mut ctx.settings.get_settings_mut(),
|
||||
&mut note_context,
|
||||
&mut app.note_options,
|
||||
&mut app.jobs,
|
||||
)
|
||||
.ui(ui)
|
||||
.map(RenderNavAction::SettingsAction),
|
||||
Route::Reply(id) => {
|
||||
let txn = if let Ok(txn) = Transaction::new(ctx.ndb) {
|
||||
txn
|
||||
|
||||
@@ -26,7 +26,7 @@ pub use preview::{Preview, PreviewApp, PreviewConfig};
|
||||
pub use profile::ProfileView;
|
||||
pub use relay::RelayView;
|
||||
pub use settings::SettingsView;
|
||||
pub use settings::ShowNoteClientOption;
|
||||
pub use settings::ShowSourceClientOption;
|
||||
pub use side_panel::{DesktopSidePanel, SidePanelAction};
|
||||
pub use thread::ThreadView;
|
||||
pub use timeline::TimelineView;
|
||||
|
||||
@@ -1,40 +1,59 @@
|
||||
use egui::{vec2, Button, Color32, ComboBox, Frame, Margin, RichText, ThemePreference};
|
||||
use notedeck::{tr, Images, LanguageIdentifier, Localization, NotedeckTextStyle, SettingsHandler};
|
||||
use notedeck_ui::NoteOptions;
|
||||
use egui::{
|
||||
vec2, Button, Color32, ComboBox, FontId, Frame, Margin, RichText, ScrollArea, ThemePreference,
|
||||
};
|
||||
use enostr::NoteId;
|
||||
use nostrdb::Transaction;
|
||||
use notedeck::{
|
||||
tr,
|
||||
ui::{is_narrow, richtext_small},
|
||||
Images, JobsCache, LanguageIdentifier, Localization, NoteContext, NotedeckTextStyle, Settings,
|
||||
SettingsHandler, DEFAULT_NOTE_BODY_FONT_SIZE,
|
||||
};
|
||||
use notedeck_ui::{NoteOptions, NoteView};
|
||||
use strum::Display;
|
||||
|
||||
use crate::{nav::RouterAction, Damus, Route};
|
||||
|
||||
const PREVIEW_NOTE_ID: &str = "note1edjc8ggj07hwv77g2405uh6j2jkk5aud22gktxrvc2wnre4vdwgqzlv2gw";
|
||||
|
||||
const THEME_LIGHT: &str = "Light";
|
||||
const THEME_DARK: &str = "Dark";
|
||||
|
||||
const MIN_ZOOM: f32 = 0.5;
|
||||
const MAX_ZOOM: f32 = 3.0;
|
||||
const ZOOM_STEP: f32 = 0.1;
|
||||
const RESET_ZOOM: f32 = 1.0;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Display)]
|
||||
pub enum ShowNoteClientOption {
|
||||
pub enum ShowSourceClientOption {
|
||||
Hide,
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl From<ShowNoteClientOption> for String {
|
||||
fn from(value: ShowNoteClientOption) -> Self {
|
||||
match value {
|
||||
ShowNoteClientOption::Hide => "hide".to_string(),
|
||||
ShowNoteClientOption::Top => "top".to_string(),
|
||||
ShowNoteClientOption::Bottom => "bottom".to_string(),
|
||||
impl Into<String> for ShowSourceClientOption {
|
||||
fn into(self) -> String {
|
||||
match self {
|
||||
Self::Hide => "hide".to_string(),
|
||||
Self::Top => "top".to_string(),
|
||||
Self::Bottom => "bottom".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NoteOptions> for ShowNoteClientOption {
|
||||
impl From<NoteOptions> for ShowSourceClientOption {
|
||||
fn from(note_options: NoteOptions) -> Self {
|
||||
if note_options.contains(NoteOptions::ShowNoteClientTop) {
|
||||
ShowNoteClientOption::Top
|
||||
ShowSourceClientOption::Top
|
||||
} else if note_options.contains(NoteOptions::ShowNoteClientBottom) {
|
||||
ShowNoteClientOption::Bottom
|
||||
ShowSourceClientOption::Bottom
|
||||
} else {
|
||||
ShowNoteClientOption::Hide
|
||||
ShowSourceClientOption::Hide
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ShowNoteClientOption {
|
||||
impl From<String> for ShowSourceClientOption {
|
||||
fn from(s: String) -> Self {
|
||||
match s.to_lowercase().as_str() {
|
||||
"hide" => Self::Hide,
|
||||
@@ -45,7 +64,7 @@ impl From<String> for ShowNoteClientOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl ShowNoteClientOption {
|
||||
impl ShowSourceClientOption {
|
||||
pub fn set_note_options(self, note_options: &mut NoteOptions) {
|
||||
match self {
|
||||
Self::Hide => {
|
||||
@@ -62,13 +81,23 @@ impl ShowNoteClientOption {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn label<'a>(&self, i18n: &'a mut Localization) -> String {
|
||||
match self {
|
||||
Self::Hide => tr!(i18n, "Hide", "Option in settings section to hide the source client label in note display"),
|
||||
Self::Top => tr!(i18n, "Top", "Option in settings section to show the source client label at the top of the note"),
|
||||
Self::Bottom => tr!(i18n, "Bottom", "Option in settings section to show the source client label at the bottom of the note"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SettingsAction {
|
||||
SetZoomFactor(f32),
|
||||
SetTheme(ThemePreference),
|
||||
SetShowSourceClient(ShowNoteClientOption),
|
||||
SetShowSourceClient(ShowSourceClientOption),
|
||||
SetLocale(LanguageIdentifier),
|
||||
SetRepliestNewestFirst(bool),
|
||||
SetNoteBodyFontSize(f32),
|
||||
OpenRelays,
|
||||
OpenCacheFolder,
|
||||
ClearCacheFolder,
|
||||
@@ -78,7 +107,7 @@ impl SettingsAction {
|
||||
pub fn process_settings_action<'a>(
|
||||
self,
|
||||
app: &mut Damus,
|
||||
settings_handler: &'a mut SettingsHandler,
|
||||
settings: &'a mut SettingsHandler,
|
||||
i18n: &'a mut Localization,
|
||||
img_cache: &mut Images,
|
||||
ctx: &egui::Context,
|
||||
@@ -86,425 +115,513 @@ impl SettingsAction {
|
||||
let mut route_action: Option<RouterAction> = None;
|
||||
|
||||
match self {
|
||||
SettingsAction::OpenRelays => {
|
||||
Self::OpenRelays => {
|
||||
route_action = Some(RouterAction::route_to(Route::Relays));
|
||||
}
|
||||
SettingsAction::SetZoomFactor(zoom_factor) => {
|
||||
Self::SetZoomFactor(zoom_factor) => {
|
||||
ctx.set_zoom_factor(zoom_factor);
|
||||
settings_handler.set_zoom_factor(zoom_factor);
|
||||
settings.set_zoom_factor(zoom_factor);
|
||||
}
|
||||
SettingsAction::SetShowSourceClient(option) => {
|
||||
Self::SetShowSourceClient(option) => {
|
||||
option.set_note_options(&mut app.note_options);
|
||||
|
||||
settings_handler.set_show_source_client(option);
|
||||
settings.set_show_source_client(option);
|
||||
}
|
||||
SettingsAction::SetTheme(theme) => {
|
||||
ctx.options_mut(|o| {
|
||||
o.theme_preference = theme;
|
||||
});
|
||||
settings_handler.set_theme(theme);
|
||||
Self::SetTheme(theme) => {
|
||||
ctx.set_theme(theme);
|
||||
settings.set_theme(theme);
|
||||
}
|
||||
SettingsAction::SetLocale(language) => {
|
||||
Self::SetLocale(language) => {
|
||||
if i18n.set_locale(language.clone()).is_ok() {
|
||||
settings_handler.set_locale(language.to_string());
|
||||
settings.set_locale(language.to_string());
|
||||
}
|
||||
}
|
||||
SettingsAction::OpenCacheFolder => {
|
||||
Self::SetRepliestNewestFirst(value) => {
|
||||
app.note_options.set(NoteOptions::RepliesNewestFirst, value);
|
||||
settings.set_show_replies_newest_first(value);
|
||||
}
|
||||
Self::OpenCacheFolder => {
|
||||
use opener;
|
||||
let _ = opener::open(img_cache.base_path.clone());
|
||||
}
|
||||
SettingsAction::ClearCacheFolder => {
|
||||
Self::ClearCacheFolder => {
|
||||
let _ = img_cache.clear_folder_contents();
|
||||
}
|
||||
Self::SetNoteBodyFontSize(size) => {
|
||||
let mut style = (*ctx.style()).clone();
|
||||
style.text_styles.insert(
|
||||
NotedeckTextStyle::NoteBody.text_style(),
|
||||
FontId::proportional(size),
|
||||
);
|
||||
ctx.set_style(style);
|
||||
|
||||
settings.set_note_body_font_size(size);
|
||||
}
|
||||
}
|
||||
settings_handler.save();
|
||||
route_action
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SettingsView<'a> {
|
||||
theme: &'a mut String,
|
||||
selected_language: &'a mut String,
|
||||
show_note_client: &'a mut ShowNoteClientOption,
|
||||
i18n: &'a mut Localization,
|
||||
img_cache: &'a mut Images,
|
||||
settings: &'a mut Settings,
|
||||
note_context: &'a mut NoteContext<'a>,
|
||||
note_options: &'a mut NoteOptions,
|
||||
jobs: &'a mut JobsCache,
|
||||
}
|
||||
|
||||
fn settings_group<S>(ui: &mut egui::Ui, title: S, contents: impl FnOnce(&mut egui::Ui))
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
Frame::group(ui.style())
|
||||
.fill(ui.style().visuals.widgets.open.bg_fill)
|
||||
.inner_margin(10.0)
|
||||
.show(ui, |ui| {
|
||||
ui.label(RichText::new(title).text_style(NotedeckTextStyle::Body.text_style()));
|
||||
ui.separator();
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(10.0, 10.0);
|
||||
|
||||
contents(ui)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
impl<'a> SettingsView<'a> {
|
||||
pub fn new(
|
||||
img_cache: &'a mut Images,
|
||||
selected_language: &'a mut String,
|
||||
theme: &'a mut String,
|
||||
show_note_client: &'a mut ShowNoteClientOption,
|
||||
i18n: &'a mut Localization,
|
||||
settings: &'a mut Settings,
|
||||
note_context: &'a mut NoteContext<'a>,
|
||||
note_options: &'a mut NoteOptions,
|
||||
jobs: &'a mut JobsCache,
|
||||
) -> Self {
|
||||
Self {
|
||||
show_note_client,
|
||||
theme,
|
||||
img_cache,
|
||||
selected_language,
|
||||
i18n,
|
||||
settings,
|
||||
note_context,
|
||||
note_options,
|
||||
jobs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the localized name for a language identifier
|
||||
fn get_selected_language_name(&mut self) -> String {
|
||||
if let Ok(lang_id) = self.selected_language.parse::<LanguageIdentifier>() {
|
||||
self.i18n
|
||||
if let Ok(lang_id) = self.settings.locale.parse::<LanguageIdentifier>() {
|
||||
self.note_context
|
||||
.i18n
|
||||
.get_locale_native_name(&lang_id)
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(|| lang_id.to_string())
|
||||
} else {
|
||||
self.selected_language.clone()
|
||||
self.settings.locale.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the localized label for ShowNoteClientOption
|
||||
fn get_show_note_client_label(&mut self, option: ShowNoteClientOption) -> String {
|
||||
match option {
|
||||
ShowNoteClientOption::Hide => tr!(
|
||||
self.i18n,
|
||||
"Hide",
|
||||
"Option in settings section to hide the source client label in note display"
|
||||
),
|
||||
ShowNoteClientOption::Top => tr!(
|
||||
self.i18n,
|
||||
"Top",
|
||||
"Option in settings section to show the source client label at the top of the note"
|
||||
),
|
||||
ShowNoteClientOption::Bottom => tr!(
|
||||
self.i18n,
|
||||
"Bottom",
|
||||
"Option in settings section to show the source client label at the bottom of the note"
|
||||
),
|
||||
}.to_string()
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> {
|
||||
let id = ui.id();
|
||||
pub fn appearance_section(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> {
|
||||
let mut action = None;
|
||||
|
||||
Frame::default()
|
||||
.inner_margin(Margin::symmetric(10, 10))
|
||||
.show(ui, |ui| {
|
||||
Frame::group(ui.style())
|
||||
.fill(ui.style().visuals.widgets.open.bg_fill)
|
||||
.inner_margin(10.0)
|
||||
.show(ui, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Appearance",
|
||||
"Label for appearance settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Body.text_style()),
|
||||
);
|
||||
ui.separator();
|
||||
ui.spacing_mut().item_spacing = vec2(10.0, 10.0);
|
||||
|
||||
let current_zoom = ui.ctx().zoom_factor();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Zoom Level:",
|
||||
"Label for zoom level, Appearance settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
|
||||
if ui
|
||||
.button(
|
||||
RichText::new("-")
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
let new_zoom = (current_zoom - 0.1).max(0.1);
|
||||
action = Some(SettingsAction::SetZoomFactor(new_zoom));
|
||||
};
|
||||
|
||||
ui.label(
|
||||
RichText::new(format!("{:.0}%", current_zoom * 100.0))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
|
||||
if ui
|
||||
.button(
|
||||
RichText::new("+")
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
let new_zoom = (current_zoom + 0.1).min(10.0);
|
||||
action = Some(SettingsAction::SetZoomFactor(new_zoom));
|
||||
};
|
||||
|
||||
if ui
|
||||
.button(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Reset",
|
||||
"Label for reset zoom level, Appearance settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetZoomFactor(1.0));
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Language:",
|
||||
"Label for language, Appearance settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
ComboBox::from_label("")
|
||||
.selected_text(self.get_selected_language_name())
|
||||
.show_ui(ui, |ui| {
|
||||
for lang in self.i18n.get_available_locales() {
|
||||
let name = self.i18n
|
||||
.get_locale_native_name(lang)
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(|| lang.to_string());
|
||||
if ui
|
||||
.selectable_value(
|
||||
self.selected_language,
|
||||
lang.to_string(),
|
||||
name,
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetLocale(lang.to_owned()))
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Theme:",
|
||||
"Label for theme, Appearance settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
if ui
|
||||
.selectable_value(
|
||||
self.theme,
|
||||
"Light".into(),
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Light",
|
||||
"Label for Theme Light, Appearance settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetTheme(ThemePreference::Light));
|
||||
}
|
||||
if ui
|
||||
.selectable_value(
|
||||
self.theme,
|
||||
"Dark".into(),
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Dark",
|
||||
"Label for Theme Dark, Appearance settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetTheme(ThemePreference::Dark));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(5.0);
|
||||
|
||||
Frame::group(ui.style())
|
||||
.fill(ui.style().visuals.widgets.open.bg_fill)
|
||||
.inner_margin(10.0)
|
||||
.show(ui, |ui| {
|
||||
ui.label(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Storage",
|
||||
"Label for storage settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Body.text_style()),
|
||||
);
|
||||
ui.separator();
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(10.0, 10.0);
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let static_imgs_size = self
|
||||
.img_cache
|
||||
.static_imgs
|
||||
.cache_size
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
let gifs_size = self.img_cache.gifs.cache_size.lock().unwrap();
|
||||
|
||||
ui.label(
|
||||
RichText::new(format!("{} {}",
|
||||
tr!(
|
||||
self.i18n,
|
||||
"Image cache size:",
|
||||
"Label for Image cache size, Storage settings section"
|
||||
),
|
||||
format_size(
|
||||
[static_imgs_size, gifs_size]
|
||||
.iter()
|
||||
.fold(0_u64, |acc, cur| acc
|
||||
+ cur.unwrap_or_default())
|
||||
)
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
if !notedeck::ui::is_compiled_as_mobile() &&
|
||||
ui.button(RichText::new(tr!(self.i18n, "View folder", "Label for view folder button, Storage settings section"))
|
||||
.text_style(NotedeckTextStyle::Small.text_style())).clicked() {
|
||||
action = Some(SettingsAction::OpenCacheFolder);
|
||||
}
|
||||
|
||||
let clearcache_resp = ui.button(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Clear cache",
|
||||
"Label for clear cache button, Storage settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style())
|
||||
.color(Color32::LIGHT_RED),
|
||||
);
|
||||
|
||||
let id_clearcache = id.with("clear_cache");
|
||||
if clearcache_resp.clicked() {
|
||||
ui.data_mut(|d| d.insert_temp(id_clearcache, true));
|
||||
}
|
||||
|
||||
if ui.data_mut(|d| *d.get_temp_mut_or_default(id_clearcache)) {
|
||||
let mut confirm_pressed = false;
|
||||
clearcache_resp.show_tooltip_ui(|ui| {
|
||||
let confirm_resp = ui.button(tr!(
|
||||
self.i18n,
|
||||
"Confirm",
|
||||
"Label for confirm clear cache, Storage settings section"
|
||||
));
|
||||
if confirm_resp.clicked() {
|
||||
confirm_pressed = true;
|
||||
}
|
||||
|
||||
if confirm_resp.clicked() || ui.button(tr!(
|
||||
self.i18n,
|
||||
"Cancel",
|
||||
"Label for cancel clear cache, Storage settings section"
|
||||
)).clicked() {
|
||||
ui.data_mut(|d| d.insert_temp(id_clearcache, false));
|
||||
}
|
||||
});
|
||||
|
||||
if confirm_pressed {
|
||||
action = Some(SettingsAction::ClearCacheFolder);
|
||||
} else if !confirm_pressed
|
||||
&& clearcache_resp.clicked_elsewhere()
|
||||
{
|
||||
ui.data_mut(|d| d.insert_temp(id_clearcache, false));
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(5.0);
|
||||
|
||||
Frame::group(ui.style())
|
||||
.fill(ui.style().visuals.widgets.open.bg_fill)
|
||||
.inner_margin(10.0)
|
||||
.show(ui, |ui| {
|
||||
ui.label(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Others",
|
||||
"Label for others settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Body.text_style()),
|
||||
);
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(10.0, 10.0);
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(
|
||||
RichText::new(
|
||||
tr!(
|
||||
self.i18n,
|
||||
"Show source client",
|
||||
"Label for Show source client, others settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
|
||||
for option in [
|
||||
ShowNoteClientOption::Hide,
|
||||
ShowNoteClientOption::Top,
|
||||
ShowNoteClientOption::Bottom,
|
||||
] {
|
||||
let label = self.get_show_note_client_label(option);
|
||||
|
||||
if ui
|
||||
.selectable_value(
|
||||
self.show_note_client,
|
||||
option,
|
||||
RichText::new(label)
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
action = Some(SettingsAction::SetShowSourceClient(option));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(10.0);
|
||||
let title = tr!(
|
||||
self.note_context.i18n,
|
||||
"Appearance",
|
||||
"Label for appearance settings section",
|
||||
);
|
||||
settings_group(ui, title, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Font size:",
|
||||
"Label for font size, Appearance settings section",
|
||||
)));
|
||||
|
||||
if ui
|
||||
.add_sized(
|
||||
[ui.available_width(), 30.0],
|
||||
.add(
|
||||
egui::Slider::new(&mut self.settings.note_body_font_size, 8.0..=32.0)
|
||||
.text(""),
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
action = Some(SettingsAction::SetNoteBodyFontSize(
|
||||
self.settings.note_body_font_size,
|
||||
));
|
||||
};
|
||||
|
||||
if ui
|
||||
.button(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Reset",
|
||||
"Label for reset note body font size, Appearance settings section",
|
||||
)))
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetNoteBodyFontSize(
|
||||
DEFAULT_NOTE_BODY_FONT_SIZE,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
let txn = Transaction::new(self.note_context.ndb).unwrap();
|
||||
if let Some(note_id) = NoteId::from_bech(PREVIEW_NOTE_ID) {
|
||||
if let Ok(preview_note) =
|
||||
self.note_context.ndb.get_note_by_id(&txn, ¬e_id.bytes())
|
||||
{
|
||||
notedeck_ui::padding(8.0, ui, |ui| {
|
||||
if is_narrow(ui.ctx()) {
|
||||
ui.set_max_width(ui.available_width());
|
||||
}
|
||||
|
||||
NoteView::new(
|
||||
self.note_context,
|
||||
&preview_note,
|
||||
self.note_options.clone(),
|
||||
self.jobs,
|
||||
)
|
||||
.actionbar(false)
|
||||
.options_button(false)
|
||||
.show(ui);
|
||||
});
|
||||
ui.separator();
|
||||
}
|
||||
}
|
||||
|
||||
let current_zoom = ui.ctx().zoom_factor();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Zoom Level:",
|
||||
"Label for zoom level, Appearance settings section",
|
||||
)));
|
||||
|
||||
let min_reached = current_zoom <= MIN_ZOOM;
|
||||
let max_reached = current_zoom >= MAX_ZOOM;
|
||||
|
||||
if ui
|
||||
.add_enabled(
|
||||
!min_reached,
|
||||
Button::new(
|
||||
RichText::new(tr!(
|
||||
self.i18n,
|
||||
"Configure relays",
|
||||
"Label for configure relays, settings section"
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
RichText::new("-").text_style(NotedeckTextStyle::Small.text_style()),
|
||||
),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::OpenRelays);
|
||||
let new_zoom = (current_zoom - ZOOM_STEP).max(MIN_ZOOM);
|
||||
action = Some(SettingsAction::SetZoomFactor(new_zoom));
|
||||
};
|
||||
|
||||
ui.label(
|
||||
RichText::new(format!("{:.0}%", current_zoom * 100.0))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
|
||||
if ui
|
||||
.add_enabled(
|
||||
!max_reached,
|
||||
Button::new(
|
||||
RichText::new("+").text_style(NotedeckTextStyle::Small.text_style()),
|
||||
),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
let new_zoom = (current_zoom + ZOOM_STEP).min(MAX_ZOOM);
|
||||
action = Some(SettingsAction::SetZoomFactor(new_zoom));
|
||||
};
|
||||
|
||||
if ui
|
||||
.button(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Reset",
|
||||
"Label for reset zoom level, Appearance settings section",
|
||||
)))
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetZoomFactor(RESET_ZOOM));
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Language:",
|
||||
"Label for language, Appearance settings section",
|
||||
)));
|
||||
|
||||
//
|
||||
ComboBox::from_label("")
|
||||
.selected_text(self.get_selected_language_name())
|
||||
.show_ui(ui, |ui| {
|
||||
for lang in self.note_context.i18n.get_available_locales() {
|
||||
let name = self
|
||||
.note_context
|
||||
.i18n
|
||||
.get_locale_native_name(lang)
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(|| lang.to_string());
|
||||
if ui
|
||||
.selectable_value(&mut self.settings.locale, lang.to_string(), name)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetLocale(lang.to_owned()))
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Theme:",
|
||||
"Label for theme, Appearance settings section",
|
||||
)));
|
||||
|
||||
if ui
|
||||
.selectable_value(
|
||||
&mut self.settings.theme,
|
||||
ThemePreference::Light,
|
||||
richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
THEME_LIGHT,
|
||||
"Label for Theme Light, Appearance settings section",
|
||||
)),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetTheme(ThemePreference::Light));
|
||||
}
|
||||
|
||||
if ui
|
||||
.selectable_value(
|
||||
&mut self.settings.theme,
|
||||
ThemePreference::Dark,
|
||||
richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
THEME_DARK,
|
||||
"Label for Theme Dark, Appearance settings section",
|
||||
)),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetTheme(ThemePreference::Dark));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
pub fn storage_section(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> {
|
||||
let id = ui.id();
|
||||
let mut action: Option<SettingsAction> = None;
|
||||
let title = tr!(
|
||||
self.note_context.i18n,
|
||||
"Storage",
|
||||
"Label for storage settings section"
|
||||
);
|
||||
settings_group(ui, title, |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let static_imgs_size = self
|
||||
.note_context
|
||||
.img_cache
|
||||
.static_imgs
|
||||
.cache_size
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
let gifs_size = self.note_context.img_cache.gifs.cache_size.lock().unwrap();
|
||||
|
||||
ui.label(
|
||||
RichText::new(format!(
|
||||
"{} {}",
|
||||
tr!(
|
||||
self.note_context.i18n,
|
||||
"Image cache size:",
|
||||
"Label for Image cache size, Storage settings section"
|
||||
),
|
||||
format_size(
|
||||
[static_imgs_size, gifs_size]
|
||||
.iter()
|
||||
.fold(0_u64, |acc, cur| acc + cur.unwrap_or_default())
|
||||
)
|
||||
))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
);
|
||||
|
||||
ui.end_row();
|
||||
|
||||
if !notedeck::ui::is_compiled_as_mobile()
|
||||
&& ui
|
||||
.button(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"View folder",
|
||||
"Label for view folder button, Storage settings section",
|
||||
)))
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::OpenCacheFolder);
|
||||
}
|
||||
|
||||
let clearcache_resp = ui.button(
|
||||
richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Clear cache",
|
||||
"Label for clear cache button, Storage settings section",
|
||||
))
|
||||
.color(Color32::LIGHT_RED),
|
||||
);
|
||||
|
||||
let id_clearcache = id.with("clear_cache");
|
||||
if clearcache_resp.clicked() {
|
||||
ui.data_mut(|d| d.insert_temp(id_clearcache, true));
|
||||
}
|
||||
|
||||
if ui.data_mut(|d| *d.get_temp_mut_or_default(id_clearcache)) {
|
||||
let mut confirm_pressed = false;
|
||||
clearcache_resp.show_tooltip_ui(|ui| {
|
||||
let confirm_resp = ui.button(tr!(
|
||||
self.note_context.i18n,
|
||||
"Confirm",
|
||||
"Label for confirm clear cache, Storage settings section"
|
||||
));
|
||||
if confirm_resp.clicked() {
|
||||
confirm_pressed = true;
|
||||
}
|
||||
|
||||
if confirm_resp.clicked()
|
||||
|| ui
|
||||
.button(tr!(
|
||||
self.note_context.i18n,
|
||||
"Cancel",
|
||||
"Label for cancel clear cache, Storage settings section"
|
||||
))
|
||||
.clicked()
|
||||
{
|
||||
ui.data_mut(|d| d.insert_temp(id_clearcache, false));
|
||||
}
|
||||
});
|
||||
|
||||
if confirm_pressed {
|
||||
action = Some(SettingsAction::ClearCacheFolder);
|
||||
} else if !confirm_pressed && clearcache_resp.clicked_elsewhere() {
|
||||
ui.data_mut(|d| d.insert_temp(id_clearcache, false));
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
fn other_options_section(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> {
|
||||
let mut action = None;
|
||||
|
||||
let title = tr!(
|
||||
self.note_context.i18n,
|
||||
"Others",
|
||||
"Label for others settings section"
|
||||
);
|
||||
settings_group(ui, title, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Sort replies newest first",
|
||||
"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", "ON"))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
action = Some(SettingsAction::SetRepliestNewestFirst(
|
||||
self.settings.show_replies_newest_first,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Source client",
|
||||
"Label for Source client, others settings section",
|
||||
)));
|
||||
|
||||
for option in [
|
||||
ShowSourceClientOption::Hide,
|
||||
ShowSourceClientOption::Top,
|
||||
ShowSourceClientOption::Bottom,
|
||||
] {
|
||||
let mut current: ShowSourceClientOption =
|
||||
self.settings.show_source_client.clone().into();
|
||||
|
||||
if ui
|
||||
.selectable_value(
|
||||
&mut current,
|
||||
option,
|
||||
RichText::new(option.label(self.note_context.i18n))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
action = Some(SettingsAction::SetShowSourceClient(option));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
fn manage_relays_section(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> {
|
||||
let mut action = None;
|
||||
|
||||
if ui
|
||||
.add_sized(
|
||||
[ui.available_width(), 30.0],
|
||||
Button::new(richtext_small(tr!(
|
||||
self.note_context.i18n,
|
||||
"Configure relays",
|
||||
"Label for configure relays, settings section",
|
||||
))),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::OpenRelays);
|
||||
}
|
||||
|
||||
action
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<SettingsAction> {
|
||||
let mut action: Option<SettingsAction> = None;
|
||||
|
||||
Frame::default()
|
||||
.inner_margin(Margin::symmetric(10, 10))
|
||||
.show(ui, |ui| {
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
if let Some(new_action) = self.appearance_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
|
||||
ui.add_space(5.0);
|
||||
|
||||
if let Some(new_action) = self.storage_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
|
||||
ui.add_space(5.0);
|
||||
|
||||
if let Some(new_action) = self.other_options_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
|
||||
if let Some(new_action) = self.manage_relays_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
action
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,10 @@ impl<'a, 'd> ThreadView<'a, 'd> {
|
||||
.unwrap()
|
||||
.list;
|
||||
|
||||
let notes = note_builder.into_notes(&mut self.threads.seen_flags);
|
||||
let notes = note_builder.into_notes(
|
||||
self.note_options.contains(NoteOptions::RepliesNewestFirst),
|
||||
&mut self.threads.seen_flags,
|
||||
);
|
||||
|
||||
if !full_chain {
|
||||
// TODO(kernelkind): insert UI denoting we don't have the full chain yet
|
||||
@@ -223,7 +226,11 @@ impl<'a> ThreadNoteBuilder<'a> {
|
||||
self.replies.push(note);
|
||||
}
|
||||
|
||||
pub fn into_notes(mut self, seen_flags: &mut NoteSeenFlags) -> ThreadNotes<'a> {
|
||||
pub fn into_notes(
|
||||
mut self,
|
||||
replies_newer_first: bool,
|
||||
seen_flags: &mut NoteSeenFlags,
|
||||
) -> ThreadNotes<'a> {
|
||||
let mut notes = Vec::new();
|
||||
|
||||
let selected_is_root = self.chain.is_empty();
|
||||
@@ -246,6 +253,11 @@ impl<'a> ThreadNoteBuilder<'a> {
|
||||
unread_and_have_replies: false,
|
||||
});
|
||||
|
||||
if replies_newer_first {
|
||||
self.replies
|
||||
.sort_by(|a, b| b.created_at().cmp(&a.created_at()));
|
||||
}
|
||||
|
||||
for reply in self.replies {
|
||||
notes.push(ThreadNote {
|
||||
unread_and_have_replies: *seen_flags.get(reply.id()).unwrap_or(&false),
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::ProfilePreview;
|
||||
use egui::Sense;
|
||||
use enostr::Pubkey;
|
||||
use nostrdb::{Ndb, Transaction};
|
||||
use notedeck::{name::get_display_name, Images, NoteAction};
|
||||
use notedeck::{name::get_display_name, Images, NoteAction, NotedeckTextStyle};
|
||||
|
||||
pub struct Mention<'a> {
|
||||
ndb: &'a Ndb,
|
||||
@@ -75,7 +75,9 @@ fn mention_ui(
|
||||
get_display_name(profile.as_ref()).username_or_displayname()
|
||||
);
|
||||
|
||||
let mut text = egui::RichText::new(name).color(link_color);
|
||||
let mut text = egui::RichText::new(name)
|
||||
.color(link_color)
|
||||
.text_style(NotedeckTextStyle::NoteBody.text_style());
|
||||
if let Some(size) = size {
|
||||
text = text.size(size);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ use crate::{
|
||||
};
|
||||
use notedeck::{JobsCache, RenderableMedia};
|
||||
|
||||
use egui::{Color32, Hyperlink, RichText};
|
||||
use egui::{vec2, Color32, Hyperlink, Label, RichText};
|
||||
use nostrdb::{BlockType, Mention, Note, NoteKey, Transaction};
|
||||
use tracing::warn;
|
||||
|
||||
use super::media::image_carousel;
|
||||
use notedeck::{update_imeta_blurhashes, IsFollowing, NoteCache, NoteContext};
|
||||
use notedeck::{update_imeta_blurhashes, IsFollowing, NoteCache, NoteContext, NotedeckTextStyle};
|
||||
|
||||
pub struct NoteContents<'a, 'd> {
|
||||
note_context: &'a mut NoteContext<'d>,
|
||||
@@ -42,6 +42,8 @@ impl<'a, 'd> NoteContents<'a, 'd> {
|
||||
|
||||
impl egui::Widget for &mut NoteContents<'_, '_> {
|
||||
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||
ui.spacing_mut().item_spacing = vec2(0.0, 0.0);
|
||||
|
||||
if self.options.contains(NoteOptions::ShowNoteClientTop) {
|
||||
render_client(ui, self.note_context.note_cache, self.note);
|
||||
}
|
||||
@@ -158,9 +160,9 @@ pub fn render_note_contents<'a>(
|
||||
return;
|
||||
};
|
||||
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.spacing_mut().item_spacing = vec2(0.0, 0.0);
|
||||
|
||||
for block in blocks.iter(note) {
|
||||
'block_loop: for block in blocks.iter(note) {
|
||||
match block.blocktype() {
|
||||
BlockType::MentionBech32 => match block.as_mention().unwrap() {
|
||||
Mention::Profile(profile) => {
|
||||
@@ -200,13 +202,24 @@ pub fn render_note_contents<'a>(
|
||||
}
|
||||
|
||||
_ => {
|
||||
ui.colored_label(link_color, format!("@{}", &block.as_str()[..16]));
|
||||
ui.colored_label(
|
||||
link_color,
|
||||
RichText::new(format!("@{}", &block.as_str()[..16]))
|
||||
.text_style(NotedeckTextStyle::NoteBody.text_style()),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
BlockType::Hashtag => {
|
||||
if block.as_str().trim().len() == 0 {
|
||||
continue 'block_loop;
|
||||
}
|
||||
let resp = ui
|
||||
.colored_label(link_color, format!("#{}", block.as_str()))
|
||||
.colored_label(
|
||||
link_color,
|
||||
RichText::new(format!("#{}", block.as_str()))
|
||||
.text_style(NotedeckTextStyle::NoteBody.text_style()),
|
||||
)
|
||||
.on_hover_cursor(egui::CursorIcon::PointingHand);
|
||||
|
||||
if resp.clicked() {
|
||||
@@ -231,8 +244,13 @@ pub fn render_note_contents<'a>(
|
||||
};
|
||||
|
||||
if hide_media || !found_supported() {
|
||||
if block.as_str().trim().len() == 0 {
|
||||
continue 'block_loop;
|
||||
}
|
||||
ui.add(Hyperlink::from_label_and_url(
|
||||
RichText::new(block.as_str()).color(link_color),
|
||||
RichText::new(block.as_str())
|
||||
.color(link_color)
|
||||
.text_style(NotedeckTextStyle::NoteBody.text_style()),
|
||||
block.as_str(),
|
||||
));
|
||||
}
|
||||
@@ -258,17 +276,28 @@ pub fn render_note_contents<'a>(
|
||||
current_len += block_str.len();
|
||||
block_str
|
||||
};
|
||||
|
||||
if block_str.trim().len() == 0 {
|
||||
continue 'block_loop;
|
||||
}
|
||||
if options.contains(NoteOptions::ScrambleText) {
|
||||
ui.add(
|
||||
egui::Label::new(rot13(block_str))
|
||||
.wrap()
|
||||
.selectable(selectable),
|
||||
Label::new(
|
||||
RichText::new(rot13(block_str))
|
||||
.text_style(NotedeckTextStyle::NoteBody.text_style()),
|
||||
)
|
||||
.wrap()
|
||||
.selectable(selectable),
|
||||
);
|
||||
} else {
|
||||
ui.add(egui::Label::new(block_str).wrap().selectable(selectable));
|
||||
ui.add(
|
||||
Label::new(
|
||||
RichText::new(block_str)
|
||||
.text_style(NotedeckTextStyle::NoteBody.text_style()),
|
||||
)
|
||||
.wrap()
|
||||
.selectable(selectable),
|
||||
);
|
||||
}
|
||||
|
||||
// don't render any more blocks
|
||||
if truncate {
|
||||
break;
|
||||
|
||||
@@ -50,67 +50,73 @@ pub fn image_carousel(
|
||||
.drag_to_scroll(false)
|
||||
.id_salt(carousel_id)
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
let mut media_infos: Vec<MediaInfo> = Vec::with_capacity(medias.len());
|
||||
let mut media_action: Option<(usize, MediaUIAction)> = None;
|
||||
let response = ui
|
||||
.horizontal(|ui| {
|
||||
let spacing = ui.spacing_mut();
|
||||
spacing.item_spacing.x = 8.0;
|
||||
|
||||
for (i, media) in medias.iter().enumerate() {
|
||||
let RenderableMedia {
|
||||
url,
|
||||
media_type,
|
||||
obfuscation_type: blur_type,
|
||||
} = media;
|
||||
let mut media_infos: Vec<MediaInfo> = Vec::with_capacity(medias.len());
|
||||
let mut media_action: Option<(usize, MediaUIAction)> = None;
|
||||
|
||||
let cache = match media_type {
|
||||
MediaCacheType::Image => &mut img_cache.static_imgs,
|
||||
MediaCacheType::Gif => &mut img_cache.gifs,
|
||||
};
|
||||
let media_state = get_content_media_render_state(
|
||||
ui,
|
||||
job_pool,
|
||||
jobs,
|
||||
trusted_media,
|
||||
size,
|
||||
&mut cache.textures_cache,
|
||||
url,
|
||||
*media_type,
|
||||
&cache.cache_dir,
|
||||
blur_type,
|
||||
);
|
||||
for (i, media) in medias.iter().enumerate() {
|
||||
let RenderableMedia {
|
||||
url,
|
||||
media_type,
|
||||
obfuscation_type: blur_type,
|
||||
} = media;
|
||||
|
||||
let media_response = render_media(
|
||||
ui,
|
||||
&mut img_cache.gif_states,
|
||||
media_state,
|
||||
url,
|
||||
size,
|
||||
i18n,
|
||||
note_options.contains(NoteOptions::Wide),
|
||||
);
|
||||
let cache = match media_type {
|
||||
MediaCacheType::Image => &mut img_cache.static_imgs,
|
||||
MediaCacheType::Gif => &mut img_cache.gifs,
|
||||
};
|
||||
let media_state = get_content_media_render_state(
|
||||
ui,
|
||||
job_pool,
|
||||
jobs,
|
||||
trusted_media,
|
||||
size,
|
||||
&mut cache.textures_cache,
|
||||
url,
|
||||
*media_type,
|
||||
&cache.cache_dir,
|
||||
blur_type,
|
||||
);
|
||||
|
||||
if let Some(action) = media_response.inner {
|
||||
media_action = Some((i, action))
|
||||
let media_response = render_media(
|
||||
ui,
|
||||
&mut img_cache.gif_states,
|
||||
media_state,
|
||||
url,
|
||||
size,
|
||||
i18n,
|
||||
note_options.contains(NoteOptions::Wide),
|
||||
);
|
||||
|
||||
if let Some(action) = media_response.inner {
|
||||
media_action = Some((i, action))
|
||||
}
|
||||
|
||||
let rect = media_response.response.rect;
|
||||
media_infos.push(MediaInfo {
|
||||
url: url.clone(),
|
||||
original_position: rect,
|
||||
})
|
||||
}
|
||||
|
||||
let rect = media_response.response.rect;
|
||||
media_infos.push(MediaInfo {
|
||||
url: url.clone(),
|
||||
original_position: rect,
|
||||
})
|
||||
}
|
||||
|
||||
if let Some((i, media_action)) = media_action {
|
||||
action = media_action.into_media_action(
|
||||
ui.ctx(),
|
||||
medias,
|
||||
media_infos,
|
||||
i,
|
||||
img_cache,
|
||||
ImageType::Content(Some((size.x as u32, size.y as u32))),
|
||||
);
|
||||
}
|
||||
})
|
||||
.response
|
||||
if let Some((i, media_action)) = media_action {
|
||||
action = media_action.into_media_action(
|
||||
ui.ctx(),
|
||||
medias,
|
||||
media_infos,
|
||||
i,
|
||||
img_cache,
|
||||
ImageType::Content(Some((size.x as u32, size.y as u32))),
|
||||
);
|
||||
}
|
||||
})
|
||||
.response;
|
||||
ui.add_space(8.0);
|
||||
response
|
||||
})
|
||||
.inner
|
||||
});
|
||||
|
||||
@@ -344,7 +344,12 @@ impl<'a, 'd> NoteView<'a, 'd> {
|
||||
1.0,
|
||||
ui.visuals().noninteractive().bg_stroke.color,
|
||||
))
|
||||
.show(ui, |ui| self.show_impl(ui))
|
||||
.show(ui, |ui| {
|
||||
if is_narrow(ui.ctx()) {
|
||||
ui.set_width(ui.available_width());
|
||||
}
|
||||
self.show_impl(ui)
|
||||
})
|
||||
.inner
|
||||
} else {
|
||||
self.show_impl(ui)
|
||||
|
||||
@@ -25,6 +25,8 @@ bitflags! {
|
||||
/// Show note's client in the note header
|
||||
const ShowNoteClientTop = 1 << 12;
|
||||
const ShowNoteClientBottom = 1 << 13;
|
||||
|
||||
const RepliesNewestFirst = 1 << 14;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user