diff --git a/gui/v2/__main__.py b/gui/v2/__main__.py index 6f371c7..e7aaff3 100755 --- a/gui/v2/__main__.py +++ b/gui/v2/__main__.py @@ -9,7 +9,7 @@ from PyQt6.QtWidgets import ( ) from PyQt6.QtGui import QPixmap, QFont, QFontDatabase from PyQt6 import QtGui -from PyQt6.QtCore import Qt, QTimer +from PyQt6.QtCore import Qt, QTimer, QEvent from core.Constants import Constants from core.controllers.ConfigurationController import ConfigurationController @@ -151,6 +151,7 @@ class CustomWindow(QMainWindow): self.toggle_button.clicked.connect(self.toggle_mode) self.sync_button.clicked.connect(self.sync) + QApplication.instance().installEventFilter(self) self.create_toggle(self.is_tor_mode) self.animation_timer = QTimer() @@ -422,10 +423,15 @@ class CustomWindow(QMainWindow): self.off_label = QLabel("OFF", self.toggle_button) self.off_label.setGeometry(25, 4, 40, 14) + for widget in (self.toggle_bg, self.toggle_handle, self.on_label, self.off_label): + widget.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents) + self._update_toggle_colors(colors) if is_tor_mode: self.set_tor_icon() + self.raise_bottom_controls() + def _get_toggle_colors(self, is_tor_mode): return { 'bg': "#00a8ff" if is_tor_mode else "#2c3e50", @@ -459,20 +465,62 @@ class CustomWindow(QMainWindow): def set_toggle_state(self, is_tor_mode): self.is_tor_mode = is_tor_mode + self.toggle_button.setChecked(is_tor_mode) handle_x = 30 if is_tor_mode else 3 self.toggle_handle.setGeometry(handle_x, 3, 16, 16) self._update_toggle_colors(self._get_toggle_colors(is_tor_mode)) self.set_tor_icon() - def toggle_mode(self): + def toggle_mode(self, *_args): + if self.is_animating and not self.animation_timer.isActive(): + self.is_animating = False + if not self.is_animating: + target_is_tor = not self.is_tor_mode + new_connection = 'tor' if target_is_tor else 'system' + core_logger.info(f"Tor toggle clicked; switching connection mode to '{new_connection}'") + self.update_status(f"Tor toggle clicked: switching to {new_connection}") + + try: + ConfigurationController.set_connection(new_connection) + except Exception as error: + core_logger.exception( + f"Tor toggle failed while switching connection mode to '{new_connection}'") + self.update_status(f"Tor toggle failed: {error}") + self.set_toggle_state(self.is_tor_mode) + return + + self.set_toggle_state(target_is_tor) self.is_animating = True self.animation_step = 0 self.animation_timer.start(16) - self.is_tor_mode = not self.is_tor_mode - new_connection = 'tor' if self.is_tor_mode else 'system' - ConfigurationController.set_connection(new_connection) - core_logger.info(f"User toggled connection mode to '{new_connection}'") + return + + core_logger.info("Tor toggle clicked while animation was still running") + self.update_status("Tor toggle clicked: still switching") + + def raise_bottom_controls(self): + for widget in (self.status_label, self.tor_text, self.toggle_button, self.tor_icon, self.sync_button): + widget.raise_() + + def eventFilter(self, source, event): + if event.type() == QEvent.Type.MouseButtonPress and event.button() == Qt.MouseButton.LeftButton: + if self._is_toggle_event(event): + self.toggle_mode() + return True + return super().eventFilter(source, event) + + def _is_toggle_event(self, event): + if not hasattr(self, 'toggle_button') or not self.toggle_button.isVisible(): + return False + if hasattr(event, 'globalPosition'): + global_pos = event.globalPosition().toPoint() + else: + global_pos = event.globalPos() + for widget in (self.tor_text, self.toggle_button, self.tor_icon): + if widget.isVisible() and widget.rect().contains(widget.mapFromGlobal(global_pos)): + return True + return False def animate_toggle(self): TOTAL_STEPS = 5 @@ -699,6 +747,8 @@ class CustomWindow(QMainWindow): self.toggle_button.setGeometry(540, 541, 50, 22) self.tor_icon.setGeometry(600, 537, 25, 25) + self.raise_bottom_controls() + if current_page != previous_page: self.page_history.append(current_page) diff --git a/gui/v2/__pycache__/__init__.cpython-312.pyc b/gui/v2/__pycache__/__init__.cpython-312.pyc old mode 100644 new mode 100755 index af7ebd6..6eeaf30 Binary files a/gui/v2/__pycache__/__init__.cpython-312.pyc and b/gui/v2/__pycache__/__init__.cpython-312.pyc differ diff --git a/gui/v2/__pycache__/__main__.cpython-312.pyc b/gui/v2/__pycache__/__main__.cpython-312.pyc index 1e89a98..439c15b 100644 Binary files a/gui/v2/__pycache__/__main__.cpython-312.pyc and b/gui/v2/__pycache__/__main__.cpython-312.pyc differ diff --git a/gui/v2/actions/__pycache__/key_interpretation.cpython-312.pyc b/gui/v2/actions/__pycache__/key_interpretation.cpython-312.pyc deleted file mode 100644 index 8e630a1..0000000 Binary files a/gui/v2/actions/__pycache__/key_interpretation.cpython-312.pyc and /dev/null differ diff --git a/gui/v2/actions/__pycache__/locations.cpython-312.pyc b/gui/v2/actions/__pycache__/locations.cpython-312.pyc index 0cbb980..3138239 100644 Binary files a/gui/v2/actions/__pycache__/locations.cpython-312.pyc and b/gui/v2/actions/__pycache__/locations.cpython-312.pyc differ diff --git a/gui/v2/actions/locations.py b/gui/v2/actions/locations.py index 3e7ba6e..c773f78 100755 --- a/gui/v2/actions/locations.py +++ b/gui/v2/actions/locations.py @@ -26,6 +26,7 @@ def location_candidates(profile, preferred=None): pass for source in sources: + add(getattr(source, 'id', None)) add(getattr(source, 'country_name', None)) add(getattr(source, 'name', None)) add(getattr(source, 'code', None)) diff --git a/gui/v2/ui/builders/__pycache__/bottom_section.cpython-312.pyc b/gui/v2/ui/builders/__pycache__/bottom_section.cpython-312.pyc index a531564..a7ed5a9 100644 Binary files a/gui/v2/ui/builders/__pycache__/bottom_section.cpython-312.pyc and b/gui/v2/ui/builders/__pycache__/bottom_section.cpython-312.pyc differ diff --git a/gui/v2/ui/builders/bottom_section.py b/gui/v2/ui/builders/bottom_section.py index 378cbb8..ac2654a 100755 --- a/gui/v2/ui/builders/bottom_section.py +++ b/gui/v2/ui/builders/bottom_section.py @@ -17,6 +17,7 @@ def create_bottom_section(parent, btn_path, client_version, is_sync_urgent): tor_text = QLabel("Billing & Browsers", parent) tor_text.setGeometry(532, 541, 150, 20) + tor_text.setCursor(Qt.CursorShape.PointingHandCursor) tor_text.setStyleSheet(""" QLabel { color: cyan; @@ -28,9 +29,11 @@ def create_bottom_section(parent, btn_path, client_version, is_sync_urgent): toggle_button = QPushButton(parent) toggle_button.setGeometry(670, 541, 50, 22) toggle_button.setCheckable(True) + toggle_button.setCursor(Qt.CursorShape.PointingHandCursor) tor_icon = QLabel(parent) tor_icon.setGeometry(730, 537, 25, 25) + tor_icon.setCursor(Qt.CursorShape.PointingHandCursor) sync_button = QPushButton(parent) sync_button.setGeometry(771, 541, 22, 22) diff --git a/gui/v2/ui/pages/__pycache__/Page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/Page.cpython-312.pyc index ab55743..272ca07 100644 Binary files a/gui/v2/ui/pages/__pycache__/Page.cpython-312.pyc and b/gui/v2/ui/pages/__pycache__/Page.cpython-312.pyc differ diff --git a/gui/v2/ui/pages/__pycache__/editor_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/editor_page.cpython-312.pyc deleted file mode 100644 index cfce092..0000000 Binary files a/gui/v2/ui/pages/__pycache__/editor_page.cpython-312.pyc and /dev/null differ diff --git a/gui/v2/ui/pages/__pycache__/fast_registration_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/fast_registration_page.cpython-312.pyc deleted file mode 100644 index af4ff32..0000000 Binary files a/gui/v2/ui/pages/__pycache__/fast_registration_page.cpython-312.pyc and /dev/null differ diff --git a/gui/v2/ui/pages/__pycache__/id_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/id_page.cpython-312.pyc new file mode 100644 index 0000000..0f8512f Binary files /dev/null and b/gui/v2/ui/pages/__pycache__/id_page.cpython-312.pyc differ diff --git a/gui/v2/ui/pages/__pycache__/payment_details_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/payment_details_page.cpython-312.pyc new file mode 100644 index 0000000..288ffe6 Binary files /dev/null and b/gui/v2/ui/pages/__pycache__/payment_details_page.cpython-312.pyc differ diff --git a/gui/v2/ui/pages/__pycache__/plan_picker_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/plan_picker_page.cpython-312.pyc new file mode 100644 index 0000000..5b31cee Binary files /dev/null and b/gui/v2/ui/pages/__pycache__/plan_picker_page.cpython-312.pyc differ diff --git a/gui/v2/ui/pages/__pycache__/resume_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/resume_page.cpython-312.pyc new file mode 100644 index 0000000..12837c2 Binary files /dev/null and b/gui/v2/ui/pages/__pycache__/resume_page.cpython-312.pyc differ diff --git a/gui/v2/ui/pages/__pycache__/settings_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/settings_page.cpython-312.pyc deleted file mode 100644 index 6aa9a7d..0000000 Binary files a/gui/v2/ui/pages/__pycache__/settings_page.cpython-312.pyc and /dev/null differ diff --git a/gui/v2/ui/pages/__pycache__/sync_screen.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/sync_screen.cpython-312.pyc new file mode 100644 index 0000000..b403f5a Binary files /dev/null and b/gui/v2/ui/pages/__pycache__/sync_screen.cpython-312.pyc differ diff --git a/gui/v2/ui/pages/__pycache__/ticket_crypto_picker_page.cpython-312.pyc b/gui/v2/ui/pages/__pycache__/ticket_crypto_picker_page.cpython-312.pyc new file mode 100644 index 0000000..901dfc8 Binary files /dev/null and b/gui/v2/ui/pages/__pycache__/ticket_crypto_picker_page.cpython-312.pyc differ diff --git a/gui/v2/ui/pages/sync_screen.py b/gui/v2/ui/pages/sync_screen.py index 0bae2e8..5dcd9c6 100755 --- a/gui/v2/ui/pages/sync_screen.py +++ b/gui/v2/ui/pages/sync_screen.py @@ -54,10 +54,25 @@ class SyncScreen(Page): self.button_go.clicked.connect(self.perform_sync) self.button_back.clicked.connect(self.go_back) + def showEvent(self, event): + super().showEvent(event) + self.button_go.setVisible(True) + self.button_back.setVisible(True) + self.button_go.setEnabled(True) + self.button_back.setEnabled(True) + def go_back(self): self.custom_window.navigator.navigate("menu") def perform_sync(self): + if self.connection_manager.is_synced(): + self.button_go.setVisible(True) + self.button_back.setVisible(True) + self.button_go.setEnabled(True) + self.button_back.setEnabled(True) + self.custom_window.navigator.navigate("protocol") + return + self.button_go.setEnabled(False) self.button_back.setEnabled(False) self.update_status.update_status('Sync in progress...') @@ -98,6 +113,8 @@ class SyncScreen(Page): self.custom_window.update_values( available_locations, available_browsers, status, is_tor, locations, all_browsers) + self.button_go.setEnabled(True) + self.button_back.setEnabled(True) self.custom_window.navigator.navigate("protocol") def update_after_sync(self, available_locations, available_browsers, locations, all_browsers=None): diff --git a/gui/v2/ui/pages/ticket_crypto_picker_page.py b/gui/v2/ui/pages/ticket_crypto_picker_page.py index 36bd719..60e798d 100755 --- a/gui/v2/ui/pages/ticket_crypto_picker_page.py +++ b/gui/v2/ui/pages/ticket_crypto_picker_page.py @@ -1,10 +1,13 @@ import os -from PyQt6.QtWidgets import QButtonGroup, QMessageBox, QPushButton +from PyQt6.QtWidgets import QApplication, QButtonGroup, QMessageBox, QPushButton from PyQt6.QtGui import QIcon from PyQt6.QtCore import QSize from PyQt6 import QtCore +from core.controllers.tickets.TicketPayController import check_if_paid + +from gui.v2.infrastructure.setup_observers import connection_observer, ticket_observer from gui.v2.ui.pages.Page import Page from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread @@ -60,6 +63,22 @@ class TicketCryptoPickerPage(Page): currency = selected_button.property('currency') self.start_initiate_payment(currency) + def check_if_paid_for_existing(self, temp_billing_code): + self.update_status.update_status("Checking if paid...") + result = check_if_paid(temp_billing_code, ticket_observer, connection_observer) + if isinstance(result, dict) and result.get('valid') and result.get('payment_status') == 'paid': + self.update_status.update_status("Already Paid!") + self.custom_window.navigator.navigate("ticket_prep") + prep_page = self.custom_window.navigator.get_cached("ticket_prep") + if prep_page: + prep_page.start_prep() + else: + self.update_status.update_status("Not yet paid.") + clipboard = QApplication.clipboard() + clipboard.setText(temp_billing_code) + not_paid_msg = f"The billing code is not yet showing paid for {temp_billing_code}. Right now, your clipboard has the billing code, to paste it in any text editor. If you did pay, either wait longer for blockchain confirmation, or contact customer support with the code in your clipboard now." + QMessageBox.information(None, "Not Paid", not_paid_msg) + def start_initiate_payment(self, currency): self.update_status.update_status("Initiating payment...") self.worker = TicketingWorkerThread('INITIATE_PAYMENT', params={ @@ -81,6 +100,12 @@ class TicketCryptoPickerPage(Page): if error_code == 'already_exists' and not self.bypass_existing: self._prompt_wipe_existing(invoice) return + if error_code == 'billing_code_exists' and not self.bypass_existing: + temp_billing_code = getattr(invoice, 'temp_billing_code', None) + print(f"temp_billing_code is {temp_billing_code}") + if temp_billing_code: + self._prompt_wipe_billingcode(temp_billing_code) + return if error_code: msg = getattr(invoice, 'final_error_msg', None) or error_code self.update_status.update_status(f"Payment error: {msg}") @@ -105,6 +130,21 @@ class TicketCryptoPickerPage(Page): else: self.update_status.update_status("Cancelled.") + def _prompt_wipe_billingcode(self, temp_billing_code): + msg = QMessageBox(self) + msg.setWindowTitle("Existing billing code found") + msg.setText("You already have a ticket billing code. Do you want to use it? Only hit YES if you ALREADY paid.") + msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + result = msg.exec() + if result == QMessageBox.StandardButton.Yes: + self.update_status.update_status("Reusing same code") + self.check_if_paid_for_existing(temp_billing_code) + else: + self.bypass_existing = True + currency_btn = self.buttonGroup.checkedButton() + if currency_btn: + self.start_initiate_payment(currency_btn.property('currency')) + def on_error(self, msg): self.update_status.update_status(f"Payment error: {msg}") diff --git a/gui/v2/ui/popups/__pycache__/qrcode_dialog.cpython-312.pyc b/gui/v2/ui/popups/__pycache__/qrcode_dialog.cpython-312.pyc new file mode 100644 index 0000000..e0d2fd7 Binary files /dev/null and b/gui/v2/ui/popups/__pycache__/qrcode_dialog.cpython-312.pyc differ diff --git a/gui/v2/ui/widgets/__pycache__/__init__.cpython-312.pyc b/gui/v2/ui/widgets/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 0dfcc26..0000000 Binary files a/gui/v2/ui/widgets/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/gui/v2/ui/widgets/__pycache__/clickable_label.cpython-312.pyc b/gui/v2/ui/widgets/__pycache__/clickable_label.cpython-312.pyc deleted file mode 100644 index b9f7f9d..0000000 Binary files a/gui/v2/ui/widgets/__pycache__/clickable_label.cpython-312.pyc and /dev/null differ diff --git a/gui/v2/ui/widgets/__pycache__/terminal_widget.cpython-312.pyc b/gui/v2/ui/widgets/__pycache__/terminal_widget.cpython-312.pyc deleted file mode 100644 index 76757c5..0000000 Binary files a/gui/v2/ui/widgets/__pycache__/terminal_widget.cpython-312.pyc and /dev/null differ diff --git a/gui/v2/workers/__pycache__/worker.cpython-312.pyc b/gui/v2/workers/__pycache__/worker.cpython-312.pyc index 37efc16..35bbd93 100644 Binary files a/gui/v2/workers/__pycache__/worker.cpython-312.pyc and b/gui/v2/workers/__pycache__/worker.cpython-312.pyc differ diff --git a/gui/v2/workers/worker.py b/gui/v2/workers/worker.py index 0415579..f65f4e0 100755 --- a/gui/v2/workers/worker.py +++ b/gui/v2/workers/worker.py @@ -141,6 +141,13 @@ class Worker(QObject): return False def _maybe_auto_use_ticket(self): + profile_connection_type = self.profile.connection.code + + if profile_connection_type != "wireguard": + error_msg = "Tickets are not yet supported for Tor. We are very sorry, but we're not able to develop all features for all products." + print(error_msg) + return None + try: which_ticket, error_msg = do_we_use_a_random_ticket(ticket_observer) except Exception: