Added the edit_entry_screen.py and modified the mock entry_service_mock.py to be async and also add the screen change on diary_list_screen.py and added the rename_entry_modal.py and added all the necessary CSS on the pilgrim.css

This commit is contained in:
Gustavo Henrique Santos Souza de Miranda 2025-06-22 20:32:09 -03:00
parent 981b38f994
commit 58807460d7
7 changed files with 686 additions and 8 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
database.db database.db
__pycache__ __pycache__
/.idea/

View File

@ -1,4 +1,5 @@
from typing import List from typing import List, Tuple
import asyncio
from pilgrim.service.entry_service import EntryService from pilgrim.service.entry_service import EntryService
from pilgrim.models.entry import Entry from pilgrim.models.entry import Entry
@ -15,23 +16,61 @@ class EntryServiceMock(EntryService):
travel_diary_id=1, id=2,photos=[]), travel_diary_id=1, id=2,photos=[]),
3: Entry(title="The Mount Royal", text="The Mount Royal is fucking awesome", date="28/07/2025", 3: Entry(title="The Mount Royal", text="The Mount Royal is fucking awesome", date="28/07/2025",
travel_diary_id=1, id=3, photos=[]), travel_diary_id=1, id=3, photos=[]),
4: Entry(title="Old Montreal", text="Exploring the historic district", date="29/07/2025",
travel_diary_id=1, id=4, photos=[]),
5: Entry(title="Notre-Dame Basilica", text="Beautiful architecture", date="30/07/2025",
travel_diary_id=1, id=5, photos=[]),
6: Entry(title="Parc Jean-Drapeau", text="Great views of the city", date="31/07/2025",
travel_diary_id=1, id=6, photos=[]),
7: Entry(title="La Ronde", text="Amusement park fun", date="01/08/2025",
travel_diary_id=1, id=7, photos=[]),
8: Entry(title="Biodome", text="Nature and science", date="02/08/2025",
travel_diary_id=1, id=8, photos=[]),
9: Entry(title="Botanical Gardens", text="Peaceful walk", date="03/08/2025",
travel_diary_id=1, id=9, photos=[]),
10: Entry(title="Olympic Stadium", text="Historic venue", date="04/08/2025",
travel_diary_id=1, id=10, photos=[]),
} }
self._next_id = 4 self._next_id = 11
# Métodos síncronos (mantidos para compatibilidade)
def create(self, travel_diary_id: int, title: str, text: str, date: str) -> Entry: def create(self, travel_diary_id: int, title: str, text: str, date: str) -> Entry:
"""Versão síncrona"""
new_entry = Entry(title, text, date, travel_diary_id, id=self._next_id) new_entry = Entry(title, text, date, travel_diary_id, id=self._next_id)
self.mock_data[self._next_id] = new_entry self.mock_data[self._next_id] = new_entry
self._next_id += 1 self._next_id += 1
return new_entry return new_entry
def read_by_id(self, entry_id: int) -> Entry | None: def read_by_id(self, entry_id: int) -> Entry | None:
"""Versão síncrona"""
return self.mock_data.get(entry_id) return self.mock_data.get(entry_id)
def read_all(self) -> List[Entry]: def read_all(self) -> List[Entry]:
"""Versão síncrona"""
return list(self.mock_data.values()) return list(self.mock_data.values())
def update(self, entry_id: int, entry_dst: Entry) -> Entry | None: def read_by_travel_diary_id(self, travel_diary_id: int) -> List[Entry]:
item_to_update = self.mock_data.get(entry_id) """Versão síncrona - lê entradas por diário"""
return [entry for entry in self.mock_data.values() if entry.fk_travel_diary_id == travel_diary_id]
def read_paginated(self, travel_diary_id: int, page: int = 1, page_size: int = 5) -> Tuple[List[Entry], int, int]:
"""Versão síncrona - lê entradas paginadas por diário"""
entries = self.read_by_travel_diary_id(travel_diary_id)
entries.sort(key=lambda x: x.id, reverse=True) # Mais recentes primeiro
total_entries = len(entries)
total_pages = (total_entries + page_size - 1) // page_size
start_index = (page - 1) * page_size
end_index = start_index + page_size
page_entries = entries[start_index:end_index]
return page_entries, total_pages, total_entries
def update(self, entry_src: Entry, entry_dst: Entry) -> Entry | None:
"""Versão síncrona"""
item_to_update = self.mock_data.get(entry_src.id)
if item_to_update: if item_to_update:
item_to_update.title = entry_dst.title if entry_dst.title is not None else item_to_update.title item_to_update.title = entry_dst.title if entry_dst.title is not None else item_to_update.title
item_to_update.text = entry_dst.text if entry_dst.text is not None else item_to_update.text item_to_update.text = entry_dst.text if entry_dst.text is not None else item_to_update.text
@ -43,5 +82,42 @@ class EntryServiceMock(EntryService):
return item_to_update return item_to_update
return None return None
def delete(self, entry_id: int) -> Entry | None: def delete(self, entry_src: Entry) -> Entry | None:
return self.mock_data.pop(entry_id, None) """Versão síncrona"""
return self.mock_data.pop(entry_src.id, None)
# Métodos assíncronos (principais)
async def async_create(self, travel_diary_id: int, title: str, text: str, date: str) -> Entry:
"""Versão assíncrona"""
await asyncio.sleep(0.01) # Simula I/O
return self.create(travel_diary_id, title, text, date)
async def async_read_by_id(self, entry_id: int) -> Entry | None:
"""Versão assíncrona"""
await asyncio.sleep(0.01) # Simula I/O
return self.read_by_id(entry_id)
async def async_read_all(self) -> List[Entry]:
"""Versão assíncrona"""
await asyncio.sleep(0.01) # Simula I/O
return self.read_all()
async def async_read_by_travel_diary_id(self, travel_diary_id: int) -> List[Entry]:
"""Versão assíncrona - lê entradas por diário"""
await asyncio.sleep(0.01) # Simula I/O
return self.read_by_travel_diary_id(travel_diary_id)
async def async_read_paginated(self, travel_diary_id: int, page: int = 1, page_size: int = 5) -> Tuple[List[Entry], int, int]:
"""Versão assíncrona - lê entradas paginadas por diário"""
await asyncio.sleep(0.01) # Simula I/O
return self.read_paginated(travel_diary_id, page, page_size)
async def async_update(self, entry_src: Entry, entry_dst: Entry) -> Entry | None:
"""Versão assíncrona"""
await asyncio.sleep(0.01) # Simula I/O
return self.update(entry_src, entry_dst)
async def async_delete(self, entry_src: Entry) -> Entry | None:
"""Versão assíncrona"""
await asyncio.sleep(0.01) # Simula I/O
return self.delete(entry_src)

View File

@ -11,6 +11,7 @@ from pilgrim.models.travel_diary import TravelDiary
from pilgrim.ui.screens.about_screen import AboutScreen from pilgrim.ui.screens.about_screen import AboutScreen
from pilgrim.ui.screens.edit_diary_modal import EditDiaryModal from pilgrim.ui.screens.edit_diary_modal import EditDiaryModal
from pilgrim.ui.screens.new_diary_modal import NewDiaryModal from pilgrim.ui.screens.new_diary_modal import NewDiaryModal
from pilgrim.ui.screens.edit_entry_screen import EditEntryScreen
class DiaryListScreen(Screen): class DiaryListScreen(Screen):
@ -245,7 +246,11 @@ class DiaryListScreen(Screen):
"""Ação para abrir diário selecionado""" """Ação para abrir diário selecionado"""
if self.selected_diary_index is not None: if self.selected_diary_index is not None:
diary_id = self.diary_id_map.get(self.selected_diary_index) diary_id = self.diary_id_map.get(self.selected_diary_index)
self.notify(f"Abrindo diário ID: {diary_id}") if diary_id:
self.app.push_screen(EditEntryScreen(diary_id=diary_id))
self.notify(f"Opening diary ID: {diary_id}")
else:
self.notify("Invalid diary ID")
else: else:
self.notify("Selecione um diário para abrir") self.notify("Selecione um diário para abrir")

View File

@ -0,0 +1,430 @@
from typing import Optional, List
import asyncio
from datetime import datetime
from textual.app import ComposeResult
from textual.screen import Screen
from textual.widgets import Header, Footer, Static, TextArea
from textual.binding import Binding
from textual.containers import Container, Horizontal
from pilgrim.models.entry import Entry
from pilgrim.models.travel_diary import TravelDiary
from pilgrim.ui.screens.rename_entry_modal import RenameEntryModal
class EditEntryScreen(Screen):
TITLE = "Pilgrim - Edit Entry"
BINDINGS = [
Binding("ctrl+s", "save", "Save"),
Binding("ctrl+n", "next_entry", "Next/New Entry"),
Binding("ctrl+b", "prev_entry", "Previous Entry"),
Binding("ctrl+r", "rename_entry", "Rename Entry"),
Binding("escape", "back_to_list", "Back to List"),
Binding("r", "force_refresh", "Force refresh"),
]
def __init__(self, diary_id: int = 1):
super().__init__()
self.diary_id = diary_id
self.diary_name = "Unknown Diary"
self.current_entry_index = 0
self.entries: List[Entry] = []
self.is_new_entry = False
self.has_unsaved_changes = False
self.new_entry_content = ""
self.new_entry_title = "New Entry"
self.next_entry_id = 1
self._updating_display = False
self._original_content = ""
self.is_refreshing = False
# Main header
self.header = Header(name="Pilgrim v6", classes="EditEntryScreen-header")
# Sub-header widgets
self.diary_info = Static(f"Diary: {self.diary_name}", id="diary_info", classes="EditEntryScreen-diary-info")
self.entry_info = Static("Loading...", id="entry_info", classes="EditEntryScreen-entry-info")
self.status_indicator = Static("Saved", id="status_indicator", classes="EditEntryScreen-status-indicator")
# Sub-header container
self.sub_header = Horizontal(
self.diary_info,
Static(classes="spacer EditEntryScreen-spacer"),
self.entry_info,
self.status_indicator,
id="sub_header",
classes="EditEntryScreen-sub-header"
)
# Text area
self.text_entry = TextArea(id="text_entry", classes="EditEntryScreen-text-entry")
# Main container
self.main = Container(
self.sub_header,
self.text_entry,
id="EditEntryScreen_MainContainer",
classes="EditEntryScreen-main-container"
)
# Footer
self.footer = Footer(classes="EditEntryScreen-footer")
def compose(self) -> ComposeResult:
yield self.header
yield self.main
yield self.footer
def on_mount(self) -> None:
"""Called when the screen is mounted"""
self.refresh_entries()
self.update_diary_info()
def update_diary_info(self):
"""Updates diary information"""
try:
service_manager = self.app.service_manager
travel_diary_service = service_manager.get_travel_diary_service()
diary = travel_diary_service.read_by_id(self.diary_id)
if diary:
self.diary_name = diary.name
self.diary_info.update(f"Diary: {self.diary_name}")
except Exception as e:
self.notify(f"Error loading diary info: {str(e)}")
def refresh_entries(self):
"""Synchronous version of refresh"""
try:
service_manager = self.app.service_manager
entry_service = service_manager.get_entry_service()
# Get all entries for this diary
all_entries = entry_service.read_all()
self.entries = [entry for entry in all_entries if entry.fk_travel_diary_id == self.diary_id]
# Sort by ID
self.entries.sort(key=lambda x: x.id)
# Update next entry ID
if self.entries:
self.next_entry_id = max(entry.id for entry in self.entries) + 1
else:
self.next_entry_id = 1
self._update_entry_display()
self._update_sub_header()
except Exception as e:
self.notify(f"Error loading entries: {str(e)}")
async def async_refresh_entries(self):
"""Asynchronous version of refresh"""
if self.is_refreshing:
return
self.is_refreshing = True
try:
service_manager = self.app.service_manager
entry_service = service_manager.get_entry_service()
# For now, use synchronous method since mock doesn't have async
all_entries = entry_service.read_all()
self.entries = [entry for entry in all_entries if entry.fk_travel_diary_id == self.diary_id]
# Sort by ID
self.entries.sort(key=lambda x: x.id)
# Update next entry ID
if self.entries:
self.next_entry_id = max(entry.id for entry in self.entries) + 1
else:
self.next_entry_id = 1
self._update_entry_display()
self._update_sub_header()
except Exception as e:
self.notify(f"Error loading entries: {str(e)}")
finally:
self.is_refreshing = False
def _update_status_indicator(self, text: str, css_class: str):
"""Helper to update status indicator text and class."""
self.status_indicator.update(text)
self.status_indicator.remove_class("saved", "not-saved", "new", "read-only")
self.status_indicator.add_class(css_class)
def _update_sub_header(self):
"""Updates the sub-header with current entry information."""
if not self.entries and not self.is_new_entry:
self.entry_info.update("No entries")
self._update_status_indicator("Saved", "saved")
return
if self.is_new_entry:
self.entry_info.update(f"New Entry: {self.new_entry_title}")
if self.has_unsaved_changes:
self._update_status_indicator("Not Saved", "not-saved")
else:
self._update_status_indicator("New", "new")
else:
current_entry = self.entries[self.current_entry_index]
entry_text = f"Entry: ({self.current_entry_index + 1}/{len(self.entries)}) {current_entry.title}"
self.entry_info.update(entry_text)
self._update_status_indicator("Saved", "saved")
def _save_current_state(self):
"""Saves the current state before navigating"""
if self.is_new_entry:
self.new_entry_content = self.text_entry.text
elif self.entries and self.has_unsaved_changes:
current_entry = self.entries[self.current_entry_index]
current_entry.text = self.text_entry.text
def _finish_display_update(self):
"""Finishes the display update by reactivating change detection"""
self._updating_display = False
self._update_sub_header()
def _update_entry_display(self):
"""Updates the display of the current entry"""
if not self.entries and not self.is_new_entry:
self.text_entry.text = f"No entries found for diary '{self.diary_name}'\n\nPress Ctrl+N to create a new entry."
self.text_entry.read_only = True
self._original_content = self.text_entry.text
self._update_sub_header()
return
self._updating_display = True
if self.is_new_entry:
self.text_entry.text = self.new_entry_content
self.text_entry.read_only = False
self._original_content = self.new_entry_content
self.has_unsaved_changes = False
else:
current_entry = self.entries[self.current_entry_index]
self.text_entry.text = current_entry.text
self.text_entry.read_only = False
self._original_content = current_entry.text
self.has_unsaved_changes = False
self.call_after_refresh(self._finish_display_update)
def on_text_area_changed(self, event) -> None:
"""Detects text changes to mark as unsaved"""
if (hasattr(self, 'text_entry') and not self.text_entry.read_only and
not getattr(self, '_updating_display', False) and hasattr(self, '_original_content')):
current_content = self.text_entry.text
if current_content != self._original_content:
if not self.has_unsaved_changes:
self.has_unsaved_changes = True
self._update_sub_header()
else:
if self.has_unsaved_changes:
self.has_unsaved_changes = False
self._update_sub_header()
def action_back_to_list(self) -> None:
"""Goes back to the diary list"""
if self.is_new_entry and not self.text_entry.text.strip() and not self.has_unsaved_changes:
self.app.pop_screen()
self.notify("Returned to diary list")
return
if self.has_unsaved_changes or (self.is_new_entry and self.text_entry.text.strip()):
self.notify("There are unsaved changes! Use Ctrl+S to save before leaving.")
return
self.app.pop_screen()
self.notify("Returned to diary list")
def action_next_entry(self) -> None:
"""Goes to the next entry"""
self._save_current_state()
if not self.entries:
if not self.is_new_entry:
self.is_new_entry = True
self._update_entry_display()
self.notify("New entry created")
else:
self.notify("Already in a new entry")
return
if self.is_new_entry:
self.notify("Already at the last position (new entry)")
elif self.current_entry_index < len(self.entries) - 1:
self.current_entry_index += 1
self._update_entry_display()
current_entry = self.entries[self.current_entry_index]
self.notify(f"Navigating to: {current_entry.title}")
else:
self.is_new_entry = True
self._update_entry_display()
self.notify("New entry created")
def action_prev_entry(self) -> None:
"""Goes to the previous entry"""
self._save_current_state()
if not self.entries:
self.notify("No entries to navigate")
return
if self.is_new_entry:
if self.entries:
self.is_new_entry = False
self.current_entry_index = len(self.entries) - 1
self._update_entry_display()
current_entry = self.entries[self.current_entry_index]
self.notify(f"Navigating to: {current_entry.title}")
else:
self.notify("No previous entries")
elif self.current_entry_index > 0:
self.current_entry_index -= 1
self._update_entry_display()
current_entry = self.entries[self.current_entry_index]
self.notify(f"Navigating to: {current_entry.title}")
else:
self.notify("Already at the first entry")
def action_rename_entry(self) -> None:
"""Opens a modal to rename the entry."""
if not self.entries and not self.is_new_entry:
self.notify("No entry to rename", severity="warning")
return
if self.is_new_entry:
current_name = self.new_entry_title
else:
current_entry = self.entries[self.current_entry_index]
current_name = current_entry.title
self.app.push_screen(
RenameEntryModal(current_name=current_name),
self.handle_rename_result
)
def handle_rename_result(self, new_name: str | None) -> None:
"""Callback that processes the rename modal result."""
if new_name is None:
self.notify("Rename cancelled")
return
if not new_name.strip():
self.notify("Name cannot be empty", severity="error")
return
if self.is_new_entry:
old_name = self.new_entry_title
self.new_entry_title = new_name
self.notify(f"New entry title changed to '{new_name}'")
else:
current_entry = self.entries[self.current_entry_index]
old_name = current_entry.title
current_entry.title = new_name
self.notify(f"Title changed from '{old_name}' to '{new_name}'")
self.has_unsaved_changes = True
self._update_sub_header()
def action_save(self) -> None:
"""Saves the current entry"""
if self.is_new_entry:
content = self.text_entry.text.strip()
if not content:
self.notify("Empty entry cannot be saved")
return
# Schedule async creation
self.call_later(self._async_create_entry, content)
else:
# Schedule async update
self.call_later(self._async_update_entry)
async def _async_create_entry(self, content: str):
"""Creates a new entry asynchronously"""
try:
service_manager = self.app.service_manager
entry_service = service_manager.get_entry_service()
# Get current date
current_date = datetime.now().strftime("%d/%m/%Y")
new_entry = entry_service.create(
travel_diary_id=self.diary_id,
title=self.new_entry_title,
text=content,
date=current_date
)
if new_entry:
self.entries.append(new_entry)
self.entries.sort(key=lambda x: x.id)
# Find the new entry index
for i, entry in enumerate(self.entries):
if entry.id == new_entry.id:
self.current_entry_index = i
break
self.is_new_entry = False
self.has_unsaved_changes = False
self._original_content = new_entry.text
self.new_entry_title = "New Entry"
self.next_entry_id = max(entry.id for entry in self.entries) + 1
self._update_entry_display()
self.notify(f"New entry '{new_entry.title}' saved successfully!")
else:
self.notify("Error creating entry")
except Exception as e:
self.notify(f"Error creating entry: {str(e)}")
async def _async_update_entry(self):
"""Updates the current entry asynchronously"""
try:
if not self.entries:
self.notify("No entry to update")
return
current_entry = self.entries[self.current_entry_index]
updated_content = self.text_entry.text
# Create updated entry object
updated_entry = Entry(
title=current_entry.title,
text=updated_content,
date=current_entry.date,
travel_diary_id=current_entry.fk_travel_diary_id,
id=current_entry.id
)
service_manager = self.app.service_manager
entry_service = service_manager.get_entry_service()
result = entry_service.update(current_entry, updated_entry)
if result:
current_entry.text = updated_content
self.has_unsaved_changes = False
self._original_content = updated_content
self._update_sub_header()
self.notify(f"Entry '{current_entry.title}' saved successfully!")
else:
self.notify("Error updating entry")
except Exception as e:
self.notify(f"Error updating entry: {str(e)}")
def action_force_refresh(self):
"""Forces manual refresh"""
self.notify("Forcing refresh...")
self.refresh_entries()
self.call_later(self.async_refresh_entries)

View File

@ -0,0 +1,58 @@
from textual.app import ComposeResult
from textual.containers import Vertical, Horizontal
from textual.screen import ModalScreen
from textual.widgets import Label, Input, Button
class RenameEntryModal(ModalScreen[str]):
"""A modal screen to rename a diary entry."""
BINDINGS = [
("escape", "cancel", "Cancel"),
]
def __init__(self, current_name: str):
super().__init__()
self._current_name = current_name
self.name_input = Input(
value=self._current_name,
placeholder="Type the new name...",
id="rename_input",
classes="RenameEntryModal-name-input"
)
def compose(self) -> ComposeResult:
with Vertical(id="rename_entry_dialog", classes="RenameEntryModal-dialog"):
yield Label("Rename Entry", classes="dialog-title RenameEntryModal-title")
yield Label("New Entry Title:", classes="RenameEntryModal-label")
yield self.name_input
with Horizontal(classes="dialog-buttons RenameEntryModal-buttons"):
yield Button("Save", variant="primary", id="save", classes="RenameEntryModal-save-button")
yield Button("Cancel", variant="default", id="cancel", classes="RenameEntryModal-cancel-button")
def on_mount(self) -> None:
"""Focuses on the input when the screen is mounted."""
self.name_input.focus()
self.name_input.cursor_position = len(self.name_input.value)
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Handles button clicks."""
if event.button.id == "save":
new_name = self.name_input.value.strip()
if new_name:
self.dismiss(new_name) # Returns the new name
else:
self.dismiss(None) # Considers empty name as cancellation
else:
self.dismiss(None) # Returns None for cancellation
def on_input_submitted(self, event: Input.Submitted) -> None:
"""Allows saving by pressing Enter."""
new_name = event.value.strip()
if new_name:
self.dismiss(new_name)
else:
self.dismiss(None)
def action_cancel(self) -> None:
self.dismiss(None)

View File

@ -169,4 +169,106 @@ Screen.-modal {
.NewDiaryModal-ButtonsContainer Button{ .NewDiaryModal-ButtonsContainer Button{
margin: 0 1; margin: 0 1;
width: 1fr; width: 1fr;
}
/* EditEntryScreen Styles */
.EditEntryScreen-sub-header {
layout: horizontal;
background: $primary-darken-1;
height: 1;
padding: 0 1;
align: center middle;
width: 100%;
}
.EditEntryScreen-diary-info {
width: auto;
color: $text-muted;
margin-right: 2;
}
.EditEntryScreen-entry-info {
width: auto;
color: $text;
margin-right: 1;
}
.EditEntryScreen-spacer {
width: 1fr;
}
.EditEntryScreen-status-indicator {
width: auto;
padding: 0 1;
content-align: center middle;
}
.EditEntryScreen-status-indicator.saved {
background: #2E8B57; /* SeaGreen */
color: $text;
}
.EditEntryScreen-status-indicator.not-saved {
background: #B22222; /* FireBrick */
color: $text;
}
.EditEntryScreen-status-indicator.new {
background: #4682B4; /* SteelBlue */
color: $text;
}
.EditEntryScreen-status-indicator.read-only {
background: #696969; /* DimGray */
color: $text-muted;
}
.EditEntryScreen-main-container {
background: bisque;
margin: 0;
}
.EditEntryScreen-text-entry {
width: 100%;
height: 1fr;
border: none;
}
/* RenameEntryModal Styles */
.RenameEntryModal-dialog {
layout: vertical;
width: 60%;
height: auto;
background: $surface;
border: thick $accent;
padding: 2 4;
align: center middle;
}
.RenameEntryModal-title {
text-align: center;
text-style: bold;
color: $accent;
margin-bottom: 1;
}
.RenameEntryModal-label {
margin-bottom: 1;
}
.RenameEntryModal-name-input {
width: 1fr;
margin-bottom: 2;
}
.RenameEntryModal-buttons {
width: 1fr;
height: auto;
align: center middle;
padding-top: 1;
}
.RenameEntryModal-buttons Button {
margin: 0 1;
width: 1fr;
} }

View File

@ -8,6 +8,7 @@ from textual.screen import Screen
from pilgrim.service.servicemanager import ServiceManager from pilgrim.service.servicemanager import ServiceManager
from pilgrim.ui.screens.about_screen import AboutScreen from pilgrim.ui.screens.about_screen import AboutScreen
from pilgrim.ui.screens.diary_list_screen import DiaryListScreen from pilgrim.ui.screens.diary_list_screen import DiaryListScreen
from pilgrim.ui.screens.edit_entry_screen import EditEntryScreen
CSS_FILE_PATH = Path(__file__).parent / "styles" / "pilgrim.css" CSS_FILE_PATH = Path(__file__).parent / "styles" / "pilgrim.css"
@ -42,7 +43,12 @@ class UIApp(App):
screen.dismiss screen.dismiss
) )
elif isinstance(screen, EditEntryScreen):
yield SystemCommand(
"Back to Diary List",
"Return to the diary list",
screen.action_back_to_list
)
# Always include quit command # Always include quit command
yield SystemCommand( yield SystemCommand(