custom wrapper for GTK application and window
This commit is contained in:
parent
ee12ea81d0
commit
6e948f5df0
@ -15,16 +15,20 @@ find_package(folly REQUIRED)
|
|||||||
pkg_check_modules(GTKMM REQUIRED gtkmm-4.0)
|
pkg_check_modules(GTKMM REQUIRED gtkmm-4.0)
|
||||||
pkg_check_modules(GLIBMM REQUIRED glibmm-2.68)
|
pkg_check_modules(GLIBMM REQUIRED glibmm-2.68)
|
||||||
pkg_check_modules(LIBSOUP REQUIRED libsoup-2.4)
|
pkg_check_modules(LIBSOUP REQUIRED libsoup-2.4)
|
||||||
#pkg_check_modules(LIBFOLLY REQUIRED libfolly)
|
pkg_check_modules(LIBADWAITA REQUIRED libadwaita-1)
|
||||||
#pkg_check_modules(LIBADWAITA REQUIRED libadwaita-1)
|
pkg_check_modules(LIBSIGCPP REQUIRED sigc++-3.0)
|
||||||
|
|
||||||
include_directories(${GTKMM_INCLUDE_DIRS}
|
include_directories(${GTKMM_INCLUDE_DIRS}
|
||||||
${GLIBMM_INCLUDE_DIRS}
|
${GLIBMM_INCLUDE_DIRS}
|
||||||
${LIBSOUP_INCLUDE_DIRS})
|
${LIBSOUP_INCLUDE_DIRS}
|
||||||
|
${LIBADWAITA_INCLUDE_DIRS}
|
||||||
|
${LIBSIGCPP_INCLUDE_DIRS})
|
||||||
|
|
||||||
link_directories(${GTKMM_LIBRARY_DIRS}
|
link_directories(${GTKMM_LIBRARY_DIRS}
|
||||||
${GLIBMM_LIBRARY_DIRS}
|
${GLIBMM_LIBRARY_DIRS}
|
||||||
${LIBSOUP_LIBRARY_DIRS})
|
${LIBSOUP_LIBRARY_DIRS}
|
||||||
|
${LIBADWAITA_LIBRARY_DIRS}
|
||||||
|
${LIBSIGCPP_LIBRARY_DIRS})
|
||||||
|
|
||||||
add_executable(autocat_gnome main.cpp
|
add_executable(autocat_gnome main.cpp
|
||||||
gui/MainWindow.cpp
|
gui/MainWindow.cpp
|
||||||
@ -39,10 +43,18 @@ add_executable(autocat_gnome main.cpp
|
|||||||
services/Settings.h
|
services/Settings.h
|
||||||
gui/TitleBar.cpp
|
gui/TitleBar.cpp
|
||||||
gui/TitleBar.h
|
gui/TitleBar.h
|
||||||
coro/Coro.h coro/GLibMainContextExecutor.cpp coro/GLibMainContextExecutor.h)
|
coro/Coro.h
|
||||||
|
coro/GLibMainContextExecutor.cpp
|
||||||
|
coro/GLibMainContextExecutor.h
|
||||||
|
gtkpp/Application.cpp
|
||||||
|
gtkpp/Application.h
|
||||||
|
gtkpp/Window.cpp
|
||||||
|
gtkpp/Window.h)
|
||||||
|
|
||||||
target_link_libraries(autocat_gnome ${GTKMM_LIBRARIES}
|
target_link_libraries(autocat_gnome ${GTKMM_LIBRARIES}
|
||||||
${GLIBMM_LIBRARIES}
|
${GLIBMM_LIBRARIES}
|
||||||
${LIBSOUP_LIBRARIES}
|
${LIBSOUP_LIBRARIES}
|
||||||
|
${LIBADWAITA_LIBRARIES}
|
||||||
|
${LIBSIGCPP_LIBRARIES}
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
Folly::folly)
|
Folly::folly)
|
||||||
|
|||||||
36
gtkpp/Application.cpp
Normal file
36
gtkpp/Application.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Created by selim on 10.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "Window.h"
|
||||||
|
|
||||||
|
namespace gtkpp {
|
||||||
|
|
||||||
|
void activateCallback(GtkApplication* app, void *data) {
|
||||||
|
auto application = reinterpret_cast<Application*>(data);
|
||||||
|
application->_signalActivate.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Application::Application(const std::string &id) {
|
||||||
|
_app = adw_application_new(id.c_str(), G_APPLICATION_FLAGS_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Application::run(int argc, char **argv) {
|
||||||
|
return g_application_run(G_APPLICATION(_app), argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::onActivate(const std::function<void()>& callback) {
|
||||||
|
g_signal_connect(_app, "activate", G_CALLBACK(activateCallback), this);
|
||||||
|
_signalActivate.connect(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdwApplication *Application::gobj() const {
|
||||||
|
return _app;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::addWindow(const std::shared_ptr<Window>& window) {
|
||||||
|
gtk_application_add_window(GTK_APPLICATION(_app), GTK_WINDOW(window->gobj()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
gtkpp/Application.h
Normal file
35
gtkpp/Application.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// Created by selim on 10.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef AUTOCAT_GNOME_APPLICATION_H
|
||||||
|
#define AUTOCAT_GNOME_APPLICATION_H
|
||||||
|
|
||||||
|
#include <adwaita.h>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <sigc++/sigc++.h>
|
||||||
|
|
||||||
|
namespace gtkpp {
|
||||||
|
|
||||||
|
class Window;
|
||||||
|
|
||||||
|
class Application {
|
||||||
|
private:
|
||||||
|
AdwApplication* _app;
|
||||||
|
sigc::signal<void()> _signalActivate;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend void activateCallback(GtkApplication* app, void* data);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Application(const std::string& id);
|
||||||
|
int run(int argc, char* argv[]);
|
||||||
|
void onActivate(const std::function<void()>& callback);
|
||||||
|
AdwApplication* gobj() const;
|
||||||
|
void addWindow(const std::shared_ptr<Window>& window);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //AUTOCAT_GNOME_APPLICATION_H
|
||||||
29
gtkpp/Window.cpp
Normal file
29
gtkpp/Window.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Created by selim on 10.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Window.h"
|
||||||
|
|
||||||
|
gtkpp::Window::Window() {
|
||||||
|
_window = ADW_WINDOW(adw_window_new());
|
||||||
|
}
|
||||||
|
|
||||||
|
gtkpp::Window::Window(std::shared_ptr<gtkpp::Application> app) {
|
||||||
|
_window = ADW_WINDOW(adw_application_window_new(GTK_APPLICATION(app->gobj())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtkpp::Window::show() {
|
||||||
|
gtk_window_present(GTK_WINDOW(_window));
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtkpp::Window::setTitle(const std::string &title) {
|
||||||
|
gtk_window_set_title(GTK_WINDOW(_window), title.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
AdwWindow *gtkpp::Window::gobj() const {
|
||||||
|
return _window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gtkpp::Window::setDefaultSize(int width, int height) {
|
||||||
|
gtk_window_set_default_size(GTK_WINDOW(_window), width, height);
|
||||||
|
}
|
||||||
29
gtkpp/Window.h
Normal file
29
gtkpp/Window.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Created by selim on 10.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef AUTOCAT_GNOME_WINDOW_H
|
||||||
|
#define AUTOCAT_GNOME_WINDOW_H
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include <adwaita.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace gtkpp {
|
||||||
|
|
||||||
|
class Window {
|
||||||
|
protected:
|
||||||
|
AdwWindow* _window;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Window();
|
||||||
|
explicit Window(std::shared_ptr<Application> app);
|
||||||
|
void show();
|
||||||
|
void setTitle(const std::string& title);
|
||||||
|
void setDefaultSize(int width, int height);
|
||||||
|
AdwWindow* gobj() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //AUTOCAT_GNOME_WINDOW_H
|
||||||
@ -7,84 +7,117 @@
|
|||||||
#include "../services/Api.h"
|
#include "../services/Api.h"
|
||||||
#include "../coro/GLibMainContextExecutor.h"
|
#include "../coro/GLibMainContextExecutor.h"
|
||||||
|
|
||||||
#include <gtkmm/application.h>
|
|
||||||
#include <gtkmm/box.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <folly/experimental/coro/Task.h>
|
#include <folly/experimental/coro/Task.h>
|
||||||
#include <folly/executors/IOThreadPoolExecutor.h>
|
#include <folly/executors/IOThreadPoolExecutor.h>
|
||||||
|
|
||||||
LoginWindow::LoginWindow() {
|
LoginWindow::LoginWindow() {
|
||||||
set_title("Login");
|
setDefaultSize(640, 480);
|
||||||
set_default_size(640, 480);
|
|
||||||
|
|
||||||
_emailField.set_placeholder_text("Email");
|
auto header = adw_header_bar_new();
|
||||||
_passwordField.set_placeholder_text("Password");
|
adw_header_bar_set_title_widget(ADW_HEADER_BAR(header), gtk_label_new("Login"));
|
||||||
_passwordField.set_input_purpose(Gtk::InputPurpose::PASSWORD);
|
|
||||||
_passwordField.set_visibility(false);
|
|
||||||
|
|
||||||
_emailField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::validateFields));
|
auto loginField = gtk_entry_new();
|
||||||
_passwordField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::validateFields));
|
gtk_entry_set_placeholder_text(GTK_ENTRY(loginField), "Email");
|
||||||
|
gtk_entry_set_input_purpose(GTK_ENTRY(loginField), GTK_INPUT_PURPOSE_EMAIL);
|
||||||
|
|
||||||
_loginButton.set_margin_top(8);
|
auto passwordField = gtk_entry_new();
|
||||||
_loginButton.set_margin_bottom(8);
|
gtk_entry_set_placeholder_text(GTK_ENTRY(passwordField), "Password");
|
||||||
_loginButton.set_label("Log in");
|
gtk_entry_set_input_purpose(GTK_ENTRY(passwordField), GTK_INPUT_PURPOSE_PASSWORD);
|
||||||
_loginButton.signal_clicked().connect(sigc::mem_fun(*this, &LoginWindow::loginClicked));
|
gtk_entry_set_visibility(GTK_ENTRY(passwordField), false);
|
||||||
_loginButton.set_sensitive(false);
|
|
||||||
|
|
||||||
Gtk::Box box(Gtk::Orientation::VERTICAL, 8);
|
auto loginButton = gtk_button_new();
|
||||||
box.set_margin(48);
|
gtk_button_set_label(GTK_BUTTON(loginButton), "Log in");
|
||||||
box.set_valign(Gtk::Align::CENTER);
|
gtk_widget_set_margin_top(loginButton, 8);
|
||||||
|
gtk_widget_set_margin_bottom(loginButton, 8);
|
||||||
|
gtk_widget_set_sensitive(loginButton, false);
|
||||||
|
|
||||||
box.append(_emailField);
|
auto spinner = gtk_spinner_new();
|
||||||
box.append(_passwordField);
|
|
||||||
box.append(_loginButton);
|
|
||||||
box.append(_spinner);
|
|
||||||
|
|
||||||
set_child(box);
|
auto contentBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||||
|
gtk_box_append(GTK_BOX(contentBox), loginField);
|
||||||
|
gtk_box_append(GTK_BOX(contentBox), passwordField);
|
||||||
|
gtk_box_append(GTK_BOX(contentBox), loginButton);
|
||||||
|
gtk_box_append(GTK_BOX(contentBox), spinner);
|
||||||
|
gtk_widget_set_margin_start(contentBox, 48);
|
||||||
|
gtk_widget_set_margin_end(contentBox, 48);
|
||||||
|
gtk_widget_set_valign(contentBox, GTK_ALIGN_CENTER);
|
||||||
|
gtk_widget_set_vexpand(contentBox, true);
|
||||||
|
|
||||||
|
auto rootBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
gtk_box_append(GTK_BOX(rootBox), header);
|
||||||
|
gtk_box_append(GTK_BOX(rootBox), contentBox);
|
||||||
|
|
||||||
|
adw_window_set_content(ADW_WINDOW(_window), rootBox);
|
||||||
|
|
||||||
|
// _emailField.set_placeholder_text("Email");
|
||||||
|
// _passwordField.set_placeholder_text("Password");
|
||||||
|
// _passwordField.set_input_purpose(Gtk::InputPurpose::PASSWORD);
|
||||||
|
// _passwordField.set_visibility(false);
|
||||||
|
//
|
||||||
|
// _emailField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::validateFields));
|
||||||
|
// _passwordField.signal_changed().connect(sigc::mem_fun(*this, &LoginWindow::validateFields));
|
||||||
|
//
|
||||||
|
// _loginButton.set_margin_top(8);
|
||||||
|
// _loginButton.set_margin_bottom(8);
|
||||||
|
// _loginButton.set_label("Log in");
|
||||||
|
// _loginButton.signal_clicked().connect(sigc::mem_fun(*this, &LoginWindow::loginClicked));
|
||||||
|
// _loginButton.set_sensitive(false);
|
||||||
|
//
|
||||||
|
// Gtk::Box box(Gtk::Orientation::VERTICAL, 8);
|
||||||
|
// box.set_margin(48);
|
||||||
|
// box.set_valign(Gtk::Align::CENTER);
|
||||||
|
//
|
||||||
|
// box.append(_emailField);
|
||||||
|
// box.append(_passwordField);
|
||||||
|
// box.append(_loginButton);
|
||||||
|
// box.append(_spinner);
|
||||||
|
//
|
||||||
|
// set_child(box);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoginWindow::loginClicked() {
|
void LoginWindow::loginClicked() {
|
||||||
auto email = _emailField.get_text();
|
// auto email = _emailField.get_text();
|
||||||
auto password = _passwordField.get_text();
|
// auto password = _passwordField.get_text();
|
||||||
|
//
|
||||||
enableControls(false);
|
// enableControls(false);
|
||||||
_spinner.start();
|
// _spinner.start();
|
||||||
|
//
|
||||||
try {
|
// try {
|
||||||
User user = co_await Api::login(email, password).scheduleOn(GLibMainContextExecutor::instance());
|
// User user = co_await Api::login(email, password).scheduleOn(GLibMainContextExecutor::instance());
|
||||||
auto app = this->get_application();
|
// auto app = this->get_application();
|
||||||
auto mainWindow = new MainWindow();
|
// auto mainWindow = new MainWindow();
|
||||||
mainWindow->show();
|
// mainWindow->show();
|
||||||
hide();
|
// hide();
|
||||||
app->add_window(*mainWindow);
|
// app->add_window(*mainWindow);
|
||||||
app->remove_window(*this);
|
// app->remove_window(*this);
|
||||||
} catch (std::exception& ex) {
|
// } catch (std::exception& ex) {
|
||||||
enableControls(true);
|
// enableControls(true);
|
||||||
_spinner.stop();
|
// _spinner.stop();
|
||||||
showError(ex.what());
|
// showError(ex.what());
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoginWindow::validateFields() {
|
void LoginWindow::validateFields() {
|
||||||
bool buttonEnabled = _emailField.get_text_length() > 0 && _passwordField.get_text_length() > 0;
|
// bool buttonEnabled = _emailField.get_text_length() > 0 && _passwordField.get_text_length() > 0;
|
||||||
_loginButton.set_sensitive(buttonEnabled);
|
// _loginButton.set_sensitive(buttonEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoginWindow::showError(const std::string& message) {
|
void LoginWindow::showError(const std::string& message) {
|
||||||
_dialog = std::make_unique<Gtk::MessageDialog>("Error",
|
// _dialog = std::make_unique<Gtk::MessageDialog>("Error",
|
||||||
false,
|
// false,
|
||||||
Gtk::MessageType::ERROR,
|
// Gtk::MessageType::ERROR,
|
||||||
Gtk::ButtonsType::OK,
|
// Gtk::ButtonsType::OK,
|
||||||
true);
|
// true);
|
||||||
_dialog->set_secondary_text(message);
|
// _dialog->set_secondary_text(message);
|
||||||
_dialog->set_transient_for(*this);
|
// _dialog->set_transient_for(*this);
|
||||||
_dialog->set_hide_on_close(true);
|
// _dialog->set_hide_on_close(true);
|
||||||
_dialog->signal_response().connect(sigc::hide(sigc::mem_fun(*_dialog, &Gtk::Widget::hide)));
|
// _dialog->signal_response().connect(sigc::hide(sigc::mem_fun(*_dialog, &Gtk::Widget::hide)));
|
||||||
_dialog->show();
|
// _dialog->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoginWindow::enableControls(bool enable) {
|
void LoginWindow::enableControls(bool enable) {
|
||||||
_loginButton.set_sensitive(enable);
|
// _loginButton.set_sensitive(enable);
|
||||||
_emailField.set_sensitive(enable);
|
// _emailField.set_sensitive(enable);
|
||||||
_passwordField.set_sensitive(enable);
|
// _passwordField.set_sensitive(enable);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,24 +5,19 @@
|
|||||||
#ifndef AUTOCAT_GNOME_LOGINWINDOW_H
|
#ifndef AUTOCAT_GNOME_LOGINWINDOW_H
|
||||||
#define AUTOCAT_GNOME_LOGINWINDOW_H
|
#define AUTOCAT_GNOME_LOGINWINDOW_H
|
||||||
|
|
||||||
#include <gtkmm/window.h>
|
|
||||||
#include <gtkmm/entry.h>
|
|
||||||
#include <gtkmm/button.h>
|
|
||||||
#include <gtkmm/spinner.h>
|
|
||||||
#include <gtkmm/messagedialog.h>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "../gtkpp/Window.h"
|
||||||
|
|
||||||
class LoginWindow: public Gtk::Window {
|
class LoginWindow: public gtkpp::Window {
|
||||||
private:
|
private:
|
||||||
Gtk::Entry _emailField;
|
// Gtk::Entry _emailField;
|
||||||
Gtk::Entry _passwordField;
|
// Gtk::Entry _passwordField;
|
||||||
Gtk::Button _loginButton;
|
// Gtk::Button _loginButton;
|
||||||
Gtk::Spinner _spinner;
|
// Gtk::Spinner _spinner;
|
||||||
std::unique_ptr<Gtk::MessageDialog> _dialog;
|
// std::unique_ptr<Gtk::MessageDialog> _dialog;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LoginWindow();
|
LoginWindow();
|
||||||
~LoginWindow() override = default;
|
|
||||||
|
|
||||||
void loginClicked();
|
void loginClicked();
|
||||||
void validateFields();
|
void validateFields();
|
||||||
|
|||||||
48
main.cpp
48
main.cpp
@ -1,32 +1,54 @@
|
|||||||
#include "gui/MainWindow.h"
|
#include "gui/MainWindow.h"
|
||||||
#include "gui/LoginWindow.h"
|
#include "gui/LoginWindow.h"
|
||||||
#include "services/Settings.h"
|
#include "services/Settings.h"
|
||||||
|
#include "gtkpp/Application.h"
|
||||||
|
#include "gtkpp/Window.h"
|
||||||
|
|
||||||
#include <gtkmm/application.h>
|
#include <gtkmm/application.h>
|
||||||
#include <glibmm.h>
|
#include <glibmm.h>
|
||||||
#include <gtkmm.h>
|
#include <gtkmm.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <folly/init/Init.h>
|
#include <folly/init/Init.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
std::unique_ptr<Gtk::Window> createStartWindow() {
|
//std::unique_ptr<Gtk::Window> createStartWindow() {
|
||||||
auto settings = Settings::instance();
|
// auto settings = Settings::instance();
|
||||||
if(settings.user().token.empty()) {
|
// if(settings.user().token.empty()) {
|
||||||
return std::make_unique<LoginWindow>();
|
// return std::make_unique<LoginWindow>();
|
||||||
} else {
|
// } else {
|
||||||
return std::make_unique<MainWindow>();
|
// return std::make_unique<MainWindow>();
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
folly::init(&argc, &argv);
|
folly::init(&argc, &argv);
|
||||||
|
|
||||||
auto app = Gtk::Application::create("pro.aliencat.aliencat");
|
// auto app = Gtk::Application::create("pro.aliencat.aliencat");
|
||||||
auto window = createStartWindow();
|
// auto window = createStartWindow();
|
||||||
|
//
|
||||||
|
// app->signal_activate().connect([&](){
|
||||||
|
// app->add_window(*window);
|
||||||
|
// window->show();
|
||||||
|
// });
|
||||||
|
// return app->run(argc, argv);
|
||||||
|
|
||||||
app->signal_activate().connect([&](){
|
auto app = std::make_shared<gtkpp::Application>("pro.aliencat.autocat");
|
||||||
app->add_window(*window);
|
|
||||||
window->show();
|
app->onActivate([&](){
|
||||||
|
auto settings = Settings::instance();
|
||||||
|
|
||||||
|
if(settings.user().token.empty()) {
|
||||||
|
auto window = std::make_shared<LoginWindow>();
|
||||||
|
app->addWindow(window);
|
||||||
|
window->setTitle("Login");
|
||||||
|
window->show();
|
||||||
|
} else {
|
||||||
|
auto window = std::make_unique<gtkpp::Window>(app);
|
||||||
|
window->setTitle("Some title");
|
||||||
|
window->show();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return app->run(argc, argv);
|
return app->run(argc, argv);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user