DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

ac88eb9c308eecc286efcc302960acb35021148b — Jonni Liljamo 1 year, 9 months ago b6ae106
chore(client): cleanup old login/register screens
4 files changed, 0 insertions(+), 488 deletions(-)

D client/src/plugins/menu/accountlogin/mod.rs
D client/src/plugins/menu/accountlogin/ui.rs
D client/src/plugins/menu/accountregister/mod.rs
D client/src/plugins/menu/accountregister/ui.rs
D client/src/plugins/menu/accountlogin/mod.rs => client/src/plugins/menu/accountlogin/mod.rs +0 -156
@@ 1,156 0,0 @@
/*
 * This file is part of laurelin/client
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use bevy_console::PrintConsoleLine;
use iyes_loopless::prelude::*;

use futures_lite::future;

use crate::{
    api::{
        self,
        user::{ResponseToken, ResponseUserInfoP},
    },
    cfg::{CfgDev, CfgUser},
    plugins::config::{SaveEvent, SaveEventValue},
};

use super::MenuState;

pub mod ui;

pub struct AccountLoginPlugin;

impl Plugin for AccountLoginPlugin {
    fn build(&self, app: &mut App) {
        app.add_loopless_state(LoginState::None)
            // UI system
            .insert_resource(ui::InputsUserLogin::new())
            .add_system_set(
                ConditionSet::new()
                    .run_in_state(LoginState::Input)
                    .with_system(ui::account_login_ui)
                    .into(),
            )
            // Login system, as in calling the API
            .add_enter_system(LoginState::LoggingIn, start_login_call)
            .add_system_set(
                ConditionSet::new()
                    .run_in_state(LoginState::LoggingIn)
                    .with_system(handle_login_call)
                    .into(),
            );
    }
}

/// Login State
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum LoginState {
    None,
    Input,
    LoggingIn,
}

struct LoginCallResponse {
    token: ResponseToken,
    user_infop: Option<ResponseUserInfoP>,
}

#[derive(Component)]
struct LoginCall(Task<LoginCallResponse>);

fn start_login_call(
    mut commands: Commands,
    cfg_dev: Res<CfgDev>,
    inputs: Res<ui::InputsUserLogin>,
) {
    let api_address = cfg_dev.api_server.clone();
    let i = inputs.clone();

    let thread_pool = AsyncComputeTaskPool::get();
    let task = thread_pool.spawn(async move {
        let token_response = api::user::token(api_address.clone(), i.email, i.password);

        let mut user_infop_response = None;
        match &token_response {
            ResponseToken::Valid(res) => {
                user_infop_response = Some(api::user::userinfop(
                    api_address,
                    res.token.clone(),
                    res.id.clone(),
                ));
            }
            ResponseToken::Error(_error) => {}
        }

        LoginCallResponse {
            token: token_response,
            user_infop: user_infop_response,
        }
    });
    commands.spawn(LoginCall(task));
}

fn handle_login_call(
    mut commands: Commands,
    mut login_call_tasks: Query<(Entity, &mut LoginCall)>,
    mut inputs: ResMut<ui::InputsUserLogin>,
    mut cfg_user: ResMut<CfgUser>,
    mut save_event_writer: EventWriter<SaveEvent>,
    mut console: EventWriter<PrintConsoleLine>,
) {
    let (entity, mut task) = login_call_tasks.single_mut();
    if let Some(login_call_response) = future::block_on(future::poll_once(&mut task.0)) {
        match login_call_response.token {
            ResponseToken::Valid(res) => {
                console.send(PrintConsoleLine::new(
                    format!("Logged in with: {}", inputs.email).into(),
                ));

                match login_call_response.user_infop.unwrap() {
                    ResponseUserInfoP::Valid(infop) => {
                        *cfg_user = CfgUser {
                            logged_in: true,
                            user_token: res.token,
                            id: res.id,
                            username: infop.username,
                            email: infop.email,
                        };

                        save_event_writer.send(SaveEvent {
                            value: SaveEventValue::User(cfg_user.into_inner().clone()),
                        });

                        commands.insert_resource(NextState(LoginState::None));
                        commands.insert_resource(NextState(MenuState::AccountLoggedIn));
                    }
                    ResponseUserInfoP::Error(error) => {
                        console.send(PrintConsoleLine::new(format!(
                            "Something went wrong with getting the user information after logging in, got error: '{}'", error
                        ).into()));

                        commands.insert_resource(NextState(LoginState::None));
                        commands.insert_resource(NextState(MenuState::AccountLoggedIn));
                    }
                }
            }
            ResponseToken::Error(error) => {
                inputs.error = error.error.description;
                commands.insert_resource(NextState(LoginState::Input));
            }
        }

        // Remove the task, since it's done now
        commands.entity(entity).remove::<LoginCall>();
        commands.entity(entity).despawn_recursive();
    }
}

D client/src/plugins/menu/accountlogin/ui.rs => client/src/plugins/menu/accountlogin/ui.rs +0 -75
@@ 1,75 0,0 @@
/*
 * This file is part of laurelin/client
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use bevy::prelude::*;
use bevy_inspector_egui::bevy_egui::{egui, EguiContext};
use iyes_loopless::prelude::*;

use crate::util::eguipwd;

use crate::plugins::menu::MenuState;

use super::LoginState;

/// Login inputs
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone)]
pub struct InputsUserLogin {
    pub email: String,
    pub password: String,
    pub error: String,
}

impl InputsUserLogin {
    pub fn new() -> Self {
        Self {
            email: "".to_string(),
            password: "".to_string(),
            error: "".to_string(),
        }
    }
}

pub fn account_login_ui(
    mut commands: Commands,
    mut egui_context: ResMut<EguiContext>,
    mut inputs: ResMut<InputsUserLogin>,
) {
    egui::Window::new("Login")
        .collapsible(false)
        .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
        .show(egui_context.ctx_mut(), |ui| {
            ui.horizontal(|ui| {
                ui.label("Email: ");
                ui.text_edit_singleline(&mut inputs.email);
            });

            ui.horizontal(|ui| {
                ui.label("Password: ");
                ui.add(eguipwd::password(&mut inputs.password));
            });

            // Show an error if there is one
            if !inputs.error.is_empty() {
                ui.horizontal(|ui| {
                    ui.label(egui::RichText::new(inputs.error.clone()).color(egui::Color32::RED));
                });
            }

            ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
                if ui.button("Cancel").clicked() {
                    commands.insert_resource(NextState(LoginState::None));
                    commands.insert_resource(NextState(MenuState::AccountLoggedOut));
                }
                if ui.button("Login").clicked() {
                    commands.insert_resource(NextState(LoginState::LoggingIn));
                    // Reset error field
                    inputs.error = "".to_string();
                }
            })
        });
}

D client/src/plugins/menu/accountregister/mod.rs => client/src/plugins/menu/accountregister/mod.rs +0 -155
@@ 1,155 0,0 @@
/*
 * This file is part of laurelin/client
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use bevy_console::PrintConsoleLine;
use iyes_loopless::prelude::*;

use futures_lite::future;

use crate::{
    api::{
        self,
        user::{ResponseRegister, ResponseToken},
    },
    cfg::{CfgDev, CfgUser},
    plugins::config::{SaveEvent, SaveEventValue},
};

use super::MenuState;

pub mod ui;

pub struct AccountRegisterPlugin;

impl Plugin for AccountRegisterPlugin {
    fn build(&self, app: &mut App) {
        app.add_loopless_state(RegisterState::None)
            // UI system
            .insert_resource(ui::InputsUserRegister::new())
            .add_system_set(
                ConditionSet::new()
                    .run_in_state(RegisterState::Input)
                    .with_system(ui::account_register_ui)
                    .into(),
            )
            // Register system, as in calling the API
            .add_enter_system(RegisterState::Registering, start_register_call)
            .add_system_set(
                ConditionSet::new()
                    .run_in_state(RegisterState::Registering)
                    .with_system(handle_register_call)
                    .into(),
            );
    }
}

/// Register State
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub enum RegisterState {
    None,
    Input,
    Registering,
}

struct RegisterCallResponse {
    register: ResponseRegister,
    token: ResponseToken,
}

#[derive(Component)]
struct RegisterCall(Task<RegisterCallResponse>);

fn start_register_call(
    mut commands: Commands,
    cfg_dev: Res<CfgDev>,
    inputs: Res<ui::InputsUserRegister>,
) {
    let api_address = cfg_dev.api_server.clone();
    let i = inputs.clone();

    let thread_pool = AsyncComputeTaskPool::get();
    let task = thread_pool.spawn(async move {
        let register_response = api::user::register(
            api_address.clone(),
            i.username.clone(),
            i.email.clone(),
            i.password.clone(),
        );

        // TODO: This is bad, if the above fails, this will too. Or maybe that doesn't matter?
        let token_response =
            api::user::token(api_address.clone(), i.email.clone(), i.password.clone());

        RegisterCallResponse {
            register: register_response,
            token: token_response,
        }
    });
    commands.spawn(RegisterCall(task));
}

fn handle_register_call(
    mut commands: Commands,
    mut register_call_tasks: Query<(Entity, &mut RegisterCall)>,
    mut inputs: ResMut<ui::InputsUserRegister>,
    mut cfg_user: ResMut<CfgUser>,
    mut save_event_writer: EventWriter<SaveEvent>,
    mut console: EventWriter<PrintConsoleLine>,
) {
    let (entity, mut task) = register_call_tasks.single_mut();
    if let Some(register_call_response) = future::block_on(future::poll_once(&mut task.0)) {
        match register_call_response.register {
            ResponseRegister::Valid(register_res) => {
                console.send(PrintConsoleLine::new(
                    format!("Registered user: {}", register_res.username,).into(),
                ));

                match register_call_response.token {
                    ResponseToken::Valid(token_res) => {
                        *cfg_user = CfgUser {
                            logged_in: true,
                            user_token: token_res.token,
                            id: register_res.id,
                            username: register_res.username,
                            email: inputs.email.clone(),
                        };

                        save_event_writer.send(SaveEvent {
                            value: SaveEventValue::User(cfg_user.into_inner().clone()),
                        });

                        commands.insert_resource(NextState(RegisterState::None));
                        commands.insert_resource(NextState(MenuState::AccountLoggedIn));
                    }
                    ResponseToken::Error(error) => {
                        // TODO: Handle? Is it possible to even get here without the server shitting itself between the register and token calls?
                        // And if the server does indeed shit itself between those calls, the user can just login normally, so 🤷‍♀️
                        console.send(PrintConsoleLine::new(format!(
                            "Something went wrong with getting the user token after registering, got error: '{}'", error
                        ).into()));

                        commands.insert_resource(NextState(RegisterState::None));
                        commands.insert_resource(NextState(MenuState::AccountLoggedOut));
                    }
                }
            }
            ResponseRegister::Error(error) => {
                inputs.error = error.error.description;
                commands.insert_resource(NextState(RegisterState::Input));
            }
        }

        // Remove the task, since it's done now
        commands.entity(entity).remove::<RegisterCall>();
        commands.entity(entity).despawn_recursive();
    }
}

D client/src/plugins/menu/accountregister/ui.rs => client/src/plugins/menu/accountregister/ui.rs +0 -102
@@ 1,102 0,0 @@
/*
 * This file is part of laurelin/client
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use bevy::prelude::*;
use bevy_inspector_egui::bevy_egui::{egui, EguiContext};
use iyes_loopless::prelude::*;

use crate::util::eguipwd;

use crate::plugins::menu::MenuState;

use super::RegisterState;

/// Register inputs
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone)]
pub struct InputsUserRegister {
    pub username: String,
    pub email: String,
    pub password: String,
    pub password_confirm: String,
    pub passwords_match: bool,
    pub error: String,
}

impl InputsUserRegister {
    pub fn new() -> Self {
        Self {
            username: "".to_string(),
            email: "".to_string(),
            password: "".to_string(),
            password_confirm: "".to_string(),
            passwords_match: true,
            error: "".to_string(),
        }
    }
}

pub fn account_register_ui(
    mut commands: Commands,
    mut egui_context: ResMut<EguiContext>,
    mut inputs: ResMut<InputsUserRegister>,
) {
    egui::Window::new("Register")
        .collapsible(false)
        .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
        .show(egui_context.ctx_mut(), |ui| {
            ui.horizontal(|ui| {
                ui.label("Username: ");
                ui.text_edit_singleline(&mut inputs.username);
            });

            ui.horizontal(|ui| {
                ui.label("Email: ");
                ui.text_edit_singleline(&mut inputs.email);
            });

            ui.horizontal(|ui| {
                ui.label("Password: ");
                ui.add(eguipwd::password(&mut inputs.password));
            });

            ui.horizontal(|ui| {
                ui.label("Confirm password: ");
                ui.add(eguipwd::password(&mut inputs.password_confirm));
            });

            inputs.passwords_match = inputs.password == inputs.password_confirm;

            // Show an error if there is one
            if !inputs.passwords_match {
                ui.horizontal(|ui| {
                    ui.label(
                        egui::RichText::new("passwords don't match").color(egui::Color32::RED),
                    );
                });
            } else if !inputs.error.is_empty() {
                ui.horizontal(|ui| {
                    ui.label(egui::RichText::new(inputs.error.clone()).color(egui::Color32::RED));
                });
            }

            ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| {
                if ui.button("Cancel").clicked() {
                    commands.insert_resource(NextState(RegisterState::None));
                    commands.insert_resource(NextState(MenuState::AccountLoggedOut));
                }

                ui.add_enabled_ui(inputs.passwords_match, |ui| {
                    if ui.button("Register").clicked() {
                        commands.insert_resource(NextState(RegisterState::Registering));
                        // Reset error field
                        inputs.error = "".to_string();
                    }
                });
            })
        });
}