mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-19 01:24:21 +01:00
feat(settings): allow sorting thread replies newest first
This commit is contained in:
@@ -5,6 +5,7 @@ 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 token_handler::TokenHandler;
|
||||
|
||||
@@ -29,6 +29,7 @@ pub struct Settings {
|
||||
pub locale: String,
|
||||
pub zoom_factor: f32,
|
||||
pub show_source_client: String,
|
||||
pub show_replies_newest_first: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@@ -38,10 +39,12 @@ impl Default for Settings {
|
||||
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: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SettingsHandler {
|
||||
directory: Directory,
|
||||
current_settings: Option<Settings>,
|
||||
@@ -129,7 +132,7 @@ impl SettingsHandler {
|
||||
};
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
@@ -162,6 +165,11 @@ impl SettingsHandler {
|
||||
self.save();
|
||||
}
|
||||
|
||||
pub fn set_show_replies_newest_first(&mut self, value: bool) {
|
||||
self.get_settings_mut().show_replies_newest_first = value;
|
||||
self.save();
|
||||
}
|
||||
|
||||
pub fn update_batch<F>(&mut self, update_fn: F)
|
||||
where
|
||||
F: FnOnce(&mut Settings),
|
||||
@@ -204,6 +212,13 @@ 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(false)
|
||||
}
|
||||
|
||||
pub fn is_loaded(&self) -> bool {
|
||||
self.current_settings.is_some()
|
||||
}
|
||||
|
||||
@@ -14,18 +14,13 @@ use crate::{
|
||||
view_state::ViewState,
|
||||
Result,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
use notedeck_ui::{
|
||||
media::{MediaViewer, MediaViewerFlags, MediaViewerState},
|
||||
NoteOptions,
|
||||
tr, ui::is_narrow, Accounts, AppAction, AppContext, DataPath, DataPathType, FilterState, Images, JobsCache, Localization, SettingsHandler, UnknownIds
|
||||
};
|
||||
use notedeck_ui::{media::{MediaViewer, MediaViewerFlags, MediaViewerState}, NoteOptions};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
@@ -443,6 +438,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");
|
||||
@@ -487,37 +487,11 @@ impl Damus {
|
||||
// cache.add_deck_default(*pk);
|
||||
//}
|
||||
};
|
||||
let settings_handler = &app_context.settings_handler;
|
||||
|
||||
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,
|
||||
ShowSourceClientOption::Top == app_context.settings_handler.show_source_client().into()
|
||||
|| parsed_args.is_flag_set(ColumnsFlag::ShowNoteClientTop),
|
||||
);
|
||||
note_options.set(
|
||||
NoteOptions::ShowNoteClientBottom,
|
||||
ShowSourceClientOption::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_handler);
|
||||
|
||||
let jobs = JobsCache::default();
|
||||
|
||||
@@ -599,6 +573,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;
|
||||
@@ -620,6 +627,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,
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::{
|
||||
note::{custom_zap::CustomZapView, NewPostAction, PostAction, PostType, QuoteRepostView},
|
||||
profile::EditProfileView,
|
||||
search::{FocusState, SearchView},
|
||||
settings::{SettingsAction, ShowSourceClientOption},
|
||||
settings::SettingsAction,
|
||||
support::SupportView,
|
||||
wallet::{get_default_zap_state, WalletAction, WalletState, WalletView},
|
||||
AccountsView, PostReplyView, PostView, ProfileView, RelayView, SettingsView, ThreadView,
|
||||
@@ -586,26 +586,11 @@ fn render_nav_body(
|
||||
.map(RenderNavAction::RelayAction),
|
||||
|
||||
Route::Settings => {
|
||||
let mut show_note_client: ShowSourceClientOption = app.note_options.into();
|
||||
let mut settings = ctx.settings_handler.get_settings_mut();
|
||||
|
||||
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)
|
||||
SettingsView::new(ctx.i18n, ctx.img_cache, &mut settings)
|
||||
.ui(ui)
|
||||
.map(RenderNavAction::SettingsAction)
|
||||
}
|
||||
Route::Reply(id) => {
|
||||
let txn = if let Ok(txn) = Transaction::new(ctx.ndb) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use egui::{vec2, Button, Color32, ComboBox, Frame, Margin, RichText, ThemePreference};
|
||||
use notedeck::{tr, Images, LanguageIdentifier, Localization, NotedeckTextStyle, SettingsHandler};
|
||||
use egui::{vec2, Button, Color32, ComboBox, Frame, Margin, RichText, ScrollArea, ThemePreference};
|
||||
use notedeck::{
|
||||
tr, Images, LanguageIdentifier, Localization, NotedeckTextStyle, Settings, SettingsHandler,
|
||||
};
|
||||
use notedeck_ui::NoteOptions;
|
||||
use strum::Display;
|
||||
|
||||
@@ -97,6 +99,7 @@ pub enum SettingsAction {
|
||||
SetTheme(ThemePreference),
|
||||
SetShowSourceClient(ShowSourceClientOption),
|
||||
SetLocale(LanguageIdentifier),
|
||||
SetRepliestNewestFirst(bool),
|
||||
OpenRelays,
|
||||
OpenCacheFolder,
|
||||
ClearCacheFolder,
|
||||
@@ -135,6 +138,11 @@ impl SettingsAction {
|
||||
settings_handler.set_locale(language.to_string());
|
||||
}
|
||||
}
|
||||
Self::SetRepliestNewestFirst(value) => {
|
||||
app.note_options.set(NoteOptions::RepliesNewestFirst, value);
|
||||
settings_handler.set_show_replies_newest_first(value);
|
||||
settings_handler.save();
|
||||
}
|
||||
Self::OpenCacheFolder => {
|
||||
use opener;
|
||||
let _ = opener::open(img_cache.base_path.clone());
|
||||
@@ -149,9 +157,7 @@ impl SettingsAction {
|
||||
}
|
||||
|
||||
pub struct SettingsView<'a> {
|
||||
theme: &'a mut String,
|
||||
selected_language: &'a mut String,
|
||||
show_note_client: &'a mut ShowSourceClientOption,
|
||||
settings: &'a mut Settings,
|
||||
i18n: &'a mut Localization,
|
||||
img_cache: &'a mut Images,
|
||||
}
|
||||
@@ -181,30 +187,30 @@ where
|
||||
|
||||
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 ShowSourceClientOption,
|
||||
i18n: &'a mut Localization,
|
||||
img_cache: &'a mut Images,
|
||||
settings: &'a mut Settings,
|
||||
// theme: &'a mut String,
|
||||
// show_note_client: &'a mut ShowSourceClientOption,
|
||||
// show_wide: &'a mut bool,
|
||||
// show_replies_newest_first: &'a mut bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
show_note_client,
|
||||
theme,
|
||||
settings,
|
||||
img_cache,
|
||||
selected_language,
|
||||
i18n,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>() {
|
||||
if let Ok(lang_id) = self.settings.locale.parse::<LanguageIdentifier>() {
|
||||
self.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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +295,7 @@ impl<'a> SettingsView<'a> {
|
||||
.map(|s| s.to_owned())
|
||||
.unwrap_or_else(|| lang.to_string());
|
||||
if ui
|
||||
.selectable_value(self.selected_language, lang.to_string(), name)
|
||||
.selectable_value(&mut self.settings.locale, lang.to_string(), name)
|
||||
.clicked()
|
||||
{
|
||||
action = Some(SettingsAction::SetLocale(lang.to_owned()))
|
||||
@@ -304,10 +310,11 @@ impl<'a> SettingsView<'a> {
|
||||
"Theme:",
|
||||
"Label for theme, Appearance settings section",
|
||||
));
|
||||
|
||||
if ui
|
||||
.selectable_value(
|
||||
self.theme,
|
||||
THEME_LIGHT.into(),
|
||||
&mut self.settings.theme,
|
||||
ThemePreference::Light,
|
||||
small_richtext(
|
||||
self.i18n,
|
||||
THEME_LIGHT.into(),
|
||||
@@ -318,10 +325,11 @@ impl<'a> SettingsView<'a> {
|
||||
{
|
||||
action = Some(SettingsAction::SetTheme(ThemePreference::Light));
|
||||
}
|
||||
|
||||
if ui
|
||||
.selectable_value(
|
||||
self.theme,
|
||||
THEME_DARK.into(),
|
||||
&mut self.settings.theme,
|
||||
ThemePreference::Dark,
|
||||
small_richtext(
|
||||
self.i18n,
|
||||
THEME_DARK.into(),
|
||||
@@ -435,11 +443,32 @@ impl<'a> SettingsView<'a> {
|
||||
|
||||
let title = tr!(self.i18n, "Others", "Label for others settings section");
|
||||
settings_group(ui, title, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(small_richtext(
|
||||
self.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.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(small_richtext(
|
||||
self.i18n,
|
||||
"Show source client",
|
||||
"Label for Show source client, others settings section",
|
||||
"Source client",
|
||||
"Label for Source client, others settings section",
|
||||
));
|
||||
|
||||
for option in [
|
||||
@@ -447,9 +476,12 @@ impl<'a> SettingsView<'a> {
|
||||
ShowSourceClientOption::Top,
|
||||
ShowSourceClientOption::Bottom,
|
||||
] {
|
||||
let mut current: ShowSourceClientOption =
|
||||
self.settings.show_source_client.clone().into();
|
||||
|
||||
if ui
|
||||
.selectable_value(
|
||||
self.show_note_client,
|
||||
&mut current,
|
||||
option,
|
||||
RichText::new(option.label(self.i18n))
|
||||
.text_style(NotedeckTextStyle::Small.text_style()),
|
||||
@@ -491,27 +523,29 @@ impl<'a> SettingsView<'a> {
|
||||
Frame::default()
|
||||
.inner_margin(Margin::symmetric(10, 10))
|
||||
.show(ui, |ui| {
|
||||
if let Some(new_action) = self.appearance_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
if let Some(new_action) = self.appearance_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
|
||||
ui.add_space(5.0);
|
||||
ui.add_space(5.0);
|
||||
|
||||
if let Some(new_action) = self.storage_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
if let Some(new_action) = self.storage_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
|
||||
ui.add_space(5.0);
|
||||
ui.add_space(5.0);
|
||||
|
||||
if let Some(new_action) = self.other_options_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
if let Some(new_action) = self.other_options_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
|
||||
ui.add_space(10.0);
|
||||
ui.add_space(10.0);
|
||||
|
||||
if let Some(new_action) = self.manage_relays_section(ui) {
|
||||
action = Some(new_action);
|
||||
}
|
||||
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),
|
||||
|
||||
@@ -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