updated to pauses

This commit is contained in:
2025-06-07 21:08:23 +03:00
parent 0ebdbc6b74
commit fa3a11ee4b
17 changed files with 604 additions and 203 deletions

Binary file not shown.

View File

View File

@@ -1 +1 @@
gAAAAABoQzLdIdf3DPBo8ovchYeCI6p5KPYw_8GLixB4NpVQfJRQFWvDvi1b7BmxTqwx8UTuiOFabSPBEhR25LARU7YlTVuoYxFZjahHwvu7COTzRfi98qePYrhvb1AFoJSkDNWbbbfg
gAAAAABoRH_Q_Meahpxk7Lunh7m8alx_y11I13YKM8SbTOBFqZrqoZ6pCxNK_RMBDRXwoeJhhDCVppaX-WPMGHljzBrsTQ9mUa9yDS6Sp8t-fvZnYEcKlIoRwdqRd3b1HdyJgBxH_AjD

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -6,6 +6,7 @@
"location": {
"latitude": 45.79917944444444,
"longitude": 24.085654444444444
}
},
"name": "Lucru greu"
}
]

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 877 KiB

View File

@@ -9,7 +9,7 @@ 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,
process_preview_util
process_preview_util, optimize_route_entries_util
)
from datetime import date
from kivy.uix.popup import Popup
@@ -34,6 +34,9 @@ from PIL import Image
import time
import os
import math
from kivy.uix.filechooser import FileChooserIconView
from kivy.uix.textinput import TextInput
from widgets.pause_edit_popup import open_pauses_popup
class CreateAnimationScreen(Screen):
project_name = StringProperty("")
@@ -66,6 +69,8 @@ class CreateAnimationScreen(Screen):
count = 0
self.ids.route_entries_label.text = f"Your route has {count} entries,"
def open_rename_popup(self):
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
@@ -118,7 +123,7 @@ class CreateAnimationScreen(Screen):
return False
def optimize_route_entries(self):
# Show popup with progress bar
# Create the popup and UI elements
layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text="Processing route entries...")
progress = ProgressBar(max=100, value=0)
@@ -133,161 +138,17 @@ class CreateAnimationScreen(Screen):
)
popup.open()
def process_entries(dt):
import datetime
project_folder = os.path.join(RESOURCES_FOLDER, "projects", self.project_name)
positions_path = os.path.join(project_folder, "positions.json")
pauses_path = os.path.join(project_folder, "pauses.json")
if not os.path.exists(positions_path):
label.text = "positions.json not found!"
progress.value = 100
return
with open(positions_path, "r") as f:
positions = json.load(f)
# Detect duplicate positions at the start
start_remove = 0
if positions:
first = positions[0]
for pos in positions:
if pos['latitude'] == first['latitude'] and pos['longitude'] == first['longitude']:
start_remove += 1
else:
break
if start_remove > 0:
start_remove -= 1
# Detect duplicate positions at the end
end_remove = 0
if positions:
last = positions[-1]
for pos in reversed(positions):
if pos['latitude'] == last['latitude'] and pos['longitude'] == last['longitude']:
end_remove += 1
else:
break
if end_remove > 0:
end_remove -= 1
# Shorten the positions list
new_positions = positions[start_remove:len(positions)-end_remove if end_remove > 0 else None]
# --- PAUSE DETECTION ---
pauses = []
if new_positions:
pause_start = None
pause_end = None
pause_location = None
for i in range(1, len(new_positions)):
prev = new_positions[i-1]
curr = new_positions[i]
# Check if stopped (same location)
if curr['latitude'] == prev['latitude'] and curr['longitude'] == prev['longitude']:
if pause_start is None:
pause_start = prev['deviceTime']
pause_location = (prev['latitude'], prev['longitude'])
pause_end = curr['deviceTime']
else:
if pause_start and pause_end:
# Calculate pause duration
t1 = datetime.datetime.fromisoformat(pause_start.replace('Z', '+00:00'))
t2 = datetime.datetime.fromisoformat(pause_end.replace('Z', '+00:00'))
duration = (t2 - t1).total_seconds()
if duration >= 180:
pauses.append({
"start_time": pause_start,
"end_time": pause_end,
"duration_seconds": int(duration),
"location": {"latitude": pause_location[0], "longitude": pause_location[1]}
})
pause_start = None
pause_end = None
pause_location = None
# Check for pause at the end
if pause_start and pause_end:
t1 = datetime.datetime.fromisoformat(pause_start.replace('Z', '+00:00'))
t2 = datetime.datetime.fromisoformat(pause_end.replace('Z', '+00:00'))
duration = (t2 - t1).total_seconds()
if duration >= 120:
pauses.append({
"start_time": pause_start,
"end_time": pause_end,
"duration_seconds": int(duration),
"location": {"latitude": pause_location[0], "longitude": pause_location[1]}
})
# --- FILTER PAUSES ---
# 1. Remove pauses near start/end
start_lat, start_lon = new_positions[0]['latitude'], new_positions[0]['longitude']
end_lat, end_lon = new_positions[-1]['latitude'], new_positions[-1]['longitude']
filtered_pauses = []
for pause in pauses:
plat = pause["location"]["latitude"]
plon = pause["location"]["longitude"]
dist_start = haversine(start_lat, start_lon, plat, plon)
dist_end = haversine(end_lat, end_lon, plat, plon)
if dist_start < 50 or dist_end < 50:
continue # Skip pauses near start or end
filtered_pauses.append(pause)
# 2. Merge pauses close in time and space
merged_pauses = []
filtered_pauses.sort(key=lambda p: p["start_time"])
for pause in filtered_pauses:
if not merged_pauses:
merged_pauses.append(pause)
else:
last = merged_pauses[-1]
# Time difference in seconds
t1 = datetime.datetime.fromisoformat(last["end_time"].replace('Z', '+00:00'))
t2 = datetime.datetime.fromisoformat(pause["start_time"].replace('Z', '+00:00'))
time_diff = (t2 - t1).total_seconds()
# Distance in meters
last_lat = last["location"]["latitude"]
last_lon = last["location"]["longitude"]
plat = pause["location"]["latitude"]
plon = pause["location"]["longitude"]
dist = haversine(last_lat, last_lon, plat, plon)
if time_diff < 300 and dist < 50:
# Merge: extend last pause's end_time and duration
last["end_time"] = pause["end_time"]
last["duration_seconds"] += pause["duration_seconds"]
else:
merged_pauses.append(pause)
pauses = merged_pauses
progress.value = 100
label.text = (
f"Entries removable at start: {start_remove}\n"
f"Entries removable at end: {end_remove}\n"
f"Detected pauses: {len(pauses)}"
# Now call the utility function with these objects
optimize_route_entries_util(
self.project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
Clock,
on_save=lambda: self.on_pre_enter()
)
btn_save = Button(text="Save optimized file", background_color=(0.008, 0.525, 0.290, 1))
btn_cancel = Button(text="Cancel")
btn_box = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=44)
btn_box.add_widget(btn_save)
btn_box.add_widget(btn_cancel)
layout.add_widget(btn_box)
def save_optimized(instance):
with open(positions_path, "w") as f:
json.dump(new_positions, f, indent=2)
with open(pauses_path, "w") as f:
json.dump(pauses, f, indent=2)
label.text = "File optimized and pauses saved!"
btn_save.disabled = True
btn_cancel.disabled = True
def close_and_refresh(dt):
popup.dismiss()
self.on_pre_enter() # Refresh the screen
Clock.schedule_once(close_and_refresh, 1)
btn_save.bind(on_press=save_optimized)
btn_cancel.bind(on_press=lambda x: popup.dismiss())
Clock.schedule_once(process_entries, 0.5)
def preview_route(self):
# Show processing popup
@@ -325,13 +186,6 @@ class CreateAnimationScreen(Screen):
0.5
)
def haversine(lat1, lon1, lat2, lon2):
# Returns distance in meters between two lat/lon points
R = 6371000 # Earth radius in meters
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dlambda = math.radians(lon2 - lon1)
a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2
return 2 * R * math.asin(math.sqrt(a))
def open_pauses_popup(self):
open_pauses_popup(self, self.project_name, RESOURCES_FOLDER, on_save_callback=self.on_pre_enter)

View File

@@ -643,12 +643,45 @@
text_size: self.size
on_press: root.optimize_route_entries()
# Pauses frame
BoxLayout:
id: pauses_frame
orientation: "horizontal"
spacing: 10
padding: 10
size_hint_y: None
height: 60
canvas.before:
Color:
rgba: 0.15, 0.15, 0.18, 1
Rectangle:
pos: self.pos
size: self.size
Label:
id: pauses_label
text: "Pauses"
font_size: 16
halign: "left"
valign: "middle"
color: 1, 1, 1, 1
size_hint_x: 0.7
text_size: self.width, None
Button:
id: pauses_edit_btn
text: "Edit"
size_hint_x: 0.3
width: 120
font_size: 16
background_color: 0.341, 0.235, 0.980, 1
color: 1, 1, 1, 1
on_press: root.open_pauses_popup()
# Preview frame (label + button on first row, image on second row)
BoxLayout:
orientation: "vertical"
size_hint_y: None
height: 300 # Adjust as needed for your image size
padding: [10, 10, 10, 10]
height: 255 # Adjust as needed for your image size
padding: [5, 5, 5, 5]
spacing: 10
canvas.before:
Color:
@@ -660,8 +693,8 @@
BoxLayout:
orientation: "horizontal"
size_hint_y: None
height: 60
spacing: 10
height: 30
spacing: 5
Label:
text: "Preview your route"
@@ -701,3 +734,4 @@
color: 1, 1, 1, 1
font_size: 16
on_press: app.root.current = "home"

185
utils.py
View File

@@ -2,7 +2,8 @@ import os
import json
import requests
from cryptography.fernet import Fernet
import math
import datetime
RESOURCES_FOLDER = "resources"
CREDENTIALS_FILE = os.path.join(RESOURCES_FOLDER, "credentials.enc")
KEY_FILE = os.path.join(RESOURCES_FOLDER, "key.key")
@@ -306,3 +307,185 @@ def process_preview_util(
def close_popup(dt):
popup.dismiss()
Clock.schedule_once(close_popup, 2)
def haversine(lat1, lon1, lat2, lon2):
# Returns distance in meters between two lat/lon points
R = 6371000 # Earth radius in meters
phi1 = math.radians(lat1)
phi2 = math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dlambda = math.radians(lon2 - lon1)
a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2
return 2 * R * math.asin(math.sqrt(a))
def optimize_route_entries_util(
project_name,
RESOURCES_FOLDER,
label,
progress,
popup,
Clock,
on_save=None
):
def process_entries(dt):
project_folder = os.path.join(RESOURCES_FOLDER, "projects", project_name)
positions_path = os.path.join(project_folder, "positions.json")
pauses_path = os.path.join(project_folder, "pauses.json")
if not os.path.exists(positions_path):
label.text = "positions.json not found!"
progress.value = 100
return
with open(positions_path, "r") as f:
positions = json.load(f)
# Detect duplicate positions at the start
start_remove = 0
if positions:
first = positions[0]
for pos in positions:
if pos['latitude'] == first['latitude'] and pos['longitude'] == first['longitude']:
start_remove += 1
else:
break
if start_remove > 0:
start_remove -= 1
# Detect duplicate positions at the end
end_remove = 0
if positions:
last = positions[-1]
for pos in reversed(positions):
if pos['latitude'] == last['latitude'] and pos['longitude'] == last['longitude']:
end_remove += 1
else:
break
if end_remove > 0:
end_remove -= 1
# Shorten the positions list
new_positions = positions[start_remove:len(positions)-end_remove if end_remove > 0 else None]
# --- PAUSE DETECTION ---
pauses = []
if new_positions:
pause_start = None
pause_end = None
pause_location = None
for i in range(1, len(new_positions)):
prev = new_positions[i-1]
curr = new_positions[i]
# Check if stopped (same location)
if curr['latitude'] == prev['latitude'] and curr['longitude'] == prev['longitude']:
if pause_start is None:
pause_start = prev['deviceTime']
pause_location = (prev['latitude'], prev['longitude'])
pause_end = curr['deviceTime']
else:
if pause_start and pause_end:
# Calculate pause duration
t1 = datetime.datetime.fromisoformat(pause_start.replace('Z', '+00:00'))
t2 = datetime.datetime.fromisoformat(pause_end.replace('Z', '+00:00'))
duration = (t2 - t1).total_seconds()
if duration >= 120:
pauses.append({
"start_time": pause_start,
"end_time": pause_end,
"duration_seconds": int(duration),
"location": {"latitude": pause_location[0], "longitude": pause_location[1]}
})
pause_start = None
pause_end = None
pause_location = None
# Check for pause at the end
if pause_start and pause_end:
t1 = datetime.datetime.fromisoformat(pause_start.replace('Z', '+00:00'))
t2 = datetime.datetime.fromisoformat(pause_end.replace('Z', '+00:00'))
duration = (t2 - t1).total_seconds()
if duration >= 120:
pauses.append({
"start_time": pause_start,
"end_time": pause_end,
"duration_seconds": int(duration),
"location": {"latitude": pause_location[0], "longitude": pause_location[1]}
})
# --- FILTER PAUSES ---
# 1. Remove pauses near start/end
filtered_pauses = []
if new_positions and pauses:
start_lat, start_lon = new_positions[0]['latitude'], new_positions[0]['longitude']
end_lat, end_lon = new_positions[-1]['latitude'], new_positions[-1]['longitude']
for pause in pauses:
plat = pause["location"]["latitude"]
plon = pause["location"]["longitude"]
dist_start = haversine(start_lat, start_lon, plat, plon)
dist_end = haversine(end_lat, end_lon, plat, plon)
if dist_start < 50 or dist_end < 50:
continue # Skip pauses near start or end
filtered_pauses.append(pause)
else:
filtered_pauses = pauses
# 2. Merge pauses close in time and space
merged_pauses = []
filtered_pauses.sort(key=lambda p: p["start_time"])
for pause in filtered_pauses:
if not merged_pauses:
merged_pauses.append(pause)
else:
last = merged_pauses[-1]
# Time difference in seconds
t1 = datetime.datetime.fromisoformat(last["end_time"].replace('Z', '+00:00'))
t2 = datetime.datetime.fromisoformat(pause["start_time"].replace('Z', '+00:00'))
time_diff = (t2 - t1).total_seconds()
# Distance in meters
last_lat = last["location"]["latitude"]
last_lon = last["location"]["longitude"]
plat = pause["location"]["latitude"]
plon = pause["location"]["longitude"]
dist = haversine(last_lat, last_lon, plat, plon)
if time_diff < 300 and dist < 50:
# Merge: extend last pause's end_time and duration
last["end_time"] = pause["end_time"]
last["duration_seconds"] += pause["duration_seconds"]
else:
merged_pauses.append(pause)
pauses = merged_pauses
progress.value = 100
label.text = (
f"Entries removable at start: {start_remove}\n"
f"Entries removable at end: {end_remove}\n"
f"Detected pauses: {len(pauses)}"
)
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
btn_save = Button(text="Save optimized file", background_color=(0.008, 0.525, 0.290, 1))
btn_cancel = Button(text="Cancel")
btn_box = BoxLayout(orientation='horizontal', spacing=10, size_hint_y=None, height=44)
btn_box.add_widget(btn_save)
btn_box.add_widget(btn_cancel)
popup.content.add_widget(btn_box)
def save_optimized(instance):
with open(positions_path, "w") as f:
json.dump(new_positions, f, indent=2)
with open(pauses_path, "w") as f:
json.dump(pauses, f, indent=2)
label.text = "File optimized and pauses saved!"
btn_save.disabled = True
btn_cancel.disabled = True
def close_and_refresh(dt):
popup.dismiss()
if on_save:
on_save()
Clock.schedule_once(close_and_refresh, 1)
btn_save.bind(on_press=save_optimized)
btn_cancel.bind(on_press=lambda x: popup.dismiss())
Clock.schedule_once(process_entries, 0.5)

Binary file not shown.

329
widgets/pause_edit_popup.py Normal file
View File

@@ -0,0 +1,329 @@
from kivy.uix.popup import Popup
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.filechooser import FileChooserIconView
from kivy.graphics import Color, Rectangle
import os
import json
import shutil
def open_pauses_popup(screen_instance, project_name, RESOURCES_FOLDER, on_save_callback=None):
project_folder = os.path.join(RESOURCES_FOLDER, "projects", project_name)
pauses_path = os.path.join(project_folder, "pauses.json")
# Main layout with dark background
layout = BoxLayout(orientation='vertical', spacing=14, padding=14)
with layout.canvas.before:
Color(0.13, 0.13, 0.16, 1)
layout.bg_rect = Rectangle(pos=layout.pos, size=layout.size)
def update_bg_rect(instance, value):
layout.bg_rect.pos = layout.pos
layout.bg_rect.size = layout.size
layout.bind(pos=update_bg_rect, size=update_bg_rect)
pauses = []
if os.path.exists(pauses_path):
with open(pauses_path, "r") as f:
pauses = json.load(f)
def suggest_location_name(lat, lon):
return f"Lat {round(lat, 4)}, Lon {round(lon, 4)}"
for idx, pause in enumerate(pauses):
pause_box = BoxLayout(orientation='vertical', spacing=10, padding=[14, 12, 14, 12], size_hint_y=None, height=220)
with pause_box.canvas.before:
Color(0.20, 0.20, 0.25, 1)
pause_box.bg_rect = Rectangle(pos=pause_box.pos, size=pause_box.size)
pause_box.bind(pos=lambda inst, val: setattr(pause_box.bg_rect, 'pos', inst.pos),
size=lambda inst, val: setattr(pause_box.bg_rect, 'size', inst.size))
# Title row
title_label = Label(
text=f"[b]Pause {idx+1}[/b]",
markup=True,
font_size=18,
color=(1, 1, 1, 1),
size_hint_y=None,
height=28,
halign="left",
valign="middle"
)
def update_title_size(instance, value):
instance.text_size = (instance.width, None)
title_label.bind(width=update_title_size)
pause_box.add_widget(title_label)
# Info row: Detected location and edit button
info_box = BoxLayout(orientation='horizontal', spacing=8, size_hint_y=None, height=32)
location_label = Label(
text=pause.get('name', suggest_location_name(pause["location"]["latitude"], pause["location"]["longitude"])),
font_size=15,
color=(0.8, 0.8, 0.8, 1),
size_hint_x=0.7,
halign="left",
valign="middle"
)
location_label.bind(width=update_title_size)
edit_loc_btn = Button(
text="Edit Place",
size_hint_x=0.3,
font_size=14,
background_color=(0.341, 0.235, 0.980, 1),
color=(1, 1, 1, 1)
)
def open_edit_popup(instance, pause=pause, location_label=location_label):
edit_layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
with edit_layout.canvas.before:
Color(0.13, 0.13, 0.16, 1)
edit_layout.bg_rect = Rectangle(pos=edit_layout.pos, size=edit_layout.size)
edit_layout.bind(pos=lambda inst, val: setattr(edit_layout.bg_rect, 'pos', inst.pos),
size=lambda inst, val: setattr(edit_layout.bg_rect, 'size', inst.size))
input_field = TextInput(text=pause.get('name', ''), multiline=False, background_color=(0.18,0.18,0.22,1), foreground_color=(1,1,1,1))
save_btn = Button(text="Save", background_color=(0.341, 0.235, 0.980, 1), color=(1,1,1,1))
edit_layout.add_widget(Label(text="Edit Place Name:", color=(1,1,1,1)))
edit_layout.add_widget(input_field)
edit_layout.add_widget(save_btn)
edit_popup = Popup(title="Edit Place", content=edit_layout, size_hint=(0.7, None), size=(0, 200))
def save_name(instance):
pause['name'] = input_field.text
location_label.text = input_field.text
edit_popup.dismiss()
save_btn.bind(on_press=save_name)
edit_popup.open()
edit_loc_btn.bind(on_press=open_edit_popup)
info_box.add_widget(location_label)
info_box.add_widget(edit_loc_btn)
pause_box.add_widget(info_box)
# Pictures folder and list
pause_img_folder = os.path.join(project_folder, f"pause_{idx+1}")
os.makedirs(pause_img_folder, exist_ok=True)
img_list = [f for f in os.listdir(pause_img_folder) if os.path.isfile(os.path.join(pause_img_folder, f))]
# Photo count label
if img_list:
photo_count_label = Label(
text=f"You have {len(img_list)} photo(s) for this pause.",
font_size=13,
color=(0.8, 0.8, 0.8, 1),
size_hint_y=None,
height=22
)
photo_list_label = Label(
text=", ".join(img_list),
font_size=12,
color=(0.7, 0.7, 0.7, 1),
size_hint_y=None,
height=18
)
else:
photo_count_label = Label(
text="No photos are set for this pause.",
font_size=13,
color=(0.8, 0.8, 0.8, 1),
size_hint_y=None,
height=22
)
photo_list_label = Label(
text="",
font_size=12,
color=(0.7, 0.7, 0.7, 1),
size_hint_y=None,
height=18
)
pause_box.add_widget(photo_count_label)
pause_box.add_widget(photo_list_label)
# Bottom row: Browse, Delete, and Save Pause
bottom_box = BoxLayout(orientation='horizontal', spacing=8, size_hint_y=None, height=36)
browse_btn = Button(
text="Browse",
size_hint_x=0.33,
font_size=14,
background_color=(0.341, 0.235, 0.980, 1),
color=(1,1,1,1)
)
def open_filechooser(instance, pause_img_folder=pause_img_folder, photo_count_label=photo_count_label, photo_list_label=photo_list_label):
chooser_layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
with chooser_layout.canvas.before:
Color(0.13, 0.13, 0.16, 1)
chooser_layout.bg_rect = Rectangle(pos=chooser_layout.pos, size=chooser_layout.size)
chooser_layout.bind(pos=lambda inst, val: setattr(chooser_layout.bg_rect, 'pos', inst.pos),
size=lambda inst, val: setattr(chooser_layout.bg_rect, 'size', inst.size))
chooser = FileChooserIconView(path=".", filters=['*.png', '*.jpg', '*.jpeg'], multiselect=True)
upload_btn = Button(text="Upload Selected", size_hint_y=None, height=40, background_color=(0.341, 0.235, 0.980, 1), color=(1,1,1,1))
chooser_layout.add_widget(chooser)
chooser_layout.add_widget(upload_btn)
popup = Popup(title="Select Images", content=chooser_layout, size_hint=(0.9, 0.9))
def upload_files(instance):
selections = chooser.selection
if selections:
for selected_file in selections:
dest = os.path.join(pause_img_folder, os.path.basename(selected_file))
shutil.copy(selected_file, dest)
img_list = [f for f in os.listdir(pause_img_folder) if os.path.isfile(os.path.join(pause_img_folder, f))]
if img_list:
photo_count_label.text = f"You have {len(img_list)} photo(s) for this pause."
photo_list_label.text = ", ".join(img_list)
else:
photo_count_label.text = "No photos are set for this pause."
photo_list_label.text = ""
popup.dismiss()
upload_btn.bind(on_press=upload_files)
popup.open()
browse_btn.bind(on_press=open_filechooser)
# --- Delete Button ---
delete_btn = Button(
text="Delete",
size_hint_x=0.34,
font_size=14,
background_color=(0.8, 0.1, 0.1, 1),
color=(1,1,1,1)
)
def open_delete_popup(instance, pause_img_folder=pause_img_folder, photo_count_label=photo_count_label, photo_list_label=photo_list_label):
from kivy.uix.scrollview import ScrollView
from kivy.uix.image import Image as KivyImage
from kivy.uix.widget import Widget
delete_layout = BoxLayout(orientation='vertical', spacing=10, padding=10)
files = [f for f in os.listdir(pause_img_folder)
if f.lower().endswith(('.jpg', '.jpeg', '.png')) and os.path.isfile(os.path.join(pause_img_folder, f))
]
file_list_box = BoxLayout(orientation='vertical', spacing=8, size_hint_y=None)
file_list_box.bind(minimum_height=file_list_box.setter('height'))
selected_files = set()
file_widgets = []
for fname in files:
thumb_path = os.path.join(pause_img_folder, fname)
# Create a Button for the row
btn = Button(
size_hint_y=None,
height=60,
background_normal='',
background_color=(0.18, 0.18, 0.22, 1),
color=(1,1,1,1)
)
# Layout for image and label inside the button
box = BoxLayout(orientation='horizontal', spacing=8, padding=4)
img_widget = KivyImage(source=thumb_path, size_hint_x=None, width=60, allow_stretch=True, keep_ratio=True)
label = Label(text=fname, color=(1,1,1,1), size_hint_x=1, halign="left", valign="middle")
label.bind(size=lambda inst, val: setattr(inst, 'text_size', (inst.width, None)))
box.add_widget(img_widget)
box.add_widget(label)
btn.add_widget(box)
def make_on_release(btn, fname):
def on_release(instance):
if fname in selected_files:
selected_files.remove(fname)
btn.background_color = (0.18, 0.18, 0.22, 1)
else:
selected_files.add(fname)
btn.background_color = (0.8, 0.1, 0.1, 1)
return on_release
btn.bind(on_release=make_on_release(btn, fname))
file_widgets.append(btn)
file_list_box.add_widget(btn)
scroll = ScrollView(size_hint=(1, 0.7))
scroll.add_widget(file_list_box)
delete_file_btn = Button(
text="Delete Selected",
size_hint_y=None,
height=40,
background_color=(0.8,0.1,0.1,1),
color=(1,1,1,1)
)
close_btn = Button(
text="Close",
size_hint_y=None,
height=40,
background_color=(0.341, 0.235, 0.980, 1),
color=(1,1,1,1)
)
def delete_selected(instance):
for fname in list(selected_files):
fpath = os.path.join(pause_img_folder, fname)
if os.path.exists(fpath):
os.remove(fpath)
# Update labels in main popup
img_list = [f for f in os.listdir(pause_img_folder) if os.path.isfile(os.path.join(pause_img_folder, f))]
if img_list:
photo_count_label.text = f"You have {len(img_list)} photo(s) for this pause."
photo_list_label.text = ", ".join(img_list)
else:
photo_count_label.text = "No photos are set for this pause."
photo_list_label.text = ""
popup.dismiss()
delete_file_btn.bind(on_press=delete_selected)
close_btn.bind(on_press=lambda x: popup.dismiss())
delete_layout.add_widget(Label(text="Tap to select images to delete:", color=(1,1,1,1), size_hint_y=None, height=30))
delete_layout.add_widget(scroll)
delete_layout.add_widget(delete_file_btn)
delete_layout.add_widget(close_btn)
popup = Popup(
title="Delete Photo(s)",
content=delete_layout,
size_hint=(0.8, 0.8),
background_color=(0.13, 0.13, 0.16, 1)
)
popup.open()
delete_btn.bind(on_press=open_delete_popup)
save_pause_btn = Button(
text="Save Pause",
size_hint_x=0.33,
font_size=14,
background_color=(0.341, 0.235, 0.980, 1),
color=(1,1,1,1)
)
def save_pause(instance, pause=pause):
with open(pauses_path, "w") as f:
json.dump(pauses, f, indent=2)
save_pause_btn.bind(on_press=save_pause)
bottom_box.add_widget(browse_btn)
bottom_box.add_widget(delete_btn)
bottom_box.add_widget(save_pause_btn)
pause_box.add_widget(bottom_box)
# Separator line
from kivy.uix.widget import Widget
from kivy.graphics import Line
sep = Widget(size_hint_y=None, height=2)
with sep.canvas:
Color(0.25, 0.25, 0.30, 1)
Line(points=[0, 1, 1000, 1], width=2)
pause_box.add_widget(sep)
layout.add_widget(pause_box)
# Save all and close
save_all_btn = Button(text="Save All & Close", size_hint_y=None, height=44, background_color=(0.341, 0.235, 0.980, 1), color=(1,1,1,1))
def save_all(instance):
with open(pauses_path, "w") as f:
json.dump(pauses, f, indent=2)
if on_save_callback:
on_save_callback()
popup.dismiss()
save_all_btn.bind(on_press=save_all)
layout.add_widget(save_all_btn)
popup = Popup(title="Edit Pauses", content=layout, size_hint=(0.95, 0.95), background_color=(0.13, 0.13, 0.16, 1))
popup.open()