from logging import getLogger, Logger
from os import environ
from typing import Optional
from nextcloud_tasks_api import NextcloudTasksApi, NextcloudTasksApiFactory, ApiError
from textual.app import ComposeResult
from textual.binding import Binding
from textual.reactive import reactive, Reactive
from textual.screen import Screen
from textual.widget import Widget
from textual.widgets import Input, Button, Footer
from .status import StatusBar
from .widget import TaskWidget
class AppError(RuntimeError):
pass
class MainScreen(Screen[int]):
BINDINGS = [
Binding("escape", "quit", "Quit", show=True),
]
def __init__(self, api: NextcloudTasksApi, default_list: Optional[str]) -> None:
self._api: NextcloudTasksApi = api
self._status: StatusBar = StatusBar(f"{api.username} @ {api.base_url}")
self._widget: TaskWidget = TaskWidget(api, self._status, default_list)
super().__init__()
def compose(self) -> ComposeResult:
yield self._status
yield self._widget
yield Footer()
async def on_unmount(self) -> None:
await self._api.close()
async def action_quit(self) -> None:
self.dismiss(0)
class UnlockScreen(Screen[Optional[NextcloudTasksApi]]):
"""Modal startup password prompt, resulting in an instantiated API client."""
BINDINGS = [
Binding("escape", "quit", "Quit", show=False),
]
busy: Reactive[bool] = reactive(False)
class MiddleCenter(Widget):
"""Generic container that centers full viewport."""
DEFAULT_CSS = """
MiddleCenter {
align: center middle;
height: 1fr;
width: 1fr;
}
MiddleCenter Input {
width: 100%;
max-width: 32;
}
MiddleCenter Button {
width: 100%;
max-width: 30;
margin: 1 1 0 1;
}
"""
def __init__(self, api: NextcloudTasksApiFactory) -> None:
super().__init__()
self._logger: Logger = getLogger("Login")
self._api: NextcloudTasksApiFactory = api
self._user_input: Input = Input(placeholder="Username", value=environ.get("NEXTCLOUD_USER", ""))
self._pass_input: Input = Input(placeholder="Password", value=environ.get("NEXTCLOUD_PASS", ""), password=True)
self._submit: Button = Button(label="Login", variant="primary")
self._status: StatusBar = StatusBar(self._api.base_url)
def compose(self) -> ComposeResult:
yield self._status
yield UnlockScreen.MiddleCenter(self._user_input, self._pass_input, self._submit)
def on_mount(self) -> None:
if self._user_input.value and self._pass_input.value:
self._do_unlock(self._user_input.value, self._pass_input.value)
def on_input_submitted(self, evt: Input.Submitted) -> None:
self._do_unlock(self._user_input.value, self._pass_input.value)
def on_button_pressed(self, event: Button.Pressed) -> None:
self._do_unlock(self._user_input.value, self._pass_input.value)
def on_input_changed(self) -> None:
self._submit.variant = "primary"
def _do_unlock(self, username: str, password: str) -> None:
self.busy = True
self._submit.variant = "primary"
self.run_worker(self._try_unlock(username, password))
def watch_busy(self, busy: bool) -> None:
self._status.busy = 1 if busy else 0
self.disabled = busy
async def _try_unlock(self, username: str, password: str) -> None:
api: NextcloudTasksApi = await self._api.create(username=username, password=password)
try:
await api.open()
if not [_ async for _ in api.list_user_principal()]:
raise ApiError("Cannot check for user principal")
except ApiError as e:
await api.close()
self.busy = False
self._submit.variant = "error"
self._pass_input.focus()
self._logger.error(str(e))
return
self._submit.variant = "success" # still disabled
self.dismiss(api)
def action_quit(self) -> None:
self.dismiss(None)