Skip to content

Commit 5f110b1

Browse files
unhappychoiceclaude
andcommitted
feat: add comprehensive SNS sharing functionality
- Add multi-platform sharing support (Twitter, Reddit, LinkedIn, Facebook) - Implement sharing menu with platform selection - Add browser fallback with URL display for restricted environments - Support share action in result screen with [S] key - Add sharing functionality to exit summary screen - Prevent animation replay after sharing - Include gittype repository URL in all share formats - Add score to Reddit post titles for better visibility - Add open and urlencoding dependencies for browser integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d66765d commit 5f110b1

File tree

7 files changed

+509
-26
lines changed

7 files changed

+509
-26
lines changed

Cargo.lock

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ uuid = { version = "1.0", features = ["v4"] }
4040
rand = { version = "0.8", features = ["std_rng"] }
4141
once_cell = "1.19"
4242
rayon = "1.8"
43+
open = "5.0"
44+
urlencoding = "2.1"
4345

4446
[dev-dependencies]
4547
tempfile = "3.8"

src/game/screens/exit_summary_screen.rs

Lines changed: 128 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::Result;
22
use crate::game::{SessionSummary, ascii_digits::get_digit_patterns};
3+
use crate::sharing::{SharingService, SharingPlatform};
34
use crossterm::{
45
cursor::MoveTo,
5-
event::{self, Event},
6+
event::{self, Event, KeyCode, KeyModifiers},
67
execute,
78
style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
89
terminal::{self, ClearType},
@@ -12,11 +13,32 @@ use std::io::{stdout, Write};
1213
#[derive(Debug)]
1314
pub enum ExitAction {
1415
Exit,
16+
Share,
1517
}
1618

1719
pub struct ExitSummaryScreen;
1820

1921
impl ExitSummaryScreen {
22+
fn session_summary_to_typing_metrics(session_summary: &SessionSummary) -> crate::scoring::TypingMetrics {
23+
use crate::scoring::{TypingMetrics, ScoringEngine};
24+
25+
// Create a TypingMetrics from SessionSummary data
26+
let ranking_title = ScoringEngine::get_ranking_title_for_score(session_summary.session_score).name().to_string();
27+
28+
TypingMetrics {
29+
cpm: session_summary.overall_cpm,
30+
wpm: session_summary.overall_wpm,
31+
accuracy: session_summary.overall_accuracy,
32+
mistakes: session_summary.total_mistakes,
33+
consistency_streaks: vec![], // Not available in session summary
34+
completion_time: session_summary.total_session_time,
35+
challenge_score: session_summary.session_score,
36+
ranking_title,
37+
was_skipped: false,
38+
was_failed: false,
39+
}
40+
}
41+
2042
fn create_ascii_numbers(score: &str) -> Vec<String> {
2143
let digit_patterns = get_digit_patterns();
2244
let max_height = 4;
@@ -145,7 +167,8 @@ impl ExitSummaryScreen {
145167
execute!(stdout, ResetColor)?;
146168

147169
let options = vec![
148-
"Press any key to exit",
170+
"[S] Share Result",
171+
"[ESC] Exit",
149172
];
150173

151174
for (i, option) in options.iter().enumerate() {
@@ -158,13 +181,113 @@ impl ExitSummaryScreen {
158181

159182
stdout.flush()?;
160183

161-
// Wait for any key press
184+
// Wait for user input
185+
loop {
186+
if event::poll(std::time::Duration::from_millis(100))? {
187+
if let Event::Key(key_event) = event::read()? {
188+
match key_event.code {
189+
KeyCode::Char('s') | KeyCode::Char('S') => {
190+
return Ok(ExitAction::Share);
191+
},
192+
KeyCode::Esc => {
193+
return Ok(ExitAction::Exit);
194+
},
195+
KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
196+
std::process::exit(0);
197+
},
198+
_ => continue,
199+
}
200+
}
201+
}
202+
}
203+
}
204+
205+
pub fn show_sharing_menu(session_summary: &SessionSummary) -> Result<()> {
206+
let metrics = Self::session_summary_to_typing_metrics(session_summary);
207+
208+
let mut stdout = stdout();
209+
execute!(stdout, terminal::Clear(ClearType::All))?;
210+
211+
let (terminal_width, terminal_height) = terminal::size()?;
212+
let center_row = terminal_height / 2;
213+
let center_col = terminal_width / 2;
214+
215+
// Title
216+
let title = "📤 Share Your Session Result";
217+
let title_col = center_col.saturating_sub(title.len() as u16 / 2);
218+
execute!(stdout, MoveTo(title_col, center_row.saturating_sub(8)))?;
219+
execute!(stdout, SetAttribute(Attribute::Bold), SetForegroundColor(Color::Yellow))?;
220+
execute!(stdout, Print(title))?;
221+
execute!(stdout, ResetColor)?;
222+
223+
// Show preview of what will be shared
224+
let preview_text = format!(
225+
"\"{}\" - Score: {:.0}, CPM: {:.0}, Session Time: {:.1}min",
226+
metrics.ranking_title,
227+
metrics.challenge_score,
228+
metrics.cpm,
229+
metrics.completion_time.as_secs_f64() / 60.0
230+
);
231+
let preview_col = center_col.saturating_sub(preview_text.len() as u16 / 2);
232+
execute!(stdout, MoveTo(preview_col, center_row.saturating_sub(5)))?;
233+
execute!(stdout, SetForegroundColor(Color::Cyan))?;
234+
execute!(stdout, Print(&preview_text))?;
235+
execute!(stdout, ResetColor)?;
236+
237+
// Platform options
238+
let platforms = SharingPlatform::all();
239+
let start_row = center_row.saturating_sub(2);
240+
241+
for (i, platform) in platforms.iter().enumerate() {
242+
let option_text = format!("[{}] {}", i + 1, platform.name());
243+
let option_col = center_col.saturating_sub(option_text.len() as u16 / 2);
244+
execute!(stdout, MoveTo(option_col, start_row + i as u16))?;
245+
execute!(stdout, SetForegroundColor(Color::White))?;
246+
execute!(stdout, Print(&option_text))?;
247+
execute!(stdout, ResetColor)?;
248+
}
249+
250+
// Back option
251+
let back_text = "[ESC] Back to Exit Screen";
252+
let back_col = center_col.saturating_sub(back_text.len() as u16 / 2);
253+
execute!(stdout, MoveTo(back_col, start_row + platforms.len() as u16 + 2))?;
254+
execute!(stdout, SetForegroundColor(Color::Grey))?;
255+
execute!(stdout, Print(back_text))?;
256+
execute!(stdout, ResetColor)?;
257+
258+
stdout.flush()?;
259+
260+
// Handle input
162261
loop {
163262
if event::poll(std::time::Duration::from_millis(100))? {
164-
if let Event::Key(_) = event::read()? {
165-
return Ok(ExitAction::Exit);
263+
if let Event::Key(key_event) = event::read()? {
264+
match key_event.code {
265+
KeyCode::Char('1') => {
266+
let _ = SharingService::share_result(&metrics, SharingPlatform::Twitter);
267+
break;
268+
},
269+
KeyCode::Char('2') => {
270+
let _ = SharingService::share_result(&metrics, SharingPlatform::Reddit);
271+
break;
272+
},
273+
KeyCode::Char('3') => {
274+
let _ = SharingService::share_result(&metrics, SharingPlatform::LinkedIn);
275+
break;
276+
},
277+
KeyCode::Char('4') => {
278+
let _ = SharingService::share_result(&metrics, SharingPlatform::Facebook);
279+
break;
280+
},
281+
KeyCode::Esc => break,
282+
KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
283+
std::process::exit(0);
284+
},
285+
_ => continue,
286+
}
166287
}
167288
}
168289
}
290+
291+
Ok(())
169292
}
170293
}

0 commit comments

Comments
 (0)