updated view
This commit is contained in:
615
main.py
615
main.py
@@ -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()
|
||||
Reference in New Issue
Block a user