Redesign desktop lobby as a workspace

This commit is contained in:
Peter Stockings
2026-10-05 16:25:38 +11:00
parent 21a35d81f0
commit 0c5d4ee546
4 changed files with 330 additions and 141 deletions
+4
View File
@@ -10,6 +10,10 @@ address remains fixed. Detached chat opens docked on the next launch.
## Active sessions ## Active sessions
The desktop workspace has a sidebar for your saved display name, **Create room**,
and **Join by code**. Creation and code entry open in separate dialogs. Drag a
video onto the lobby to prepare a new room. Enter your name to enable room actions.
The lobby checks for listed rooms at startup and every 15 seconds while open. The lobby checks for listed rooms at startup and every 15 seconds while open.
Select a session and click **Choose file & join**, or double-click it, to use the Select a session and click **Choose file & join**, or double-click it, to use the
existing file verification flow. Enter your display name first. **Refresh** checks existing file verification flow. Enter your display name first. **Refresh** checks
+254 -100
View File
@@ -2,9 +2,38 @@ import os
import re import re
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
QFileDialog, QFrame, QApplication, QCheckBox, QListWidget, QListWidgetItem QFileDialog, QFrame, QApplication, QCheckBox, QListWidget, QListWidgetItem, QDialog,
QStyledItemDelegate, QStyle
) )
from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtCore import Qt, pyqtSignal, QSize
from PyQt6.QtGui import QColor, QFont, QFontMetrics
class SessionDelegate(QStyledItemDelegate):
"""Draw readable session rows with elided filenames and secondary details."""
def paint(self, painter, option, index):
painter.save()
selected = bool(option.state & QStyle.StateFlag.State_Selected)
hovered = bool(option.state & QStyle.StateFlag.State_MouseOver)
painter.fillRect(option.rect, QColor("#29243f" if selected else
"#222839" if hovered else "#191e29"))
rect = option.rect.adjusted(22, 20, -22, -20)
lines = str(index.data()).splitlines()
for i, text in enumerate(lines[:3]):
font = QFont("Segoe UI", 10)
font.setWeight(QFont.Weight.DemiBold if i == 0 else QFont.Weight.Normal)
painter.setFont(font)
painter.setPen(QColor("#edf1fa" if i == 0 else "#9ca9c0"))
line = rect.adjusted(0, i * 24, 0, 0)
line.setHeight(24)
text = QFontMetrics(font).elidedText(text, Qt.TextElideMode.ElideRight, line.width())
painter.drawText(line, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, text)
painter.setPen(QColor("#2c3546"))
painter.drawLine(option.rect.bottomLeft(), option.rect.bottomRight())
if option.state & QStyle.StateFlag.State_HasFocus:
painter.setPen(QColor("#9b8bff"))
painter.drawRect(option.rect.adjusted(1, 1, -1, -1))
painter.restore()
class RoomCodeInput(QLineEdit): class RoomCodeInput(QLineEdit):
"""QLineEdit that auto-fills from clipboard if it looks like a room code.""" """QLineEdit that auto-fills from clipboard if it looks like a room code."""
@@ -39,119 +68,226 @@ class LobbyWidget(QWidget):
self._setup_ui() self._setup_ui()
def _setup_ui(self): def _setup_ui(self):
self.setObjectName("desktopLobby")
self._connecting = False
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Container for centering header = QFrame()
container = QFrame() header.setObjectName("lobbyHeader")
container.setObjectName("lobbyCard") header_layout = QHBoxLayout(header)
container.setMinimumWidth(420) header_layout.setContentsMargins(28, 18, 28, 18)
container.setMaximumWidth(500) brand = QLabel("VideoSync")
container_layout = QVBoxLayout(container) brand.setObjectName("lobbyBrand")
container_layout.setContentsMargins(30, 30, 30, 30) header_layout.addWidget(brand)
container_layout.setSpacing(15) header_layout.addStretch()
self.connection_label = QLabel("Watch together. Stay in sync.")
self.connection_label.setObjectName("lobbyMuted")
header_layout.addWidget(self.connection_label)
layout.addWidget(header)
# Brand body = QHBoxLayout()
title = QLabel("VideoSync") body.setSpacing(0)
title.setObjectName("brandTitle") sidebar = QFrame()
title.setAlignment(Qt.AlignmentFlag.AlignCenter) sidebar.setObjectName("lobbySidebar")
tagline = QLabel("Watch together, anywhere") sidebar.setFixedWidth(260)
tagline.setObjectName("brandTagline") side = QVBoxLayout(sidebar)
tagline.setAlignment(Qt.AlignmentFlag.AlignCenter) side.setContentsMargins(24, 32, 24, 24)
side.setSpacing(12)
container_layout.addWidget(title) title = QLabel("Watch together.")
container_layout.addWidget(tagline) title.setObjectName("sidebarTitle")
container_layout.addSpacing(20) side.addWidget(title)
subtitle = QLabel("One room. Everyone in sync.")
# Username subtitle.setObjectName("lobbyMuted")
side.addWidget(subtitle)
side.addSpacing(12)
self.create_action_btn = QPushButton("+ Create room")
self.create_action_btn.setObjectName("lobbyPrimary")
self.create_action_btn.clicked.connect(self.open_create_dialog)
side.addWidget(self.create_action_btn)
self.join_action_btn = QPushButton("Join by code")
self.join_action_btn.clicked.connect(self.open_join_dialog)
side.addWidget(self.join_action_btn)
side.addSpacing(28)
name_label = QLabel("Your display name")
side.addWidget(name_label)
self.username_input = QLineEdit() self.username_input = QLineEdit()
self.username_input.setPlaceholderText("Enter a display name") self.username_input.setPlaceholderText("Enter your name")
container_layout.addWidget(QLabel("YOUR NAME")) self.username_input.setAccessibleName("Your display name")
container_layout.addWidget(self.username_input) side.addWidget(self.username_input)
name_label.setBuddy(self.username_input)
hint = QLabel("Remembered on this computer.")
hint.setObjectName("lobbyMuted")
hint.setWordWrap(True)
side.addWidget(hint)
side.addStretch()
privacy = QLabel("Your video stays on your computer.\nOnly playback and chat are shared.")
privacy.setObjectName("lobbyMuted")
privacy.setWordWrap(True)
side.addWidget(privacy)
body.addWidget(sidebar)
container_layout.addSpacing(20) workspace = QVBoxLayout()
workspace.setContentsMargins(32, 32, 32, 24)
# Actions Layout (Create vs Join) workspace.setSpacing(12)
actions_layout = QHBoxLayout()
actions_layout.setSpacing(20)
# Create Room Panel
create_panel = QVBoxLayout()
create_panel.addWidget(QLabel("Create a Room"))
self.create_file_btn = QPushButton("Choose Video File")
self.create_file_btn.setObjectName("secondaryBtn")
self.create_file_btn.clicked.connect(self.select_file)
self.create_file_info = QLabel("")
self.create_file_info.setObjectName("fileInfo")
self.create_file_info.setWordWrap(True)
self.create_file_info.hide()
self.create_room_btn = QPushButton("Create Room")
self.create_room_btn.setObjectName("primaryBtn")
self.create_room_btn.setEnabled(False)
self.create_room_btn.clicked.connect(self.create_room)
create_panel.addWidget(self.create_file_btn)
create_panel.addWidget(self.create_file_info)
self.listed_checkbox = QCheckBox("List room in active sessions")
self.listed_checkbox.setChecked(True)
self.listed_checkbox.setToolTip("Unlisted rooms can still be joined using their code.")
create_panel.addWidget(self.listed_checkbox)
create_panel.addWidget(self.create_room_btn)
# Join Room Panel
join_panel = QVBoxLayout()
join_panel.addWidget(QLabel("Join a Room"))
self.room_code_input = RoomCodeInput()
self.room_code_input.setPlaceholderText("e.g. ABC123")
self.join_room_btn = QPushButton("Join Room")
self.join_room_btn.setObjectName("secondaryBtn")
self.join_room_btn.setEnabled(False)
self.join_room_btn.clicked.connect(self.join_room)
join_panel.addWidget(self.room_code_input)
join_panel.addWidget(self.join_room_btn)
actions_layout.addLayout(create_panel)
# Divider
divider = QLabel("OR")
divider.setAlignment(Qt.AlignmentFlag.AlignCenter)
divider.setObjectName("divider")
actions_layout.addWidget(divider)
actions_layout.addLayout(join_panel)
container_layout.addLayout(actions_layout)
panels = QHBoxLayout()
panels.addWidget(container)
sessions = QVBoxLayout()
heading = QHBoxLayout() heading = QHBoxLayout()
heading.addWidget(QLabel("ALREADY WATCHING")) title_group = QVBoxLayout()
title_group.setSpacing(8)
title = QLabel("Active sessions")
title.setObjectName("workspaceTitle")
title_group.addWidget(title)
description = QLabel("See who’s watching and join in.")
description.setObjectName("lobbyMuted")
title_group.addWidget(description)
heading.addLayout(title_group)
heading.addStretch()
self.refresh_btn = QPushButton("Refresh") self.refresh_btn = QPushButton("Refresh")
self.refresh_btn.setObjectName("lobbyQuiet")
self.refresh_btn.clicked.connect(self.refresh_requested.emit) self.refresh_btn.clicked.connect(self.refresh_requested.emit)
heading.addWidget(self.refresh_btn) heading.addWidget(self.refresh_btn)
sessions.addLayout(heading) workspace.addLayout(heading)
workspace.addSpacing(12)
self.sessions_status = QLabel("Checking for active sessions…") self.sessions_status = QLabel("Checking for active sessions…")
self.sessions_status.setObjectName("lobbyMuted")
self.sessions_status.setWordWrap(True) self.sessions_status.setWordWrap(True)
sessions.addWidget(self.sessions_status) workspace.addWidget(self.sessions_status)
self.sessions_list = QListWidget() self.sessions_list = QListWidget()
self.sessions_list.setObjectName("lobbySessions")
self.sessions_list.setWordWrap(True) self.sessions_list.setWordWrap(True)
self.sessions_list.setMinimumWidth(280) self.sessions_list.setItemDelegate(SessionDelegate(self.sessions_list))
sessions.addWidget(self.sessions_list) self.sessions_list.setAccessibleName("Active sessions")
self.session_join_btn = QPushButton("Choose file & join") workspace.addWidget(self.sessions_list, 1)
bottom = QHBoxLayout()
self.session_count = QLabel("Select a session to get started.")
self.session_count.setObjectName("lobbyMuted")
bottom.addWidget(self.session_count)
bottom.addStretch()
self.session_join_btn = QPushButton("Choose file && join")
self.session_join_btn.setObjectName("lobbyPrimary")
self.session_join_btn.setEnabled(False) self.session_join_btn.setEnabled(False)
self.session_join_btn.clicked.connect(self.join_selected_session) self.session_join_btn.clicked.connect(self.join_selected_session)
bottom.addWidget(self.session_join_btn)
workspace.addLayout(bottom)
self.sessions_list.itemDoubleClicked.connect(lambda _: self.join_selected_session()) self.sessions_list.itemDoubleClicked.connect(lambda _: self.join_selected_session())
self.sessions_list.itemSelectionChanged.connect(self.check_inputs) self.sessions_list.itemSelectionChanged.connect(self.check_inputs)
sessions.addWidget(self.session_join_btn) body.addLayout(workspace, 1)
panels.addLayout(sessions) layout.addLayout(body, 1)
layout.addLayout(panels)
# Signals to enable/disable buttons self.create_dialog = self._make_dialog("Create a room", "Choose a local video, then invite your friends.")
create = self.create_dialog.layout()
self.create_file_btn = QPushButton("Choose video file")
self.create_file_btn.clicked.connect(self.select_file)
create.addWidget(self.create_file_btn)
self.create_file_info = QLabel("")
self.create_file_info.setObjectName("lobbyFileInfo")
self.create_file_info.setWordWrap(True)
self.create_file_info.hide()
create.addWidget(self.create_file_info)
self.listed_checkbox = QCheckBox("List room in active sessions")
self.listed_checkbox.setChecked(True)
create.addWidget(self.listed_checkbox)
hint = QLabel("Unlisted rooms can still be joined using their code.")
hint.setObjectName("lobbyMuted")
hint.setWordWrap(True)
create.addWidget(hint)
self.create_room_btn = QPushButton("Create room")
self.create_room_btn.setObjectName("lobbyPrimary")
self.create_room_btn.clicked.connect(self.create_room)
self._dialog_actions(self.create_dialog, self.create_room_btn)
self.join_dialog = self._make_dialog("Join by code", "Enter the room code your friend shared.")
self.room_code_input = RoomCodeInput()
self.room_code_input.setPlaceholderText("e.g. ABC123")
self.room_code_input.setAccessibleName("Room code")
self.room_code_input.setMaxLength(8)
self.room_code_input.returnPressed.connect(self._join_if_valid)
self.join_dialog.layout().addWidget(self.room_code_input)
self.join_room_btn = QPushButton("Join room")
self.join_room_btn.setObjectName("lobbyPrimary")
self.join_room_btn.clicked.connect(self.join_room)
self._dialog_actions(self.join_dialog, self.join_room_btn)
self.username_input.textChanged.connect(self.check_inputs) self.username_input.textChanged.connect(self.check_inputs)
self.room_code_input.textChanged.connect(self.check_inputs) self.room_code_input.textChanged.connect(self.check_inputs)
self._apply_lobby_style()
self.check_inputs()
def _make_dialog(self, title, description):
dialog = QDialog(self)
dialog.setWindowTitle(title)
dialog.setObjectName("lobbyDialog")
dialog.setModal(True)
dialog.setMinimumWidth(420)
layout = QVBoxLayout(dialog)
layout.setContentsMargins(28, 28, 28, 28)
layout.setSpacing(18)
label = QLabel(title)
label.setObjectName("dialogTitle")
layout.addWidget(label)
text = QLabel(description)
text.setObjectName("lobbyMuted")
text.setWordWrap(True)
layout.addWidget(text)
return dialog
def _dialog_actions(self, dialog, action):
row = QHBoxLayout()
row.addStretch()
cancel = QPushButton("Cancel")
cancel.clicked.connect(dialog.reject)
row.addWidget(cancel)
row.addWidget(action)
dialog.layout().addLayout(row)
def open_create_dialog(self):
if not self.username_input.text().strip():
self.username_input.setFocus()
return
self.create_dialog.show()
self.create_dialog.raise_()
self.create_file_btn.setFocus()
def open_join_dialog(self):
if not self.username_input.text().strip():
self.username_input.setFocus()
return
self.join_dialog.show()
self.join_dialog.raise_()
self.room_code_input.setFocus()
def _join_if_valid(self):
if self.join_room_btn.isEnabled():
self.join_room()
def _apply_lobby_style(self):
self.setStyleSheet("""
QWidget#desktopLobby, QDialog#lobbyDialog { background: #10131b; }
QLabel { color: #edf1fa; font-family: 'Segoe UI'; font-size: 13px; font-weight: normal; }
QFrame#lobbyHeader { background: #191e29; border-bottom: 1px solid #2c3546; }
QFrame#lobbySidebar { background: #191e29; border-right: 1px solid #2c3546; }
QLabel#lobbyBrand { font-size: 20px; font-weight: 600; }
QLabel#sidebarTitle, QLabel#dialogTitle { font-size: 23px; font-weight: 600; }
QLabel#workspaceTitle { font-size: 28px; font-weight: 600; }
QLabel#lobbyMuted { color: #9ca9c0; font-size: 12px; }
QLabel#lobbyFileInfo { background: #29243f; color: #c6bcff; padding: 14px; border-radius: 7px; }
QLineEdit { background: #10131b; color: #edf1fa; border: 1px solid #364157;
border-radius: 7px; padding: 11px; font: 14px 'Segoe UI'; }
QLineEdit:focus { border-color: #9b8bff; }
QPushButton { background: #191e29; color: #edf1fa; border: 1px solid #364157;
border-radius: 7px; padding: 11px 15px; font: 13px 'Segoe UI'; }
QPushButton:hover { background: #242b3b; border-color: #9b8bff; }
QPushButton#lobbyPrimary { background: #9b8bff; color: #141020; border: none; font-weight: 600; }
QPushButton#lobbyPrimary:hover { background: #b1a4ff; }
QPushButton:disabled, QPushButton#lobbyPrimary:disabled { background: #242b38; color: #738099; border-color: #2c3546; }
QPushButton#lobbyQuiet { background: transparent; }
QCheckBox { color: #edf1fa; spacing: 9px; font: 13px 'Segoe UI'; }
QListWidget#lobbySessions { background: #191e29; color: #edf1fa;
border: 1px solid #2c3546; border-radius: 9px; outline: none; font: 14px 'Segoe UI'; }
QListWidget#lobbySessions::item { padding: 20px; border-bottom: 1px solid #2c3546; }
QListWidget#lobbySessions::item:selected { background: #29243f; color: #edf1fa; }
QListWidget#lobbySessions::item:hover { background: #222839; }
QListWidget#lobbySessions:focus { border-color: #9b8bff; }
""")
def _set_local_file(self, file_path: str): def _set_local_file(self, file_path: str):
self.local_file_path = file_path self.local_file_path = file_path
@@ -185,6 +321,7 @@ class LobbyWidget(QWidget):
ext = os.path.splitext(file_path)[1].lower() ext = os.path.splitext(file_path)[1].lower()
if ext in ['.mp4', '.mkv', '.avi', '.mov', '.webm']: if ext in ['.mp4', '.mkv', '.avi', '.mov', '.webm']:
self._set_local_file(file_path) self._set_local_file(file_path)
self.open_create_dialog()
break break
def check_inputs(self): def check_inputs(self):
@@ -192,9 +329,11 @@ class LobbyWidget(QWidget):
has_file = self.local_file_path is not None has_file = self.local_file_path is not None
has_code = len(self.room_code_input.text().strip()) >= 4 has_code = len(self.room_code_input.text().strip()) >= 4
self.create_room_btn.setEnabled(has_name and has_file) self.create_room_btn.setEnabled(has_name and has_file and not self._connecting)
self.join_room_btn.setEnabled(has_name and has_code) self.create_action_btn.setEnabled(has_name and not self._connecting)
self.session_join_btn.setEnabled(has_name and self.sessions_list.currentItem() is not None) self.join_action_btn.setEnabled(has_name and not self._connecting)
self.join_room_btn.setEnabled(has_name and has_code and not self._connecting)
self.session_join_btn.setEnabled(has_name and self.sessions_list.currentItem() is not None and not self._connecting)
def update_sessions(self, rooms): def update_sessions(self, rooms):
selected = self.sessions_list.currentItem() selected = self.sessions_list.currentItem()
@@ -207,10 +346,14 @@ class LobbyWidget(QWidget):
item = QListWidgetItem(f"{room['fileInfo']['name']}\n" item = QListWidgetItem(f"{room['fileInfo']['name']}\n"
f"{state} · {seconds // 60}:{seconds % 60:02d} · {len(users)} connected\n" f"{state} · {seconds // 60}:{seconds % 60:02d} · {len(users)} connected\n"
f"{', '.join(users)}") f"{', '.join(users)}")
item.setSizeHint(QSize(0, 116))
item.setToolTip(item.text())
item.setData(Qt.ItemDataRole.UserRole, room['code']) item.setData(Qt.ItemDataRole.UserRole, room['code'])
self.sessions_list.addItem(item) self.sessions_list.addItem(item)
if room['code'] == code: if room['code'] == code:
self.sessions_list.setCurrentItem(item) self.sessions_list.setCurrentItem(item)
self.session_count.setText(f"{len(rooms)} room{'s' if len(rooms) != 1 else ''} available")
self.sessions_list.setVisible(bool(rooms))
self.sessions_status.setText("Select a session to join." if rooms else self.sessions_status.setText("Select a session to join." if rooms else
"No active sessions. Create a room to get started.") "No active sessions. Create a room to get started.")
self.check_inputs() self.check_inputs()
@@ -222,6 +365,11 @@ class LobbyWidget(QWidget):
self.join_room() self.join_room()
def create_room(self): def create_room(self):
if not self.create_room_btn.isEnabled():
return
self._connecting = True
self.create_dialog.accept()
self.check_inputs()
username = self.username_input.text().strip() username = self.username_input.text().strip()
self.create_room_btn.setText("Connecting...") self.create_room_btn.setText("Connecting...")
self.create_room_btn.setEnabled(False) self.create_room_btn.setEnabled(False)
@@ -229,6 +377,11 @@ class LobbyWidget(QWidget):
self.create_requested.emit(username, self.local_file_path, self.local_file_name, self.local_file_size) self.create_requested.emit(username, self.local_file_path, self.local_file_name, self.local_file_size)
def join_room(self): def join_room(self):
if not self.join_room_btn.isEnabled():
return
self._connecting = True
self.join_dialog.accept()
self.check_inputs()
username = self.username_input.text().strip() username = self.username_input.text().strip()
room_code = self.room_code_input.text().strip().upper() room_code = self.room_code_input.text().strip().upper()
self.join_room_btn.setText("Connecting...") self.join_room_btn.setText("Connecting...")
@@ -237,8 +390,9 @@ class LobbyWidget(QWidget):
self.join_requested.emit(username, room_code) self.join_requested.emit(username, room_code)
def reset_ui(self): def reset_ui(self):
self.create_room_btn.setText("Create Room") self._connecting = False
self.join_room_btn.setText("Join Room") self.create_room_btn.setText("Create room")
self.join_room_btn.setText("Join room")
self.check_inputs() self.check_inputs()
def clear_file(self): def clear_file(self):
+2
View File
@@ -109,6 +109,8 @@ class VlcSyncApp(QMainWindow):
self.lobby_widget.update_sessions(rooms) self.lobby_widget.update_sessions(rooms)
except (ValueError, KeyError, TypeError, OverflowError): except (ValueError, KeyError, TypeError, OverflowError):
self.lobby_widget.sessions_list.clear() self.lobby_widget.sessions_list.clear()
self.lobby_widget.sessions_list.hide()
self.lobby_widget.session_count.setText("Join by code is still available.")
self.lobby_widget.check_inputs() self.lobby_widget.check_inputs()
self.lobby_widget.sessions_status.setText("Couldn’t check for sessions. You can retry or join by code.") self.lobby_widget.sessions_status.setText("Couldn’t check for sessions. You can retry or join by code.")
finally: finally:
+29
View File
@@ -1,4 +1,5 @@
from lobby_widget import LobbyWidget from lobby_widget import LobbyWidget
from PyQt6.QtCore import Qt
def test_session_selection_uses_existing_join_flow(qtbot): def test_session_selection_uses_existing_join_flow(qtbot):
@@ -21,3 +22,31 @@ def test_session_selection_uses_existing_join_flow(qtbot):
lobby.update_sessions([]) lobby.update_sessions([])
assert not lobby.session_join_btn.isEnabled() assert not lobby.session_join_btn.isEnabled()
assert "No active sessions" in lobby.sessions_status.text() assert "No active sessions" in lobby.sessions_status.text()
def test_workspace_dialogs_and_connection_lock(qtbot, tmp_path):
lobby = LobbyWidget()
qtbot.addWidget(lobby)
lobby.show()
assert not lobby.create_action_btn.isEnabled()
lobby.username_input.setText("Alex")
qtbot.mouseClick(lobby.create_action_btn, Qt.MouseButton.LeftButton)
assert lobby.create_dialog.isVisible()
assert not lobby.create_room_btn.isEnabled()
video = tmp_path / "movie.mkv"
video.write_bytes(b"local video")
lobby._set_local_file(str(video))
lobby.listed_checkbox.setChecked(False)
with qtbot.waitSignal(lobby.create_requested) as signal:
qtbot.mouseClick(lobby.create_room_btn, Qt.MouseButton.LeftButton)
assert signal.args == ["Alex", str(video), "movie.mkv", 11]
assert not lobby.create_dialog.isVisible()
assert not lobby.create_action_btn.isEnabled()
assert not lobby.join_action_btn.isEnabled()
lobby.reset_ui()
qtbot.mouseClick(lobby.join_action_btn, Qt.MouseButton.LeftButton)
lobby.room_code_input.setText("abc123")
with qtbot.waitSignal(lobby.join_requested) as signal:
qtbot.keyClick(lobby.room_code_input, Qt.Key.Key_Return)
assert signal.args == ["Alex", "ABC123"]
assert not lobby.join_dialog.isVisible()