updated view

This commit is contained in:
2025-06-05 13:25:19 +03:00
parent 26640678cb
commit 418ebc6f49
19 changed files with 124737 additions and 8263 deletions

615
main.py
View File

@@ -1,257 +1,312 @@
import kivy
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
import requests
import os
import json
from cryptography.fernet import Fernet
from kivy.clock import Clock
from kivy.properties import StringProperty, ListProperty
from utils import get_devices_from_server
from utils import check_server_settings # Import the refactored function
from utils import test_connection # Import the refactored function
from utils import (
generate_key, load_key, encrypt_data, decrypt_data,
check_server_settings, save_server_settings,
test_connection, get_devices_from_server, save_route_to_file, fetch_positions_for_selected_day
)
from datetime import date
from kivy.uix.popup import Popup
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from threading import Thread
from kivy.clock import mainthread
kivy.require("2.0.0") # Ensure the correct Kivy version is used
kivy.require("2.0.0")
from kivy.core.window import Window
Window.size = (360, 780)
# Paths
RESOURCES_FOLDER = "resources"
CREDENTIALS_FILE = os.path.join(RESOURCES_FOLDER, "credentials.enc")
KEY_FILE = os.path.join(RESOURCES_FOLDER, "key.key")
# Utility functions for encryption
def generate_key():
"""Generate and save a key for encryption."""
if not os.path.exists(KEY_FILE):
key = Fernet.generate_key()
with open(KEY_FILE, "wb") as key_file:
key_file.write(key)
def load_key():
"""Load the encryption key."""
with open(KEY_FILE, "rb") as key_file:
return key_file.read()
def encrypt_data(data):
"""Encrypt data using the encryption key."""
key = load_key()
fernet = Fernet(key)
return fernet.encrypt(data.encode())
def decrypt_data(data):
"""Decrypt data using the encryption key."""
key = load_key()
fernet = Fernet(key)
return fernet.decrypt(data).decode()
# Login Screen
class LoginScreen(Screen):
def login(self):
"""Handle login and save credentials."""
username = self.ids.username_input.text.strip()
password = self.ids.password_input.text.strip()
password = self.ids.username_input.text.strip()
if not username or not password:
self.manager.get_screen("home").ids.result_label.text = "Please fill in all fields."
return
# Encrypt and save credentials
credentials = {"username": username, "password": password}
encrypted_data = encrypt_data(json.dumps(credentials))
with open(CREDENTIALS_FILE, "wb") as file:
file.write(encrypted_data)
# Navigate to the home screen
self.manager.current = "home"
# Settings Screen (renamed from MainScreen)
class SettingsScreen(Screen):
server_response = "Waiting to test connection..."
def on_pre_enter(self):
"""Load existing settings into the input fields when the screen is entered."""
settings_file = os.path.join(RESOURCES_FOLDER, "server_settings.enc")
if os.path.exists(settings_file):
try:
with open(settings_file, "rb") as file:
encrypted_data = file.read()
decrypted_data = decrypt_data(encrypted_data)
settings_data = json.loads(decrypted_data)
# Populate the input fields with the existing settings
self.ids.server_url_input.text = settings_data.get("server_url", "")
self.ids.username_input.text = settings_data.get("username", "")
self.ids.password_input.text = settings_data.get("password", "")
self.ids.token_input.text = settings_data.get("token", "") # Populate the token field
except Exception as e:
self.ids.result_label.text = f"Failed to load settings: {str(e)}"
settings = check_server_settings()
if settings:
self.ids.server_url_input.text = settings.get("server_url", "")
self.ids.username_input.text = settings.get("username", "")
self.ids.password_input.text = settings.get("password", "")
self.ids.token_input.text = settings.get("token", "")
else:
# Clear the input fields if no settings exist
self.ids.server_url_input.text = ""
self.ids.username_input.text = ""
self.ids.password_input.text = ""
self.ids.token_input.text = ""
def test_connection(self):
"""Test the connection with the Traccar server."""
# Get input values from the screen
server_url = self.ids.server_url_input.text.strip()
username = self.ids.username_input.text.strip()
password = self.ids.password_input.text.strip()
token = self.ids.token_input.text.strip() # Get the token input
# Call the refactored function
token = self.ids.token_input.text.strip()
result = test_connection(server_url, username, password, token)
# Update the UI based on the result
self.server_response = result["message"]
self.ids.result_label.text = self.server_response
def save_settings(self):
"""Save the server settings to an encrypted file and navigate to the HomeScreen."""
server_url = self.ids.server_url_input.text.strip()
username = self.ids.username_input.text.strip()
password = self.ids.password_input.text.strip()
token = self.ids.token_input.text.strip() # Get the token input
token = self.ids.token_input.text.strip()
if not server_url or not username or not password or not token:
self.ids.result_label.text = "Please fill in all fields."
return
settings_data = {
"server_url": server_url,
"username": username,
"password": password,
"token": token, # Include the token in the data
"token": token,
}
encrypted_data = encrypt_data(json.dumps(settings_data))
try:
# Save the encrypted data to the server_settings.enc file
settings_file = os.path.join(RESOURCES_FOLDER, "server_settings.enc")
with open(settings_file, "wb") as file:
file.write(encrypted_data)
save_server_settings(settings_data)
self.ids.result_label.text = "Settings saved successfully!"
# Navigate back to the HomeScreen
self.manager.current = "home"
except Exception as e:
self.ids.result_label.text = f"Failed to save settings: {str(e)}"
# Get Trip From Server Screen
class GetTripFromServer(Screen): # Renamed from HomeScreen
server_info_text = StringProperty("LOADING DATA...") # Default text for the label
server_box_color = ListProperty([0.984, 0.553, 0.078, 1]) # Default yellow color (#FB8D14)
device_mapping = {} # Store the mapping of device names to IDs
class GetTripFromServer(Screen):
server_info_text = StringProperty("LOADING DATA...")
server_box_color = ListProperty([0.984, 0.553, 0.078, 1])
device_mapping = {}
def on_pre_enter(self):
"""Start the flow for checking server information and connection."""
self.server_box_color = [0.984, 0.553, 0.078, 1] # Yellow color (#FB8D14)
self.server_box_color = [0.984, 0.553, 0.078, 1]
self.server_info_text = "LOADING DATA..."
Clock.schedule_once(self.check_server_settings, 1) # Wait 1 second before checking settings
Clock.schedule_once(self.check_server_settings, 1)
def check_server_settings(self, dt):
"""Check server settings and update the UI."""
settings = check_server_settings() # Call the refactored function
settings = check_server_settings()
if settings:
server_url = settings["server_url"]
username = settings["username"]
password = settings["password"]
token = settings["token"]
# Update the label to indicate checking connection
self.server_info_text = f"CHECKING server: {server_url}"
self.server_box_color = [0.984, 0.553, 0.078, 1] # Keep yellow color (#FB8D14)
self.ids.devices_spinner.text = "Loading devices..." # Show loading text
# Test the connection after loading settings
self.server_box_color = [0.984, 0.553, 0.078, 1]
self.ids.devices_spinner.text = "Loading devices..."
Clock.schedule_once(lambda dt: self.test_connection(server_url, username, password, token), 1)
else:
self.server_info_text = "Go to settings and set Traccar Server"
self.server_box_color = [0.909, 0.031, 0.243, 1] # Red color (#E8083E)
self.server_box_color = [0.909, 0.031, 0.243, 1]
self.ids.devices_spinner.text = "No devices available"
def set_result_message(self, message, color=(1, 1, 1, 1)):
self.ids.result_label.text = message
self.ids.result_label.color = color
def update_devices_spinner(self, devices):
if devices:
self.device_mapping = devices
device_names = list(devices.keys())
self.ids.devices_spinner.values = device_names
self.ids.devices_spinner.text = "Select a device"
else:
self.ids.devices_spinner.text = "No devices found"
self.ids.devices_spinner.values = []
def test_connection(self, server_url, username, password, token):
"""Test the connection with the Traccar server."""
try:
# Test the connection with the server
headers = {"Authorization": f"Bearer {token}"} if token else None
auth = None if token else (username, password)
print(f"Testing connection to {server_url}")
response = requests.get(f"{server_url}/api/server", headers=headers, auth=auth, timeout=10)
if response.status_code == 200:
# Update the label and box color for a successful connection
self.server_info_text = f"Connected to {server_url}"
self.server_box_color = [0.008, 0.525, 0.290, 1] # Green color (#02864A)
print(f"Connection successful: {self.server_info_text}")
# Fetch devices and populate the dropdown
devices = get_devices_from_server()
if devices:
self.device_mapping = devices # Store the mapping of device names to IDs
device_names = list(devices.keys()) # Get the list of device names
self.ids.devices_spinner.values = device_names # Populate the dropdown
self.ids.devices_spinner.text = "Select a device" # Default text after loading
else:
self.ids.devices_spinner.text = "No devices found"
self.ids.devices_spinner.values = []
else:
# Update the label and box color for a failed connection
self.server_info_text = f"Error: {response.status_code} - {response.reason}"
self.server_box_color = [0.909, 0.031, 0.243, 1] # Red color (#E8083E)
print(f"Connection failed: {self.server_info_text}")
except requests.exceptions.RequestException as e:
# Update the label and box color for a connection error
self.server_info_text = f"Connection failed: {str(e)}"
self.server_box_color = [0.909, 0.031, 0.243, 1] # Red color (#E8083E)
print(f"Connection error: {str(e)}")
result = test_connection(server_url, username, password, token)
if result["status"]:
self.server_info_text = f"Connected to {server_url}"
self.server_box_color = [0.008, 0.525, 0.290, 1]
devices = get_devices_from_server()
self.update_devices_spinner(devices)
else:
self.server_info_text = result["message"]
self.server_box_color = [0.909, 0.031, 0.243, 1]
def on_device_selected(self, device_name):
"""Change the background color of the Spinner when a device is selected."""
if device_name != "Loading devices..." and device_name != "No devices found":
self.ids.devices_spinner.background_color = (0.008, 0.525, 0.290, 1) # Green color (#02864A)
print(f"Device selected: {device_name}")
self.ids.devices_spinner.background_color = (0.008, 0.525, 0.290, 1)
else:
self.ids.devices_spinner.background_color = (1, 1, 1, 1) # Reset to white if no valid device is selected
self.ids.devices_spinner.background_color = (1, 1, 1, 1)
def open_date_picker(self, which):
"""Open a popup to select a date for start or end."""
today = date.today()
selected_date = [None]
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.popup import Popup
def on_date_selected(instance):
selected_date[0] = instance.text
date_str = f"{today.year}-{today.month:02d}-{int(selected_date[0]):02d}"
if which == 'start':
self.ids.start_date_picker_button.text = date_str
import calendar
today = date.today()
selected = {"year": today.year, "month": today.month}
def update_grid():
grid.clear_widgets()
days_in_month = calendar.monthrange(selected["year"], selected["month"])[1]
for day in range(1, days_in_month + 1):
btn = Button(
text=str(day),
size_hint=(None, None),
size=(38, 38),
background_color=(0.341, 0.235, 0.980, 1),
color=(1, 1, 1, 1),
font_size=15
)
def on_date_selected(instance, day=day):
date_str = f"{selected['year']}-{selected['month']:02d}-{day:02d}"
if which == 'start':
self.ids.start_date_picker_button.text = date_str
else:
self.ids.end_date_picker_button.text = date_str
popup.dismiss()
btn.bind(on_press=on_date_selected)
grid.add_widget(btn)
def prev_month(instance):
if selected["month"] > 1:
selected["month"] -= 1
else:
self.ids.end_date_picker_button.text = date_str
selected["year"] -= 1
selected["month"] = 12
# Don't allow future months
if (selected["year"], selected["month"]) > (today.year, today.month):
selected["year"], selected["month"] = today.year, today.month
title_label.text = f"Select a Date ({selected['year']}-{selected['month']:02d})"
update_grid()
def next_month(instance):
# Only allow up to current month
if (selected["year"], selected["month"]) < (today.year, today.month):
if selected["month"] < 12:
selected["month"] += 1
else:
selected["year"] += 1
selected["month"] = 1
title_label.text = f"Select a Date ({selected['year']}-{selected['month']:02d})"
update_grid()
def on_cancel(instance):
popup.dismiss()
layout = GridLayout(cols=7, spacing=5, padding=10)
for day in range(1, 32):
try:
current_date = date(today.year, today.month, day)
button = Button(text=str(day), size_hint=(None, None), size=(40, 40))
button.bind(on_press=on_date_selected)
layout.add_widget(button)
except ValueError:
pass
# Main vertical layout
main_layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
popup = Popup(title="Select a Date", content=layout, size_hint=(0.8, 0.8))
# Month navigation row
nav_layout = BoxLayout(orientation='horizontal', size_hint_y=None, height=40, spacing=10)
prev_btn = Button(text="<", size_hint_x=None, width=40, font_size=18, background_color=(0.341, 0.235, 0.980, 1), color=(1,1,1,1))
next_btn = Button(text=">", size_hint_x=None, width=40, font_size=18, background_color=(0.341, 0.235, 0.980, 1), color=(1,1,1,1))
title_label = Label(
text=f"Select a Date ({selected['year']}-{selected['month']:02d})",
size_hint_x=1,
font_size=18,
color=(1, 1, 1, 1)
)
prev_btn.bind(on_press=prev_month)
next_btn.bind(on_press=next_month)
nav_layout.add_widget(prev_btn)
nav_layout.add_widget(title_label)
nav_layout.add_widget(next_btn)
main_layout.add_widget(nav_layout)
# Grid of days: 6 columns
grid = GridLayout(cols=6, spacing=6, size_hint_y=None)
grid.bind(minimum_height=grid.setter('height'))
main_layout.add_widget(grid)
# Cancel button
cancel_btn = Button(
text="Cancel",
size_hint_y=None,
height=44,
background_color=(0.909, 0.031, 0.243, 1),
color=(1, 1, 1, 1),
font_size=16
)
cancel_btn.bind(on_press=on_cancel)
main_layout.add_widget(cancel_btn)
popup = Popup(
title="",
content=main_layout,
size_hint=(0.95, 0.8),
background_color=(0.11, 0.10, 0.15, 1),
separator_height=0,
auto_dismiss=False
)
update_grid()
popup.open()
def open_hour_picker(self, which):
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
popup_layout = BoxLayout(orientation='vertical', spacing=8, padding=8)
scroll = ScrollView(size_hint=(1, 1))
grid = GridLayout(cols=1, size_hint_y=None, spacing=4)
grid.bind(minimum_height=grid.setter('height'))
# Add hour buttons
for h in range(24):
hour_str = f"{h:02d}"
btn = Button(
text=hour_str,
size_hint_y=None,
height=44,
font_size=18,
background_color=(0.341, 0.235, 0.980, 1),
color=(1, 1, 1, 1)
)
def set_hour(instance, hour=hour_str):
if which == 'start':
self.ids.start_hour_button.text = hour
else:
self.ids.end_hour_button.text = hour
popup.dismiss()
btn.bind(on_press=set_hour)
grid.add_widget(btn)
scroll.add_widget(grid)
popup_layout.add_widget(scroll)
# Cancel button
cancel_btn = Button(
text="Cancel",
size_hint_y=None,
height=44,
background_color=(0.909, 0.031, 0.243, 1),
color=(1, 1, 1, 1),
font_size=16
)
cancel_btn.bind(on_press=lambda x: popup.dismiss())
popup_layout.add_widget(cancel_btn)
popup = Popup(
title="Select Hour",
content=popup_layout,
size_hint=(0.6, 0.7),
auto_dismiss=False
)
popup.open()
def update_points_count(self, count):
@@ -261,30 +316,18 @@ class GetTripFromServer(Screen): # Renamed from HomeScreen
def save_route(self):
"""Save the current list of positions as a route in resources/projects/<route_name>/positions.json."""
route_name = self.ids.route_name_input.text.strip()
if not route_name:
self.ids.result_label.text = "Please enter a route name."
return
positions = getattr(self, "last_positions", None)
if not positions:
self.ids.result_label.text = "No positions to save."
return
folder_path = os.path.join("resources", "projects", route_name)
os.makedirs(folder_path, exist_ok=True)
file_path = os.path.join(folder_path, "positions.json")
try:
with open(file_path, "w") as f:
json.dump(positions, f, indent=2)
self.ids.result_label.text = f"Route '{route_name}' saved!"
success, message, file_path = save_route_to_file(route_name, positions)
self.ids.result_label.text = message
if success:
# Reset UI fields
self.ids.devices_spinner.text = "Select a device"
self.ids.start_date_picker_button.text = "Select Start Date"
self.ids.end_date_picker_button.text = "Select End Date"
self.ids.start_hour_spinner.text = "00"
self.ids.end_hour_spinner.text = "23"
self.ids.start_hour_button.text = "00"
self.ids.end_hour_button.text = "23"
self.ids.points_count_label.text = "Points: 0"
self.ids.route_name_input.text = ""
@@ -299,9 +342,6 @@ class GetTripFromServer(Screen): # Renamed from HomeScreen
self.manager.current = "home"
Clock.schedule_once(close_and_go_home, 3)
except Exception as e:
self.ids.result_label.text = f"Failed to save route: {str(e)}"
def get_trip_server_data(self):
"""Handle the Get trip server data button press."""
selected_device = self.ids.devices_spinner.text
@@ -333,68 +373,38 @@ class GetTripFromServer(Screen): # Renamed from HomeScreen
print("No positions found or error occurred.")
def fetch_positions_for_selected_day(self):
"""Fetch all positions for the selected device and date/time range from the Traccar server."""
settings = check_server_settings()
if not settings:
self.ids.result_label.text = "Server settings not found."
return []
server_url = settings["server_url"]
token = settings["token"]
selected_device = self.ids.devices_spinner.text
if selected_device not in self.device_mapping:
self.ids.result_label.text = "Please select a valid device."
return []
device_id = self.device_mapping[selected_device]
# Get start/end date and hour from UI
device_name = self.ids.devices_spinner.text
start_date = self.ids.start_date_picker_button.text
start_hour = self.ids.start_hour_spinner.text
end_date = self.ids.end_date_picker_button.text
end_hour = self.ids.end_hour_spinner.text
end_date = self.ids.end_date_picker.text
start_hour = self.ids.start_hour_button.text
end_hour = self.ids.end_hour_button.text
# Validate
if "Select" in start_date or "Select" in end_date:
self.ids.result_label.text = "Please select both start and end dates."
positions, error = fetch_positions_for_selected_day(
settings,
self.device_mapping,
device_name,
start_date,
end_date,
start_hour,
end_hour
)
if error:
self.ids.result_label.text = error
return []
self.ids.result_label.text = f"Retrieved {len(positions)} positions."
return positions
def fetch_devices_async(self):
Thread(target=self._fetch_devices_worker).start()
# Build ISO 8601 time strings
from_time = f"{start_date}T{start_hour}:00:00Z"
to_time = f"{end_date}T{end_hour}:59:59Z"
def _fetch_devices_worker(self):
devices = get_devices_from_server()
self.update_devices_spinner_mainthread(devices)
# Prepare request for /reports/route
url = f"{server_url}/reports/route" # If server_url ends with /api
# OR
url = f"{server_url}/api/reports/route" # If server_url does NOT end with /api
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
params = {
"deviceId": device_id,
"from": from_time,
"to": to_time
}
try:
print(f"Request Payload: {params}")
response = requests.get(url, params=params, headers=headers, timeout=15)
print(f"Response Status Code: {response.status_code}")
print(f"Response Content: {response.text}")
if response.status_code == 200:
positions = response.json()
print(f"Retrieved {len(positions)} positions.")
self.ids.result_label.text = f"Retrieved {len(positions)} positions."
return positions
elif response.status_code == 400:
self.ids.result_label.text = "Bad Request: Please check the request payload and token."
return []
else:
self.ids.result_label.text = f"Failed: {response.status_code} - {response.reason}"
return []
except requests.exceptions.RequestException as e:
self.ids.result_label.text = f"Error fetching positions: {str(e)}"
return []
@mainthread
def update_devices_spinner_mainthread(self, devices):
self.update_devices_spinner(devices)
class RegisterScreen(Screen):
@@ -452,31 +462,78 @@ class RegisterScreen(Screen):
self.ids.result_label.text = f"Error checking user: {str(e)}"
return False
class CreateAnimationScreen(Screen):
pass
# Home Screen
class HomeScreen(Screen):
def on_pre_enter(self):
"""Load existing projects/trips when the screen is entered."""
self.load_existing_projects()
def load_existing_projects(self):
"""Load the list of existing projects/trips."""
projects_folder = os.path.join(RESOURCES_FOLDER, "projects")
archive_folder = os.path.join(RESOURCES_FOLDER, "trip_archive")
if not os.path.exists(projects_folder):
os.makedirs(projects_folder)
if not os.path.exists(archive_folder):
os.makedirs(archive_folder)
# Clear the list area
self.ids.projects_list.clear_widgets()
# Populate the list with existing projects/trips
for project in os.listdir(projects_folder):
project_button = Button(
text=project,
row = BoxLayout(
orientation="horizontal",
size_hint_y=None,
height=40,
on_press=lambda instance: self.open_project(instance.text)
height=38,
spacing=6,
padding=(8, 4)
)
self.ids.projects_list.add_widget(project_button)
from kivy.graphics import Color, Rectangle
with row.canvas.before:
Color(0.11, 0.10, 0.15, 1) # Match app background
row.bg_rect = Rectangle(pos=row.pos, size=row.size)
def update_bg_rect(instance, value):
row.bg_rect.pos = row.pos
row.bg_rect.size = row.size
row.bind(pos=update_bg_rect, size=update_bg_rect)
project_label = Label(
text=project,
size_hint_x=0.64,
color=(1, 1, 1, 1),
font_size=15,
shorten=True,
shorten_from='right'
)
# Edit icon button
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior
class IconButton(ButtonBehavior, Image):
pass
edit_button = IconButton(
source="resources/images/edit.png",
size_hint_x=0.18,
allow_stretch=True,
keep_ratio=True
)
edit_button.bind(on_press=lambda instance, p=project: self.edit_project(p))
# Delete icon button
delete_button = IconButton(
source="resources/images/delete.png",
size_hint_x=0.18,
allow_stretch=True,
keep_ratio=True
)
delete_button.bind(on_press=lambda instance, p=project: self.confirm_delete_project(p))
row.add_widget(project_label)
row.add_widget(edit_button)
row.add_widget(delete_button)
self.ids.projects_list.add_widget(row)
def open_project(self, project_name):
"""Handle opening an existing project/trip."""
@@ -487,30 +544,76 @@ class HomeScreen(Screen):
"""Navigate to the GetTripFromServer screen to create a new project/trip."""
self.manager.current = "get_trip_from_server"
def edit_project(self, project_name):
# Navigate to the create_animation screen and pass the project name if needed
self.manager.current = "create_animation"
# Optionally, set a property or method to load the project in create_animation
def confirm_delete_project(self, project_name):
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text=f"Delete project '{project_name}'?\nChoose an option:")
btn_delete = Button(text="Delete Completely", background_color=(1, 0, 0, 1))
btn_archive = Button(text="Archive Trip", background_color=(0.341, 0.235, 0.980, 1))
btn_cancel = Button(text="Cancel")
popup = Popup(title="Delete Project", content=layout, size_hint=(None, None), size=(400, 250), auto_dismiss=False)
layout.add_widget(label)
layout.add_widget(btn_delete)
layout.add_widget(btn_archive)
layout.add_widget(btn_cancel)
def do_delete(instance):
self.delete_project(project_name)
popup.dismiss()
self.load_existing_projects()
def do_archive(instance):
self.archive_project(project_name)
popup.dismiss()
self.load_existing_projects()
btn_delete.bind(on_press=do_delete)
btn_archive.bind(on_press=do_archive)
btn_cancel.bind(on_press=lambda x: popup.dismiss())
popup.open()
def delete_project(self, project_name):
import shutil
folder_path = os.path.join(RESOURCES_FOLDER, "projects", project_name)
if os.path.exists(folder_path):
shutil.rmtree(folder_path)
def archive_project(self, project_name):
import shutil
src_file = os.path.join(RESOURCES_FOLDER, "projects", project_name, "positions.json")
archive_folder = os.path.join(RESOURCES_FOLDER, "trip_archive")
if not os.path.exists(archive_folder):
os.makedirs(archive_folder)
dst_file = os.path.join(archive_folder, f"{project_name}.json")
if os.path.exists(src_file):
shutil.copy2(src_file, dst_file)
# Optionally, delete the project folder after archiving
self.delete_project(project_name)
# Main App
class TraccarApp(App):
def build(self):
# Ensure resources folder exists
if not os.path.exists(RESOURCES_FOLDER):
os.makedirs(RESOURCES_FOLDER)
# Generate encryption key if it doesn't exist
generate_key()
# Screen manager
sm = ScreenManager()
sm.add_widget(LoginScreen(name="login"))
sm.add_widget(HomeScreen(name="home")) # Add the HomeScreen
sm.add_widget(GetTripFromServer(name="get_trip_from_server")) # Updated reference
sm.add_widget(SettingsScreen(name="settings")) # Add the renamed SettingsScreen
sm.add_widget(RegisterScreen(name="register")) # Add the RegisterScreen
# Debugging: Print all screen names
sm.add_widget(HomeScreen(name="home"))
sm.add_widget(GetTripFromServer(name="get_trip_from_server"))
sm.add_widget(SettingsScreen(name="settings"))
sm.add_widget(RegisterScreen(name="register"))
sm.add_widget(CreateAnimationScreen(name="create_animation"))
print("Screens added to ScreenManager:", [screen.name for screen in sm.screens])
return sm
if __name__ == "__main__":
TraccarApp().run()