friending work

This commit is contained in:
2026-02-21 00:19:57 -05:00
parent 3c242a7b77
commit 17eaf3e324
9 changed files with 186 additions and 52 deletions

2
iris

Submodule iris updated: 426ff0adfc...1aadef0e7e

View File

@@ -44,6 +44,7 @@ impl DefaultAppState for Client {
_: Proxy<Self::Event>, _: Proxy<Self::Event>,
) -> Self { ) -> Self {
let notif = WidgetPtr::default().add(rsc); let notif = WidgetPtr::default().add(rsc);
// let popup = WidgetPtr::default().add(rsc);
let main_ui = WidgetPtr::default().add(rsc); let main_ui = WidgetPtr::default().add(rsc);
let bg = ( let bg = (
image(include_bytes!("./assets/fuit.jpg")), image(include_bytes!("./assets/fuit.jpg")),

View File

@@ -1,9 +1,9 @@
use openworm::net::{CreateAccount, CreateAccountResp, Login, LoginResp}; use openworm::net::{CreateAccount, CreateAccountResp, Login, LoginResp};
use crate::{ use crate::{
session::Session,
data::{AccountInfo, ClientData, ServerInfo, ServerList}, data::{AccountInfo, ClientData, ServerInfo, ServerList},
net::{ConnectInfo, NetHandle}, net::{ConnectInfo, NetHandle},
session::Session,
}; };
use super::*; use super::*;
@@ -51,7 +51,7 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget {
let mut fail = |reason: &str| { let mut fail = |reason: &str| {
let reason = reason.to_string(); let reason = reason.to_string();
ctx.update(move |ctx, rsc| { ctx.update(move |ctx, rsc| {
rsc[ctx.notif].inner = Some(werror(&reason, rsc)); ctx.error(&reason, rsc);
}) })
}; };
let con = match NetHandle::connect( let con = match NetHandle::connect(
@@ -122,11 +122,11 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget {
} }
pub fn create_account(rsc: &mut Rsc) -> WeakWidget { pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
let url = field("", "server", rsc); let url = field("", "server").add(rsc);
let token = field("", "account creation token", rsc); let token = field("", "account creation token").add(rsc);
let cert = field("", "certificate hex", rsc); let cert = field("", "certificate hex").add(rsc);
let username = field("", "username", rsc); let username = field("", "username").add(rsc);
let password = field("", "password", rsc); let password = field("", "password").add(rsc);
let create = Button::submit("create", rsc); let create = Button::submit("create", rsc);
rsc.events.register(create, Submit, move |ctx, rsc| { rsc.events.register(create, Submit, move |ctx, rsc| {
@@ -145,7 +145,7 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
let mut fail = |reason: &str| { let mut fail = |reason: &str| {
let reason = reason.to_string(); let reason = reason.to_string();
ctx.update(move |ctx, rsc| { ctx.update(move |ctx, rsc| {
rsc[ctx.notif].inner = Some(werror(&reason, rsc)); ctx.error(&reason, rsc);
create.enable(rsc); create.enable(rsc);
}) })
}; };
@@ -200,11 +200,11 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
( (
wtext("Create Account").text_align(Align::CENTER).size(30), wtext("Create Account").text_align(Align::CENTER).size(30),
field_box(url, rsc), field_box(url),
field_box(token, rsc), field_box(token),
field_box(cert, rsc), field_box(cert),
field_box(username, rsc), field_box(username),
field_box(password, rsc), field_box(password),
create, create,
) )
.span(Dir::DOWN) .span(Dir::DOWN)

View File

@@ -1,12 +1,113 @@
// pub fn view(rsc: &mut Rsc, session: &Session) -> StrongWidget { use openworm::net::{AddFriend, AddFriendResp, GenerateToken, RequestFriends, ServerPerms};
// let mut view = WidgetSelector::new(View::Info, info(rsc));
// view.set(View::User, users(rsc, session)); use super::*;
// let view = view.add(rsc);
// let [info, server] = tabs(rsc, view, [("info", View::Info), ("users", View::User)]); pub fn view(rsc: &mut Rsc, session: &Session) -> StrongWidget {
// (
// let side_bar = rect(Color::BLACK.alpha(150)) gen_token(rsc, session),
// .foreground((info, server).span(Dir::DOWN)) add_friend_area(rsc, session),
// .width(260); friends_list(rsc, session),
// )
// (side_bar, view).span(Dir::RIGHT).add_strong(rsc) .span(Dir::DOWN)
// } .add_strong(rsc)
}
fn add_friend_area(rsc: &mut Rsc, session: &Session) -> WeakWidget {
let username_field = field("", "username").add(rsc);
let add = Button::submit("add friend", rsc);
let con = session.con.clone();
rsc.events.register(add, Submit, move |ctx, rsc| {
let con = con.clone();
let username = username_field.edit(rsc).take();
add.disable(rsc);
rsc.tasks.spawn(async move |mut ctx| {
let Ok(resp) = con.request(AddFriend { username }).await else {
ctx.update(move |ctx, rsc| {
ctx.error("failed to add user", rsc);
});
return;
};
ctx.update(move |ctx, rsc| {
add.enable(rsc);
match resp {
AddFriendResp::Ok => {}
AddFriendResp::UnknownUser => {
ctx.error("unknown user", rsc);
}
AddFriendResp::CannotAddSelf => {
ctx.error("cannot add self", rsc);
}
AddFriendResp::AlreadySent => {
ctx.error("already sent request to user", rsc);
}
AddFriendResp::AlreadyFriends => {
ctx.error("already friends with user", rsc);
}
}
});
});
});
(field_box(username_field).width(300), add.width(200))
.span(Dir::RIGHT)
.add(rsc)
}
fn gen_token(rsc: &mut Rsc, session: &Session) -> WeakWidget {
let generate = Button::normal("generate token", rsc);
let con = session.con.clone();
let token = wtext("")
.size(30)
.editable(EditMode::SingleLine)
.attr::<Selectable>(())
.add(rsc);
rsc.events.register(generate, Submit, move |_, rsc| {
let con = con.clone();
token.edit(rsc).set("loading...");
rsc.spawn_task(async move |mut ctx| {
let resp = con
.request(GenerateToken {
perms: ServerPerms::NONE,
})
.await;
ctx.update(move |_, rsc| match resp {
Ok(resp) => {
token.edit(rsc).set(&resp.token);
}
Err(_) => {
token.edit(rsc).set("failed to generate token");
}
});
});
});
(generate.width(200), token).span(Dir::RIGHT).add(rsc)
}
fn friends_list(rsc: &mut Rsc, session: &Session) -> WeakWidget {
let ptr = loading_area("loading friends").add(rsc);
let con = session.con.clone();
rsc.events.register(ptr, Draw, move |_, rsc| {
let con = con.clone();
// TODO: maybe have rsc.request method that takes in &con?
// need to also handle error tho
rsc.spawn_task(async move |mut ctx| {
let Ok(resp) = con.request(RequestFriends).await else {
return;
};
ctx.update(move |_, rsc| {
let mut all = Span::empty(Dir::DOWN);
if !resp.incoming.is_empty() {
all.push(section_label("incoming").add_strong(rsc));
all.push(user_list(&resp.incoming, rsc).add_strong(rsc))
}
all.push(section_label("friends").add_strong(rsc));
all.push(user_list(&resp.current, rsc).add_strong(rsc));
if !resp.outgoing.is_empty() {
all.push(section_label("outgoing").add_strong(rsc));
all.push(user_list(&resp.outgoing, rsc).add_strong(rsc))
}
all.set_ptr(ptr, rsc);
});
});
});
ptr
}

View File

@@ -1,7 +1,5 @@
use std::hash::Hash; use std::hash::Hash;
use crate::{Rsc, session::Session};
use super::*; use super::*;
pub const SIZE: u32 = 20; pub const SIZE: u32 = 20;
@@ -16,6 +14,7 @@ pub enum MainView {
pub fn main_view(rsc: &mut Rsc, session: Session) -> WeakWidget { pub fn main_view(rsc: &mut Rsc, session: Session) -> WeakWidget {
let mut view = WidgetSelector::new(MainView::Channel, channel::view(rsc)); let mut view = WidgetSelector::new(MainView::Channel, channel::view(rsc));
view.set(MainView::Server, server::view(rsc, &session)); view.set(MainView::Server, server::view(rsc, &session));
view.set(MainView::Friends, friends::view(rsc, &session));
let view = view.add(rsc); let view = view.add(rsc);
let [channel, friends, server] = tabs( let [channel, friends, server] = tabs(
rsc, rsc,

View File

@@ -1,3 +1,7 @@
use openworm::net::UserId;
use crate::Client;
use super::*; use super::*;
pub fn werror(msg: &str, rsc: &mut Rsc) -> StrongWidget { pub fn werror(msg: &str, rsc: &mut Rsc) -> StrongWidget {
@@ -16,20 +20,18 @@ pub fn large_hint_text(msg: impl Into<String>) -> TextBuilder<Rsc> {
wtext(msg).size(30).color(Color::GRAY) wtext(msg).size(30).color(Color::GRAY)
} }
pub fn field(default: &str, hint: &str, rsc: &mut Rsc) -> WeakWidget<TextEdit> { pub fn field(default: &str, hint: &str) -> impl FnOnce(&mut Rsc) -> TextEdit {
wtext(default) wtext(default)
.editable(EditMode::SingleLine) .editable(EditMode::SingleLine)
.size(20) .size(20)
.hint(hint_text(hint)) .hint(hint_text(hint))
.add(rsc)
} }
pub fn field_box(field: WeakWidget<TextEdit>, rsc: &mut Rsc) -> WeakWidget { pub fn field_box(field: WeakWidget<TextEdit>) -> impl WidgetIdFn<Rsc, Stack> {
field field
.pad(10) .pad(10)
.background(rect(Color::BLACK.brighter(0.1)).radius(15)) .background(rect(Color::BLACK.brighter(0.1)).radius(15))
.attr::<Selector>(field) .attr::<Selector>(field)
.add(rsc)
} }
#[derive(Clone, Copy, WidgetView)] #[derive(Clone, Copy, WidgetView)]
@@ -92,6 +94,10 @@ impl Button {
Self::new(text, color::GREEN, rsc) Self::new(text, color::GREEN, rsc)
} }
pub fn normal(text: &str, rsc: &mut Rsc) -> Self {
Self::new(text, color::DARK, rsc)
}
pub fn disable(&self, rsc: &mut Rsc) { pub fn disable(&self, rsc: &mut Rsc) {
rsc[self.enabled] = false; rsc[self.enabled] = false;
rsc[self.rect].color = self.color.darker(0.8); rsc[self.rect].color = self.color.darker(0.8);
@@ -123,10 +129,43 @@ pub fn tabs<T: Eq + std::hash::Hash + 'static + Copy, const N: usize>(
list: [(&'static str, T); N], list: [(&'static str, T); N],
) -> [WeakWidget; N] { ) -> [WeakWidget; N] {
list.map(|(name, sel)| { list.map(|(name, sel)| {
Button::new(name, color::DARK, rsc) Button::normal(name, rsc)
.on(Submit, move |_, rsc: &mut Rsc| { .on(Submit, move |_, rsc: &mut Rsc| {
rsc[view].select(sel); rsc[view].select(sel);
}) })
.add(rsc) .add(rsc)
}) })
} }
pub fn loading_area(text: &str) -> impl FnOnce(&mut Rsc) -> WidgetPtr {
move |rsc| {
WidgetPtr::new(
large_hint_text(text)
.center_text()
.width(rest(1))
.add_strong(rsc),
)
}
}
pub fn section_label(text: impl Into<String>) -> TextBuilder<Rsc> {
wtext(text).size(30)
}
pub fn user_list<'a>(ids: impl IntoIterator<Item = &'a UserId>, rsc: &mut Rsc) -> Span {
let mut span = Span::empty(Dir::DOWN);
for id in ids {
let thing = (wtext(id.to_string()).size(20),)
.span(Dir::RIGHT)
.gap(30)
.pad(15);
span.push(thing.add_strong(rsc));
}
span
}
impl Client {
pub fn error(&self, text: &str, rsc: &mut Rsc) {
rsc[self.notif].inner = Some(werror(text, rsc));
}
}

View File

@@ -1,4 +1,4 @@
use crate::Rsc; use crate::{Rsc, session::Session};
use iris::prelude::*; use iris::prelude::*;
mod channel; mod channel;

View File

@@ -28,13 +28,7 @@ fn info(rsc: &mut Rsc) -> StrongWidget {
} }
fn users(rsc: &mut Rsc, session: &Session) -> StrongWidget { fn users(rsc: &mut Rsc, session: &Session) -> StrongWidget {
let ptr = WidgetPtr::new( let ptr = loading_area("loading users").add(rsc);
large_hint_text("loading users...")
.center_text()
.width(rest(1))
.add_strong(rsc),
)
.add(rsc);
let con = session.con.clone(); let con = session.con.clone();
rsc.events.register(ptr, Draw, move |_, rsc| { rsc.events.register(ptr, Draw, move |_, rsc| {
let con = con.clone(); let con = con.clone();

View File

@@ -158,26 +158,26 @@ impl ClientHandler {
let Some(mut user) = tx.get(&db.users, &user_id) else { let Some(mut user) = tx.get(&db.users, &user_id) else {
reply!(AccountDeleted); reply!(AccountDeleted);
}; };
let Some(friend_id) = tx.get(&db.usernames, &info.username) else { let Some(other_id) = tx.get(&db.usernames, &info.username) else {
reply!(AddFriendResp::UnknownUser); reply!(AddFriendResp::UnknownUser);
}; };
if user.friends.outgoing.contains(&friend_id) { if user.friends.outgoing.contains(&other_id) {
reply!(AddFriendResp::AlreadySent); reply!(AddFriendResp::AlreadySent);
} }
if friend_id == user_id { if other_id == user_id {
reply!(AddFriendResp::CannotAddSelf); reply!(AddFriendResp::CannotAddSelf);
} }
let Some(mut other) = tx.get(&db.users, &user_id) else { let Some(mut other) = tx.get(&db.users, &other_id) else {
println!("WARNING: username without valid user!"); println!("WARNING: username without valid user!");
reply!(AddFriendResp::UnknownUser); reply!(AddFriendResp::UnknownUser);
}; };
if other.friends.current.contains(&user_id) { if other.friends.current.contains(&user_id) {
reply!(AddFriendResp::AlreadyFriends); reply!(AddFriendResp::AlreadyFriends);
} }
user.friends.outgoing.insert(friend_id); user.friends.outgoing.insert(other_id);
other.friends.incoming.insert(user_id); other.friends.incoming.insert(user_id);
tx.insert(&db.users, &user_id, &user); tx.insert(&db.users, &user_id, &user);
tx.insert(&db.users, &friend_id, &other); tx.insert(&db.users, &other_id, &other);
if tx.commit() { if tx.commit() {
break; break;
} }
@@ -189,15 +189,15 @@ impl ClientHandler {
loop { loop {
let mut tx = db.write_tx(); let mut tx = db.write_tx();
let mut user = tx.get(&db.users, &user_id)?; let mut user = tx.get(&db.users, &user_id)?;
let friend_id = info.id; let other_id = info.id;
let Some(mut other) = tx.get(&db.users, &user_id) else { let Some(mut other) = tx.get(&db.users, &other_id) else {
println!("WARNING: username without valid user!"); println!("WARNING: username without valid user!");
return None; return None;
}; };
user.friends.current.remove(&friend_id); user.friends.current.remove(&other_id);
other.friends.current.remove(&user_id); other.friends.current.remove(&user_id);
tx.insert(&db.users, &user_id, &user); tx.insert(&db.users, &user_id, &user);
tx.insert(&db.users, &friend_id, &other); tx.insert(&db.users, &other_id, &other);
if tx.commit() { if tx.commit() {
break; break;
} }
@@ -218,7 +218,7 @@ impl ClientHandler {
let mut tx = db.write_tx(); let mut tx = db.write_tx();
let mut user = tx.get(&db.users, &user_id)?; let mut user = tx.get(&db.users, &user_id)?;
let other_id = answer.id; let other_id = answer.id;
let Some(mut other) = tx.get(&db.users, &user_id) else { let Some(mut other) = tx.get(&db.users, &other_id) else {
println!("WARNING: username without valid user!"); println!("WARNING: username without valid user!");
return None; return None;
}; };