Merge Add External Notifications Column setting #395
kernelkind (2):
init external notifs column
use AcquireKeyState for AddColumn
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
18
src/nav.rs
18
src/nav.rs
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
26
src/route.rs
26
src/route.rs
@@ -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"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user