mirror of
https://github.com/aclist/dztui.git
synced 2026-08-26 09:47:36 +02:00
chore: clear typehinting errors
This commit is contained in:
parent
806e05977d
commit
5757f79755
@ -51,7 +51,7 @@ def query_defunct() -> None:
|
||||
|
||||
def concat_mods(mods: list[str]) -> str:
|
||||
for mod in mods:
|
||||
mods[mod] = "@" + mod
|
||||
mods[mod] = f"@{mod}"
|
||||
return ";".join(mods)
|
||||
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from typing import Any, TYPE_CHECKING
|
||||
from typing import Any, TYPE_CHECKING, Union
|
||||
|
||||
import dzgui.util._json as JSON # noqa
|
||||
|
||||
@ -110,14 +110,14 @@ class Controller(GObject.GObject):
|
||||
return bool(self.query_config(Preferences.INSTALL))
|
||||
|
||||
def suppress_signal(
|
||||
self, owner: Gtk.Widget, widget: Gtk.Widget, func_name: str, state: bool
|
||||
self, owner: Any, child: Any, func_name: str, state: bool
|
||||
) -> None:
|
||||
|
||||
func = getattr(owner, func_name)
|
||||
if state:
|
||||
widget.handler_block_by_func(func)
|
||||
child.handler_block_by_func(func)
|
||||
else:
|
||||
widget.handler_unblock_by_func(func)
|
||||
child.handler_unblock_by_func(func)
|
||||
|
||||
def get_active_context(self) -> "ServerTab":
|
||||
return self.get_active_treeview().get_enum()
|
||||
@ -139,7 +139,7 @@ class Controller(GObject.GObject):
|
||||
def remove_statusbar(self, context: "NotebookPage | ServerTab") -> None:
|
||||
self.mediator.statusbar.pop(context)
|
||||
|
||||
def set_statusbar_dist(self, haversine: "Haversine", enum: "ServerTab") -> None:
|
||||
def set_statusbar_dist(self, haversine: Union["Haversine", None], enum: "ServerTab") -> None:
|
||||
"""
|
||||
NOTE: prevents race condition when server tab changed,
|
||||
but allows caching the distance in the background
|
||||
@ -153,6 +153,9 @@ class Controller(GObject.GObject):
|
||||
self.emitter.emit("distcalc_ended", None, context)
|
||||
return
|
||||
|
||||
if haversine is None:
|
||||
self.emitter.emit("distcalc_ended", None, context)
|
||||
return
|
||||
dist = haversine.get_rounded(self.prefs.use_miles)
|
||||
self.emitter.emit("distcalc_ended", dist, context)
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ class ConfigManager:
|
||||
def __init__(self, prefs: "UserPrefs", controller: "Controller") -> None:
|
||||
self.prefs = prefs
|
||||
self.config = prefs.paths.config
|
||||
self.controller = controller
|
||||
self.emitter = controller.get_emitter()
|
||||
self.thread_man = ThreadingManager(controller)
|
||||
|
||||
@ -95,7 +96,7 @@ class ConfigManager:
|
||||
return
|
||||
|
||||
def get_favorites(self) -> list[str]:
|
||||
return self.lookup(Preferences.IP_LIST)
|
||||
return list(self.lookup(Preferences.IP_LIST))
|
||||
|
||||
def get_favorite(self) -> tuple[str, str] | tuple[None, None]:
|
||||
fav = str(self.lookup(Preferences.FAV_LBL))
|
||||
@ -112,7 +113,7 @@ class ConfigManager:
|
||||
return False
|
||||
|
||||
def enum_to_key(self, enum: Preferences) -> str:
|
||||
return enum.dict["key"]
|
||||
return str(enum.dict["key"])
|
||||
|
||||
def get_client_index(self, client: str) -> int:
|
||||
if client == STEAM_CMD:
|
||||
@ -121,6 +122,7 @@ class ConfigManager:
|
||||
return 1
|
||||
if client == FLATPAK_SANDBOX:
|
||||
return 2
|
||||
return 0
|
||||
|
||||
def toggle_config(self, key: Preferences) -> None:
|
||||
try:
|
||||
@ -149,7 +151,7 @@ class ConfigManager:
|
||||
except Exception as e:
|
||||
logger.critical(e)
|
||||
trace = traceback.format_exc()
|
||||
dialog = ExceptionDialog(self, trace)
|
||||
dialog = ExceptionDialog(self.controller, trace)
|
||||
dialog.run()
|
||||
raise e
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@ from dzgui.util.clip import copy_clipboard
|
||||
from dzgui.util.open_links import open_workshop_page
|
||||
|
||||
from dzgui.views.dialogs.note import NoteDialog
|
||||
from dzgui.views.trees.tree_mods import ModTreeView
|
||||
from dzgui.views.trees.tree_log import LogTreeView
|
||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||
|
||||
|
||||
@ -19,13 +21,12 @@ logger = logging.getLogger(APP_NAME)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from dzgui.controllers.mc import Controller
|
||||
from dzgui.views.trees.tree_servers import ServerTreeView
|
||||
|
||||
|
||||
class ContextMenuManager:
|
||||
def __init__(
|
||||
self,
|
||||
treeview: "ServerTreeView",
|
||||
treeview: LogTreeView | ModTreeView | ServerTreeView,
|
||||
controller: "Controller",
|
||||
) -> None:
|
||||
self.controller = controller
|
||||
@ -49,28 +50,40 @@ class ContextMenuManager:
|
||||
self.open_mod_page()
|
||||
case ContextMenu.SET_FAV:
|
||||
name = self.treeview.get_value_at_index(0)
|
||||
record = self.treeview.get_record_string()
|
||||
record_str = self.treeview.get_record_string()
|
||||
simple = self.treeview.get_simplified_ip()
|
||||
self.controller.set_fav(name, record, simple)
|
||||
self.controller.set_fav(name, record_str, simple)
|
||||
|
||||
# THREADED
|
||||
case ContextMenu.ADD_SERVER:
|
||||
record = self.treeview.get_record()
|
||||
if record is None:
|
||||
return
|
||||
self.controller.add_by_record(record)
|
||||
case ContextMenu.CONNECT:
|
||||
record = self.treeview.get_record()
|
||||
if record is None:
|
||||
return
|
||||
self.controller.connect_by_record(record)
|
||||
case ContextMenu.REFRESH_PLAYERS:
|
||||
record = self.treeview.get_record()
|
||||
if record is None:
|
||||
return
|
||||
self.controller.refresh_players(record)
|
||||
case ContextMenu.REMOVE_HISTORY:
|
||||
record = self.treeview.get_record()
|
||||
if record is None:
|
||||
return
|
||||
self.controller.remove_from_history(record)
|
||||
case ContextMenu.REMOVE_SERVER:
|
||||
record = self.treeview.get_record()
|
||||
if record is None:
|
||||
return
|
||||
self.controller.remove_by_record(record)
|
||||
case ContextMenu.SHOW_DETAILS:
|
||||
record = self.treeview.get_record()
|
||||
if record is None:
|
||||
return
|
||||
self.controller.get_details(record)
|
||||
case ContextMenu.SHOW_MODS:
|
||||
record = self.treeview.get_record()
|
||||
@ -89,11 +102,15 @@ class ContextMenuManager:
|
||||
copy_clipboard(name)
|
||||
|
||||
def open_mod_page(self) -> None:
|
||||
if not isinstance(self.treeview, ModTreeView):
|
||||
return
|
||||
mod = self.treeview.get_selected_mod()
|
||||
cmd = self.controller.query_config(Preferences.CLIENT)
|
||||
open_workshop_page(mod, cmd)
|
||||
|
||||
def copy_log(self) -> None:
|
||||
if not isinstance(self.treeview, LogTreeView):
|
||||
return
|
||||
log = self.treeview.concatenate_rows()
|
||||
if log is None:
|
||||
return
|
||||
|
||||
@ -42,7 +42,7 @@ class FilterManager:
|
||||
def get_active_map_name(self) -> str:
|
||||
return self.active_map[1]
|
||||
|
||||
def get_active_map(self) -> int:
|
||||
def get_active_map(self) -> tuple[int, str]:
|
||||
return self.active_map
|
||||
|
||||
def set_active_map(self, ind: int, name: str) -> None:
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import inspect
|
||||
import logging
|
||||
import threading
|
||||
from typing import Any, TYPE_CHECKING
|
||||
from typing import Any, Literal, TYPE_CHECKING
|
||||
|
||||
from functools import wraps
|
||||
from typing import Callable
|
||||
@ -23,7 +23,7 @@ logger = logging.getLogger(APP_NAME)
|
||||
def call_on_thread(dialog_str: str) -> Callable:
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Callable:
|
||||
def wrapper(*args: Any, **kwargs: Any) -> None:
|
||||
self = args[0]
|
||||
stored = StoredFunc(func, *args, **kwargs)
|
||||
if not hasattr(self, "thread_man"):
|
||||
@ -40,7 +40,7 @@ def call_on_thread(dialog_str: str) -> Callable:
|
||||
|
||||
|
||||
class StoredFunc:
|
||||
def __init__(self, func: Callable, *args, **kwargs) -> None:
|
||||
def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None:
|
||||
sig = inspect.signature(func)
|
||||
self.func = func
|
||||
self.bindings = sig.bind(*args, **kwargs)
|
||||
@ -53,7 +53,7 @@ class ThreadingManager:
|
||||
def __init__(self, controller: "Controller") -> None:
|
||||
self.controller = controller
|
||||
self.jobs = 1
|
||||
self.cleanup_func = None
|
||||
self.cleanup_func: StoredFunc | None
|
||||
self.destroy_first = False
|
||||
|
||||
def call_on_thread(self, dialog_str: str, func: StoredFunc) -> None:
|
||||
@ -75,7 +75,7 @@ class ThreadingManager:
|
||||
def increment_dialog_with_str(self, text: str) -> None:
|
||||
GLib.idle_add(lambda: self.wait_dialog.increment(text))
|
||||
|
||||
def set_cleanup_func(self, func: StoredFunc, destroy_first: bool = False) -> None:
|
||||
def set_cleanup_func(self, func: StoredFunc | None, destroy_first: bool = False) -> None:
|
||||
if type(func) not in (StoredFunc, type(None)):
|
||||
msg = f"Callback function '{func}' is not of type StoredFunc or None"
|
||||
logger.critical(msg)
|
||||
@ -83,10 +83,10 @@ class ThreadingManager:
|
||||
self.destroy_first = destroy_first
|
||||
self.cleanup_func = func
|
||||
|
||||
def get_cleanup_func(self) -> StoredFunc:
|
||||
def get_cleanup_func(self) -> StoredFunc | None:
|
||||
return self.cleanup_func
|
||||
|
||||
def _destroy_on_idle(self) -> GLib.SOURCE_REMOVE:
|
||||
def _destroy_on_idle(self) -> Literal[False]:
|
||||
if self.destroy_first:
|
||||
self.wait_dialog.destroy()
|
||||
|
||||
@ -97,7 +97,7 @@ class ThreadingManager:
|
||||
if not self.destroy_first:
|
||||
self.wait_dialog.destroy()
|
||||
|
||||
return GLib.SOURCE_REMOVE
|
||||
return False
|
||||
|
||||
def get_wait_dialog(self) -> WaitDialog:
|
||||
return self.wait_dialog
|
||||
|
||||
@ -86,7 +86,7 @@ class ServerModelManager:
|
||||
key = config_man.lookup(Preferences.STEAM)
|
||||
job = Servers.query_api
|
||||
params = Servers.params
|
||||
servers = []
|
||||
servers: list[dict[Any, Any]] = []
|
||||
with ThreadPoolExecutor() as executor:
|
||||
futures = [executor.submit(job, key, APPID_DAYZ, param) for param in params]
|
||||
for future in as_completed(futures):
|
||||
|
||||
@ -33,7 +33,7 @@ import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version("GLibUnix", "2.0")
|
||||
from gi.repository import Gtk, GLib, GLibUnix, Gdk, GObject, Pango # noqa E402
|
||||
from gi.repository import Gtk, GLib, GLibUnix, Gdk # type: ignore # noqa E402
|
||||
|
||||
logger = logging.getLogger(APP_NAME)
|
||||
# https://bugzilla.gnome.org/show_bug.cgi?id=708676
|
||||
@ -201,7 +201,8 @@ class Notebook(ScrollableMixin, Gtk.Notebook): # type: ignore
|
||||
if widget is None:
|
||||
return
|
||||
|
||||
widget.grab_content_area()
|
||||
if hasattr(widget, "grab_content_area"):
|
||||
widget.grab_content_area()
|
||||
|
||||
def get_page(self) -> Gtk.Widget | None:
|
||||
ind = self.get_current_page()
|
||||
|
||||
@ -133,6 +133,9 @@ class FavPanel(Gtk.Frame):
|
||||
emitter.connect("fav_server_changed", self._on_fav_server_changed)
|
||||
|
||||
# TODO: do not return a None, None tuple
|
||||
self.server_name: str
|
||||
self.server_ip: str
|
||||
|
||||
self.server_name, self.server_ip = (
|
||||
self.controller.get_config_man().get_favorite()
|
||||
)
|
||||
|
||||
@ -45,7 +45,7 @@ class ButtonGrid(Gtk.Grid):
|
||||
checkbox = Gtk.CheckButton(label=check)
|
||||
label = checkbox.get_child()
|
||||
if label is not None:
|
||||
label.set_ellipsize(Pango.EllipsizeMode.END)
|
||||
label.set_ellipsize(Pango.EllipsizeMode.END) # type: ignore
|
||||
|
||||
if defaults[check]:
|
||||
checkbox.set_active(True)
|
||||
@ -186,7 +186,7 @@ class FilterPanel(Gtk.Box):
|
||||
self.maps_combo = Gtk.ComboBox.new_with_model_and_entry(self.map_store)
|
||||
self.maps_combo.set_entry_text_column(0)
|
||||
|
||||
self.maps_entry = self.maps_combo.get_child()
|
||||
self.maps_entry: Gtk.Entry = self.maps_combo.get_child() # type: ignore
|
||||
self.maps_entry.set_completion(completion)
|
||||
self.maps_entry.set_placeholder_text("Filter by map")
|
||||
self.maps_entry.connect("changed", self._on_map_completion, True)
|
||||
@ -215,9 +215,10 @@ class FilterPanel(Gtk.Box):
|
||||
|
||||
i: int
|
||||
row: Gtk.TreeModelRow
|
||||
for i, row in enumerate(model):
|
||||
if text == row[0]:
|
||||
for i, row in enumerate(model): # type: ignore
|
||||
if text == row[0]: # type: ignore
|
||||
self.maps_combo.set_active(i)
|
||||
return
|
||||
|
||||
def _on_maps_loaded(self, emitter: "Emitter", store: Gtk.ListStore) -> None:
|
||||
self.maps_combo.set_model(store)
|
||||
@ -262,7 +263,7 @@ class FilterPanel(Gtk.Box):
|
||||
) -> None:
|
||||
self.maps_combo.set_active_iter(it)
|
||||
|
||||
def _on_map_completion(self, entry: Gtk.Entry, editable: Literal[True]):
|
||||
def _on_map_completion(self, entry: Gtk.Entry, editable: Literal[True]) -> None:
|
||||
text = entry.get_text()
|
||||
completion = entry.get_completion()
|
||||
store = self.controller.get_map_store()
|
||||
|
||||
@ -98,7 +98,7 @@ class Statusbar(Gtk.Grid):
|
||||
def _on_server_row_changed(self, statusbar: Self) -> None:
|
||||
self.spinner.start()
|
||||
|
||||
def _on_distcalc_started(self, e) -> None:
|
||||
def _on_distcalc_started(self, emitter: "Emitter") -> None:
|
||||
self.spinner.start()
|
||||
|
||||
def _on_distcalc_ended(
|
||||
|
||||
@ -44,7 +44,7 @@ class GenericDialog(Gtk.MessageDialog):
|
||||
|
||||
self.set_default_response(Gtk.ResponseType.OK)
|
||||
|
||||
self.get_action_area().set_layout(Gtk.ButtonBoxStyle.CENTER)
|
||||
self.get_action_area().set_layout(Gtk.ButtonBoxStyle.CENTER) # type: ignore
|
||||
self.get_action_area().set_margin_bottom(20)
|
||||
self.outer = self.get_content_area()
|
||||
self.outer.set_margin_start(30)
|
||||
@ -76,7 +76,10 @@ class IgnoreDialog(GenericDialog):
|
||||
secondary=secondary,
|
||||
)
|
||||
cancel = self.get_widget_for_response(Gtk.ResponseType.CANCEL)
|
||||
cancel.set_label("Ignore")
|
||||
if cancel is None:
|
||||
return
|
||||
if hasattr(cancel, "set_label"):
|
||||
cancel.set_label("Ignore")
|
||||
|
||||
|
||||
class NotifyDialog(GenericDialog):
|
||||
@ -199,7 +202,11 @@ class ExceptionDialog(GenericDialog):
|
||||
match response:
|
||||
case Gtk.ResponseType.OK:
|
||||
self.destroy()
|
||||
return None
|
||||
case Gtk.ResponseType.NONE:
|
||||
return True
|
||||
case Gtk.ResponseType.DELETE_EVENT:
|
||||
self.destroy()
|
||||
return None
|
||||
case _:
|
||||
return None
|
||||
|
||||
@ -19,7 +19,7 @@ class ContextMixin(TreeView):
|
||||
self,
|
||||
widget: Gtk.Widget,
|
||||
event: Gdk.EventButton | Gdk.EventKey,
|
||||
) -> None:
|
||||
) -> bool:
|
||||
|
||||
if self.is_selection_empty():
|
||||
return False
|
||||
@ -56,6 +56,7 @@ class ContextMixin(TreeView):
|
||||
self.context_menu.popup_at_pointer(event)
|
||||
|
||||
self.context_menu.select_first(False)
|
||||
return True
|
||||
|
||||
def _process_dynamic_row(self, row: ContextMenu) -> Gtk.MenuItem:
|
||||
if row == ContextMenu.ADD_SERVER and self.is_in_favs(): # type: ignore
|
||||
|
||||
@ -18,9 +18,6 @@ class Help(Gtk.ScrolledWindow):
|
||||
|
||||
self.controller = controller
|
||||
|
||||
# FIXME: remember focused row
|
||||
#self.treeview.focus_first_row()
|
||||
|
||||
def get_treeview(self) -> MenuTreeView:
|
||||
return self.treeview
|
||||
|
||||
|
||||
@ -59,8 +59,8 @@ class Options(Gtk.Box):
|
||||
css.add_class(label, "page-heading")
|
||||
self.add(label)
|
||||
|
||||
self.steam_entry: Gtk.Entry = None
|
||||
self.bm_entry: Gtk.Entry = None
|
||||
self.steam_entry: Gtk.Entry
|
||||
self.bm_entry: Gtk.Entry
|
||||
|
||||
self.steam = WebButton(label=strings.options.steam_web)
|
||||
self.steam.connect("clicked", self._on_link_button_clicked, STEAM_API_SETUP)
|
||||
@ -436,7 +436,8 @@ class Options(Gtk.Box):
|
||||
install = config["auto_install"]
|
||||
|
||||
steam_path = Path(default_steam_path)
|
||||
self.uid = find_user_id(steam_path)
|
||||
uid = find_user_id(steam_path)
|
||||
self.uid = "" if uid is None else uid
|
||||
|
||||
self.old_steam = steam
|
||||
self.old_bm = bm
|
||||
@ -444,7 +445,9 @@ class Options(Gtk.Box):
|
||||
|
||||
self.steam_entry.set_text(steam)
|
||||
self.bm_entry.set_text(bm)
|
||||
self.player_box.get_children()[0].set_text(name)
|
||||
p = self.player_box.get_children()[0]
|
||||
if hasattr(p, "set_text"):
|
||||
p.set_text(name)
|
||||
|
||||
# NOTE: suppress toggle signal until radios are built
|
||||
self._suppress_toggles(True)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import logging
|
||||
|
||||
from typing import Optional, Self, TYPE_CHECKING
|
||||
from typing import Self, TYPE_CHECKING
|
||||
|
||||
from dzgui.const.constants import APP_NAME
|
||||
from dzgui.const.enum import ContextMenuGroup, ServerTab
|
||||
@ -77,6 +77,8 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
||||
return
|
||||
page = self.notebook.get_nth_page(saved)
|
||||
label = self.notebook.get_tab_label(page)
|
||||
if label is None:
|
||||
return
|
||||
label.set_text(f"{server_labels.saved}*")
|
||||
|
||||
def _on_servers_loaded(self, emitter: "Emitter", tab: "ServerTab") -> None:
|
||||
@ -86,6 +88,7 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
||||
state = False if tv.get_model() is None else True
|
||||
tv.set_headers_visible(state)
|
||||
tv.set_loaded(True)
|
||||
tv.focus_first_row()
|
||||
tv.grab_focus()
|
||||
|
||||
def _on_map(self, widget: Self) -> None:
|
||||
@ -94,7 +97,7 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
||||
def _on_unmap(self, widget: Self) -> None:
|
||||
self.emitter.emit("server_page_toggled", False)
|
||||
|
||||
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> Optional[False]:
|
||||
def _on_keypress(self, widget: Self, event: Gdk.EventKey) -> bool:
|
||||
# NOTE: abort if modifier mask is active
|
||||
if event.state != 0:
|
||||
return False
|
||||
@ -116,6 +119,8 @@ class ServerNotebook(Gtk.ScrolledWindow):
|
||||
def get_current_tab_text(self) -> str:
|
||||
ind = self.notebook.get_current_page()
|
||||
child = self.notebook.get_nth_page(ind)
|
||||
if child is None:
|
||||
return ""
|
||||
return self.notebook.get_tab_label_text(child)
|
||||
|
||||
def _on_page_changed(
|
||||
|
||||
@ -172,7 +172,7 @@ class ServerTreeView(ContextMixin, TreeView):
|
||||
self.start_queue_checker()
|
||||
self.start_distcalc()
|
||||
|
||||
def _on_unmap(self, a) -> None:
|
||||
def _on_unmap(self, tree: Self) -> None:
|
||||
# NOTE: removes queue checker for this tab
|
||||
GLib.Source.remove(self.queue_id)
|
||||
self.emitter.disconnect(self.handler_id)
|
||||
@ -229,7 +229,7 @@ class ServerTreeView(ContextMixin, TreeView):
|
||||
|
||||
def _on_server_keypress(
|
||||
self, treeview: Gtk.TreeView, event: Gdk.EventKey
|
||||
) -> bool | None:
|
||||
) -> None:
|
||||
if event.state is Gdk.ModifierType.CONTROL_MASK:
|
||||
match event.keyval:
|
||||
case Gdk.KEY_f:
|
||||
@ -248,10 +248,14 @@ class ServerTreeView(ContextMixin, TreeView):
|
||||
case Gdk.KEY_r:
|
||||
# TODO: unimplemented, needs threading
|
||||
self.controller.menu_action(ContextMenu.REFRESH_PLAYERS, self)
|
||||
case _:
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
match event.keyval:
|
||||
case Gdk.KEY_l | Gdk.KEY_Right:
|
||||
self.emitter.emit("request_button_box_focus")
|
||||
return True
|
||||
case _:
|
||||
self.emitter.emit("check_button_pressed", event.keyval)
|
||||
|
||||
@ -307,7 +311,8 @@ class ServerTreeView(ContextMixin, TreeView):
|
||||
try:
|
||||
ip, gameport, qport = r.split(":")
|
||||
return Record(ip, int(gameport), int(qport))
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
logger.critical(e)
|
||||
return None
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user