Merge follow/unfollow from kernel
Jakub Gladysz (1):
ui: add follow button
kernelkind (14):
bump nostrdb
move polling responsibility to `AccountData`
`AccountData`: decouple query from constructor
add constructor for `AccountData`
add `Contacts`
use `Contacts` in `AccountData`
expose `AccountSubs`
Unify sub for contacts in accounts & timeline
move `styled_button_toggleable` to notedeck_ui
construct NoteBuilder from existing note
send kind 3 event
add actions for follow/unfollow
add UI for (un)follow
send contact list event on account creation
This commit is contained in:
@@ -623,6 +623,7 @@ pub fn render_add_column_routes(
|
||||
ctx.pool,
|
||||
ctx.note_cache,
|
||||
app.since_optimize,
|
||||
ctx.accounts,
|
||||
);
|
||||
|
||||
app.columns_mut(ctx.accounts)
|
||||
@@ -664,6 +665,7 @@ pub fn render_add_column_routes(
|
||||
ctx.pool,
|
||||
ctx.note_cache,
|
||||
app.since_optimize,
|
||||
ctx.accounts,
|
||||
);
|
||||
|
||||
app.columns_mut(ctx.accounts)
|
||||
|
||||
@@ -9,9 +9,10 @@ use nostrdb::{Ndb, ProfileRecord, Transaction};
|
||||
use notedeck::{
|
||||
fonts::get_font_size, get_profile_url, name::get_display_name, Images, NotedeckTextStyle,
|
||||
};
|
||||
use notedeck_ui::{app_images, colors, profile::display_name_widget, AnimationHelper, ProfilePic};
|
||||
|
||||
use crate::ui::widgets::styled_button_toggleable;
|
||||
use notedeck_ui::{
|
||||
app_images, colors, profile::display_name_widget, widgets::styled_button_toggleable,
|
||||
AnimationHelper, ProfilePic,
|
||||
};
|
||||
|
||||
pub struct CustomZapView<'a> {
|
||||
images: &'a mut Images,
|
||||
|
||||
@@ -4,6 +4,7 @@ pub use edit::EditProfileView;
|
||||
use egui::{vec2, Color32, CornerRadius, Layout, Rect, RichText, ScrollArea, Sense, Stroke};
|
||||
use enostr::Pubkey;
|
||||
use nostrdb::{ProfileRecord, Transaction};
|
||||
use notedeck_ui::profile::follow_button;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
@@ -11,8 +12,8 @@ use crate::{
|
||||
ui::timeline::{tabs_ui, TimelineTabView},
|
||||
};
|
||||
use notedeck::{
|
||||
name::get_display_name, profile::get_profile_url, Accounts, MuteFun, NoteAction, NoteContext,
|
||||
NotedeckTextStyle,
|
||||
name::get_display_name, profile::get_profile_url, Accounts, IsFollowing, MuteFun, NoteAction,
|
||||
NoteContext, NotedeckTextStyle,
|
||||
};
|
||||
use notedeck_ui::{
|
||||
app_images,
|
||||
@@ -35,6 +36,8 @@ pub struct ProfileView<'a, 'd> {
|
||||
pub enum ProfileViewAction {
|
||||
EditProfile,
|
||||
Note(NoteAction),
|
||||
Unfollow(Pubkey),
|
||||
Follow(Pubkey),
|
||||
}
|
||||
|
||||
impl<'a, 'd> ProfileView<'a, 'd> {
|
||||
@@ -79,8 +82,8 @@ impl<'a, 'd> ProfileView<'a, 'd> {
|
||||
.ndb
|
||||
.get_profile_by_pubkey(&txn, self.pubkey.bytes())
|
||||
{
|
||||
if self.profile_body(ui, profile) {
|
||||
action = Some(ProfileViewAction::EditProfile);
|
||||
if let Some(profile_view_action) = self.profile_body(ui, profile) {
|
||||
action = Some(profile_view_action);
|
||||
}
|
||||
}
|
||||
let profile_timeline = self
|
||||
@@ -131,8 +134,12 @@ impl<'a, 'd> ProfileView<'a, 'd> {
|
||||
output.inner
|
||||
}
|
||||
|
||||
fn profile_body(&mut self, ui: &mut egui::Ui, profile: ProfileRecord<'_>) -> bool {
|
||||
let mut action = false;
|
||||
fn profile_body(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
profile: ProfileRecord<'_>,
|
||||
) -> Option<ProfileViewAction> {
|
||||
let mut action = None;
|
||||
ui.vertical(|ui| {
|
||||
banner(
|
||||
ui,
|
||||
@@ -169,13 +176,49 @@ impl<'a, 'd> ProfileView<'a, 'd> {
|
||||
ui.ctx().copy_text(to_copy)
|
||||
}
|
||||
|
||||
if self.accounts.contains_full_kp(self.pubkey) {
|
||||
ui.with_layout(Layout::right_to_left(egui::Align::Max), |ui| {
|
||||
if ui.add(edit_profile_button()).clicked() {
|
||||
action = true;
|
||||
ui.with_layout(Layout::right_to_left(egui::Align::RIGHT), |ui| {
|
||||
ui.add_space(24.0);
|
||||
|
||||
let target_key = self.pubkey;
|
||||
let selected = self.accounts.get_selected_account();
|
||||
|
||||
let profile_type = if selected.key.secret_key.is_none() {
|
||||
ProfileType::ReadOnly
|
||||
} else if &selected.key.pubkey == self.pubkey {
|
||||
ProfileType::MyProfile
|
||||
} else {
|
||||
ProfileType::Followable(selected.is_following(target_key))
|
||||
};
|
||||
|
||||
match profile_type {
|
||||
ProfileType::MyProfile => {
|
||||
if ui.add(edit_profile_button()).clicked() {
|
||||
action = Some(ProfileViewAction::EditProfile);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
ProfileType::Followable(is_following) => {
|
||||
let follow_button = ui.add(follow_button(is_following));
|
||||
|
||||
if follow_button.clicked() {
|
||||
action = match is_following {
|
||||
IsFollowing::Unknown => {
|
||||
// don't do anything, we don't have contact list
|
||||
None
|
||||
}
|
||||
|
||||
IsFollowing::Yes => {
|
||||
Some(ProfileViewAction::Unfollow(target_key.to_owned()))
|
||||
}
|
||||
|
||||
IsFollowing::No => {
|
||||
Some(ProfileViewAction::Follow(target_key.to_owned()))
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
ProfileType::ReadOnly => {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(18.0);
|
||||
@@ -215,6 +258,12 @@ impl<'a, 'd> ProfileView<'a, 'd> {
|
||||
}
|
||||
}
|
||||
|
||||
enum ProfileType {
|
||||
MyProfile,
|
||||
ReadOnly,
|
||||
Followable(IsFollowing),
|
||||
}
|
||||
|
||||
fn handle_link(ui: &mut egui::Ui, website_url: &str) {
|
||||
let img = if ui.visuals().dark_mode {
|
||||
app_images::link_image()
|
||||
|
||||
@@ -154,6 +154,9 @@ fn timeline_ui(
|
||||
error!("tried to render timeline in column, but timeline was missing");
|
||||
// TODO (jb55): render error when timeline is missing?
|
||||
// this shouldn't happen...
|
||||
//
|
||||
// NOTE (jb55): it can easily happen if you add a timeline column without calling
|
||||
// add_new_timeline_column, since that sets up the initial subs, etc
|
||||
return None;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,49 +1,7 @@
|
||||
use egui::{Button, Widget};
|
||||
use notedeck::NotedeckTextStyle;
|
||||
use egui::Widget;
|
||||
use notedeck_ui::widgets::styled_button_toggleable;
|
||||
|
||||
/// Sized and styled to match the figma design
|
||||
pub fn styled_button(text: &str, fill_color: egui::Color32) -> impl Widget + '_ {
|
||||
styled_button_toggleable(text, fill_color, true)
|
||||
}
|
||||
|
||||
pub fn styled_button_toggleable(
|
||||
text: &str,
|
||||
fill_color: egui::Color32,
|
||||
enabled: bool,
|
||||
) -> impl Widget + '_ {
|
||||
move |ui: &mut egui::Ui| -> egui::Response {
|
||||
let painter = ui.painter();
|
||||
let text_color = if ui.visuals().dark_mode {
|
||||
egui::Color32::WHITE
|
||||
} else {
|
||||
egui::Color32::BLACK
|
||||
};
|
||||
|
||||
let galley = painter.layout(
|
||||
text.to_owned(),
|
||||
NotedeckTextStyle::Body.get_font_id(ui.ctx()),
|
||||
text_color,
|
||||
ui.available_width(),
|
||||
);
|
||||
|
||||
let size = galley.rect.expand2(egui::vec2(16.0, 8.0)).size();
|
||||
let mut button = Button::new(galley).corner_radius(8.0);
|
||||
|
||||
if !enabled {
|
||||
button = button
|
||||
.sense(egui::Sense::focusable_noninteractive())
|
||||
.fill(ui.visuals().noninteractive().bg_fill)
|
||||
.stroke(ui.visuals().noninteractive().bg_stroke);
|
||||
} else {
|
||||
button = button.fill(fill_color);
|
||||
}
|
||||
|
||||
let mut resp = ui.add_sized(size, button);
|
||||
|
||||
if !enabled {
|
||||
resp = resp.on_hover_cursor(egui::CursorIcon::NotAllowed);
|
||||
}
|
||||
|
||||
resp
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user