gm_lib/tui/app/widgets/
invite_popup.rs1use std::{sync::mpsc, time::Duration};
2
3use crossterm::event::{KeyCode, KeyEventKind};
4use ratatui::{
5 buffer::Buffer,
6 layout::Rect,
7 widgets::{Block, Widget},
8};
9use tokio::task::JoinHandle;
10
11use crate::tui::{
12 app::{widgets::popup::Popup, SharedState},
13 traits::{CustomRender, HandleResult},
14 Event,
15};
16
17#[derive(Copy, Clone, Debug, Default, PartialEq)]
18pub enum InviteCodeValidity {
19 #[default]
20 Checking,
21 Valid,
22 Invalid,
23 Claimed,
24}
25
26#[derive(Clone, Debug, Default, PartialEq)]
27pub enum InviteCodeClaimStatus {
28 #[default]
29 Idle,
30 Claiming,
31 Success,
32 Failed(String),
33}
34
35pub fn start_check_thread(
36 invite_code: &str,
37 tr: &mpsc::Sender<Event>,
38) -> crate::Result<JoinHandle<()>> {
39 let tr = tr.clone();
40 let invite_code = invite_code.to_string();
41 Ok(tokio::spawn(async move {
42 let _ = invite_code;
44 let result = InviteCodeValidity::Valid;
45 tokio::time::sleep(Duration::from_secs(1)).await;
46
47 let _ = tr.send(Event::InviteCodeValidity(result));
48 }))
49}
50
51pub fn start_claim_thread(
52 invite_code: &str,
53 tr: &mpsc::Sender<Event>,
54) -> crate::Result<JoinHandle<()>> {
55 let tr = tr.clone();
56 let invite_code = invite_code.to_string();
57 Ok(tokio::spawn(async move {
58 let _ = tr.send(Event::InviteCodeClaimStatus(
59 InviteCodeClaimStatus::Claiming,
60 ));
61 let _ = invite_code;
63 tokio::time::sleep(Duration::from_secs(1)).await;
64
65 let _ = tr.send(Event::InviteCodeClaimStatus(InviteCodeClaimStatus::Success));
66 }))
67}
68
69#[derive(Default)]
70pub struct InvitePopup {
71 invite_code: Option<String>,
72 validity: InviteCodeValidity,
73 claim_status: InviteCodeClaimStatus,
74 check_thread: Option<JoinHandle<()>>,
75 claim_thread: Option<JoinHandle<()>>,
76 open: bool,
77}
78
79impl InvitePopup {
80 pub fn is_open(&self) -> bool {
81 self.open
82 }
83
84 pub fn open(&mut self) {
85 self.open = true;
86 }
87
88 pub fn close(&mut self) {
89 self.open = false;
90 }
91
92 pub fn set_invite_code(&mut self, text: &str) {
93 self.reset();
94 self.invite_code = Some(text.to_string());
95 }
96
97 fn reset(&mut self) {
98 if let Some(thread) = self.check_thread.take() {
99 thread.abort();
100 }
101
102 if let Some(thread) = self.claim_thread.take() {
103 thread.abort();
104 }
105 }
106
107 pub fn handle_event(
108 &mut self,
109 event: &Event,
110 tr: &mpsc::Sender<Event>,
111 ) -> crate::Result<HandleResult> {
112 let mut result = HandleResult::default();
113
114 if self.check_thread.is_none() {
115 if let Some(invite_code) = self.invite_code.as_ref() {
116 let check_thread = start_check_thread(invite_code, tr)?;
117 self.check_thread = Some(check_thread);
118 }
119 }
120
121 match event {
122 Event::Input(key_event) => {
123 if key_event.kind == KeyEventKind::Press {
124 match key_event.code {
125 KeyCode::Enter => {
126 if self.validity == InviteCodeValidity::Valid
127 && self.claim_status == InviteCodeClaimStatus::Idle
128 {
129 if let Some(invite_code) = self.invite_code.as_ref() {
130 let claim_thread = start_claim_thread(invite_code, tr)?;
131 self.claim_thread = Some(claim_thread);
132 }
133 }
134 }
135 KeyCode::Esc => {
136 self.close();
137 result.esc_ignores = 1;
138 }
139 _ => {}
140 }
141 }
142 }
143 Event::InviteCodeValidity(validity) => {
144 self.validity = *validity;
145 }
146 Event::InviteCodeClaimStatus(status) => {
147 self.claim_status = status.clone();
148 }
149 _ => {}
150 }
151 result.esc_ignores = 1;
152 Ok(result)
153 }
154
155 pub fn render(&self, area: Rect, buf: &mut Buffer, shared_state: &SharedState)
156 where
157 Self: Sized,
158 {
159 if self.is_open() {
160 let theme = shared_state.theme.popup();
165
166 Popup.render(area, buf, &theme);
167
168 let inner_area = Popup::inner_area(area);
169 let block = Block::bordered();
170 let block_inner_area = block.inner(inner_area);
171 block.render(inner_area, buf);
172
173 let area = block_inner_area;
174
175 [
176 "Welcome! And thanks for joining gm's alpha testing program!".to_string(),
177 if let Some(invite_code) = self.invite_code.as_ref() {
178 match self.validity {
179 InviteCodeValidity::Checking => {
180 format!("Invite Code: \"{invite_code}\", checking validity...")
181 }
182 InviteCodeValidity::Valid => {
183 format!("Invite code: \"{invite_code}\", valid!")
184 }
185 InviteCodeValidity::Invalid => {
186 format!(
187 "Invite Code: \"{invite_code}\" is invalid, please check the code"
188 )
189 }
190 InviteCodeValidity::Claimed => {
191 format!("Invite Code: \"{invite_code}\", claimed already")
192 }
193 }
194 } else {
195 "Invite Code not provided, this should not happen".to_string()
196 },
197 match self.claim_status {
198 InviteCodeClaimStatus::Idle => {
199 if self.validity == InviteCodeValidity::Valid {
200 "Press Enter to claim".to_string()
201 } else {
202 "".to_string()
203 }
204 }
205 InviteCodeClaimStatus::Claiming => "Claiming invite code...".to_string(),
206 InviteCodeClaimStatus::Success => "Claimed successfully!".to_string(),
207 InviteCodeClaimStatus::Failed(ref msg) => {
208 format!("Failed to claim invite code: {msg}")
209 }
210 },
211 ]
212 .render(area, buf, true);
213 }
214 }
215}