license possibility
This commit is contained in:
parent
dbc6361027
commit
4ee7f50d65
@ -6,3 +6,8 @@ EMAIL_ADDRESS=your_email@gmail.com
|
||||
EMAIL_PASSWORD=your_app_password
|
||||
EMAIL_SMTP_SERVER=smtp.gmail.com
|
||||
EMAIL_SMTP_PORT=587
|
||||
|
||||
# Licensing configuration
|
||||
LICENSE_API_URL=https://your-server.com/api/licenses
|
||||
PURCHASE_URL=https://your-website.com/buy
|
||||
LICENSE_API_KEY=your_license_api_key
|
||||
220
app/core/license_manager.py
Normal file
220
app/core/license_manager.py
Normal file
@ -0,0 +1,220 @@
|
||||
import hashlib
|
||||
import platform
|
||||
import uuid
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict
|
||||
from PyQt6.QtCore import QObject, pyqtSignal
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import app.utils.paths as paths
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv(paths.resource_path(".env"))
|
||||
|
||||
class LicenseManager(QObject):
|
||||
"""Gestionnaire de licences avec liaison matérielle"""
|
||||
|
||||
license_activated = pyqtSignal(str) # Signal émis lors de l'activation
|
||||
license_expired = pyqtSignal()
|
||||
|
||||
def __init__(self, settings_manager):
|
||||
super().__init__()
|
||||
self.settings_manager = settings_manager
|
||||
self.licensing_enabled = settings_manager.get_config("enable_licensing")
|
||||
|
||||
# Si le système de licence est désactivé, initialiser en mode gratuit
|
||||
if not self.licensing_enabled:
|
||||
self.hardware_id = None
|
||||
self.license_key = ""
|
||||
self.license_data = None
|
||||
return
|
||||
|
||||
# Charger l'URL de l'API depuis .env
|
||||
self.api_url = os.getenv("LICENSE_API_URL")
|
||||
if not self.api_url:
|
||||
raise ValueError("LICENSE_API_URL non définie dans le fichier .env")
|
||||
|
||||
self.hardware_id = self._get_hardware_id()
|
||||
|
||||
# Charger la licence sauvegardée
|
||||
self.license_key = settings_manager.settings.value("license_key", "")
|
||||
self.license_data = None
|
||||
|
||||
if self.license_key:
|
||||
self._load_license_data()
|
||||
|
||||
def _get_hardware_id(self) -> str:
|
||||
"""Génère un identifiant unique basé sur le matériel"""
|
||||
# Combine plusieurs éléments matériels pour un ID unique
|
||||
mac = ':'.join(['{:02x}'.format((uuid.getnode() >> elements) & 0xff)
|
||||
for elements in range(0, 2*6, 2)][::-1])
|
||||
system = platform.system()
|
||||
machine = platform.machine()
|
||||
processor = platform.processor()
|
||||
|
||||
# Créer un hash unique
|
||||
unique_string = f"{mac}{system}{machine}{processor}"
|
||||
return hashlib.sha256(unique_string.encode()).hexdigest()
|
||||
|
||||
def get_hardware_id(self) -> str:
|
||||
"""Retourne le hardware ID pour l'afficher à l'utilisateur"""
|
||||
return self.hardware_id
|
||||
|
||||
def activate_license(self, license_key: str) -> Dict:
|
||||
"""
|
||||
Active une licence avec le serveur
|
||||
|
||||
Returns:
|
||||
dict: {"success": bool, "message": str, "data": dict}
|
||||
"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/activate",
|
||||
json={
|
||||
"license_key": license_key,
|
||||
"hardware_id": self.hardware_id,
|
||||
"app_version": self.settings_manager.get_config("app_version"),
|
||||
"platform": platform.system()
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
|
||||
if response.status_code == 200 and data.get("success"):
|
||||
# Sauvegarder la licence
|
||||
self.license_key = license_key
|
||||
self.license_data = data.get("license_data", {})
|
||||
self.settings_manager.settings.setValue("license_key", license_key)
|
||||
self.settings_manager.settings.setValue("license_data", self.license_data)
|
||||
|
||||
self.license_activated.emit(license_key)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "Licence activée avec succès",
|
||||
"data": self.license_data
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"message": data.get("message", "Erreur d'activation"),
|
||||
"data": None
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Erreur de connexion au serveur: {str(e)}",
|
||||
"data": None
|
||||
}
|
||||
|
||||
def verify_license(self) -> bool:
|
||||
"""Vérifie la validité de la licence avec le serveur"""
|
||||
if not self.license_key:
|
||||
return False
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/verify",
|
||||
json={
|
||||
"license_key": self.license_key,
|
||||
"hardware_id": self.hardware_id
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
|
||||
if response.status_code == 200 and data.get("valid"):
|
||||
self.license_data = data.get("license_data", {})
|
||||
self.settings_manager.settings.setValue("license_data", self.license_data)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
# En cas d'erreur réseau, utiliser les données en cache
|
||||
return self._verify_offline()
|
||||
|
||||
def _verify_offline(self) -> bool:
|
||||
"""Vérification hors ligne basique"""
|
||||
if not self.license_key or not self.license_data:
|
||||
return False
|
||||
|
||||
# Vérifier que le hardware_id correspond
|
||||
if self.license_data.get("hardware_id") != self.hardware_id:
|
||||
return False
|
||||
|
||||
# Vérifier la date d'expiration si applicable
|
||||
expires_at = self.license_data.get("expires_at")
|
||||
if expires_at:
|
||||
expiry_date = datetime.fromisoformat(expires_at)
|
||||
if datetime.now() > expiry_date:
|
||||
self.license_expired.emit()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _load_license_data(self):
|
||||
"""Charge les données de licence depuis les settings"""
|
||||
self.license_data = self.settings_manager.settings.value("license_data", {})
|
||||
|
||||
def is_activated(self) -> bool:
|
||||
"""Vérifie si l'application est activée"""
|
||||
# Si le système de licence est désactivé, toujours retourner False
|
||||
if not self.licensing_enabled:
|
||||
return False
|
||||
|
||||
return bool(self.license_key) and self.verify_license()
|
||||
|
||||
def get_license_type(self) -> str:
|
||||
"""Retourne le type de licence (free, premium, etc.)"""
|
||||
# Si le système de licence est désactivé, retourner "free"
|
||||
if not self.licensing_enabled:
|
||||
return "free"
|
||||
|
||||
if not self.license_data:
|
||||
return "free"
|
||||
return self.license_data.get("type", "free")
|
||||
|
||||
def is_feature_available(self, feature_id: str) -> bool:
|
||||
"""
|
||||
Vérifie si une fonctionnalité est disponible
|
||||
|
||||
Args:
|
||||
feature_id: Identifiant de la fonctionnalité (ex: "advanced_export")
|
||||
"""
|
||||
# Si le système de licence est désactivé, toutes les fonctionnalités sont disponibles
|
||||
if not self.licensing_enabled:
|
||||
return True
|
||||
|
||||
# Si pas de licence, vérifier dans la config des features gratuites
|
||||
if not self.is_activated():
|
||||
free_features = self.settings_manager.get_config("features_by_license").get("free", [])
|
||||
return feature_id in free_features
|
||||
|
||||
# Vérifier les features autorisées par la licence
|
||||
license_type = self.get_license_type()
|
||||
features_by_type = self.settings_manager.get_config("features_by_license")
|
||||
allowed_features = features_by_type.get(license_type, [])
|
||||
|
||||
return feature_id in allowed_features or feature_id in self.settings_manager.get_config("free_features")
|
||||
|
||||
def get_license_info(self) -> Dict:
|
||||
"""Retourne les informations de la licence"""
|
||||
if not self.license_data:
|
||||
return {
|
||||
"type": "free",
|
||||
"status": "inactive",
|
||||
"expires_at": None
|
||||
}
|
||||
|
||||
return {
|
||||
"type": self.get_license_type(),
|
||||
"status": "active" if self.is_activated() else "inactive",
|
||||
"expires_at": self.license_data.get("expires_at"),
|
||||
"email": self.license_data.get("email"),
|
||||
"activated_at": self.license_data.get("activated_at")
|
||||
}
|
||||
@ -4,6 +4,8 @@ from app.core.theme_manager import ThemeManager
|
||||
from app.core.settings_manager import SettingsManager
|
||||
from app.core.alert_manager import AlertManager
|
||||
from app.core.update_manager import UpdateManager
|
||||
from app.core.license_manager import LicenseManager
|
||||
|
||||
from typing import Optional
|
||||
|
||||
class MainManager:
|
||||
@ -20,7 +22,7 @@ class MainManager:
|
||||
self.language_manager: LanguageManager = LanguageManager(self.settings_manager)
|
||||
self.alert_manager: AlertManager = AlertManager(self.language_manager, self.theme_manager)
|
||||
self.update_manager: UpdateManager = UpdateManager(self.settings_manager, self.language_manager, self.alert_manager)
|
||||
|
||||
self.license_manager: LicenseManager = LicenseManager(self.settings_manager)
|
||||
@classmethod
|
||||
def get_instance(cls) -> 'MainManager':
|
||||
if cls._instance is None:
|
||||
@ -44,3 +46,6 @@ class MainManager:
|
||||
|
||||
def get_update_manager(self) -> UpdateManager:
|
||||
return self.update_manager
|
||||
|
||||
def get_license_manager(self) -> LicenseManager:
|
||||
return self.license_manager
|
||||
@ -66,9 +66,17 @@ class ThemeManager:
|
||||
border-radius: 3px;
|
||||
}}
|
||||
QTextEdit {{
|
||||
border: 1px solid {self.current_theme.get_color("border_color")};
|
||||
border: 2px solid {self.current_theme.get_color("border_color")};
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
background-color: {self.current_theme.get_color("background_secondary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
}}
|
||||
QLineEdit {{
|
||||
border: 2px solid {self.current_theme.get_color("border_color")};
|
||||
border-radius: 8px;
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
background-color: {self.current_theme.get_color("background_secondary_color")};
|
||||
color: {self.current_theme.get_color("text_color")};
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
from PyQt6.QtWidgets import QApplication, QMainWindow
|
||||
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel
|
||||
from PyQt6.QtGui import QResizeEvent, QCloseEvent
|
||||
from PyQt6.QtCore import QSize, QEvent
|
||||
from app.core.main_manager import MainManager, NotificationType
|
||||
from app.ui.widgets.tabs_widget import TabsWidget, MenuDirection, ButtonPosition, BorderSide, TabSide
|
||||
from app.ui.windows.settings_window import SettingsWindow
|
||||
from app.ui.windows.suggestion_window import SuggestionWindow
|
||||
from app.ui.windows.activation_window import ActivationWindow
|
||||
import app.utils.paths as paths, shutil
|
||||
from typing import Optional
|
||||
|
||||
@ -31,18 +32,24 @@ class MainWindow(QMainWindow):
|
||||
self.current_size: QSize = QSize(window_size["width"], window_size["height"])
|
||||
self.previous_size: QSize = QSize(window_size["width"], window_size["height"])
|
||||
|
||||
# Configuration des tailles de police de référence
|
||||
self.base_width = 600 # Largeur de référence
|
||||
self.base_height = 450 # Hauteur de référence
|
||||
|
||||
# Cache pour stocker les font-sizes de base de chaque widget
|
||||
self._base_font_sizes = {}
|
||||
|
||||
# UI elements
|
||||
self.side_menu: TabsWidget
|
||||
self.settings_window: SettingsWindow
|
||||
self.suggestion_window: SuggestionWindow
|
||||
|
||||
self.setMinimumSize(600, 400)
|
||||
self.setMinimumSize(600, 450)
|
||||
|
||||
# Initialiser l'UI immédiatement (sera fait pendant le splash)
|
||||
self.setup_ui()
|
||||
|
||||
# Différer l'application des paramètres de fenêtre jusqu'à l'affichage réel
|
||||
# (cela évite des bugs de taille pendant le préchargement)
|
||||
self._window_state_applied = False
|
||||
|
||||
def showEvent(self, event):
|
||||
@ -81,6 +88,92 @@ class MainWindow(QMainWindow):
|
||||
self.previous_size = self.current_size
|
||||
self.current_size = self.size()
|
||||
|
||||
# Ajuster dynamiquement les font-sizes
|
||||
self.adjust_all_font_sizes()
|
||||
|
||||
def adjust_all_font_sizes(self):
|
||||
"""Ajuste dynamiquement les font-sizes de tous les labels dans toutes les tabs"""
|
||||
# Calculer le ratio basé sur la largeur ET la hauteur actuelle
|
||||
current_width = self.width()
|
||||
current_height = self.height()
|
||||
|
||||
# Calculer les ratios séparément
|
||||
width_ratio = current_width / self.base_width
|
||||
height_ratio = current_height / self.base_height
|
||||
|
||||
# Prendre le ratio le plus petit pour éviter que le texte dépasse
|
||||
ratio = min(width_ratio, height_ratio)
|
||||
|
||||
# Récupérer tous les widgets des tabs
|
||||
all_widgets = []
|
||||
if hasattr(self, 'side_menu'):
|
||||
all_widgets = self.side_menu.widgets
|
||||
|
||||
# Parcourir tous les widgets et ajuster leurs labels
|
||||
for widget in all_widgets:
|
||||
if widget:
|
||||
self._adjust_widget_labels(widget, ratio)
|
||||
|
||||
def _adjust_widget_labels(self, widget, ratio):
|
||||
"""Ajuste récursivement tous les QLabel, QPushButton, QLineEdit, QTextEdit et QComboBox d'un widget"""
|
||||
from PyQt6.QtWidgets import QPushButton, QLineEdit, QTextEdit, QComboBox
|
||||
|
||||
# Types de widgets à ajuster
|
||||
widget_types = [QLabel, QPushButton, QLineEdit, QTextEdit, QComboBox]
|
||||
font_size_dict = self.extract_base_font_size()
|
||||
for widget_type in widget_types:
|
||||
for child in widget.findChildren(widget_type):
|
||||
# Obtenir l'identifiant unique du widget
|
||||
widget_id = id(child)
|
||||
|
||||
# Si c'est la première fois qu'on voit ce widget, extraire sa font-size de base
|
||||
if widget_id not in self._base_font_sizes:
|
||||
base_size = font_size_dict.get(child.__class__.__name__, 14)
|
||||
self._base_font_sizes[widget_id] = base_size
|
||||
else:
|
||||
base_size = self._base_font_sizes[widget_id]
|
||||
|
||||
# Calculer la nouvelle taille
|
||||
new_size = int(base_size * ratio)
|
||||
|
||||
|
||||
# Appliquer le style (en préservant les autres styles existants)
|
||||
current_style = child.styleSheet()
|
||||
# Retirer l'ancienne font-size si elle existe
|
||||
style_parts = [s.strip() for s in current_style.split(';') if s.strip()]
|
||||
style_parts = [s for s in style_parts if not s.startswith('font-size')]
|
||||
|
||||
# Ajouter la nouvelle font-size
|
||||
style_parts.append(f'font-size: {new_size}px')
|
||||
|
||||
new_style = '; '.join(style_parts)
|
||||
child.setStyleSheet(new_style)
|
||||
|
||||
def extract_base_font_size(self) -> dict:
|
||||
"""Extrait la font-size de base d'un widget depuis son stylesheet"""
|
||||
base_font_sizes = {}
|
||||
try:
|
||||
style = self.theme_manager.get_sheet()
|
||||
|
||||
# Chercher "font-size: XXpx" dans le style, puis chercher à quel widget cela correspond
|
||||
lines = style.splitlines()
|
||||
component = None
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line.startswith("font-size:"):
|
||||
size_part = line.split(":")[1].strip().rstrip(";")
|
||||
if size_part.endswith("px"):
|
||||
size_value = int(size_part[:-2])
|
||||
base_font_sizes[component] = size_value
|
||||
elif line.startswith("Q"):
|
||||
component = line.split("{")[0].strip()
|
||||
return base_font_sizes
|
||||
|
||||
|
||||
except Exception:
|
||||
# En cas d'erreur, retourner une valeur par défaut
|
||||
return {}
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Handle application close event"""
|
||||
super().closeEvent(event)
|
||||
@ -106,6 +199,11 @@ class MainWindow(QMainWindow):
|
||||
self.settings_window = SettingsWindow(self)
|
||||
self.side_menu.add_widget(self.settings_window, "", paths.get_asset_svg_path("settings"), position=ButtonPosition.CENTER)
|
||||
|
||||
# Ajouter la tab d'activation uniquement si le système de licence est activé
|
||||
if self.settings_manager.get_config("enable_licensing"):
|
||||
self.activation_window = ActivationWindow(self)
|
||||
self.side_menu.add_widget(self.activation_window, "", paths.get_asset_svg_path("license"), position=ButtonPosition.END)
|
||||
|
||||
self.setCentralWidget(self.side_menu)
|
||||
|
||||
def update_theme(self) -> None:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from PyQt6.QtWidgets import QLayout, QWidget, QHBoxLayout, QVBoxLayout, QPushButton, QStackedWidget, QSizePolicy, QSpacerItem
|
||||
from PyQt6.QtGui import QIcon
|
||||
from PyQt6.QtCore import QSize
|
||||
from PyQt6.QtCore import QSize, Qt
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
@ -204,6 +204,9 @@ class TabsWidget(QWidget):
|
||||
# Make button square with specified ratio
|
||||
self._style_square_button(button)
|
||||
|
||||
# Configurer le widget pour qu'il soit responsive
|
||||
widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
|
||||
# Add to collections first
|
||||
widget_index = len(self.widgets)
|
||||
self.buttons.append(button)
|
||||
|
||||
241
app/ui/windows/activation_window.py
Normal file
241
app/ui/windows/activation_window.py
Normal file
@ -0,0 +1,241 @@
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QLineEdit, QPushButton, QFrame, QSizePolicy)
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
||||
from app.core.main_manager import MainManager, NotificationType
|
||||
from app.ui.widgets.loading_bar import LoadingBar
|
||||
import webbrowser
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import app.utils.paths as paths
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv(paths.resource_path(".env"))
|
||||
|
||||
class ActivationThread(QThread):
|
||||
"""Thread pour l'activation afin de ne pas bloquer l'UI"""
|
||||
finished = pyqtSignal(dict)
|
||||
progress = pyqtSignal(int)
|
||||
|
||||
def __init__(self, license_manager, license_key):
|
||||
super().__init__()
|
||||
self.license_manager = license_manager
|
||||
self.license_key = license_key
|
||||
|
||||
def run(self):
|
||||
self.progress.emit(30)
|
||||
result = self.license_manager.activate_license(self.license_key)
|
||||
self.progress.emit(100)
|
||||
self.finished.emit(result)
|
||||
|
||||
class ActivationWindow(QWidget):
|
||||
"""Fenêtre d'activation de licence modernisée"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.main_manager = MainManager.get_instance()
|
||||
self.license_manager = self.main_manager.get_license_manager()
|
||||
self.language_manager = self.main_manager.get_language_manager()
|
||||
self.theme_manager = self.main_manager.get_theme_manager()
|
||||
self.alert_manager = self.main_manager.get_alert_manager()
|
||||
self.observer_manager = self.main_manager.get_observer_manager()
|
||||
self.settings_manager = self.main_manager.get_settings_manager()
|
||||
|
||||
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
|
||||
self.observer_manager.subscribe(NotificationType.THEME, self.update_theme)
|
||||
|
||||
self.activation_thread = None
|
||||
|
||||
self.setup_ui()
|
||||
self.update_license_status()
|
||||
|
||||
def setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(20)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# === Section titre ===
|
||||
self.title_label = QLabel(self.language_manager.get_text("activate_license"))
|
||||
self.title_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
self.title_label.setWordWrap(True)
|
||||
layout.addWidget(self.title_label)
|
||||
|
||||
# === Spacer flexible ===
|
||||
layout.addStretch(1)
|
||||
|
||||
# === Section clé de licence ===
|
||||
key_layout = QHBoxLayout()
|
||||
|
||||
self.key_title = QLabel(self.language_manager.get_text("license_key_section"))
|
||||
self.key_title.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
key_layout.addWidget(self.key_title)
|
||||
|
||||
self.key_input = QLineEdit()
|
||||
self.key_input.setPlaceholderText("XXXX-XXXX-XXXX-XXXX-XXXX")
|
||||
self.key_input.textChanged.connect(self.format_license_key)
|
||||
self.key_input.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
key_layout.addWidget(self.key_input)
|
||||
|
||||
layout.addLayout(key_layout)
|
||||
# === Barre de progression ===
|
||||
self.progress_bar = LoadingBar(self.language_manager.get_text("loading"))
|
||||
self.progress_bar.setVisible(False)
|
||||
self.progress_bar.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
layout.addWidget(self.progress_bar)
|
||||
|
||||
# === Spacer flexible ===
|
||||
layout.addStretch(1)
|
||||
|
||||
# === Section statut ===
|
||||
self.status_label = QLabel()
|
||||
self.status_label.setWordWrap(True)
|
||||
self.status_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
# === Spacer flexible ===
|
||||
layout.addStretch(1)
|
||||
|
||||
# === Boutons d'action ===
|
||||
button_layout = QHBoxLayout()
|
||||
button_layout.setSpacing(10)
|
||||
|
||||
self.activate_btn = QPushButton(self.language_manager.get_text("activate"))
|
||||
self.activate_btn.clicked.connect(self.activate_license)
|
||||
self.activate_btn.setDefault(True)
|
||||
self.activate_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
button_layout.addWidget(self.activate_btn)
|
||||
|
||||
self.buy_btn = QPushButton(self.language_manager.get_text("buy_license"))
|
||||
self.buy_btn.clicked.connect(self.open_purchase_page)
|
||||
self.buy_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
button_layout.addWidget(self.buy_btn)
|
||||
|
||||
self.compare_btn = QPushButton(self.language_manager.get_text("compare_versions"))
|
||||
self.compare_btn.clicked.connect(self.show_features_comparison)
|
||||
self.compare_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
button_layout.addWidget(self.compare_btn)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
def show_features_comparison(self):
|
||||
"""Affiche la comparaison des versions dans une alerte"""
|
||||
comparison_text = f"{self.language_manager.get_text('comparaisons')}\n\n"
|
||||
|
||||
for version in self.settings_manager.get_config("features_by_license").keys():
|
||||
comparison_text += f"{self.language_manager.get_text(version+'_version')}:\n"
|
||||
features = self.settings_manager.get_config("features_by_license").get(version, [])
|
||||
for feature in features:
|
||||
feature_desc = self.language_manager.get_text(feature)
|
||||
comparison_text += f" • {feature_desc}\n"
|
||||
comparison_text += "\n"
|
||||
|
||||
|
||||
self.alert_manager.show_info(comparison_text, parent=self)
|
||||
|
||||
def update_license_status(self):
|
||||
"""Met à jour l'affichage du statut de la licence"""
|
||||
if self.license_manager.is_activated():
|
||||
license_info = self.license_manager.get_license_info()
|
||||
license_type = license_info.get("type", "free").upper()
|
||||
|
||||
status_text = f"✓ {self.language_manager.get_text('license_active')}\n"
|
||||
status_text += f"{self.language_manager.get_text('license_type')}: {license_type}\n"
|
||||
if license_info.get("email"):
|
||||
status_text += f"{self.language_manager.get_text('license_email')}: {license_info['email']}\n"
|
||||
if license_info.get("expires_at"):
|
||||
status_text += f"{self.language_manager.get_text('license_expires')}: {license_info['expires_at']}"
|
||||
|
||||
self.status_label.setText(status_text)
|
||||
|
||||
self.key_input.setEnabled(False)
|
||||
self.activate_btn.setEnabled(False)
|
||||
else:
|
||||
status_text = f"{self.language_manager.get_text('license_free_mode')}"
|
||||
self.status_label.setText(status_text)
|
||||
|
||||
def format_license_key(self, text):
|
||||
"""Formate automatiquement la clé de licence (XXXX-XXXX-...)"""
|
||||
text = text.replace("-", "").upper()
|
||||
formatted = "-".join([text[i:i+4] for i in range(0, len(text), 4)])
|
||||
if len(formatted) > 24:
|
||||
formatted = formatted[:24]
|
||||
self.key_input.blockSignals(True)
|
||||
self.key_input.setText(formatted)
|
||||
self.key_input.blockSignals(False)
|
||||
self.key_input.setCursorPosition(len(formatted))
|
||||
|
||||
def activate_license(self):
|
||||
"""Lance l'activation de la licence"""
|
||||
license_key = self.key_input.text().replace("-", "")
|
||||
|
||||
if len(license_key) < 16:
|
||||
self.alert_manager.show_error("invalid_license_key")
|
||||
return
|
||||
|
||||
# Disable inputs during activation
|
||||
self.activate_btn.setEnabled(False)
|
||||
self.key_input.setEnabled(False)
|
||||
self.buy_btn.setEnabled(False)
|
||||
|
||||
# Show progress bar with initial message
|
||||
self.progress_bar.set_label(self.language_manager.get_text("loading"))
|
||||
self.progress_bar.set_progress(0)
|
||||
self.progress_bar.setVisible(True)
|
||||
|
||||
# Start activation thread
|
||||
self.activation_thread = ActivationThread(self.license_manager, license_key)
|
||||
self.activation_thread.finished.connect(self.on_activation_finished)
|
||||
self.activation_thread.progress.connect(self.on_activation_progress)
|
||||
self.activation_thread.start()
|
||||
|
||||
def on_activation_progress(self, value):
|
||||
"""Update progress bar during activation"""
|
||||
self.progress_bar.set_progress(value)
|
||||
|
||||
def on_activation_finished(self, result):
|
||||
"""Callback quand l'activation est terminée"""
|
||||
# Hide progress bar
|
||||
self.progress_bar.setVisible(False)
|
||||
|
||||
# Re-enable inputs
|
||||
self.activate_btn.setEnabled(True)
|
||||
self.key_input.setEnabled(True)
|
||||
self.buy_btn.setEnabled(True)
|
||||
|
||||
if result["success"]:
|
||||
# Show success message using AlertManager
|
||||
success_msg = result['message']
|
||||
if result.get("data"):
|
||||
success_msg += f"\n\n{self.language_manager.get_text('license_type')}: {result['data'].get('type', 'N/A')}"
|
||||
success_msg += f"\n{self.language_manager.get_text('license_email')}: {result['data'].get('email', 'N/A')}"
|
||||
|
||||
self.alert_manager.show_info(success_msg, parent=self)
|
||||
self.update_license_status()
|
||||
|
||||
# Clear the input field
|
||||
self.key_input.clear()
|
||||
else:
|
||||
# Show error message using AlertManager
|
||||
self.alert_manager.show_info(f"✗ {result['message']}", parent=self)
|
||||
|
||||
def open_purchase_page(self):
|
||||
"""Ouvre la page d'achat dans le navigateur"""
|
||||
purchase_url = os.getenv("PURCHASE_URL")
|
||||
if purchase_url:
|
||||
webbrowser.open(purchase_url)
|
||||
else:
|
||||
self.alert_manager.show_error("PURCHASE_URL non définie dans .env", parent=self)
|
||||
|
||||
def update_language(self):
|
||||
"""Met à jour tous les textes selon la nouvelle langue"""
|
||||
self.title_label.setText(self.language_manager.get_text("activate_license"))
|
||||
self.activate_btn.setText(self.language_manager.get_text("activate"))
|
||||
self.buy_btn.setText(self.language_manager.get_text("buy_license"))
|
||||
self.compare_btn.setText(self.language_manager.get_text("compare_versions"))
|
||||
self.progress_bar.set_label(self.language_manager.get_text("loading"))
|
||||
self.key_title.setText(self.language_manager.get_text("license_key_section"))
|
||||
self.update_license_status()
|
||||
|
||||
def update_theme(self):
|
||||
"""Met à jour le style selon le nouveau thème"""
|
||||
self.update_license_status()
|
||||
@ -1,4 +1,4 @@
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QLabel, QHBoxLayout
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QComboBox, QLabel, QHBoxLayout, QSizePolicy
|
||||
from PyQt6.QtCore import Qt
|
||||
from app.core.main_manager import MainManager, NotificationType
|
||||
from typing import Optional
|
||||
@ -26,38 +26,42 @@ class SettingsWindow(QWidget):
|
||||
|
||||
def setup_ui(self) -> None:
|
||||
layout: QVBoxLayout = QVBoxLayout(self)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.setSpacing(20)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
|
||||
layout.addStretch()
|
||||
layout.addStretch(1)
|
||||
|
||||
self.language_layout = QHBoxLayout()
|
||||
# Paramètres de langue
|
||||
self.languageLabel = QLabel(self.language_manager.get_text("language"),self)
|
||||
self.languageLabel.setFixedWidth(120) # Largeur fixe pour l'alignement
|
||||
self.languageLabel.setMinimumWidth(100)
|
||||
self.languageLabel.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
self.language_layout.addWidget(self.languageLabel)
|
||||
|
||||
self.languageCombo = self.createLanguageSelector()
|
||||
self.languageCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.language_layout.addWidget(self.languageCombo)
|
||||
|
||||
layout.addLayout(self.language_layout)
|
||||
|
||||
layout.addStretch()
|
||||
layout.addStretch(1)
|
||||
|
||||
# Paramètres de thème
|
||||
self.theme_layout = QHBoxLayout()
|
||||
|
||||
self.themeLabel = QLabel(self.language_manager.get_text("theme"), self)
|
||||
self.themeLabel.setFixedWidth(120) # Même largeur fixe pour l'alignement
|
||||
self.themeLabel.setMinimumWidth(100)
|
||||
self.themeLabel.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
self.theme_layout.addWidget(self.themeLabel)
|
||||
|
||||
self.themeCombo = self.createThemeSelector()
|
||||
self.themeCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
self.theme_layout.addWidget(self.themeCombo)
|
||||
|
||||
layout.addLayout(self.theme_layout)
|
||||
|
||||
layout.addStretch()
|
||||
layout.addStretch(1)
|
||||
|
||||
def createLanguageSelector(self) -> QComboBox:
|
||||
combo: QComboBox = QComboBox()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QLabel, QHBoxLayout
|
||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QTextEdit, QPushButton, QLabel, QHBoxLayout, QSizePolicy
|
||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal
|
||||
import smtplib, os
|
||||
from email.mime.text import MIMEText
|
||||
@ -62,6 +62,7 @@ class SuggestionWindow(QWidget):
|
||||
self.language_manager = self.main_manager.get_language_manager()
|
||||
self.settings_manager = self.main_manager.get_settings_manager()
|
||||
self.alert_manager = self.main_manager.get_alert_manager()
|
||||
self.license_manager = self.main_manager.get_license_manager()
|
||||
|
||||
self.observer_manager = self.main_manager.get_observer_manager()
|
||||
self.observer_manager.subscribe(NotificationType.LANGUAGE, self.update_language)
|
||||
@ -78,16 +79,16 @@ class SuggestionWindow(QWidget):
|
||||
|
||||
# Title
|
||||
self.title_label: QLabel = QLabel(self.language_manager.get_text("suggestion_text"), self)
|
||||
self.title_label.setStyleSheet("font-size: 18px; font-weight: bold; margin-bottom: 10px;")
|
||||
self.title_label.setWordWrap(True) # Permet le retour à la ligne automatique
|
||||
self.title_label.setSizePolicy(self.title_label.sizePolicy().horizontalPolicy(),
|
||||
self.title_label.sizePolicy().verticalPolicy())
|
||||
self.title_label.setWordWrap(True)
|
||||
self.title_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
layout.addWidget(self.title_label)
|
||||
|
||||
# Text area for suggestion
|
||||
self.text_edit: QTextEdit = QTextEdit(self)
|
||||
self.text_edit.setPlaceholderText(self.language_manager.get_text("suggestion_placeholder"))
|
||||
layout.addWidget(self.text_edit)
|
||||
self.text_edit.setMinimumHeight(150)
|
||||
self.text_edit.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
layout.addWidget(self.text_edit, 1)
|
||||
|
||||
# Button layout
|
||||
button_layout: QHBoxLayout = QHBoxLayout()
|
||||
@ -111,8 +112,13 @@ class SuggestionWindow(QWidget):
|
||||
self.send_button.setEnabled(False)
|
||||
self.send_button.setText(self.language_manager.get_text("sending"))
|
||||
|
||||
# Create subject with app name
|
||||
subject: str = f"Suggestion pour {self.settings_manager.get_config('app_name')}"
|
||||
content = self.settings_manager.get_config('app_name').replace(' ', '_')
|
||||
|
||||
# Ajouter le préfixe "PRIORITAIRE" uniquement si le système de licence est activé ET que l'utilisateur a le support prioritaire
|
||||
if self.settings_manager.get_config("enable_licensing") and self.license_manager.is_feature_available("priority_support"):
|
||||
subject = "PRIORITAIRE - "+content
|
||||
else:
|
||||
subject = "Suggestion pour "+content
|
||||
|
||||
# Create and start email sender thread
|
||||
self.email_sender = EmailSender(subject, message)
|
||||
|
||||
66
app/utils/licence.py
Normal file
66
app/utils/licence.py
Normal file
@ -0,0 +1,66 @@
|
||||
from functools import wraps
|
||||
from PyQt6.QtWidgets import QMessageBox
|
||||
from app.core.main_manager import MainManager
|
||||
|
||||
def require_license(feature_id: str, show_upgrade_dialog: bool = True):
|
||||
"""
|
||||
Décorateur pour protéger une fonctionnalité premium
|
||||
|
||||
Usage:
|
||||
@require_license("advanced_export")
|
||||
def export_to_excel(self):
|
||||
# Code de la fonctionnalité premium
|
||||
"""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
main_manager = MainManager.get_instance()
|
||||
license_manager = main_manager.get_license_manager()
|
||||
settings_manager = main_manager.get_settings_manager()
|
||||
|
||||
# Si le système de licence est désactivé, autoriser toutes les fonctionnalités
|
||||
if not settings_manager.get_config("enable_licensing"):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if not license_manager.is_feature_available(feature_id):
|
||||
if show_upgrade_dialog:
|
||||
show_upgrade_message(feature_id)
|
||||
return None
|
||||
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def show_upgrade_message(feature_id: str):
|
||||
"""Affiche un message invitant à acheter la version premium"""
|
||||
main_manager = MainManager.get_instance()
|
||||
language_manager = main_manager.get_language_manager()
|
||||
settings_manager = main_manager.get_settings_manager()
|
||||
|
||||
# Si le système de licence est désactivé, ne rien afficher
|
||||
if not settings_manager.get_config("enable_licensing"):
|
||||
return
|
||||
|
||||
feature_name = settings_manager.get_config("feature_descriptions", {}).get(
|
||||
feature_id, feature_id
|
||||
)
|
||||
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Icon.Information)
|
||||
msg.setWindowTitle(language_manager.get_text("premium_feature"))
|
||||
msg.setText(language_manager.get_text("premium_feature_message").format(feature=feature_name))
|
||||
msg.setInformativeText(language_manager.get_text("upgrade_prompt"))
|
||||
|
||||
buy_btn = msg.addButton(language_manager.get_text("buy_now"), QMessageBox.ButtonRole.AcceptRole)
|
||||
activate_btn = msg.addButton(language_manager.get_text("activate_license"), QMessageBox.ButtonRole.ActionRole)
|
||||
msg.addButton(language_manager.get_text("cancel"), QMessageBox.ButtonRole.RejectRole)
|
||||
|
||||
msg.exec()
|
||||
|
||||
if msg.clickedButton() == buy_btn:
|
||||
import webbrowser
|
||||
webbrowser.open(settings_manager.get_config("purchase_url"))
|
||||
elif msg.clickedButton() == activate_btn:
|
||||
from app.ui.windows.activation_window import ActivationWindow
|
||||
activation_window = ActivationWindow()
|
||||
activation_window.exec()
|
||||
18
config.json
18
config.json
@ -1,10 +1,24 @@
|
||||
{
|
||||
"app_name": "Applications",
|
||||
"app_name": "Application",
|
||||
"app_os": "Windows",
|
||||
"app_version": "1.0.0",
|
||||
"architecture": "x64",
|
||||
"icon_path": "data/assets/icon.ico",
|
||||
"splash_image": "splash",
|
||||
"main_script": "main.py",
|
||||
"git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate"
|
||||
"git_repo": "https://gitea.louismazin.ovh/LouisMazin/PythonApplicationTemplate",
|
||||
"enable_licensing": true,
|
||||
"features_by_license": {
|
||||
"free": [
|
||||
"basic_features"
|
||||
],
|
||||
"premium": [
|
||||
"basic_features",
|
||||
"priority_support"
|
||||
],
|
||||
"enterprise": [
|
||||
"basic_features",
|
||||
"priority_support"
|
||||
]
|
||||
}
|
||||
}
|
||||
4
data/assets/license.svg
Normal file
4
data/assets/license.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<ns0:svg width="800px" height="800px" viewBox="0 0 24 24" xmlns:ns0="http://www.w3.org/2000/svg">
|
||||
<ns0:path fill-rule="evenodd" clip-rule="evenodd" d="M15.6807 14.5869C19.1708 14.5869 22 11.7692 22 8.29344C22 4.81767 19.1708 2 15.6807 2C12.1907 2 9.3615 4.81767 9.3615 8.29344C9.3615 9.90338 10.0963 11.0743 10.0963 11.0743L2.45441 18.6849C2.1115 19.0264 1.63143 19.9143 2.45441 20.7339L3.33616 21.6121C3.67905 21.9048 4.54119 22.3146 5.2466 21.6121L6.27531 20.5876C7.30403 21.6121 8.4797 21.0267 8.92058 20.4412C9.65538 19.4167 8.77362 18.3922 8.77362 18.3922L9.06754 18.0995C10.4783 19.5045 11.7128 18.6849 12.1537 18.0995C12.8885 17.075 12.1537 16.0505 12.1537 16.0505C11.8598 15.465 11.272 15.465 12.0067 14.7333L12.8885 13.8551C13.5939 14.4405 15.0439 14.5869 15.6807 14.5869Z M17.8853 8.29353C17.8853 9.50601 16.8984 10.4889 15.681 10.4889C14.4635 10.4889 13.4766 9.50601 13.4766 8.29353C13.4766 7.08105 14.4635 6.09814 15.681 6.09814C16.8984 6.09814 17.8853 7.08105 17.8853 8.29353Z" fill="#D1D1D6"/>
|
||||
|
||||
</ns0:svg>
|
||||
@ -1 +1,3 @@
|
||||
<ns0:svg xmlns:ns0="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="100px" height="100px"><ns0:path d="M 22.205078 2 A 1.0001 1.0001 0 0 0 21.21875 2.8378906 L 20.246094 8.7929688 C 19.076509 9.1331971 17.961243 9.5922728 16.910156 10.164062 L 11.996094 6.6542969 A 1.0001 1.0001 0 0 0 10.708984 6.7597656 L 6.8183594 10.646484 A 1.0001 1.0001 0 0 0 6.7070312 11.927734 L 10.164062 16.873047 C 9.583454 17.930271 9.1142098 19.051824 8.765625 20.232422 L 2.8359375 21.21875 A 1.0001 1.0001 0 0 0 2.0019531 22.205078 L 2.0019531 27.705078 A 1.0001 1.0001 0 0 0 2.8261719 28.691406 L 8.7597656 29.742188 C 9.1064607 30.920739 9.5727226 32.043065 10.154297 33.101562 L 6.6542969 37.998047 A 1.0001 1.0001 0 0 0 6.7597656 39.285156 L 10.648438 43.175781 A 1.0001 1.0001 0 0 0 11.927734 43.289062 L 16.882812 39.820312 C 17.936999 40.39548 19.054994 40.857928 20.228516 41.201172 L 21.21875 47.164062 A 1.0001 1.0001 0 0 0 22.205078 48 L 27.705078 48 A 1.0001 1.0001 0 0 0 28.691406 47.173828 L 29.751953 41.1875 C 30.920633 40.838997 32.033372 40.369697 33.082031 39.791016 L 38.070312 43.291016 A 1.0001 1.0001 0 0 0 39.351562 43.179688 L 43.240234 39.287109 A 1.0001 1.0001 0 0 0 43.34375 37.996094 L 39.787109 33.058594 C 40.355783 32.014958 40.813915 30.908875 41.154297 29.748047 L 47.171875 28.693359 A 1.0001 1.0001 0 0 0 47.998047 27.707031 L 47.998047 22.207031 A 1.0001 1.0001 0 0 0 47.160156 21.220703 L 41.152344 20.238281 C 40.80968 19.078827 40.350281 17.974723 39.78125 16.931641 L 43.289062 11.933594 A 1.0001 1.0001 0 0 0 43.177734 10.652344 L 39.287109 6.7636719 A 1.0001 1.0001 0 0 0 37.996094 6.6601562 L 33.072266 10.201172 C 32.023186 9.6248101 30.909713 9.1579916 29.738281 8.8125 L 28.691406 2.828125 A 1.0001 1.0001 0 0 0 27.705078 2 L 22.205078 2 z M 23.056641 4 L 26.865234 4 L 27.861328 9.6855469 A 1.0001 1.0001 0 0 0 28.603516 10.484375 C 30.066026 10.848832 31.439607 11.426549 32.693359 12.185547 A 1.0001 1.0001 0 0 0 33.794922 12.142578 L 38.474609 8.7792969 L 41.167969 11.472656 L 37.835938 16.220703 A 1.0001 1.0001 0 0 0 37.796875 17.310547 C 38.548366 18.561471 39.118333 19.926379 39.482422 21.380859 A 1.0001 1.0001 0 0 0 40.291016 22.125 L 45.998047 23.058594 L 45.998047 26.867188 L 40.279297 27.871094 A 1.0001 1.0001 0 0 0 39.482422 28.617188 C 39.122545 30.069817 38.552234 31.434687 37.800781 32.685547 A 1.0001 1.0001 0 0 0 37.845703 33.785156 L 41.224609 38.474609 L 38.53125 41.169922 L 33.791016 37.84375 A 1.0001 1.0001 0 0 0 32.697266 37.808594 C 31.44975 38.567585 30.074755 39.148028 28.617188 39.517578 A 1.0001 1.0001 0 0 0 27.876953 40.3125 L 26.867188 46 L 23.052734 46 L 22.111328 40.337891 A 1.0001 1.0001 0 0 0 21.365234 39.53125 C 19.90185 39.170557 18.522094 38.59371 17.259766 37.835938 A 1.0001 1.0001 0 0 0 16.171875 37.875 L 11.46875 41.169922 L 8.7734375 38.470703 L 12.097656 33.824219 A 1.0001 1.0001 0 0 0 12.138672 32.724609 C 11.372652 31.458855 10.793319 30.079213 10.427734 28.609375 A 1.0001 1.0001 0 0 0 9.6328125 27.867188 L 4.0019531 26.867188 L 4.0019531 23.052734 L 9.6289062 22.117188 A 1.0001 1.0001 0 0 0 10.435547 21.373047 C 10.804273 19.898143 11.383325 18.518729 12.146484 17.255859 A 1.0001 1.0001 0 0 0 12.111328 16.164062 L 8.8261719 11.46875 L 11.523438 8.7734375 L 16.185547 12.105469 A 1.0001 1.0001 0 0 0 17.28125 12.148438 C 18.536908 11.394293 19.919867 10.822081 21.384766 10.462891 A 1.0001 1.0001 0 0 0 22.132812 9.6523438 L 23.056641 4 z M 25 17 C 20.593567 17 17 20.593567 17 25 C 17 29.406433 20.593567 33 25 33 C 29.406433 33 33 29.406433 33 25 C 33 20.593567 29.406433 17 25 17 z M 25 19 C 28.325553 19 31 21.674447 31 25 C 31 28.325553 28.325553 31 25 31 C 21.674447 31 19 28.325553 19 25 C 19 21.674447 21.674447 19 25 19 z" fill="#D1D1D6"/></ns0:svg>
|
||||
<ns0:svg xmlns:ns0="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="100px" height="100px">
|
||||
<ns0:path d="M 22.205078 2 A 1.0001 1.0001 0 0 0 21.21875 2.8378906 L 20.246094 8.7929688 C 19.076509 9.1331971 17.961243 9.5922728 16.910156 10.164062 L 11.996094 6.6542969 A 1.0001 1.0001 0 0 0 10.708984 6.7597656 L 6.8183594 10.646484 A 1.0001 1.0001 0 0 0 6.7070312 11.927734 L 10.164062 16.873047 C 9.583454 17.930271 9.1142098 19.051824 8.765625 20.232422 L 2.8359375 21.21875 A 1.0001 1.0001 0 0 0 2.0019531 22.205078 L 2.0019531 27.705078 A 1.0001 1.0001 0 0 0 2.8261719 28.691406 L 8.7597656 29.742188 C 9.1064607 30.920739 9.5727226 32.043065 10.154297 33.101562 L 6.6542969 37.998047 A 1.0001 1.0001 0 0 0 6.7597656 39.285156 L 10.648438 43.175781 A 1.0001 1.0001 0 0 0 11.927734 43.289062 L 16.882812 39.820312 C 17.936999 40.39548 19.054994 40.857928 20.228516 41.201172 L 21.21875 47.164062 A 1.0001 1.0001 0 0 0 22.205078 48 L 27.705078 48 A 1.0001 1.0001 0 0 0 28.691406 47.173828 L 29.751953 41.1875 C 30.920633 40.838997 32.033372 40.369697 33.082031 39.791016 L 38.070312 43.291016 A 1.0001 1.0001 0 0 0 39.351562 43.179688 L 43.240234 39.287109 A 1.0001 1.0001 0 0 0 43.34375 37.996094 L 39.787109 33.058594 C 40.355783 32.014958 40.813915 30.908875 41.154297 29.748047 L 47.171875 28.693359 A 1.0001 1.0001 0 0 0 47.998047 27.707031 L 47.998047 22.207031 A 1.0001 1.0001 0 0 0 47.160156 21.220703 L 41.152344 20.238281 C 40.80968 19.078827 40.350281 17.974723 39.78125 16.931641 L 43.289062 11.933594 A 1.0001 1.0001 0 0 0 43.177734 10.652344 L 39.287109 6.7636719 A 1.0001 1.0001 0 0 0 37.996094 6.6601562 L 33.072266 10.201172 C 32.023186 9.6248101 30.909713 9.1579916 29.738281 8.8125 L 28.691406 2.828125 A 1.0001 1.0001 0 0 0 27.705078 2 L 22.205078 2 z M 23.056641 4 L 26.865234 4 L 27.861328 9.6855469 A 1.0001 1.0001 0 0 0 28.603516 10.484375 C 30.066026 10.848832 31.439607 11.426549 32.693359 12.185547 A 1.0001 1.0001 0 0 0 33.794922 12.142578 L 38.474609 8.7792969 L 41.167969 11.472656 L 37.835938 16.220703 A 1.0001 1.0001 0 0 0 37.796875 17.310547 C 38.548366 18.561471 39.118333 19.926379 39.482422 21.380859 A 1.0001 1.0001 0 0 0 40.291016 22.125 L 45.998047 23.058594 L 45.998047 26.867188 L 40.279297 27.871094 A 1.0001 1.0001 0 0 0 39.482422 28.617188 C 39.122545 30.069817 38.552234 31.434687 37.800781 32.685547 A 1.0001 1.0001 0 0 0 37.845703 33.785156 L 41.224609 38.474609 L 38.53125 41.169922 L 33.791016 37.84375 A 1.0001 1.0001 0 0 0 32.697266 37.808594 C 31.44975 38.567585 30.074755 39.148028 28.617188 39.517578 A 1.0001 1.0001 0 0 0 27.876953 40.3125 L 26.867188 46 L 23.052734 46 L 22.111328 40.337891 A 1.0001 1.0001 0 0 0 21.365234 39.53125 C 19.90185 39.170557 18.522094 38.59371 17.259766 37.835938 A 1.0001 1.0001 0 0 0 16.171875 37.875 L 11.46875 41.169922 L 8.7734375 38.470703 L 12.097656 33.824219 A 1.0001 1.0001 0 0 0 12.138672 32.724609 C 11.372652 31.458855 10.793319 30.079213 10.427734 28.609375 A 1.0001 1.0001 0 0 0 9.6328125 27.867188 L 4.0019531 26.867188 L 4.0019531 23.052734 L 9.6289062 22.117188 A 1.0001 1.0001 0 0 0 10.435547 21.373047 C 10.804273 19.898143 11.383325 18.518729 12.146484 17.255859 A 1.0001 1.0001 0 0 0 12.111328 16.164062 L 8.8261719 11.46875 L 11.523438 8.7734375 L 16.185547 12.105469 A 1.0001 1.0001 0 0 0 17.28125 12.148438 C 18.536908 11.394293 19.919867 10.822081 21.384766 10.462891 A 1.0001 1.0001 0 0 0 22.132812 9.6523438 L 23.056641 4 z M 25 17 C 20.593567 17 17 20.593567 17 25 C 17 29.406433 20.593567 33 25 33 C 29.406433 33 33 29.406433 33 25 C 33 20.593567 29.406433 17 25 17 z M 25 19 C 28.325553 19 31 21.674447 31 25 C 31 28.325553 28.325553 31 25 31 C 21.674447 31 19 28.325553 19 25 C 19 21.674447 21.674447 19 25 19 z" fill="#D1D1D6"/>
|
||||
</ns0:svg>
|
||||
@ -9,7 +9,7 @@
|
||||
"confirmation": "Confirmation",
|
||||
"information": "Information",
|
||||
"close": "Close",
|
||||
"suggestion_text": "Do you have a question or an idea to improve this application? Send me a message!",
|
||||
"suggestion_text": "Do you have a question or an idea to improve my software? Send me a message!",
|
||||
"suggestion_placeholder": "Type your message here...",
|
||||
"send_suggestion": "Send",
|
||||
"sending": "Sending...",
|
||||
@ -30,7 +30,34 @@
|
||||
"update_details": "Update Details",
|
||||
"update_aborted": "Update aborted by user",
|
||||
"loading": "Loading...",
|
||||
"verifying_license": "Verifying license...",
|
||||
"checking_updates": "Checking for updates...",
|
||||
"initializing": "Initializing interface...",
|
||||
"loading_complete": "Loading complete"
|
||||
"loading_complete": "Loading complete",
|
||||
"activate_license": "Activate your license to unlock all features",
|
||||
"hardware_id_info": "This unique identifier is tied to your hardware. Share it with support if needed.",
|
||||
"hardware_id_copied": "Hardware ID copied to clipboard!",
|
||||
"license_key_section": "License Key :",
|
||||
"enter_license_key": "Enter your license key",
|
||||
"activate": "Activate",
|
||||
"buy_license": "Buy License",
|
||||
"comparaisons": "Version Comparison",
|
||||
"free_version": "Free Version",
|
||||
"premium_version": "Premium Version",
|
||||
"enterprise_version": "Enterprise Version",
|
||||
"basic_features": "Basic features",
|
||||
"priority_support": "Priority support",
|
||||
"license_active": "License active",
|
||||
"license_type": "Type",
|
||||
"license_email": "Email",
|
||||
"license_expires": "Expires on",
|
||||
"license_free_mode": "Free mode - Activate a license for more features",
|
||||
"invalid_license_key": "Invalid license key. It must contain at least 16 characters.",
|
||||
"premium_feature": "Premium Feature",
|
||||
"premium_feature_message": "The feature '{feature}' requires a Premium or Enterprise license.",
|
||||
"upgrade_prompt": "Would you like to upgrade your license?",
|
||||
"buy_now": "Buy Now",
|
||||
"cancel": "Cancel",
|
||||
"activation_required": "Activation is required to continue.",
|
||||
"compare_versions": "Compare Versions"
|
||||
}
|
||||
@ -9,7 +9,7 @@
|
||||
"confirmation": "Confirmation",
|
||||
"information": "Information",
|
||||
"close": "Fermer",
|
||||
"suggestion_text": "Vous avez une question ou une idée pour améliorer cette application ? Envoyez-moi un message !",
|
||||
"suggestion_text": "Vous avez une question ou une idée pour améliorer mon logiciel ? Envoyez-moi un message !",
|
||||
"suggestion_placeholder": "Tapez votre message ici...",
|
||||
"send_suggestion": "Envoyer",
|
||||
"sending": "Envoi...",
|
||||
@ -30,7 +30,34 @@
|
||||
"update_details": "Détails de la mise à jour",
|
||||
"update_aborted": "Mise à jour annulée par l'utilisateur",
|
||||
"loading": "Chargement...",
|
||||
"verifying_license": "Vérification de la licence...",
|
||||
"checking_updates": "Vérification des mises à jour...",
|
||||
"initializing": "Initialisation de l'interface...",
|
||||
"loading_complete": "Chargement terminé"
|
||||
"loading_complete": "Chargement terminé",
|
||||
"activate_license": "Activez votre licence pour débloquer toutes les fonctionnalités",
|
||||
"hardware_id_info": "Cet identifiant unique est lié à votre matériel. Partagez-le avec le support si nécessaire.",
|
||||
"hardware_id_copied": "ID matériel copié dans le presse-papier !",
|
||||
"license_key_section": "Clé de licence :",
|
||||
"enter_license_key": "Entrez votre clé de licence",
|
||||
"activate": "Activer",
|
||||
"buy_license": "Acheter une licence",
|
||||
"comparaisons": "Comparaison des versions",
|
||||
"free_version": "Version Gratuite",
|
||||
"premium_version": "Version Premium",
|
||||
"enterprise_version": "Version Enterprise",
|
||||
"basic_features": "Fonctionnalités de base",
|
||||
"priority_support": "Support prioritaire",
|
||||
"license_active": "Licence active",
|
||||
"license_type": "Type",
|
||||
"license_email": "Email",
|
||||
"license_expires": "Expire le",
|
||||
"license_free_mode": "Mode gratuit - Activez une licence pour plus de fonctionnalités",
|
||||
"invalid_license_key": "Clé de licence invalide. Elle doit contenir au moins 16 caractères.",
|
||||
"premium_feature": "Fonctionnalité Premium",
|
||||
"premium_feature_message": "La fonctionnalité '{feature}' nécessite une licence Premium ou Enterprise.",
|
||||
"upgrade_prompt": "Souhaitez-vous mettre à niveau votre licence ?",
|
||||
"buy_now": "Acheter maintenant",
|
||||
"cancel": "Annuler",
|
||||
"activation_required": "L'activation est requise pour continuer.",
|
||||
"compare_versions": "Comparer les versions"
|
||||
}
|
||||
7
main.py
7
main.py
@ -25,6 +25,13 @@ def preload_application(progress_callback, splash=None):
|
||||
main_manager = MainManager.get_instance()
|
||||
language_manager = main_manager.get_language_manager()
|
||||
update_manager = main_manager.get_update_manager()
|
||||
license_manager = main_manager.get_license_manager()
|
||||
settings_manager = main_manager.get_settings_manager()
|
||||
|
||||
# Vérifier la licence uniquement si le système est activé
|
||||
if settings_manager.get_config("enable_licensing"):
|
||||
progress_callback(language_manager.get_text("verifying_license"))
|
||||
license_manager.verify_license()
|
||||
|
||||
progress_callback(language_manager.get_text("checking_updates"))
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user