Add pages abstraction

This commit is contained in:
2023-12-14 22:11:10 +03:00
parent 62ec2d52f8
commit 7f4aeb921e
14 changed files with 110 additions and 80 deletions

View File

@@ -3,7 +3,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class APISettings(BaseSettings):
model_config = SettingsConfigDict(prefix="backend_api_")
model_config = SettingsConfigDict(env_prefix="backend_api_")
port: conint(ge=1, le=65535) = 8001

View File

@@ -3,6 +3,6 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class DatabaseSettings(BaseSettings):
model_config = SettingsConfigDict(prefix="backend_database_")
model_config = SettingsConfigDict(env_prefix="backend_database_")
dsn: AnyUrl = "sqlite+aiosqlite:///soul_diary.sqlite3"

View File

@@ -9,7 +9,6 @@ from soul_diary.ui.app.routes import AUTH, SENSE_LIST
async def middleware(page: flet.Page, params: Params, basket: Basket):
local_storage = LocalStorage(client_storage=page.client_storage)
auth_data = await local_storage.get_auth_data()
# await local_storage._client_storage.clear_async()
if auth_data is None:
await page.go_async(AUTH)
return

View File

@@ -1,15 +0,0 @@
from typing import Callable
import flet
from flet_route import Basket, Params
class BaseMiddleware:
async def __call__(
self,
page: flet.Page,
params: Params,
basket: Basket,
next_handler: Callable,
):
raise NotImplementedError

View File

@@ -0,0 +1,61 @@
from functools import partial
import flet
from soul_diary.ui.app.models import BackendType
from soul_diary.ui.app.pages.base import BasePage
class ChooseBackendPage(BasePage):
BACKENDS = {
BackendType.LOCAL: "Локально",
BackendType.SOUL: "Soul Diary сервер",
}
def __init__(self, backend: BackendType | None = None):
self.backend: BackendType | None = backend
super().__init__()
def build(self) -> flet.Container:
label = flet.Container(
content=flet.Text("Выберите сервер"),
alignment=flet.alignment.center,
)
dropdown = flet.Dropdown(
label="Бэкенд",
options=[
flet.dropdown.Option(text=text, key=key.value)
for key, text in self.BACKENDS.items()
],
value=None if self.backend is None else self.backend.value,
on_change=self.callback_change_backend,
)
connect_button = flet.ElevatedButton(
"Выбрать",
width=300,
height=50,
on_click=partial(self.callback_choose_backend, dropdown=dropdown),
)
return flet.Container(
content=flet.Column(
controls=[label, dropdown, connect_button],
width=300,
),
alignment=flet.alignment.center,
)
async def callback_change_backend(self, event: flet.ControlEvent):
self.backend = BackendType(event.control.value)
event.control.error_text = None
await event.control.update_async()
async def callback_choose_backend(self, event: flet.ControlEvent, dropdown: flet.Dropdown):
if self.backend == BackendType.LOCAL:
await self.credentials_view(page=event.page)
elif self.backend == BackendType.SOUL:
await self.soul_server_data_view(page=event.page)
else:
dropdown.error_text = "Выберите тип бекенда"
await self.update_async()

View File

@@ -0,0 +1,8 @@
import flet
from soul_diary.ui.app.pages.base import BasePage
class SoulBackendDataPage(BasePage):
def build(self) -> flet.Container:
pass

View File

@@ -0,0 +1,7 @@
import flet
class BasePage(flet.UserControl):
async def apply(self, page: flet.Page):
await page.clean_async()
await page.add_async(self)

View File

@@ -1,6 +1,6 @@
import asyncio
from functools import partial
from typing import Any, Callable, Sequence
from typing import Any
import flet
from pydantic import AnyHttpUrl
@@ -8,7 +8,6 @@ from pydantic import AnyHttpUrl
from soul_diary.ui.app.backend.exceptions import IncorrectCredentialsException, UserAlreadyExistsException
from soul_diary.ui.app.backend.soul import SoulBackend
from soul_diary.ui.app.local_storage import LocalStorage
from soul_diary.ui.app.middlewares.base import BaseMiddleware
from soul_diary.ui.app.models import BackendType, Options
from soul_diary.ui.app.routes import AUTH, SENSE_LIST
from soul_diary.ui.app.views.exceptions import SoulServerIncorrectURL
@@ -21,7 +20,6 @@ class AuthView(BaseView):
local_storage: LocalStorage,
backend: BackendType | None = None,
backend_data: dict[str, Any] | None = None,
middlewares: Sequence[BaseMiddleware | Callable] = (),
):
self.top_container: flet.Container
self.center_container: flet.Container
@@ -33,7 +31,7 @@ class AuthView(BaseView):
self.username: str | None = None
self.password: str | None = None
super().__init__(local_storage=local_storage, middlewares=middlewares)
super().__init__(local_storage=local_storage)
async def clear(self):
self.top_container.content = None
@@ -81,7 +79,6 @@ class AuthView(BaseView):
label = flet.Text("Выберите сервер")
self.top_container.content = label
backend_controls = flet.Column()
backend_dropdown = flet.Dropdown(
label="Бэкенд",
options=[
@@ -91,14 +88,7 @@ class AuthView(BaseView):
value=None if self.backend is None else self.backend.value,
on_change=self.callback_change_backend,
)
container = flet.Container(
content=flet.Column(
controls=[backend_dropdown, backend_controls],
width=300,
),
)
self.center_container.content = container
self.center_container.content = backend_dropdown
connect_button = flet.ElevatedButton(
"Выбрать",

View File

@@ -1,6 +1,6 @@
import asyncio
from contextlib import asynccontextmanager
from functools import partial, reduce
from typing import Any, Callable, Sequence
from typing import Any, Callable
import flet
from flet_route import Basket, Params
@@ -10,7 +10,6 @@ from soul_diary.ui.app.backend.exceptions import NonAuthenticatedException
from soul_diary.ui.app.backend.local import LocalBackend
from soul_diary.ui.app.backend.soul import SoulBackend
from soul_diary.ui.app.local_storage import LocalStorage
from soul_diary.ui.app.middlewares.base import BaseMiddleware
from soul_diary.ui.app.models import BackendType
@@ -29,21 +28,6 @@ def view(initial: bool = False, disabled: bool = False):
return decorator
def setup_middlewares(function: Callable):
async def wrapper(self, page: flet.Page, params: Params, basket: Basket) -> flet.View:
entrypoint = partial(function, self)
if self.middlewares:
entrypoint = partial(self.middlewares[-1], next_handler=entrypoint)
entrypoint = reduce(
lambda entrypoint, function: partial(entrypoint, next_handler=function),
self.middlewares[-2::-1],
entrypoint,
)
return await entrypoint(page=page, params=params, basket=basket)
return wrapper
class MetaView(type):
def __init__(cls, name: str, bases: tuple[type], attrs: dict[str, Any]):
super().__init__(name, bases, attrs)
@@ -76,28 +60,23 @@ class BaseView(metaclass=MetaView):
is_abstract = True
_initial_view: Callable | None
def __init__(
self,
local_storage: LocalStorage,
middlewares: Sequence[BaseMiddleware | Callable] = (),
):
def __init__(self, local_storage: LocalStorage):
self.local_storage = local_storage
self.middlewares = middlewares
self.container: flet.Container
self.stack: flet.Stack
self.view: flet.View
@setup_middlewares
async def entrypoint(self, page: flet.Page, params: Params, basket: Basket) -> flet.View:
self.container = flet.Container()
self.stack = flet.Stack(controls=[self.container])
self.view = flet.View(controls=[self.stack], route="/test")
self.view = flet.View(controls=[self.container])
await self.setup()
await self.clear()
self.clear_data()
await self.run_initial_view(page=page)
loop = asyncio.get_running_loop()
loop.create_task(self.run_initial_view(page=page))
return self.view
async def setup(self):
@@ -111,21 +90,20 @@ class BaseView(metaclass=MetaView):
@asynccontextmanager
async def in_progress(self, page: flet.Page, tooltip: str | None = None):
for control in self.stack.controls:
control.disabled = True
self.stack.controls.append(
flet.Container(
self.container.disabled = True
page.splash = flet.Column(
controls=[flet.Container(
content=flet.ProgressRing(tooltip=tooltip),
alignment=flet.alignment.center,
),
)],
alignment=flet.MainAxisAlignment.CENTER,
)
await page.update_async()
yield
self.stack.controls.pop()
for control in self.stack.controls:
control.disabled = False
self.container.disabled = False
page.splash = None
await page.update_async()
async def run_initial_view(self, page: flet.Page):

View File

@@ -1,10 +1,8 @@
from functools import partial
from typing import Awaitable, Callable, Sequence
import flet
from soul_diary.ui.app.local_storage import LocalStorage
from soul_diary.ui.app.middlewares.base import BaseMiddleware
from soul_diary.ui.app.models import Emotion
from soul_diary.ui.app.routes import SENSE_ADD, SENSE_LIST
from .base import BaseView, view
@@ -14,7 +12,6 @@ class SenseAddView(BaseView):
def __init__(
self,
local_storage: LocalStorage,
middlewares: Sequence[BaseMiddleware | Callable[[flet.Page], Awaitable]] = (),
):
self.title: flet.Text
self.content_container: flet.Container
@@ -25,7 +22,7 @@ class SenseAddView(BaseView):
self.body: str | None = None
self.desires: str | None = None
super().__init__(local_storage=local_storage, middlewares=middlewares)
super().__init__(local_storage=local_storage)
async def clear(self):
self.title.value = ""

View File

@@ -1,12 +1,10 @@
import asyncio
from typing import Awaitable, Callable, Sequence
import flet
from soul_diary.ui.app.backend.exceptions import NonAuthenticatedException
from soul_diary.ui.app.local_storage import LocalStorage
from soul_diary.ui.app.middlewares.base import BaseMiddleware
from soul_diary.ui.app.models import BackendType, Sense
from soul_diary.ui.app.models import Sense
from soul_diary.ui.app.routes import AUTH, SENSE_ADD, SENSE_LIST
from .base import BaseView, view
@@ -15,13 +13,12 @@ class SenseListView(BaseView):
def __init__(
self,
local_storage: LocalStorage,
middlewares: Sequence[BaseMiddleware | Callable[[flet.Page], Awaitable]] = (),
):
self.cards: flet.Column
self.local_storage = local_storage
super().__init__(local_storage=local_storage, middlewares=middlewares)
super().__init__(local_storage=local_storage)
async def setup(self):
self.cards = flet.Column(alignment=flet.alignment.center, width=400)
@@ -97,5 +94,6 @@ class SenseListView(BaseView):
async def callback_logout(self, event: flet.ControlEvent):
backend_client = await self.get_backend_client()
await backend_client.logout()
async with self.in_progress(page=event.page):
await backend_client.logout()
await event.page.go_async(AUTH)

View File

@@ -25,8 +25,9 @@ class WebService(ServiceMixin):
async def start(self):
app = flet_fastapi.app(SoulDiaryApp(
backend=BackendType.SOUL,
backend_data=self._backend_data,
# backend=BackendType.SOUL,
# backend_data=self._backend_data,
# backend=BackendType.LOCAL,
).run)
config = uvicorn.Config(app=app, host="0.0.0.0", port=self._port)
server = UvicornServer(config)