Merge Add External Notifications Column setting #395

kernelkind (2):
      init external notifs column
      use AcquireKeyState for AddColumn
This commit is contained in:
William Casarin
2024-11-13 13:04:48 -08:00
10 changed files with 278 additions and 88 deletions

View File

@@ -6,7 +6,7 @@ use nostrdb::Ndb;
use crate::{ use crate::{
column::Columns, column::Columns,
imgcache::ImageCache, imgcache::ImageCache,
login_manager::LoginState, login_manager::AcquireKeyState,
route::{Route, Router}, route::{Route, Router},
storage::{KeyStorageResponse, KeyStorageType}, storage::{KeyStorageResponse, KeyStorageType},
ui::{ ui::{
@@ -47,7 +47,7 @@ pub fn render_accounts_route(
columns: &mut Columns, columns: &mut Columns,
img_cache: &mut ImageCache, img_cache: &mut ImageCache,
accounts: &mut AccountManager, accounts: &mut AccountManager,
login_state: &mut LoginState, login_state: &mut AcquireKeyState,
route: AccountsRoute, route: AccountsRoute,
) { ) {
let router = columns.column_mut(col).router_mut(); let router = columns.column_mut(col).router_mut();

View File

@@ -61,7 +61,9 @@ impl Columns {
} }
pub fn new_column_picker(&mut self) { pub fn new_column_picker(&mut self) {
self.add_column(Column::new(vec![Route::AddColumn])); self.add_column(Column::new(vec![Route::AddColumn(
crate::ui::add_column::AddColumnRoute::Base,
)]));
} }
fn get_new_id() -> u32 { fn get_new_id() -> u32 {

View File

@@ -8,21 +8,23 @@ use reqwest::{Request, Response};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum LoginError { pub enum AcquireKeyError {
InvalidKey, InvalidKey,
Nip05Failed(String), Nip05Failed(String),
} }
impl std::fmt::Display for LoginError { impl std::fmt::Display for AcquireKeyError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self { match self {
LoginError::InvalidKey => write!(f, "The inputted key is invalid."), AcquireKeyError::InvalidKey => write!(f, "The inputted key is invalid."),
LoginError::Nip05Failed(e) => write!(f, "Failed to get pubkey from Nip05 address: {e}"), AcquireKeyError::Nip05Failed(e) => {
write!(f, "Failed to get pubkey from Nip05 address: {e}")
}
} }
} }
} }
impl std::error::Error for LoginError {} impl std::error::Error for AcquireKeyError {}
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Nip05Result { pub struct Nip05Result {
@@ -95,9 +97,9 @@ fn retrieving_nip05_pubkey(key: &str) -> bool {
key.contains('@') key.contains('@')
} }
pub fn perform_key_retrieval(key: &str) -> Promise<Result<Keypair, LoginError>> { pub fn perform_key_retrieval(key: &str) -> Promise<Result<Keypair, AcquireKeyError>> {
let key_string = String::from(key); let key_string = String::from(key);
Promise::spawn_async(async move { get_login_key(&key_string).await }) Promise::spawn_async(async move { get_key(&key_string).await })
} }
/// Attempts to turn a string slice key from the user into a Nostr-Sdk Keypair object. /// Attempts to turn a string slice key from the user into a Nostr-Sdk Keypair object.
@@ -108,7 +110,7 @@ pub fn perform_key_retrieval(key: &str) -> Promise<Result<Keypair, LoginError>>
/// - Private hex key: "5dab..." /// - Private hex key: "5dab..."
/// - NIP-05 address: "example@nostr.com" /// - NIP-05 address: "example@nostr.com"
/// ///
pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> { pub async fn get_key(key: &str) -> Result<Keypair, AcquireKeyError> {
let tmp_key: &str = if let Some(stripped) = key.strip_prefix('@') { let tmp_key: &str = if let Some(stripped) = key.strip_prefix('@') {
stripped stripped
} else { } else {
@@ -118,7 +120,7 @@ pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> {
if retrieving_nip05_pubkey(tmp_key) { if retrieving_nip05_pubkey(tmp_key) {
match get_nip05_pubkey(tmp_key).await { match get_nip05_pubkey(tmp_key).await {
Ok(pubkey) => Ok(Keypair::only_pubkey(pubkey)), Ok(pubkey) => Ok(Keypair::only_pubkey(pubkey)),
Err(e) => Err(LoginError::Nip05Failed(e.to_string())), Err(e) => Err(AcquireKeyError::Nip05Failed(e.to_string())),
} }
} else if let Ok(pubkey) = Pubkey::try_from_bech32_string(tmp_key, true) { } else if let Ok(pubkey) = Pubkey::try_from_bech32_string(tmp_key, true) {
Ok(Keypair::only_pubkey(pubkey)) Ok(Keypair::only_pubkey(pubkey))
@@ -127,7 +129,7 @@ pub async fn get_login_key(key: &str) -> Result<Keypair, LoginError> {
} else if let Ok(secret_key) = SecretKey::from_str(tmp_key) { } else if let Ok(secret_key) = SecretKey::from_str(tmp_key) {
Ok(Keypair::from_secret(secret_key)) Ok(Keypair::from_secret(secret_key))
} else { } else {
Err(LoginError::InvalidKey) Err(AcquireKeyError::InvalidKey)
} }
} }
@@ -141,7 +143,7 @@ mod tests {
let pubkey_str = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"; let pubkey_str = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s";
let expected_pubkey = let expected_pubkey =
Pubkey::try_from_bech32_string(pubkey_str, false).expect("Should not have errored."); Pubkey::try_from_bech32_string(pubkey_str, false).expect("Should not have errored.");
let login_key_result = get_login_key(pubkey_str).await; let login_key_result = get_key(pubkey_str).await;
assert_eq!(Ok(Keypair::only_pubkey(expected_pubkey)), login_key_result); assert_eq!(Ok(Keypair::only_pubkey(expected_pubkey)), login_key_result);
} }

View File

@@ -1,47 +1,47 @@
use crate::key_parsing::perform_key_retrieval; use crate::key_parsing::perform_key_retrieval;
use crate::key_parsing::LoginError; use crate::key_parsing::AcquireKeyError;
use egui::{TextBuffer, TextEdit}; use egui::{TextBuffer, TextEdit};
use enostr::Keypair; use enostr::Keypair;
use poll_promise::Promise; use poll_promise::Promise;
/// The UI view interface to log in to a nostr account. /// The state data for acquiring a nostr key
#[derive(Default)] #[derive(Default)]
pub struct LoginState { pub struct AcquireKeyState {
login_key: String, desired_key: String,
promise_query: Option<(String, Promise<Result<Keypair, LoginError>>)>, promise_query: Option<(String, Promise<Result<Keypair, AcquireKeyError>>)>,
error: Option<LoginError>, error: Option<AcquireKeyError>,
key_on_error: Option<String>, key_on_error: Option<String>,
should_create_new: bool, should_create_new: bool,
} }
impl<'a> LoginState { impl<'a> AcquireKeyState {
pub fn new() -> Self { pub fn new() -> Self {
LoginState::default() AcquireKeyState::default()
} }
/// Get the textedit for the login UI without exposing the key variable /// Get the textedit for the UI without exposing the key variable
pub fn get_login_textedit( pub fn get_acquire_textedit(
&'a mut self, &'a mut self,
textedit_closure: fn(&'a mut dyn TextBuffer) -> TextEdit<'a>, textedit_closure: fn(&'a mut dyn TextBuffer) -> TextEdit<'a>,
) -> TextEdit<'a> { ) -> TextEdit<'a> {
textedit_closure(&mut self.login_key) textedit_closure(&mut self.desired_key)
} }
/// User pressed the 'login' button /// User pressed the 'acquire' button
pub fn apply_login(&'a mut self) { pub fn apply_acquire(&'a mut self) {
let new_promise = match &self.promise_query { let new_promise = match &self.promise_query {
Some((query, _)) => { Some((query, _)) => {
if query != &self.login_key { if query != &self.desired_key {
Some(perform_key_retrieval(&self.login_key)) Some(perform_key_retrieval(&self.desired_key))
} else { } else {
None None
} }
} }
None => Some(perform_key_retrieval(&self.login_key)), None => Some(perform_key_retrieval(&self.desired_key)),
}; };
if let Some(new_promise) = new_promise { if let Some(new_promise) = new_promise {
self.promise_query = Some((self.login_key.clone(), new_promise)); self.promise_query = Some((self.desired_key.clone(), new_promise));
} }
} }
@@ -51,9 +51,9 @@ impl<'a> LoginState {
} }
/// Whether to indicate to the user that a login error occured /// Whether to indicate to the user that a login error occured
pub fn check_for_error(&'a mut self) -> Option<&'a LoginError> { pub fn check_for_error(&'a mut self) -> Option<&'a AcquireKeyError> {
if let Some(error_key) = &self.key_on_error { if let Some(error_key) = &self.key_on_error {
if self.login_key != *error_key { if self.desired_key != *error_key {
self.error = None; self.error = None;
self.key_on_error = None; self.key_on_error = None;
} }
@@ -73,7 +73,7 @@ impl<'a> LoginState {
} }
Err(e) => { Err(e) => {
self.error = Some(e); self.error = Some(e);
self.key_on_error = Some(self.login_key.clone()); self.key_on_error = Some(self.desired_key.clone());
} }
}; };
} }
@@ -100,7 +100,7 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_retrieve_key() { async fn test_retrieve_key() {
let mut manager = LoginState::new(); let mut manager = AcquireKeyState::new();
let expected_str = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"; let expected_str = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681";
let expected_key = Keypair::only_pubkey(Pubkey::from_hex(expected_str).unwrap()); let expected_key = Keypair::only_pubkey(Pubkey::from_hex(expected_str).unwrap());
@@ -110,21 +110,21 @@ mod tests {
let cur_time = start_time.elapsed(); let cur_time = start_time.elapsed();
if cur_time < Duration::from_millis(10u64) { if cur_time < Duration::from_millis(10u64) {
let _ = manager.get_login_textedit(|text| { let _ = manager.get_acquire_textedit(|text| {
text.clear(); text.clear();
text.insert_text("test", 0); text.insert_text("test", 0);
egui::TextEdit::singleline(text) egui::TextEdit::singleline(text)
}); });
manager.apply_login(); manager.apply_acquire();
} else if cur_time < Duration::from_millis(30u64) { } else if cur_time < Duration::from_millis(30u64) {
let _ = manager.get_login_textedit(|text| { let _ = manager.get_acquire_textedit(|text| {
text.clear(); text.clear();
text.insert_text("test2", 0); text.insert_text("test2", 0);
egui::TextEdit::singleline(text) egui::TextEdit::singleline(text)
}); });
manager.apply_login(); manager.apply_acquire();
} else { } else {
let _ = manager.get_login_textedit(|text| { let _ = manager.get_acquire_textedit(|text| {
text.clear(); text.clear();
text.insert_text( text.insert_text(
"3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681",
@@ -132,7 +132,7 @@ mod tests {
); );
egui::TextEdit::singleline(text) egui::TextEdit::singleline(text)
}); });
manager.apply_login(); manager.apply_acquire();
} }
if let Some(key) = manager.check_for_successful_login() { if let Some(key) = manager.check_for_successful_login() {

View File

@@ -13,7 +13,7 @@ use crate::{
}, },
ui::{ ui::{
self, self,
add_column::{AddColumnResponse, AddColumnView}, add_column::render_add_column_routes,
anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE}, anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
note::PostAction, note::PostAction,
support::SupportView, support::SupportView,
@@ -101,19 +101,9 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
None None
} }
Route::AddColumn => { Route::AddColumn(route) => {
let resp = render_add_column_routes(ui, app, col, route);
AddColumnView::new(&app.ndb, app.accounts.get_selected_account()).ui(ui);
if let Some(resp) = resp {
match resp {
AddColumnResponse::Timeline(timeline) => {
let id = timeline.id;
app.columns_mut().add_timeline_to_column(col, timeline);
app.subscribe_new_timeline(id);
}
};
}
None None
} }
@@ -205,7 +195,7 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
let cur_router = app.columns_mut().column_mut(col).router_mut(); let cur_router = app.columns_mut().column_mut(col).router_mut();
cur_router.navigating = false; cur_router.navigating = false;
if cur_router.is_replacing() { if cur_router.is_replacing() {
cur_router.remove_previous_route(); cur_router.remove_previous_routes();
} }
} }

View File

@@ -6,7 +6,10 @@ use crate::{
account_manager::AccountsRoute, account_manager::AccountsRoute,
column::Columns, column::Columns,
timeline::{TimelineId, TimelineRoute}, timeline::{TimelineId, TimelineRoute},
ui::profile::preview::{get_note_users_displayname_string, get_profile_displayname_string}, ui::{
add_column::AddColumnRoute,
profile::preview::{get_note_users_displayname_string, get_profile_displayname_string},
},
}; };
/// App routing. These describe different places you can go inside Notedeck. /// App routing. These describe different places you can go inside Notedeck.
@@ -16,7 +19,7 @@ pub enum Route {
Accounts(AccountsRoute), Accounts(AccountsRoute),
Relays, Relays,
ComposeNote, ComposeNote,
AddColumn, AddColumn(AddColumnRoute),
Profile(Pubkey), Profile(Pubkey),
Support, Support,
} }
@@ -97,7 +100,13 @@ impl Route {
AccountsRoute::AddAccount => "Add Account".to_owned(), AccountsRoute::AddAccount => "Add Account".to_owned(),
}, },
Route::ComposeNote => "Compose Note".to_owned(), Route::ComposeNote => "Compose Note".to_owned(),
Route::AddColumn => "Add Column".to_owned(), Route::AddColumn(c) => match c {
AddColumnRoute::Base => "Add Column".to_owned(),
AddColumnRoute::UndecidedNotification => "Add Notifications Column".to_owned(),
AddColumnRoute::ExternalNotification => {
"Add External Notifications Column".to_owned()
}
},
Route::Profile(pubkey) => { Route::Profile(pubkey) => {
format!("{}'s Profile", get_profile_displayname_string(ndb, pubkey)) format!("{}'s Profile", get_profile_displayname_string(ndb, pubkey))
} }
@@ -142,7 +151,7 @@ impl<R: Clone> Router<R> {
self.routes.push(route); self.routes.push(route);
} }
// Route to R. Then when it is successfully placed, should call `remove_previous_route` // Route to R. Then when it is successfully placed, should call `remove_previous_routes` to remove all previous routes
pub fn route_to_replaced(&mut self, route: R) { pub fn route_to_replaced(&mut self, route: R) {
self.navigating = true; self.navigating = true;
self.replacing = true; self.replacing = true;
@@ -167,14 +176,15 @@ impl<R: Clone> Router<R> {
self.routes.pop() self.routes.pop()
} }
pub fn remove_previous_route(&mut self) -> Option<R> { pub fn remove_previous_routes(&mut self) {
let num_routes = self.routes.len(); let num_routes = self.routes.len();
if num_routes <= 1 { if num_routes <= 1 {
return None; return;
} }
self.returning = false; self.returning = false;
self.replacing = false; self.replacing = false;
Some(self.routes.remove(num_routes - 2)) self.routes.drain(..num_routes - 1);
} }
pub fn is_replacing(&self) -> bool { pub fn is_replacing(&self) -> bool {
@@ -208,7 +218,7 @@ impl fmt::Display for Route {
}, },
Route::ComposeNote => write!(f, "Compose Note"), Route::ComposeNote => write!(f, "Compose Note"),
Route::AddColumn => write!(f, "Add Column"), Route::AddColumn(_) => write!(f, "Add Column"),
Route::Profile(_) => write!(f, "Profile"), Route::Profile(_) => write!(f, "Profile"),
Route::Support => write!(f, "Support"), Route::Support => write!(f, "Support"),
} }

View File

@@ -1,13 +1,13 @@
use crate::app_style::NotedeckTextStyle; use crate::app_style::NotedeckTextStyle;
use crate::key_parsing::LoginError; use crate::key_parsing::AcquireKeyError;
use crate::login_manager::LoginState; use crate::login_manager::AcquireKeyState;
use crate::ui::{Preview, PreviewConfig, View}; use crate::ui::{Preview, PreviewConfig, View};
use egui::TextEdit; use egui::TextEdit;
use egui::{Align, Button, Color32, Frame, InnerResponse, Margin, RichText, Vec2}; use egui::{Align, Button, Color32, Frame, InnerResponse, Margin, RichText, Vec2};
use enostr::Keypair; use enostr::Keypair;
pub struct AccountLoginView<'a> { pub struct AccountLoginView<'a> {
manager: &'a mut LoginState, manager: &'a mut AcquireKeyState,
} }
pub enum AccountLoginResponse { pub enum AccountLoginResponse {
@@ -16,7 +16,7 @@ pub enum AccountLoginResponse {
} }
impl<'a> AccountLoginView<'a> { impl<'a> AccountLoginView<'a> {
pub fn new(state: &'a mut LoginState) -> Self { pub fn new(state: &'a mut AcquireKeyState) -> Self {
AccountLoginView { manager: state } AccountLoginView { manager: state }
} }
@@ -43,7 +43,7 @@ impl<'a> AccountLoginView<'a> {
self.loading_and_error(ui); self.loading_and_error(ui);
if ui.add(login_button()).clicked() { if ui.add(login_button()).clicked() {
self.manager.apply_login(); self.manager.apply_acquire();
} }
}); });
@@ -90,13 +90,13 @@ impl<'a> AccountLoginView<'a> {
} }
} }
fn show_error(ui: &mut egui::Ui, err: &LoginError) { fn show_error(ui: &mut egui::Ui, err: &AcquireKeyError) {
ui.horizontal(|ui| { ui.horizontal(|ui| {
let error_label = match err { let error_label = match err {
LoginError::InvalidKey => { AcquireKeyError::InvalidKey => {
egui::Label::new(RichText::new("Invalid key.").color(ui.visuals().error_fg_color)) egui::Label::new(RichText::new("Invalid key.").color(ui.visuals().error_fg_color))
} }
LoginError::Nip05Failed(e) => { AcquireKeyError::Nip05Failed(e) => {
egui::Label::new(RichText::new(e).color(ui.visuals().error_fg_color)) egui::Label::new(RichText::new(e).color(ui.visuals().error_fg_color))
} }
}; };
@@ -126,8 +126,8 @@ fn login_button() -> Button<'static> {
.min_size(Vec2::new(0.0, 40.0)) .min_size(Vec2::new(0.0, 40.0))
} }
fn login_textedit(manager: &mut LoginState) -> TextEdit { fn login_textedit(manager: &mut AcquireKeyState) -> TextEdit {
manager.get_login_textedit(|text| { manager.get_acquire_textedit(|text| {
egui::TextEdit::singleline(text) egui::TextEdit::singleline(text)
.hint_text( .hint_text(
RichText::new("Enter your public key (npub, nip05), or private key (nsec) here...") RichText::new("Enter your public key (npub, nip05), or private key (nsec) here...")
@@ -143,7 +143,7 @@ mod preview {
use super::*; use super::*;
pub struct AccountLoginPreview { pub struct AccountLoginPreview {
manager: LoginState, manager: AcquireKeyState,
} }
impl View for AccountLoginPreview { impl View for AccountLoginPreview {
@@ -157,7 +157,7 @@ mod preview {
fn preview(cfg: PreviewConfig) -> Self::Prev { fn preview(cfg: PreviewConfig) -> Self::Prev {
let _ = cfg; let _ = cfg;
let manager = LoginState::new(); let manager = AcquireKeyState::new();
AccountLoginPreview { manager } AccountLoginPreview { manager }
} }
} }

View File

@@ -1,26 +1,51 @@
use egui::{pos2, vec2, Color32, FontId, ImageSource, Pos2, Rect, Separator, Ui}; use core::f32;
use std::collections::HashMap;
use egui::{
pos2, vec2, Align, Color32, FontId, Id, ImageSource, Margin, Pos2, Rect, RichText, Separator,
Ui, Vec2,
};
use nostrdb::Ndb; use nostrdb::Ndb;
use tracing::error;
use crate::{ use crate::{
app_style::{get_font_size, NotedeckTextStyle}, app_style::{get_font_size, NotedeckTextStyle},
login_manager::AcquireKeyState,
timeline::{PubkeySource, Timeline, TimelineKind}, timeline::{PubkeySource, Timeline, TimelineKind},
ui::anim::ICON_EXPANSION_MULTIPLE, ui::anim::ICON_EXPANSION_MULTIPLE,
user_account::UserAccount, user_account::UserAccount,
Damus,
}; };
use super::anim::AnimationHelper; use super::{anim::AnimationHelper, padding};
pub enum AddColumnResponse { pub enum AddColumnResponse {
Timeline(Timeline), Timeline(Timeline),
UndecidedNotification,
ExternalNotification,
}
pub enum NotificationColumnType {
Home,
External,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum AddColumnOption { enum AddColumnOption {
Universe, Universe,
UndecidedNotification,
ExternalNotification,
Notification(PubkeySource), Notification(PubkeySource),
Home(PubkeySource), Home(PubkeySource),
} }
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum AddColumnRoute {
Base,
UndecidedNotification,
ExternalNotification,
}
impl AddColumnOption { impl AddColumnOption {
pub fn take_as_response( pub fn take_as_response(
self, self,
@@ -34,28 +59,41 @@ impl AddColumnOption {
AddColumnOption::Notification(pubkey) => TimelineKind::Notifications(pubkey) AddColumnOption::Notification(pubkey) => TimelineKind::Notifications(pubkey)
.into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes())) .into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes()))
.map(AddColumnResponse::Timeline), .map(AddColumnResponse::Timeline),
AddColumnOption::UndecidedNotification => {
Some(AddColumnResponse::UndecidedNotification)
}
AddColumnOption::Home(pubkey) => { AddColumnOption::Home(pubkey) => {
let tlk = TimelineKind::contact_list(pubkey); let tlk = TimelineKind::contact_list(pubkey);
tlk.into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes())) tlk.into_timeline(ndb, cur_account.map(|a| a.pubkey.bytes()))
.map(AddColumnResponse::Timeline) .map(AddColumnResponse::Timeline)
} }
AddColumnOption::ExternalNotification => Some(AddColumnResponse::ExternalNotification),
} }
} }
} }
pub struct AddColumnView<'a> { pub struct AddColumnView<'a> {
key_state_map: &'a mut HashMap<Id, AcquireKeyState>,
ndb: &'a Ndb, ndb: &'a Ndb,
cur_account: Option<&'a UserAccount>, cur_account: Option<&'a UserAccount>,
} }
impl<'a> AddColumnView<'a> { impl<'a> AddColumnView<'a> {
pub fn new(ndb: &'a Ndb, cur_account: Option<&'a UserAccount>) -> Self { pub fn new(
Self { ndb, cur_account } key_state_map: &'a mut HashMap<Id, AcquireKeyState>,
ndb: &'a Ndb,
cur_account: Option<&'a UserAccount>,
) -> Self {
Self {
key_state_map,
ndb,
cur_account,
}
} }
pub fn ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> { pub fn ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> {
let mut selected_option: Option<AddColumnResponse> = None; let mut selected_option: Option<AddColumnResponse> = None;
for column_option_data in self.get_column_options() { for column_option_data in self.get_base_options() {
let option = column_option_data.option.clone(); let option = column_option_data.option.clone();
if self.column_option_ui(ui, column_option_data).clicked() { if self.column_option_ui(ui, column_option_data).clicked() {
selected_option = option.take_as_response(self.ndb, self.cur_account); selected_option = option.take_as_response(self.ndb, self.cur_account);
@@ -67,6 +105,66 @@ impl<'a> AddColumnView<'a> {
selected_option selected_option
} }
fn notifications_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> {
let mut selected_option: Option<AddColumnResponse> = None;
for column_option_data in self.get_notifications_options() {
let option = column_option_data.option.clone();
if self.column_option_ui(ui, column_option_data).clicked() {
selected_option = option.take_as_response(self.ndb, self.cur_account);
}
ui.add(Separator::default().spacing(0.0));
}
selected_option
}
fn external_notification_ui(&mut self, ui: &mut Ui) -> Option<AddColumnResponse> {
padding(16.0, ui, |ui| {
let id = ui.id().with("external_notif");
let key_state = self.key_state_map.entry(id).or_default();
let text_edit = key_state.get_acquire_textedit(|text| {
egui::TextEdit::singleline(text)
.hint_text(
RichText::new("Enter the user's key (npub, hex, nip05) here...")
.text_style(NotedeckTextStyle::Body.text_style()),
)
.vertical_align(Align::Center)
.desired_width(f32::INFINITY)
.min_size(Vec2::new(0.0, 40.0))
.margin(Margin::same(12.0))
});
ui.add(text_edit);
if ui.button("Add").clicked() {
key_state.apply_acquire();
}
if key_state.is_awaiting_network() {
ui.spinner();
}
if let Some(error) = key_state.check_for_error() {
error!("acquire key error: {}", error);
ui.colored_label(
Color32::RED,
"Please enter a valid npub, public hex key or nip05",
);
}
if let Some(keypair) = key_state.check_for_successful_login() {
key_state.should_create_new();
AddColumnOption::Notification(PubkeySource::Explicit(keypair.pubkey))
.take_as_response(self.ndb, self.cur_account)
} else {
None
}
})
.inner
}
fn column_option_ui(&mut self, ui: &mut Ui, data: ColumnOptionData) -> egui::Response { fn column_option_ui(&mut self, ui: &mut Ui, data: ColumnOptionData) -> egui::Response {
let icon_padding = 8.0; let icon_padding = 8.0;
let min_icon_width = 32.0; let min_icon_width = 32.0;
@@ -168,7 +266,7 @@ impl<'a> AddColumnView<'a> {
helper.take_animation_response() helper.take_animation_response()
} }
fn get_column_options(&self) -> Vec<ColumnOptionData> { fn get_base_options(&self) -> Vec<ColumnOptionData> {
let mut vec = Vec::new(); let mut vec = Vec::new();
vec.push(ColumnOptionData { vec.push(ColumnOptionData {
title: "Universe", title: "Universe",
@@ -190,14 +288,42 @@ impl<'a> AddColumnView<'a> {
icon: egui::include_image!("../../assets/icons/home_icon_dark_4x.png"), icon: egui::include_image!("../../assets/icons/home_icon_dark_4x.png"),
option: AddColumnOption::Home(source.clone()), option: AddColumnOption::Home(source.clone()),
}); });
}
vec.push(ColumnOptionData {
title: "Notifications",
description: "Stay up to date with notifications and mentions",
icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"),
option: AddColumnOption::UndecidedNotification,
});
vec
}
fn get_notifications_options(&self) -> Vec<ColumnOptionData> {
let mut vec = Vec::new();
if let Some(acc) = self.cur_account {
let source = if acc.secret_key.is_some() {
PubkeySource::DeckAuthor
} else {
PubkeySource::Explicit(acc.pubkey)
};
vec.push(ColumnOptionData { vec.push(ColumnOptionData {
title: "Notifications", title: "Your Notifications",
description: "Stay up to date with notifications and mentions", description: "Stay up to date with your notifications and mentions",
icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"), icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"),
option: AddColumnOption::Notification(source), option: AddColumnOption::Notification(source),
}); });
} }
vec.push(ColumnOptionData {
title: "Someone else's Notifications",
description: "Stay up to date with someone else's notifications and mentions",
icon: egui::include_image!("../../assets/icons/notifications_icon_dark_4x.png"),
option: AddColumnOption::ExternalNotification,
});
vec vec
} }
} }
@@ -209,6 +335,54 @@ struct ColumnOptionData {
option: AddColumnOption, option: AddColumnOption,
} }
pub fn render_add_column_routes(
ui: &mut egui::Ui,
app: &mut Damus,
col: usize,
route: &AddColumnRoute,
) {
let resp = match route {
AddColumnRoute::Base => AddColumnView::new(
&mut app.view_state.id_state_map,
&app.ndb,
app.accounts.get_selected_account(),
)
.ui(ui),
AddColumnRoute::UndecidedNotification => AddColumnView::new(
&mut app.view_state.id_state_map,
&app.ndb,
app.accounts.get_selected_account(),
)
.notifications_ui(ui),
AddColumnRoute::ExternalNotification => AddColumnView::new(
&mut app.view_state.id_state_map,
&app.ndb,
app.accounts.get_selected_account(),
)
.external_notification_ui(ui),
};
if let Some(resp) = resp {
match resp {
AddColumnResponse::Timeline(timeline) => {
let id = timeline.id;
app.columns_mut().add_timeline_to_column(col, timeline);
app.subscribe_new_timeline(id);
}
AddColumnResponse::UndecidedNotification => {
app.columns_mut().column_mut(col).router_mut().route_to(
crate::route::Route::AddColumn(AddColumnRoute::UndecidedNotification),
);
}
AddColumnResponse::ExternalNotification => {
app.columns_mut().column_mut(col).router_mut().route_to(
crate::route::Route::AddColumn(AddColumnRoute::ExternalNotification),
);
}
};
}
}
mod preview { mod preview {
use crate::{ use crate::{
test_data, test_data,
@@ -232,7 +406,12 @@ mod preview {
impl View for AddColumnPreview { impl View for AddColumnPreview {
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui) {
AddColumnView::new(&self.app.ndb, self.app.accounts.get_selected_account()).ui(ui); AddColumnView::new(
&mut self.app.view_state.id_state_map,
&self.app.ndb,
self.app.accounts.get_selected_account(),
)
.ui(ui);
} }
} }

View File

@@ -196,7 +196,11 @@ impl<'a> DesktopSidePanel<'a> {
} }
} }
SidePanelAction::Columns => { SidePanelAction::Columns => {
if router.routes().iter().any(|&r| r == Route::AddColumn) { if router
.routes()
.iter()
.any(|&r| matches!(r, Route::AddColumn(_)))
{
router.go_back(); router.go_back();
} else { } else {
columns.new_column_picker(); columns.new_column_picker();

View File

@@ -1,13 +1,16 @@
use crate::login_manager::LoginState; use std::collections::HashMap;
use crate::login_manager::AcquireKeyState;
/// Various state for views /// Various state for views
#[derive(Default)] #[derive(Default)]
pub struct ViewState { pub struct ViewState {
pub login: LoginState, pub login: AcquireKeyState,
pub id_state_map: HashMap<egui::Id, AcquireKeyState>,
} }
impl ViewState { impl ViewState {
pub fn login_mut(&mut self) -> &mut LoginState { pub fn login_mut(&mut self) -> &mut AcquireKeyState {
&mut self.login &mut self.login
} }
} }