From 0c5d4ee5465a5f23859dff66ff309d567dd016b2 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Mon, 5 Oct 2026 16:25:38 +1100 Subject: [PATCH] Redesign desktop lobby as a workspace --- desktop-client/README.md | 4 + desktop-client/lobby_widget.py | 436 +++++++++++++++++--------- desktop-client/main.py | 2 + desktop-client/test_lobby_sessions.py | 29 ++ 4 files changed, 330 insertions(+), 141 deletions(-) diff --git a/desktop-client/README.md b/desktop-client/README.md index 1de56ef..f7fe4d4 100644 --- a/desktop-client/README.md +++ b/desktop-client/README.md @@ -10,6 +10,10 @@ address remains fixed. Detached chat opens docked on the next launch. ## 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. 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 diff --git a/desktop-client/lobby_widget.py b/desktop-client/lobby_widget.py index 8c332da..0a3aff8 100644 --- a/desktop-client/lobby_widget.py +++ b/desktop-client/lobby_widget.py @@ -2,9 +2,38 @@ import os import re from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, - QFileDialog, QFrame, QApplication, QCheckBox, QListWidget, QListWidgetItem -) -from PyQt6.QtCore import Qt, pyqtSignal + QFileDialog, QFrame, QApplication, QCheckBox, QListWidget, QListWidgetItem, QDialog, + QStyledItemDelegate, QStyle +) +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): """QLineEdit that auto-fills from clipboard if it looks like a room code.""" @@ -23,9 +52,9 @@ class RoomCodeInput(QLineEdit): class LobbyWidget(QWidget): # Signals to communicate to VlcSyncApp create_requested = pyqtSignal(str, str, str, object) # username, path, filename, size - join_requested = pyqtSignal(str, str) # username, room_code - file_selected = pyqtSignal(str) - refresh_requested = pyqtSignal() + join_requested = pyqtSignal(str, str) # username, room_code + file_selected = pyqtSignal(str) + refresh_requested = pyqtSignal() def __init__(self): super().__init__() @@ -33,132 +62,239 @@ class LobbyWidget(QWidget): self.local_file_path = None self.local_file_name = None - self.local_file_size = 0 - self.media_folder = "" + self.local_file_size = 0 + self.media_folder = "" self._setup_ui() def _setup_ui(self): + self.setObjectName("desktopLobby") + self._connecting = False layout = QVBoxLayout(self) - layout.setAlignment(Qt.AlignmentFlag.AlignCenter) - - # Container for centering - container = QFrame() - container.setObjectName("lobbyCard") - container.setMinimumWidth(420) - container.setMaximumWidth(500) - container_layout = QVBoxLayout(container) - container_layout.setContentsMargins(30, 30, 30, 30) - container_layout.setSpacing(15) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) - # Brand - title = QLabel("VideoSync") - title.setObjectName("brandTitle") - title.setAlignment(Qt.AlignmentFlag.AlignCenter) - tagline = QLabel("Watch together, anywhere") - tagline.setObjectName("brandTagline") - tagline.setAlignment(Qt.AlignmentFlag.AlignCenter) - - container_layout.addWidget(title) - container_layout.addWidget(tagline) - container_layout.addSpacing(20) + header = QFrame() + header.setObjectName("lobbyHeader") + header_layout = QHBoxLayout(header) + header_layout.setContentsMargins(28, 18, 28, 18) + brand = QLabel("VideoSync") + brand.setObjectName("lobbyBrand") + header_layout.addWidget(brand) + 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) - # Username + body = QHBoxLayout() + body.setSpacing(0) + sidebar = QFrame() + sidebar.setObjectName("lobbySidebar") + sidebar.setFixedWidth(260) + side = QVBoxLayout(sidebar) + side.setContentsMargins(24, 32, 24, 24) + side.setSpacing(12) + title = QLabel("Watch together.") + title.setObjectName("sidebarTitle") + side.addWidget(title) + subtitle = QLabel("One room. Everyone in sync.") + 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.setPlaceholderText("Enter a display name") - container_layout.addWidget(QLabel("YOUR NAME")) - container_layout.addWidget(self.username_input) - - container_layout.addSpacing(20) - - # Actions Layout (Create vs Join) - 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.username_input.setPlaceholderText("Enter your name") + self.username_input.setAccessibleName("Your display name") + 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) + + workspace = QVBoxLayout() + workspace.setContentsMargins(32, 32, 32, 24) + workspace.setSpacing(12) + heading = QHBoxLayout() + 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.setObjectName("lobbyQuiet") + self.refresh_btn.clicked.connect(self.refresh_requested.emit) + heading.addWidget(self.refresh_btn) + workspace.addLayout(heading) + workspace.addSpacing(12) + self.sessions_status = QLabel("Checking for active sessions…") + self.sessions_status.setObjectName("lobbyMuted") + self.sessions_status.setWordWrap(True) + workspace.addWidget(self.sessions_status) + self.sessions_list = QListWidget() + self.sessions_list.setObjectName("lobbySessions") + self.sessions_list.setWordWrap(True) + self.sessions_list.setItemDelegate(SessionDelegate(self.sessions_list)) + self.sessions_list.setAccessibleName("Active sessions") + 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.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.itemSelectionChanged.connect(self.check_inputs) + body.addLayout(workspace, 1) + layout.addLayout(body, 1) + + 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("fileInfo") + self.create_file_info.setObjectName("lobbyFileInfo") 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) + 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) - - 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._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.join_room_btn = QPushButton("Join Room") - self.join_room_btn.setObjectName("secondaryBtn") - self.join_room_btn.setEnabled(False) + 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) - - 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.addWidget(QLabel("ALREADY WATCHING")) - self.refresh_btn = QPushButton("Refresh") - self.refresh_btn.clicked.connect(self.refresh_requested.emit) - heading.addWidget(self.refresh_btn) - sessions.addLayout(heading) - self.sessions_status = QLabel("Checking for active sessions…") - self.sessions_status.setWordWrap(True) - sessions.addWidget(self.sessions_status) - self.sessions_list = QListWidget() - self.sessions_list.setWordWrap(True) - self.sessions_list.setMinimumWidth(280) - sessions.addWidget(self.sessions_list) - self.session_join_btn = QPushButton("Choose file & join") - self.session_join_btn.setEnabled(False) - self.session_join_btn.clicked.connect(self.join_selected_session) - self.sessions_list.itemDoubleClicked.connect(lambda _: self.join_selected_session()) - self.sessions_list.itemSelectionChanged.connect(self.check_inputs) - sessions.addWidget(self.session_join_btn) - panels.addLayout(sessions) - layout.addLayout(panels) - - # Signals to enable/disable buttons + self._dialog_actions(self.join_dialog, self.join_room_btn) self.username_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): self.local_file_path = file_path self.local_file_name = os.path.basename(file_path) - self.local_file_size = os.path.getsize(file_path) - self.media_folder = os.path.dirname(file_path) - self.file_selected.emit(file_path) + self.local_file_size = os.path.getsize(file_path) + self.media_folder = os.path.dirname(file_path) + self.file_selected.emit(file_path) size_mb = self.local_file_size / (1024 * 1024) self.create_file_info.setText(f"{self.local_file_name}\n{size_mb:.1f} MB") @@ -167,7 +303,7 @@ class LobbyWidget(QWidget): def select_file(self): file_path, _ = QFileDialog.getOpenFileName( - self, "Select Video File", self.media_folder, "Video Files (*.mp4 *.mkv *.avi *.mov *.webm);;All Files (*)" + self, "Select Video File", self.media_folder, "Video Files (*.mp4 *.mkv *.avi *.mov *.webm);;All Files (*)" ) if file_path: self._set_local_file(file_path) @@ -185,6 +321,7 @@ class LobbyWidget(QWidget): ext = os.path.splitext(file_path)[1].lower() if ext in ['.mp4', '.mkv', '.avi', '.mov', '.webm']: self._set_local_file(file_path) + self.open_create_dialog() break def check_inputs(self): @@ -192,36 +329,47 @@ class LobbyWidget(QWidget): has_file = self.local_file_path is not None has_code = len(self.room_code_input.text().strip()) >= 4 - self.create_room_btn.setEnabled(has_name and has_file) - self.join_room_btn.setEnabled(has_name and has_code) - self.session_join_btn.setEnabled(has_name and self.sessions_list.currentItem() is not None) - - def update_sessions(self, rooms): - selected = self.sessions_list.currentItem() - code = selected.data(Qt.ItemDataRole.UserRole) if selected else None - self.sessions_list.clear() - for room in rooms: - seconds = max(0, int(room.get("position", 0))) - state = "Playing" if room.get("playing") else "Paused" - users = room.get("users", []) - item = QListWidgetItem(f"{room['fileInfo']['name']}\n" - f"{state} · {seconds // 60}:{seconds % 60:02d} · {len(users)} connected\n" - f"{', '.join(users)}") - item.setData(Qt.ItemDataRole.UserRole, room['code']) - self.sessions_list.addItem(item) - if room['code'] == code: - self.sessions_list.setCurrentItem(item) - self.sessions_status.setText("Select a session to join." if rooms else - "No active sessions. Create a room to get started.") - self.check_inputs() - - def join_selected_session(self): - item = self.sessions_list.currentItem() - if item and self.username_input.text().strip(): - self.room_code_input.setText(item.data(Qt.ItemDataRole.UserRole)) - self.join_room() + self.create_room_btn.setEnabled(has_name and has_file and not self._connecting) + self.create_action_btn.setEnabled(has_name and not self._connecting) + 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): + selected = self.sessions_list.currentItem() + code = selected.data(Qt.ItemDataRole.UserRole) if selected else None + self.sessions_list.clear() + for room in rooms: + seconds = max(0, int(room.get("position", 0))) + state = "Playing" if room.get("playing") else "Paused" + users = room.get("users", []) + item = QListWidgetItem(f"{room['fileInfo']['name']}\n" + f"{state} · {seconds // 60}:{seconds % 60:02d} · {len(users)} connected\n" + f"{', '.join(users)}") + item.setSizeHint(QSize(0, 116)) + item.setToolTip(item.text()) + item.setData(Qt.ItemDataRole.UserRole, room['code']) + self.sessions_list.addItem(item) + if room['code'] == code: + 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 + "No active sessions. Create a room to get started.") + self.check_inputs() + + def join_selected_session(self): + item = self.sessions_list.currentItem() + if item and self.username_input.text().strip(): + self.room_code_input.setText(item.data(Qt.ItemDataRole.UserRole)) + self.join_room() 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() self.create_room_btn.setText("Connecting...") 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) 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() room_code = self.room_code_input.text().strip().upper() self.join_room_btn.setText("Connecting...") @@ -237,8 +390,9 @@ class LobbyWidget(QWidget): self.join_requested.emit(username, room_code) def reset_ui(self): - self.create_room_btn.setText("Create Room") - self.join_room_btn.setText("Join Room") + self._connecting = False + self.create_room_btn.setText("Create room") + self.join_room_btn.setText("Join room") self.check_inputs() def clear_file(self): diff --git a/desktop-client/main.py b/desktop-client/main.py index b81bdf1..4607834 100644 --- a/desktop-client/main.py +++ b/desktop-client/main.py @@ -109,6 +109,8 @@ class VlcSyncApp(QMainWindow): self.lobby_widget.update_sessions(rooms) except (ValueError, KeyError, TypeError, OverflowError): 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.sessions_status.setText("Couldn’t check for sessions. You can retry or join by code.") finally: diff --git a/desktop-client/test_lobby_sessions.py b/desktop-client/test_lobby_sessions.py index 39499ec..7f74d40 100644 --- a/desktop-client/test_lobby_sessions.py +++ b/desktop-client/test_lobby_sessions.py @@ -1,4 +1,5 @@ from lobby_widget import LobbyWidget +from PyQt6.QtCore import Qt 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([]) assert not lobby.session_join_btn.isEnabled() 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()