from time import time
from typing import Optional
from nextcloud_tasks_api.ical import Task
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import Vertical, Horizontal
from textual.events import Blur
from textual.message import Message
from textual.reactive import reactive, Reactive
from textual.timer import Timer
from textual.widgets import Input, TextArea, Checkbox
from .repo import TaskUpdater
from .status import StatusBar
class TaskEntry(Vertical):
class CheckCheckbox(Checkbox):
BUTTON_INNER = "✔"
class QuitAction(Message):
pass
DEFAULT_CSS = """
Horizontal {
height: auto;
margin: 0 0 1 0;
}
Input {
width: 1fr;
text-style: bold;
}
"""
BINDINGS = [
Binding("escape", "quit", "Back", show=True),
]
uid: Reactive[Optional[str]] = reactive(None)
def __init__(self, status: StatusBar, repo: TaskUpdater) -> None:
self._status: StatusBar = status
self._repo: TaskUpdater = repo
self._timer: Optional[Timer] = None
self._entry: Optional[Task] = None
self._completed: Checkbox = TaskEntry.CheckCheckbox(label="")
self._summary: Input = Input()
self._description: TextArea = TextArea(language="markdown", theme="monokai")
self._description.border_title = None
super().__init__()
def compose(self) -> ComposeResult:
yield Horizontal(self._completed, self._summary)
yield self._description
def action_quit(self) -> None:
self.post_message(self.QuitAction())
def set_focus(self) -> None:
self._description.focus()
def toggle(self, uid: str) -> None:
if self._entry is not None and self._entry.uid == uid:
cb: Checkbox = self.query_one(Checkbox)
cb.value = not cb.value
def flush(self) -> None:
if self._timer is not None:
self._timer.stop()
self._timer = None
if self._status.dirty:
self._status.dirty = False
if self._entry is not None:
self._repo.set(self._entry)
def _schedule_flush(self) -> None:
if self._timer is None:
self._timer = self.set_timer(3.0, self.flush, name="update_timer", pause=False)
else:
self._timer.reset()
def watch_uid(self, uid: Optional[str]) -> None:
self.flush()
if uid is None:
self.visible = False
self._entry = None
else:
self.visible = True
self.disabled = True
self.loading = True
self.run_worker(self._load(uid))
async def _load(self, uid: str) -> None:
new_entry: Task = await self._repo.get(uid)
if new_entry.uid == self.uid: # run_worker exclusive=True might lead to 'coroutine was never awaited'
self._entry = new_entry
self._summary.value = self._entry.summary or ""
self._completed.value = self._entry.completed is not None
self._description.text = self._entry.description or ""
self.loading = False
self.disabled = False
def on_input_changed(self, evt: Input.Changed) -> None:
if self._entry is not None and evt.value != (self._entry.summary or ""):
self._entry.summary = evt.value or None
self._status.dirty = True
self._schedule_flush()
def on_checkbox_changed(self, evt: Checkbox.Changed) -> None:
if self._entry is not None and evt.value != (self._entry.completed is not None):
self._entry.completed = round(time()) if evt.value else None
self._status.dirty = True
self.flush()
def on_text_area_changed(self, evt: TextArea.Changed) -> None:
if self._entry is not None and evt.text_area.text != (self._entry.description or ""):
self._entry.description = evt.text_area.text or None
self._status.dirty = True
self._schedule_flush()
def on_descendant_blur(self, event: Blur) -> None:
self.flush()