[Add] Added Eula / ToS / PP handeling,
AS OF NOW All have to be accepted due to the inexistence of Decentralized mode
This commit is contained in:
parent
c4975ef8cc
commit
925fd4d1b4
4 changed files with 534 additions and 32 deletions
|
|
@ -1,26 +0,0 @@
|
|||
use crate::util::file_util::{load_file, save_file};
|
||||
|
||||
pub fn check_eula() -> bool {
|
||||
let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\
|
||||
\nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\
|
||||
\neula=false";
|
||||
let file = load_file("", "eula.txt");
|
||||
if file.is_empty() {
|
||||
save_file("", "eula.txt", eula);
|
||||
return false;
|
||||
}
|
||||
|
||||
if file.contains("eula=false") {
|
||||
false
|
||||
} else if file.contains("eula=true") {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
pub fn accept_eula() {
|
||||
let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\
|
||||
\nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\
|
||||
\neula=true";
|
||||
save_file("", "eula.txt", eula);
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
pub mod eula_checker;
|
||||
pub mod terms_checker;
|
||||
|
|
|
|||
519
src/eula/terms_checker.rs
Normal file
519
src/eula/terms_checker.rs
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
use crate::util::file_util::{load_file, save_file};
|
||||
use crossterm::{
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||
};
|
||||
use ratatui::{
|
||||
Terminal,
|
||||
backend::CrosstermBackend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Borders, Paragraph},
|
||||
};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{io, time::Duration};
|
||||
|
||||
pub struct ConsentManager;
|
||||
impl ConsentManager {
|
||||
pub fn check() -> (bool, bool) {
|
||||
let file = load_file("", "agreements");
|
||||
let existing = ConsentUiState::from_str(&file).sanitize();
|
||||
|
||||
let final_state = if existing.eula {
|
||||
existing
|
||||
} else {
|
||||
let choice = run_consent_ui();
|
||||
let state = match choice {
|
||||
UserChoice::Deny => ConsentUiState::denied(),
|
||||
UserChoice::AcceptEULA => ConsentUiState {
|
||||
eula: true,
|
||||
tos: false,
|
||||
pp: false,
|
||||
focus: Focus::Cancel,
|
||||
},
|
||||
UserChoice::AcceptAll => ConsentUiState {
|
||||
eula: true,
|
||||
tos: true,
|
||||
pp: true,
|
||||
focus: Focus::Cancel,
|
||||
},
|
||||
};
|
||||
let state = state.sanitize();
|
||||
save_file("", "agreements", &state.to_string());
|
||||
state
|
||||
};
|
||||
|
||||
(final_state.eula, final_state.pp && final_state.tos)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum UserChoice {
|
||||
Deny,
|
||||
AcceptEULA,
|
||||
AcceptAll,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct ConsentUiState {
|
||||
eula: bool,
|
||||
tos: bool,
|
||||
pp: bool,
|
||||
focus: Focus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum Focus {
|
||||
Eula,
|
||||
Tos,
|
||||
Pp,
|
||||
Cancel,
|
||||
Continue,
|
||||
ContinueAll,
|
||||
}
|
||||
|
||||
impl ConsentUiState {
|
||||
fn denied() -> Self {
|
||||
Self {
|
||||
eula: false,
|
||||
pp: false,
|
||||
tos: false,
|
||||
focus: Focus::Cancel,
|
||||
}
|
||||
}
|
||||
|
||||
fn sanitize(mut self) -> Self {
|
||||
if !self.eula {
|
||||
self.tos = false;
|
||||
self.pp = false;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn to_string(self) -> String {
|
||||
let current_secs = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
format!(
|
||||
"EULA=true indicates that you read and accepted the End User Licence agreement. You can find our EULA at https://docs.tensamin.net/legal/eula/
|
||||
\nEULA={}\
|
||||
\nPrivacyPolicy=true indicates that you read and accepted the Privacy Policy. You can find our Privacy Policy at https://docs.tensamin.net/legal/privacy-policy/
|
||||
\nPrivacyPolicy={}\
|
||||
\nToS=true indicates that you read and accepted the Terms of Service. You can find our Terms of Service at https://docs.tensamin.net/legal/terms-of-service/
|
||||
\nToS={}\
|
||||
\nThis file reflects the current consent state used by the application.\
|
||||
\nIt may be regenerated or overwritten by the application.\
|
||||
\nThis file was last edited by Tensamin at:\
|
||||
\nUNIX={}",
|
||||
self.eula, self.pp, self.tos, current_secs
|
||||
)
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> Self {
|
||||
let mut eula = false;
|
||||
let mut pp = false;
|
||||
let mut tos = false;
|
||||
|
||||
for line in s.lines() {
|
||||
if let Some(v) = line.strip_prefix("EULA=") {
|
||||
eula = v == "true";
|
||||
} else if let Some(v) = line.strip_prefix("ToS=") {
|
||||
tos = v == "true";
|
||||
} else if let Some(v) = line.strip_prefix("PrivacyPolicy=") {
|
||||
pp = v == "true";
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
eula,
|
||||
pp,
|
||||
tos,
|
||||
focus: Focus::Cancel,
|
||||
}
|
||||
.sanitize()
|
||||
}
|
||||
fn can_continue(&self) -> bool {
|
||||
self.eula
|
||||
}
|
||||
fn can_continue_all(&self) -> bool {
|
||||
self.eula && self.pp && self.tos
|
||||
}
|
||||
|
||||
fn next(&mut self) {
|
||||
self.focus = match self.focus {
|
||||
Focus::Eula => Focus::Tos,
|
||||
Focus::Tos => Focus::Pp,
|
||||
Focus::Pp => Focus::Cancel,
|
||||
Focus::Cancel => {
|
||||
if self.can_continue() {
|
||||
Focus::Continue
|
||||
} else {
|
||||
Focus::Eula
|
||||
}
|
||||
}
|
||||
Focus::Continue => Focus::ContinueAll,
|
||||
Focus::ContinueAll => Focus::Eula,
|
||||
};
|
||||
}
|
||||
|
||||
fn prev(&mut self) {
|
||||
self.focus = match self.focus {
|
||||
Focus::Eula => Focus::ContinueAll,
|
||||
Focus::Tos => Focus::Eula,
|
||||
Focus::Pp => Focus::Tos,
|
||||
Focus::Cancel => Focus::Pp,
|
||||
Focus::Continue => Focus::Cancel,
|
||||
Focus::ContinueAll => Focus::Continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn run_consent_ui() -> UserChoice {
|
||||
enable_raw_mode().unwrap();
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen).unwrap();
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
let mut state = ConsentUiState {
|
||||
eula: false,
|
||||
tos: false,
|
||||
pp: false,
|
||||
focus: Focus::Eula,
|
||||
};
|
||||
|
||||
let result = loop {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.area();
|
||||
|
||||
let mut needed_height = 5;
|
||||
|
||||
let margin = if size.width > 80 && size.height > 18 {
|
||||
2
|
||||
} else if size.width > 78 && size.height > 16 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(7), Constraint::Length(3)])
|
||||
.margin(margin)
|
||||
.split(size);
|
||||
let eula_text = if size.width < 76 {
|
||||
"EULA ¹ (https://docs.tensamin.net/legal/eula/)"
|
||||
} else {
|
||||
"End User Licence Agreement ¹ (https://docs.tensamin.net/legal/eula/)"
|
||||
};
|
||||
let tos_text = if size.width < 76 {
|
||||
"ToS ² (https://docs.tensamin.net/legal/terms-of-service/)"
|
||||
} else {
|
||||
"Terms of Service ² (https://docs.tensamin.net/legal/terms-of-service/)"
|
||||
};
|
||||
let pp_text = if size.width < 76 {
|
||||
"PP ² (https://docs.tensamin.net/legal/privacy-policy/)"
|
||||
} else {
|
||||
"Privacy Policy ² (https://docs.tensamin.net/legal/privacy-policy/)"
|
||||
};
|
||||
|
||||
let (mut optional_lines, agree_lines): (Vec<i16>, Vec<&str>) =
|
||||
if size.width > 131 {
|
||||
(
|
||||
vec![7, 3, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have read and agree to the End User License Agreement and applicable Terms of Service",
|
||||
"",
|
||||
"Tensamin services require acceptance of the Terms of Service and Privacy Policy.",
|
||||
]
|
||||
)
|
||||
} else if size.width > 67 {
|
||||
(
|
||||
vec![8, 3, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have read and agree",
|
||||
"to the End User License Agreement and applicable Terms of Service",
|
||||
"",
|
||||
"Tensamin services require acceptance of the ToS and Privacy Policy",
|
||||
]
|
||||
)
|
||||
} else {
|
||||
(
|
||||
vec![9, 3, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have",
|
||||
"read and agree to the End User License Agreement",
|
||||
"and applicable Terms of Service",
|
||||
"",
|
||||
"Tensamin services require acceptance of the",
|
||||
"Terms of Service and Privacy Policy"
|
||||
]
|
||||
)
|
||||
};
|
||||
let mut text_lines = vec![
|
||||
checkbox(eula_text, state.eula, state.focus == Focus::Eula, true),
|
||||
checkbox(tos_text, state.tos, state.focus == Focus::Tos, state.eula),
|
||||
checkbox(pp_text, state.pp, state.focus == Focus::Pp, state.eula),
|
||||
Line::from(""),
|
||||
Line::from("¹ Necessary, ² Optional"),
|
||||
];
|
||||
|
||||
for line in agree_lines {
|
||||
text_lines.insert(text_lines.len(), Line::from(line));
|
||||
}
|
||||
|
||||
while size.height - 5 < text_lines.len() as u16 {
|
||||
if optional_lines.len() == 0 {
|
||||
break;
|
||||
}
|
||||
text_lines.remove(optional_lines[0] as usize);
|
||||
optional_lines.remove(0);
|
||||
}
|
||||
needed_height += text_lines.len();
|
||||
|
||||
|
||||
|
||||
if size.width < 63 || size.height < needed_height as u16 {
|
||||
let width_style = if size.width > 76 {
|
||||
Style::default().fg(Color::Green)
|
||||
} else if size.width >= 63 {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::default().fg(Color::Red)
|
||||
};
|
||||
|
||||
let height_style = if size.height < needed_height as u16 {
|
||||
Style::default().fg(Color::Red)
|
||||
} else if size.height >= 11 {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::default().fg(Color::Green)
|
||||
};
|
||||
|
||||
let warning_text = Text::from(vec![
|
||||
Line::from(vec![
|
||||
Span::raw("Width: "),
|
||||
Span::styled(format!("{}", size.width), width_style),
|
||||
Span::raw(" / 63"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::raw("Height: "),
|
||||
Span::styled(format!("{}", size.height), height_style),
|
||||
Span::raw(format!(" / {}", needed_height)),
|
||||
]),
|
||||
]);
|
||||
|
||||
let warning = Paragraph::new(warning_text)
|
||||
.alignment(Alignment::Center)
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("UI Too Small (Q to Quit)"),
|
||||
);
|
||||
|
||||
f.render_widget(warning, size);
|
||||
return;
|
||||
}
|
||||
|
||||
let consent_block = Paragraph::new(Text::from(text_lines))
|
||||
.block(Block::default().title("Consent").borders(Borders::ALL));
|
||||
f.render_widget(consent_block, chunks[0]);
|
||||
|
||||
draw_buttons(f, chunks[1], &state);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if event::poll(Duration::from_millis(200)).unwrap() {
|
||||
if let Event::Key(key) = event::read().unwrap() {
|
||||
match key.code {
|
||||
KeyCode::Esc => break UserChoice::Deny,
|
||||
KeyCode::Up => state.prev(),
|
||||
KeyCode::Down | KeyCode::Tab => state.next(),
|
||||
KeyCode::Char('q') | KeyCode::Char('Q') => break UserChoice::Deny,
|
||||
KeyCode::Char(' ') => match state.focus {
|
||||
Focus::Eula => {
|
||||
state.eula = !state.eula;
|
||||
state.tos = false;
|
||||
state.pp = false;
|
||||
}
|
||||
Focus::Tos => {
|
||||
if state.eula {
|
||||
state.tos = !state.tos
|
||||
}
|
||||
}
|
||||
Focus::Pp => {
|
||||
if state.eula {
|
||||
state.pp = !state.pp
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
KeyCode::Enter => match state.focus {
|
||||
Focus::Cancel => break UserChoice::Deny,
|
||||
Focus::Continue if state.can_continue() => break UserChoice::AcceptEULA,
|
||||
Focus::ContinueAll if state.can_continue_all() => {
|
||||
break UserChoice::AcceptAll;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
disable_raw_mode().unwrap();
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
|
||||
terminal.show_cursor().unwrap();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[allow(mismatched_lifetime_syntaxes)]
|
||||
fn checkbox(label: &str, checked: bool, active: bool, allowed: bool) -> Line {
|
||||
let box_char = if checked { "[x]" } else { "[ ]" };
|
||||
let (box_style, text_style) = if active {
|
||||
if allowed {
|
||||
(
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Style::default().fg(Color::Gray),
|
||||
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(Style::default(), Style::default())
|
||||
};
|
||||
Line::from(vec![
|
||||
Span::styled(box_char, box_style),
|
||||
Span::raw(" "),
|
||||
Span::styled(label, text_style),
|
||||
])
|
||||
}
|
||||
|
||||
fn draw_button(f: &mut ratatui::Frame, area: Rect, label: &str, style: Style) {
|
||||
let p = Paragraph::new(Span::styled(label, style))
|
||||
.alignment(Alignment::Center)
|
||||
.block(Block::default().borders(Borders::ALL));
|
||||
f.render_widget(p, area);
|
||||
}
|
||||
fn draw_buttons(f: &mut ratatui::Frame, area: Rect, state: &ConsentUiState) {
|
||||
let buttons = vec![
|
||||
("[Q] Cancel", Focus::Cancel),
|
||||
("Continue", Focus::Continue),
|
||||
("Continue with Tensamin Services", Focus::ContinueAll),
|
||||
];
|
||||
|
||||
let padding = 2;
|
||||
let min_widths: Vec<u16> = buttons
|
||||
.iter()
|
||||
.map(|(label, _)| label.len() as u16 + padding)
|
||||
.collect();
|
||||
|
||||
let widths = compute_widths(area.width, &min_widths);
|
||||
|
||||
let mut x = area.x;
|
||||
|
||||
for ((label, focus), width) in buttons.iter().zip(widths) {
|
||||
let chunk = Rect {
|
||||
x,
|
||||
y: area.y,
|
||||
width,
|
||||
height: area.height,
|
||||
};
|
||||
x += width;
|
||||
|
||||
let style = match focus {
|
||||
Focus::Cancel => {
|
||||
if state.focus == Focus::Cancel {
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Red)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(Color::Red)
|
||||
}
|
||||
}
|
||||
Focus::Continue => {
|
||||
if state.focus == Focus::Continue && state.can_continue() {
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if state.can_continue() {
|
||||
Style::default().fg(Color::Green)
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
}
|
||||
}
|
||||
Focus::ContinueAll => {
|
||||
if state.focus == Focus::ContinueAll && state.can_continue_all() {
|
||||
Style::default()
|
||||
.fg(Color::Black)
|
||||
.bg(Color::Green)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if state.can_continue() && state.pp {
|
||||
Style::default().fg(Color::Green)
|
||||
} else {
|
||||
Style::default().fg(Color::DarkGray)
|
||||
}
|
||||
}
|
||||
_ => Style::default().fg(Color::DarkGray),
|
||||
};
|
||||
|
||||
draw_button(f, chunk, label, style);
|
||||
}
|
||||
}
|
||||
fn compute_widths(area_width: u16, min_widths: &[u16]) -> Vec<u16> {
|
||||
let mut widths = vec![0; min_widths.len()];
|
||||
let mut remaining: Vec<usize> = (0..min_widths.len()).collect();
|
||||
|
||||
let mut remaining_width = area_width;
|
||||
|
||||
while !remaining.is_empty() {
|
||||
let count = remaining.len() as u16;
|
||||
let equal = remaining_width / count;
|
||||
|
||||
let mut clamped = Vec::new();
|
||||
|
||||
for &i in &remaining {
|
||||
if min_widths[i] > equal {
|
||||
widths[i] = min_widths[i];
|
||||
remaining_width -= min_widths[i];
|
||||
clamped.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if clamped.is_empty() {
|
||||
let mut remainder = remaining_width % count;
|
||||
for &i in &remaining {
|
||||
widths[i] = equal
|
||||
+ if remainder > 0 {
|
||||
remainder -= 1;
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
remaining.retain(|i| !clamped.contains(i));
|
||||
}
|
||||
|
||||
widths
|
||||
}
|
||||
19
src/main.rs
19
src/main.rs
|
|
@ -1,4 +1,3 @@
|
|||
use json::{self};
|
||||
use once_cell::sync::Lazy;
|
||||
use pnet::datalink::NetworkInterface;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -20,6 +19,7 @@ mod util;
|
|||
|
||||
use crate::communities::community_manager;
|
||||
use crate::communities::interactables::registry;
|
||||
use crate::eula::terms_checker;
|
||||
use crate::gui::app_state::AppState;
|
||||
use crate::gui::input_handler;
|
||||
use crate::gui::log_panel;
|
||||
|
|
@ -48,10 +48,19 @@ async fn main() {
|
|||
*SHUTDOWN.write().await = false;
|
||||
|
||||
// EULA
|
||||
//if !eula_checker::check_eula() {
|
||||
// println!("Please accept the end user license agreement before launching!");
|
||||
// return;
|
||||
//}
|
||||
let (tos, pp) = terms_checker::ConsentManager::check();
|
||||
if !tos {
|
||||
println!("You need to accept our End User Licence Agreement before launching!");
|
||||
println!("You can find this at 'agreements'!");
|
||||
return;
|
||||
}
|
||||
if !pp {
|
||||
println!(
|
||||
"Please accept our Privacy Policy & Terms of Serivce before using Tensamin Services!"
|
||||
);
|
||||
println!("You can find this at 'agreements'!");
|
||||
return;
|
||||
}
|
||||
|
||||
// LANGUAGE PACK
|
||||
if let Err(e) = language_creator::create_languages() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue