chiark / gitweb /
Implement responding to follow requests.
authorSimon Tatham <anakin@pobox.com>
Sat, 3 Feb 2024 11:32:41 +0000 (11:32 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 3 Feb 2024 11:51:15 +0000 (11:51 +0000)
If a user has requested to follow you, you get an extra menu item in
the options menu for that user, where you can accept or reject the
request, or leave it unresponded-to for another time.

To make this optional menu item less painful to implement, I made the
MenuKeypressLineGeneral trait also apply to Option<T> for any inner T
that also implements it. So I can do all the routine admin like
ensure_widths without writing three annoying 'if let Some(...)'.

(But that doesn't let me get_value() on an optional CyclingMenuLine -
that would be silly! For retrieving the value, or cycling the line,
you still need to check if it's actually there.)

src/client.rs
src/options.rs
src/text.rs

index 29a181607dfcd5c7deb069ebf4bc8606e24f27b1..e15328f724a91c59e72749c6252cd354aea48318 100644 (file)
@@ -1499,6 +1499,17 @@ impl Client {
         Ok(())
     }
 
+    pub fn respond_to_follow_request(
+        &mut self,
+        id: &str,
+        accept: bool,
+    ) -> Result<(), ClientError> {
+        let verb = if accept { "authorize" } else { "reject" };
+        let req = Req::post(&format!("api/v1/follow_requests/{id}/{verb}"));
+        self.api_request_ok(req)?;
+        Ok(())
+    }
+
     pub fn set_account_flag(
         &mut self,
         id: &str,
index 58d2d44649d611c20af7bc764c29f23a5676a5e2..084a58d5419b96060684680c52607713de40d180 100644 (file)
@@ -321,6 +321,13 @@ impl EditableMenuLineData for LanguageVector {
     }
 }
 
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum FollowRequestResponse {
+    Accept,
+    Reject,
+    Undecided,
+}
+
 struct OtherUserOptionsMenu {
     title: FileHeader,
     normal_status: FileStatusLineFinal,
@@ -328,6 +335,7 @@ struct OtherUserOptionsMenu {
     cl_follow: CyclingMenuLine<bool>,
     cl_boosts: CyclingMenuLine<Boosts>,
     el_languages: EditableMenuLine<LanguageVector>,
+    cl_follow_request: Option<CyclingMenuLine<FollowRequestResponse>>,
     cl_block: CyclingMenuLine<bool>,
     cl_mute: CyclingMenuLine<bool>,
     id: String,
@@ -408,6 +416,32 @@ impl OtherUserOptionsMenu {
             ),
         );
 
+        let cl_follow_request = if rel.requested_by {
+            Some(CyclingMenuLine::new(
+                Pr('R'),
+                ColouredString::plain(
+                    "Approve user's request to follow you: ",
+                ),
+                &[
+                    (
+                        FollowRequestResponse::Undecided,
+                        ColouredString::plain("no action"),
+                    ),
+                    (
+                        FollowRequestResponse::Accept,
+                        ColouredString::uniform("accept", 'f'),
+                    ),
+                    (
+                        FollowRequestResponse::Reject,
+                        ColouredString::uniform("reject", 'r'),
+                    ),
+                ],
+                FollowRequestResponse::Undecided,
+            ))
+        } else {
+            None
+        };
+
         let cl_block = CyclingMenuLine::new(
             Ctrl('B'),
             ColouredString::plain("Block this user: "),
@@ -435,6 +469,7 @@ impl OtherUserOptionsMenu {
             cl_follow,
             cl_boosts,
             el_languages,
+            cl_follow_request,
             cl_block,
             cl_mute,
             id: id.to_owned(),
@@ -452,18 +487,22 @@ impl OtherUserOptionsMenu {
         self.cl_follow.check_widths(&mut lmaxwid, &mut rmaxwid);
         self.cl_boosts.check_widths(&mut lmaxwid, &mut rmaxwid);
         self.el_languages.check_widths(&mut lmaxwid, &mut rmaxwid);
+        self.cl_follow_request
+            .check_widths(&mut lmaxwid, &mut rmaxwid);
         self.cl_block.check_widths(&mut lmaxwid, &mut rmaxwid);
         self.cl_mute.check_widths(&mut lmaxwid, &mut rmaxwid);
 
         self.cl_follow.reset_widths();
         self.cl_boosts.reset_widths();
         self.el_languages.reset_widths();
+        self.cl_follow_request.reset_widths();
         self.cl_block.reset_widths();
         self.cl_mute.reset_widths();
 
         self.cl_follow.ensure_widths(lmaxwid, rmaxwid);
         self.cl_boosts.ensure_widths(lmaxwid, rmaxwid);
         self.el_languages.ensure_widths(lmaxwid, rmaxwid);
+        self.cl_follow_request.ensure_widths(lmaxwid, rmaxwid);
         self.cl_block.ensure_widths(lmaxwid, rmaxwid);
         self.cl_mute.ensure_widths(lmaxwid, rmaxwid);
 
@@ -506,6 +545,22 @@ impl OtherUserOptionsMenu {
             }
         }
 
+        if let Some(ref cl) = self.cl_follow_request {
+            let action = cl.get_value();
+            let result = match action {
+                FollowRequestResponse::Accept
+                | FollowRequestResponse::Reject => client
+                    .respond_to_follow_request(
+                        &self.id,
+                        action == FollowRequestResponse::Accept,
+                    ),
+                FollowRequestResponse::Undecided => Ok(()),
+            };
+            if result.is_err() {
+                return LogicalAction::Beep; // FIXME: report the error!
+            }
+        }
+
         LogicalAction::Pop
     }
 }
@@ -524,6 +579,7 @@ impl ActivityState for OtherUserOptionsMenu {
         lines.extend_from_slice(&self.cl_boosts.render(w));
         lines.push(self.el_languages.render(w, &mut cursorpos, lines.len()));
         lines.extend_from_slice(&BlankLine::render_static());
+        lines.extend_from_slice(&self.cl_follow_request.render(w));
         lines.extend_from_slice(&self.cl_block.render(w));
         lines.extend_from_slice(&self.cl_mute.render(w));
 
@@ -559,6 +615,13 @@ impl ActivityState for OtherUserOptionsMenu {
             Pr('l') | Pr('L') => self.el_languages.start_editing(),
             Ctrl('B') => self.cl_block.cycle(),
             Ctrl('U') => self.cl_mute.cycle(),
+            Pr('r') | Pr('R') => {
+                if let Some(ref mut cl) = self.cl_follow_request {
+                    cl.cycle()
+                } else {
+                    LogicalAction::Nothing
+                }
+            }
             _ => LogicalAction::Nothing,
         }
     }
index bf6824f81aa870ee67a61733422b9b5e048d2a1f..e3e67f30a46022142f48cdda048c51ae4dad9293 100644 (file)
@@ -2042,6 +2042,24 @@ pub trait MenuKeypressLineGeneral {
     fn ensure_widths(&mut self, lmaxwid: usize, rmaxwid: usize);
 }
 
+impl<T: MenuKeypressLineGeneral> MenuKeypressLineGeneral for Option<T> {
+    fn check_widths(&self, lmaxwid: &mut usize, rmaxwid: &mut usize) {
+        if let Some(inner) = self {
+            inner.check_widths(lmaxwid, rmaxwid);
+        }
+    }
+    fn reset_widths(&mut self) {
+        if let Some(inner) = self {
+            inner.reset_widths();
+        }
+    }
+    fn ensure_widths(&mut self, lmaxwid: usize, rmaxwid: usize) {
+        if let Some(inner) = self {
+            inner.ensure_widths(lmaxwid, rmaxwid);
+        }
+    }
+}
+
 impl MenuKeypressLine {
     pub fn new(key: OurKey, description: ColouredString) -> Self {
         // +2 for [] around the key name