Refactoring
This commit is contained in:
@@ -22,34 +22,37 @@ class SoulDiaryApp:
|
||||
self._backend = backend
|
||||
self._backend_data = backend_data
|
||||
|
||||
def get_routes(self, page: flet.Page) -> dict[str, BaseView]:
|
||||
local_storage = LocalStorage(client_storage=page.client_storage)
|
||||
sense_list_view = SenseListView(local_storage=local_storage)
|
||||
def get_routes(self) -> dict[str, BaseView]:
|
||||
sense_list_view = SenseListView()
|
||||
|
||||
return {
|
||||
INDEX: sense_list_view,
|
||||
AUTH: AuthView(
|
||||
local_storage=local_storage,
|
||||
backend=self._backend,
|
||||
backend_data=self._backend_data,
|
||||
),
|
||||
SENSE_LIST: sense_list_view,
|
||||
SENSE_ADD: SenseAddView(local_storage=local_storage),
|
||||
SENSE_ADD: SenseAddView(),
|
||||
}
|
||||
|
||||
async def run(self, page: flet.Page):
|
||||
page.title = "Soul Diary"
|
||||
page.app = self
|
||||
page.on_disconect = self.callback_disconnect
|
||||
|
||||
routes = self.get_routes(page)
|
||||
routes = self.get_routes()
|
||||
Routing(
|
||||
page=page,
|
||||
async_is=True,
|
||||
app_routes=[
|
||||
path(url=url, clear=False, view=view.entrypoint)
|
||||
path(url=url, clear=False, view=view)
|
||||
for url, view in routes.items()
|
||||
],
|
||||
middleware=middleware,
|
||||
)
|
||||
|
||||
return await page.go_async(page.route)
|
||||
|
||||
async def callback_disconnect(self, event: flet.ControlEvent):
|
||||
local_storage = LocalStorage(event.page.client_storage)
|
||||
await local_storage.clear_shared_data()
|
||||
|
||||
@@ -104,7 +104,7 @@ class BaseBackend:
|
||||
self._token = None
|
||||
self._encryption_key = None
|
||||
self._username = None
|
||||
await self._local_storage.remove_auth_data()
|
||||
await self._local_storage.clear_auth_data()
|
||||
|
||||
@property
|
||||
def is_auth(self) -> bool:
|
||||
|
||||
30
soul_diary/ui/app/backend/utils.py
Normal file
30
soul_diary/ui/app/backend/utils.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import BackendType
|
||||
from .base import BaseBackend
|
||||
from .exceptions import NonAuthenticatedException
|
||||
from .local import LocalBackend
|
||||
from .soul import SoulBackend
|
||||
|
||||
|
||||
BACKEND_MAPPING = {
|
||||
BackendType.LOCAL: LocalBackend,
|
||||
BackendType.SOUL: SoulBackend,
|
||||
}
|
||||
|
||||
|
||||
async def get_backend_client(local_storage: LocalStorage) -> BaseBackend:
|
||||
auth_data = await local_storage.get_auth_data()
|
||||
if auth_data is None:
|
||||
raise NonAuthenticatedException()
|
||||
|
||||
backend_client_class = BACKEND_MAPPING.get(auth_data.backend, None)
|
||||
if backend_client_class is None:
|
||||
raise
|
||||
|
||||
return backend_client_class(
|
||||
local_storage=local_storage,
|
||||
username=auth_data.username,
|
||||
encryption_key=auth_data.encryption_key,
|
||||
token=auth_data.token,
|
||||
**auth_data.backend_data,
|
||||
)
|
||||
20
soul_diary/ui/app/controls/utils.py
Normal file
20
soul_diary/ui/app/controls/utils.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
import flet
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def in_progress(page: flet.Page, tooltip: str | None = None):
|
||||
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
|
||||
|
||||
page.splash = None
|
||||
await page.update_async()
|
||||
@@ -14,6 +14,9 @@ class AuthData(BaseModel):
|
||||
|
||||
|
||||
class LocalStorage:
|
||||
AUTH_DATA_KEY = "soul_diary.client.auth_data"
|
||||
SHARED_DATA_KEY = "soul_diary.client.shared_data"
|
||||
|
||||
def __init__(self, client_storage):
|
||||
self._client_storage = client_storage
|
||||
|
||||
@@ -32,21 +35,40 @@ class LocalStorage:
|
||||
encryption_key=encryption_key,
|
||||
token=token,
|
||||
)
|
||||
await self.raw_write("soul_diary.client", auth_data.model_dump(mode="json"))
|
||||
await self.raw_write(self.AUTH_DATA_KEY, auth_data.model_dump(mode="json"))
|
||||
|
||||
async def get_auth_data(self) -> AuthData | None:
|
||||
if not await self.raw_contains("soul_diary.client"):
|
||||
if not await self.raw_contains(self.AUTH_DATA_KEY):
|
||||
return None
|
||||
|
||||
data = await self.raw_read("soul_diary.client")
|
||||
data = await self.raw_read(self.AUTH_DATA_KEY)
|
||||
return AuthData.model_validate(data)
|
||||
|
||||
async def remove_auth_data(self):
|
||||
if await self.raw_contains("soul_diary.client"):
|
||||
await self.raw_remove("soul_diary.client")
|
||||
async def clear_auth_data(self):
|
||||
if await self.raw_contains(self.AUTH_DATA_KEY):
|
||||
await self.raw_remove(self.AUTH_DATA_KEY)
|
||||
|
||||
async def clear(self):
|
||||
await self._client_storage.clear_async()
|
||||
async def add_shared_data(self, **kwargs):
|
||||
if await self.raw_contains(self.SHARED_DATA_KEY):
|
||||
tmp_data = await self.raw_read(self.SHARED_DATA_KEY)
|
||||
else:
|
||||
tmp_data = {}
|
||||
|
||||
tmp_data.update(kwargs)
|
||||
await self.raw_write(self.SHARED_DATA_KEY, tmp_data)
|
||||
|
||||
async def get_shared_data(self, key: str):
|
||||
if not await self.raw_contains(self.SHARED_DATA_KEY):
|
||||
return None
|
||||
|
||||
tmp_data = await self.raw_read(self.SHARED_DATA_KEY)
|
||||
return tmp_data.get(key)
|
||||
|
||||
async def clear_shared_data(self):
|
||||
if not await self.raw_contains(self.SHARED_DATA_KEY):
|
||||
return
|
||||
|
||||
await self.raw_remove(self.SHARED_DATA_KEY)
|
||||
|
||||
async def raw_contains(self, key: str) -> bool:
|
||||
return await self._client_storage.contains_key_async(key)
|
||||
|
||||
@@ -2,20 +2,27 @@ from functools import partial
|
||||
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import BackendType
|
||||
from soul_diary.ui.app.pages.base import BasePage
|
||||
from soul_diary.ui.app.pages.base import BasePage, callback_error_handle
|
||||
|
||||
|
||||
class ChooseBackendPage(BasePage):
|
||||
class BackendPage(BasePage):
|
||||
BACKENDS = {
|
||||
BackendType.LOCAL: "Локально",
|
||||
BackendType.SOUL: "Soul Diary сервер",
|
||||
}
|
||||
|
||||
def __init__(self, backend: BackendType | None = None):
|
||||
self.backend: BackendType | None = backend
|
||||
def __init__(
|
||||
self,
|
||||
view: flet.View,
|
||||
local_storage: LocalStorage,
|
||||
backend: BackendType | None = None,
|
||||
):
|
||||
self.local_storage = local_storage
|
||||
self.backend = backend
|
||||
|
||||
super().__init__()
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
label = flet.Container(
|
||||
@@ -46,16 +53,40 @@ class ChooseBackendPage(BasePage):
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
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()
|
||||
|
||||
@callback_error_handle
|
||||
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:
|
||||
if self.backend is None or self.backend not in BackendType:
|
||||
dropdown.error_text = "Выберите тип бекенда"
|
||||
await self.update_async()
|
||||
return
|
||||
|
||||
await self.local_storage.add_shared_data(backend=self.backend.value)
|
||||
|
||||
if self.backend == BackendType.LOCAL:
|
||||
from .login import LoginPage
|
||||
|
||||
await LoginPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
backend=self.backend,
|
||||
backend_registration_enabled=True,
|
||||
username=await self.local_storage.get_shared_data(key="username"),
|
||||
password=await self.local_storage.get_shared_data(key="password"),
|
||||
).apply()
|
||||
elif self.backend == BackendType.SOUL:
|
||||
from .soul_server import SoulServerPage
|
||||
|
||||
backend_data = await self.local_storage.get_shared_data(key="backend_data")
|
||||
if backend_data is None:
|
||||
backend_data = {}
|
||||
await SoulServerPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
url=backend_data.get("url"),
|
||||
).apply()
|
||||
203
soul_diary/ui/app/pages/auth/login.py
Normal file
203
soul_diary/ui/app/pages/auth/login.py
Normal file
@@ -0,0 +1,203 @@
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.backend.exceptions import (
|
||||
IncorrectCredentialsException,
|
||||
UserAlreadyExistsException,
|
||||
)
|
||||
from soul_diary.ui.app.backend.utils import BACKEND_MAPPING
|
||||
from soul_diary.ui.app.controls.utils import in_progress
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import BackendType
|
||||
from soul_diary.ui.app.pages.base import BasePage, callback_error_handle
|
||||
from soul_diary.ui.app.routes import SENSE_LIST
|
||||
|
||||
|
||||
class LoginPage(BasePage):
|
||||
def __init__(
|
||||
self,
|
||||
view: flet.View,
|
||||
local_storage: LocalStorage,
|
||||
backend: BackendType,
|
||||
backend_registration_enabled: bool,
|
||||
backend_data: dict[str, Any] | None = None,
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
can_return_back: bool = True,
|
||||
):
|
||||
self.local_storage = local_storage
|
||||
self.backend = backend
|
||||
self.backend_registration_enabled = backend_registration_enabled
|
||||
self.backend_data = backend_data
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.can_return_back = can_return_back
|
||||
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
controls = []
|
||||
if self.can_return_back:
|
||||
return_back_button = flet.IconButton(
|
||||
icon=flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_return_back,
|
||||
)
|
||||
controls.append(return_back_button)
|
||||
top_row = flet.Row(
|
||||
controls=controls,
|
||||
width=300,
|
||||
alignment=flet.MainAxisAlignment.START,
|
||||
)
|
||||
|
||||
username_field = flet.TextField(
|
||||
label="Логин",
|
||||
value=self.username,
|
||||
on_change=self.callback_change_username,
|
||||
)
|
||||
password_field = flet.TextField(
|
||||
label="Пароль",
|
||||
password=True,
|
||||
can_reveal_password=True,
|
||||
on_change=self.callback_change_password,
|
||||
)
|
||||
signin_button = flet.ElevatedButton(
|
||||
text="Войти",
|
||||
width=300,
|
||||
height=50,
|
||||
on_click=partial(
|
||||
self.callback_signin,
|
||||
username_field=username_field,
|
||||
password_field=password_field,
|
||||
),
|
||||
)
|
||||
signup_button = flet.ElevatedButton(
|
||||
text="Зарегистрироваться",
|
||||
width=300,
|
||||
height=50,
|
||||
disabled=not self.backend_registration_enabled,
|
||||
on_click=partial(
|
||||
self.callback_signup,
|
||||
username_field=username_field,
|
||||
password_field=password_field,
|
||||
),
|
||||
)
|
||||
return flet.Container(
|
||||
content=flet.Column(
|
||||
controls=[top_row, username_field, password_field, signin_button, signup_button],
|
||||
width=300,
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_return_back(self, event: flet.ControlEvent):
|
||||
await self.local_storage.add_shared_data(username=self.username)
|
||||
|
||||
if self.backend == BackendType.LOCAL:
|
||||
from .backend import BackendPage
|
||||
|
||||
backend = await self.local_storage.get_shared_data(key="backend")
|
||||
if backend is not None:
|
||||
backend = BackendType(backend)
|
||||
await BackendPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
backend=backend,
|
||||
).apply()
|
||||
elif self.backend == BackendType.SOUL:
|
||||
from .soul_server import SoulServerPage
|
||||
|
||||
backend_data = await self.local_storage.get_shared_data("backend_data")
|
||||
if backend_data is None:
|
||||
backend_data = {}
|
||||
await SoulServerPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
url=backend_data.get("url"),
|
||||
).apply()
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_change_username(self, event: flet.ControlEvent):
|
||||
self.username = event.control.value
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_change_password(self, event: flet.ControlEvent):
|
||||
self.password = event.control.value
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_signup(
|
||||
self,
|
||||
event: flet.ControlEvent,
|
||||
username_field: flet.TextField,
|
||||
password_field: flet.TextField,
|
||||
):
|
||||
if not self.username:
|
||||
username_field.error_text = "Заполните имя пользователя"
|
||||
await username_field.update_async()
|
||||
if not self.password:
|
||||
password_field.error_text = "Заполните пароль"
|
||||
await password_field.update_async()
|
||||
if not self.username or not self.password:
|
||||
return
|
||||
|
||||
backend_client_class = BACKEND_MAPPING[self.backend]
|
||||
backend_data = await self.local_storage.get_shared_data("backend_data")
|
||||
if backend_data is None:
|
||||
backend_data = self.backend_data or {}
|
||||
backend_client = backend_client_class(
|
||||
local_storage=self.local_storage,
|
||||
**backend_data,
|
||||
)
|
||||
|
||||
async with in_progress(page=event.page):
|
||||
try:
|
||||
await backend_client.registration(username=self.username, password=self.password)
|
||||
except UserAlreadyExistsException:
|
||||
username_field.error_text = "Пользователь с таким именем уже существует"
|
||||
password_field.error_text = None
|
||||
await username_field.update_async()
|
||||
await password_field.update_async()
|
||||
return
|
||||
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_signin(
|
||||
self,
|
||||
event: flet.ControlEvent,
|
||||
username_field: flet.TextField,
|
||||
password_field: flet.TextField,
|
||||
):
|
||||
if not self.username:
|
||||
username_field.error_text = "Заполните имя пользователя"
|
||||
await username_field.update_async()
|
||||
if not self.password:
|
||||
password_field.error_text = "Заполните пароль"
|
||||
await password_field.update_async()
|
||||
if not self.username or not self.password:
|
||||
return
|
||||
|
||||
backend_client_class = BACKEND_MAPPING[self.backend]
|
||||
backend_data = await self.local_storage.get_shared_data("backend_data")
|
||||
if backend_data is None:
|
||||
backend_data = self.backend_data or {}
|
||||
backend_client = backend_client_class(
|
||||
local_storage=self.local_storage,
|
||||
**backend_data,
|
||||
)
|
||||
|
||||
async with in_progress(page=event.page):
|
||||
try:
|
||||
await backend_client.login(username=self.username, password=self.password)
|
||||
except IncorrectCredentialsException:
|
||||
username_field.error_text = None
|
||||
password_field.error_text = "Неверные имя пользователя и пароль"
|
||||
await username_field.update_async()
|
||||
await password_field.update_async()
|
||||
return
|
||||
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
@@ -1,8 +0,0 @@
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.pages.base import BasePage
|
||||
|
||||
|
||||
class SoulBackendDataPage(BasePage):
|
||||
def build(self) -> flet.Container:
|
||||
pass
|
||||
114
soul_diary/ui/app/pages/auth/soul_server.py
Normal file
114
soul_diary/ui/app/pages/auth/soul_server.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from functools import partial
|
||||
|
||||
import flet
|
||||
from pydantic import AnyHttpUrl
|
||||
|
||||
from soul_diary.ui.app.backend.soul import SoulBackend
|
||||
from soul_diary.ui.app.controls.utils import in_progress
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import BackendType
|
||||
from soul_diary.ui.app.pages.base import BasePage, callback_error_handle
|
||||
|
||||
|
||||
class SoulServerPage(BasePage):
|
||||
def __init__(self, view: flet.View, local_storage: LocalStorage, url: str | None = None):
|
||||
self.local_storage = local_storage
|
||||
self.url = url
|
||||
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
label = flet.Text("Soul Diary сервер")
|
||||
backend_button = flet.IconButton(
|
||||
icon=flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_return_back,
|
||||
)
|
||||
top_row = flet.Row(
|
||||
controls=[backend_button, label],
|
||||
alignment=flet.MainAxisAlignment.START,
|
||||
)
|
||||
|
||||
url_field = flet.TextField(
|
||||
width=300,
|
||||
label="URL",
|
||||
value=self.url,
|
||||
on_change=self.callback_change_url,
|
||||
)
|
||||
connect_button = flet.ElevatedButton(
|
||||
"Подключиться",
|
||||
width=300,
|
||||
height=50,
|
||||
on_click=partial(self.callback_connect, url_field=url_field),
|
||||
)
|
||||
|
||||
return flet.Container(
|
||||
content=flet.Column(
|
||||
controls=[top_row, url_field, connect_button],
|
||||
width=300,
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_change_url(self, event: flet.ControlEvent):
|
||||
try:
|
||||
AnyHttpUrl(event.control.value or "")
|
||||
except:
|
||||
event.control.error_text = "Некорректный URL"
|
||||
self.url = None
|
||||
else:
|
||||
event.control.error_text = None
|
||||
self.url = event.control.value
|
||||
await event.control.update_async()
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_return_back(self, event: flet.ControlEvent):
|
||||
try:
|
||||
backend_url = AnyHttpUrl(self.url)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
await self.local_storage.add_shared_data(backend_data={"url": str(backend_url)})
|
||||
|
||||
backend = await self.local_storage.get_shared_data("backend")
|
||||
if backend is not None:
|
||||
backend = BackendType(backend)
|
||||
|
||||
from .backend import BackendPage
|
||||
await BackendPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
backend=backend,
|
||||
).apply()
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_connect(self, event: flet.ControlEvent, url_field: flet.TextField):
|
||||
try:
|
||||
backend_url = AnyHttpUrl(self.url)
|
||||
except ValueError:
|
||||
url_field.error_text = "Некорректный URL"
|
||||
await url_field.update_async()
|
||||
return
|
||||
|
||||
backend_client = SoulBackend(local_storage=self.local_storage, url=str(backend_url))
|
||||
async with in_progress(page=event.page):
|
||||
try:
|
||||
options = await backend_client.get_options()
|
||||
except:
|
||||
url_field.error_text = "Невозможно подключиться к серверу"
|
||||
await url_field.update_async()
|
||||
return
|
||||
|
||||
await self.local_storage.add_shared_data(backend_data={"url": str(backend_url)})
|
||||
username = await self.local_storage.get_shared_data(key="username")
|
||||
password = await self.local_storage.get_shared_data(key="password")
|
||||
|
||||
from .login import LoginPage
|
||||
await LoginPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
backend=BackendType.SOUL,
|
||||
backend_registration_enabled=options.registration_enabled,
|
||||
username=username,
|
||||
password=password,
|
||||
).apply()
|
||||
@@ -1,7 +1,40 @@
|
||||
from typing import Callable
|
||||
|
||||
import flet
|
||||
|
||||
|
||||
class BasePage(flet.UserControl):
|
||||
async def apply(self, page: flet.Page):
|
||||
await page.clean_async()
|
||||
await page.add_async(self)
|
||||
def __init__(self, view: flet.View):
|
||||
self.view = view
|
||||
|
||||
super().__init__()
|
||||
|
||||
async def apply(self):
|
||||
self.view.controls = [self]
|
||||
await self.view.update_async()
|
||||
|
||||
|
||||
def callback_error_handle(function: Callable) -> Callable:
|
||||
async def wrapper(self, event: flet.ControlEvent, *args, **kwargs):
|
||||
async def close_dialog(event: flet.ControlEvent):
|
||||
dialog.open = False
|
||||
await dialog.update_async()
|
||||
|
||||
try:
|
||||
await function(self, event, *args, **kwargs)
|
||||
except:
|
||||
text = flet.Text("Ошибка")
|
||||
close_button = flet.IconButton(icon=flet.icons.CLOSE, on_click=close_dialog)
|
||||
dialog = flet.AlertDialog(
|
||||
modal=True,
|
||||
open=True,
|
||||
title=flet.Row(
|
||||
controls=[text, close_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
content=flet.Text("Произошла ошибка"),
|
||||
)
|
||||
event.page.dialog = dialog
|
||||
await event.page.update_async()
|
||||
|
||||
return wrapper
|
||||
|
||||
95
soul_diary/ui/app/pages/sense_add/body.py
Normal file
95
soul_diary/ui/app/pages/sense_add/body.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from functools import partial
|
||||
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.pages.base import BasePage, callback_error_handle
|
||||
from soul_diary.ui.app.routes import SENSE_LIST
|
||||
|
||||
|
||||
class BodyPage(BasePage):
|
||||
def __init__(
|
||||
self,
|
||||
view: flet.View,
|
||||
local_storage: LocalStorage,
|
||||
body: str | None = None,
|
||||
):
|
||||
self.local_storage = local_storage
|
||||
self.body = body
|
||||
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
title = flet.Text("Опиши свои телесные ощущения")
|
||||
close_button = flet.IconButton(icon=flet.icons.CLOSE, on_click=self.callback_close)
|
||||
top_row = flet.Row(
|
||||
controls=[title, close_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
)
|
||||
|
||||
body_field = flet.TextField(
|
||||
value=self.body,
|
||||
multiline=True,
|
||||
min_lines=10,
|
||||
max_lines=10,
|
||||
on_change=self.callback_change_body,
|
||||
)
|
||||
|
||||
previous_button = flet.IconButton(
|
||||
flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_previous,
|
||||
)
|
||||
next_button = flet.IconButton(
|
||||
flet.icons.ARROW_FORWARD,
|
||||
on_click=partial(self.callback_next, body_field=body_field),
|
||||
)
|
||||
bottom_row = flet.Row(
|
||||
controls=[previous_button, next_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
)
|
||||
|
||||
return flet.Container(
|
||||
content=flet.Column(
|
||||
controls=[top_row, body_field, bottom_row],
|
||||
width=600,
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_close(self, event: flet.ControlEvent):
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_change_body(self, event: flet.ControlEvent):
|
||||
self.body = event.control.value
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_previous(self, event: flet.ControlEvent):
|
||||
await self.local_storage.add_shared_data(body=self.body)
|
||||
feelings = await self.local_storage.get_shared_data("feelings")
|
||||
|
||||
from .feelings import FeelingsPage
|
||||
await FeelingsPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
feelings=feelings,
|
||||
).apply()
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_next(self, event: flet.ControlEvent, body_field: flet.TextField):
|
||||
if self.body is None or not self.body.strip():
|
||||
body_field.error_text = "Коротко опиши свои телесные ощущения"
|
||||
await body_field.update_async()
|
||||
return
|
||||
|
||||
await self.local_storage.add_shared_data(body=self.body)
|
||||
desires = await self.local_storage.get_shared_data("desires")
|
||||
|
||||
from .desires import DesiresPage
|
||||
await DesiresPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
desires=desires,
|
||||
).apply()
|
||||
102
soul_diary/ui/app/pages/sense_add/desires.py
Normal file
102
soul_diary/ui/app/pages/sense_add/desires.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from functools import partial
|
||||
|
||||
import flet
|
||||
from soul_diary.ui.app.backend.utils import get_backend_client
|
||||
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import Emotion
|
||||
from soul_diary.ui.app.pages.base import BasePage, callback_error_handle
|
||||
from soul_diary.ui.app.routes import SENSE_LIST
|
||||
|
||||
|
||||
class DesiresPage(BasePage):
|
||||
def __init__(
|
||||
self,
|
||||
view: flet.View,
|
||||
local_storage: LocalStorage,
|
||||
desires: str | None = None,
|
||||
):
|
||||
self.local_storage = local_storage
|
||||
self.desires = desires
|
||||
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
title = flet.Text("Опиши свои желания на данный момент")
|
||||
close_button = flet.IconButton(icon=flet.icons.CLOSE, on_click=self.callback_close)
|
||||
top_row = flet.Row(
|
||||
controls=[title, close_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
)
|
||||
|
||||
desires_field = flet.TextField(
|
||||
value=self.desires,
|
||||
multiline=True,
|
||||
min_lines=10,
|
||||
max_lines=10,
|
||||
on_change=self.callback_change_desires,
|
||||
)
|
||||
|
||||
previous_button = flet.IconButton(
|
||||
flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_previous,
|
||||
)
|
||||
add_button = flet.IconButton(
|
||||
flet.icons.CREATE,
|
||||
on_click=partial(self.callback_add_sense, desires_field=desires_field),
|
||||
)
|
||||
bottom_row = flet.Row(
|
||||
controls=[previous_button, add_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
)
|
||||
|
||||
return flet.Container(
|
||||
content=flet.Column(
|
||||
controls=[top_row, desires_field, bottom_row],
|
||||
width=600,
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_close(self, event: flet.ControlEvent):
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_change_desires(self, event: flet.ControlEvent):
|
||||
self.desires = event.control.value
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_previous(self, event: flet.ControlEvent):
|
||||
await self.local_storage.add_shared_data(desires=self.desires)
|
||||
body = await self.local_storage.get_shared_data("body")
|
||||
|
||||
from .body import BodyPage
|
||||
await BodyPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
body=body,
|
||||
).apply()
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_add_sense(self, event: flet.ControlEvent, desires_field: flet.TextField):
|
||||
if self.desires is None or not self.desires.strip():
|
||||
desires_field.error_text = "Коротко опиши свои желания"
|
||||
await desires_field.update_async()
|
||||
return
|
||||
|
||||
emotions = await self.local_storage.get_shared_data("emotions") or []
|
||||
emotions = [Emotion(emotion) for emotion in emotions]
|
||||
feelings = await self.local_storage.get_shared_data("feelings")
|
||||
body = await self.local_storage.get_shared_data("body")
|
||||
backend_client = await get_backend_client(local_storage=self.local_storage)
|
||||
await backend_client.create_sense(
|
||||
emotions=emotions,
|
||||
feelings=feelings,
|
||||
body=body,
|
||||
desires=self.desires,
|
||||
)
|
||||
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
105
soul_diary/ui/app/pages/sense_add/emotions.py
Normal file
105
soul_diary/ui/app/pages/sense_add/emotions.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from functools import partial
|
||||
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import Emotion
|
||||
from soul_diary.ui.app.pages.base import BasePage, callback_error_handle
|
||||
from soul_diary.ui.app.routes import SENSE_LIST
|
||||
|
||||
|
||||
class EmotionsPage(BasePage):
|
||||
def __init__(
|
||||
self,
|
||||
view: flet.View,
|
||||
local_storage: LocalStorage,
|
||||
emotions: list[Emotion] | None = None,
|
||||
):
|
||||
self.local_storage = local_storage
|
||||
self.emotions = emotions or []
|
||||
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
title = flet.Text("Что ты чувствуешь?")
|
||||
close_button = flet.IconButton(icon=flet.icons.CLOSE, on_click=self.callback_close)
|
||||
top_row = flet.Row(
|
||||
controls=[title, close_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
)
|
||||
|
||||
error_text = flet.Text(
|
||||
"Выберите как минимум одну эмоцию",
|
||||
color=flet.colors.RED,
|
||||
visible=False,
|
||||
)
|
||||
emotions_chips = flet.Row(
|
||||
controls=[
|
||||
flet.Chip(
|
||||
label=flet.Text(emotion.value),
|
||||
show_checkmark=False,
|
||||
selected=emotion.value in self.emotions,
|
||||
on_select=partial(
|
||||
self.callback_choose_emotion,
|
||||
emotion=emotion,
|
||||
error_text=error_text,
|
||||
),
|
||||
)
|
||||
for emotion in Emotion
|
||||
],
|
||||
wrap=True,
|
||||
)
|
||||
emotions = flet.Column(controls=[emotions_chips, error_text])
|
||||
|
||||
next_button = flet.IconButton(
|
||||
flet.icons.ARROW_FORWARD,
|
||||
on_click=partial(self.callback_next, error_text=error_text),
|
||||
)
|
||||
bottom_row = flet.Row(
|
||||
controls=[next_button],
|
||||
alignment=flet.MainAxisAlignment.END,
|
||||
)
|
||||
|
||||
return flet.Container(
|
||||
content=flet.Column(
|
||||
controls=[top_row, emotions, bottom_row],
|
||||
width=600,
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_close(self, event: flet.ControlEvent):
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_choose_emotion(
|
||||
self,
|
||||
event: flet.ControlEvent,
|
||||
emotion: Emotion,
|
||||
error_text: flet.Text,
|
||||
):
|
||||
if event.control.selected:
|
||||
self.emotions.append(emotion)
|
||||
if error_text.visible:
|
||||
error_text.visible = False
|
||||
await error_text.update_async()
|
||||
else:
|
||||
self.emotions.remove(emotion)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_next(self, event: flet.ControlEvent, error_text: flet.Text):
|
||||
if not self.emotions:
|
||||
error_text.visible = True
|
||||
await self.update_async()
|
||||
return
|
||||
await self.local_storage.add_shared_data(emotions=self.emotions)
|
||||
|
||||
from .feelings import FeelingsPage
|
||||
|
||||
await FeelingsPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
feelings=await self.local_storage.get_shared_data("feelings"),
|
||||
).apply()
|
||||
95
soul_diary/ui/app/pages/sense_add/feelings.py
Normal file
95
soul_diary/ui/app/pages/sense_add/feelings.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from functools import partial
|
||||
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.pages.base import BasePage, callback_error_handle
|
||||
from soul_diary.ui.app.routes import SENSE_LIST
|
||||
|
||||
|
||||
class FeelingsPage(BasePage):
|
||||
def __init__(
|
||||
self,
|
||||
view: flet.View,
|
||||
local_storage: LocalStorage,
|
||||
feelings: str | None = None,
|
||||
):
|
||||
self.local_storage = local_storage
|
||||
self.feelings = feelings
|
||||
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
title = flet.Text("Опиши свои чувства")
|
||||
close_button = flet.IconButton(icon=flet.icons.CLOSE, on_click=self.callback_close)
|
||||
top_row = flet.Row(
|
||||
controls=[title, close_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
)
|
||||
|
||||
feelings_field = flet.TextField(
|
||||
value=self.feelings,
|
||||
multiline=True,
|
||||
min_lines=10,
|
||||
max_lines=10,
|
||||
on_change=self.callback_change_feelings,
|
||||
)
|
||||
|
||||
previous_button = flet.IconButton(
|
||||
flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_previous,
|
||||
)
|
||||
next_button = flet.IconButton(
|
||||
flet.icons.ARROW_FORWARD,
|
||||
on_click=partial(self.callback_next, feelings_field=feelings_field),
|
||||
)
|
||||
bottom_row = flet.Row(
|
||||
controls=[previous_button, next_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
)
|
||||
|
||||
return flet.Container(
|
||||
content=flet.Column(
|
||||
controls=[top_row, feelings_field, bottom_row],
|
||||
width=600,
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_close(self, event: flet.ControlEvent):
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_change_feelings(self, event: flet.ControlEvent):
|
||||
self.feelings = event.control.value
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_previous(self, event: flet.ControlEvent):
|
||||
await self.local_storage.add_shared_data(feelings=self.feelings)
|
||||
emotions = await self.local_storage.get_shared_data("emotions")
|
||||
|
||||
from .emotions import EmotionsPage
|
||||
await EmotionsPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
emotions=emotions,
|
||||
).apply()
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_next(self, event: flet.ControlEvent, feelings_field: flet.TextField):
|
||||
if self.feelings is None or not self.feelings.strip():
|
||||
feelings_field.error_text = "Коротко опиши свои чувства"
|
||||
await feelings_field.update_async()
|
||||
return
|
||||
|
||||
await self.local_storage.add_shared_data(feelings=self.feelings)
|
||||
body = await self.local_storage.get_shared_data("body")
|
||||
|
||||
from .body import BodyPage
|
||||
await BodyPage(
|
||||
view=self.view,
|
||||
local_storage=self.local_storage,
|
||||
body=body,
|
||||
).apply()
|
||||
84
soul_diary/ui/app/pages/sense_list.py
Normal file
84
soul_diary/ui/app/pages/sense_list.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.backend.utils import get_backend_client
|
||||
from soul_diary.ui.app.controls.utils import in_progress
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import Sense
|
||||
from soul_diary.ui.app.routes import AUTH, SENSE_ADD
|
||||
from .base import BasePage, callback_error_handle
|
||||
|
||||
|
||||
class SenseListPage(BasePage):
|
||||
def __init__(self, view: flet.View, local_storage: LocalStorage):
|
||||
self.local_storage = local_storage
|
||||
self.senses_cards: flet.Column
|
||||
|
||||
super().__init__(view=view)
|
||||
|
||||
def build(self) -> flet.Container:
|
||||
self.view.vertical_alignment = flet.MainAxisAlignment.START
|
||||
|
||||
add_button = flet.IconButton(
|
||||
icon=flet.icons.ADD_CIRCLE_OUTLINE,
|
||||
on_click=self.callback_add_sense,
|
||||
)
|
||||
settings_button = flet.IconButton(
|
||||
icon=flet.icons.SETTINGS,
|
||||
)
|
||||
logout_button = flet.IconButton(
|
||||
icon=flet.icons.LOGOUT,
|
||||
on_click=self.callback_logout,
|
||||
)
|
||||
top_container = flet.Container(
|
||||
content=flet.Row(
|
||||
controls=[add_button, settings_button, logout_button],
|
||||
alignment=flet.MainAxisAlignment.END,
|
||||
),
|
||||
)
|
||||
|
||||
self.senses_cards = flet.Column(alignment=flet.alignment.center)
|
||||
|
||||
return flet.Container(
|
||||
content=flet.Column(
|
||||
controls=[top_container, self.senses_cards],
|
||||
width=400,
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
async def did_mount_async(self):
|
||||
await self.render_cards()
|
||||
|
||||
async def render_cards(self):
|
||||
backend_client = await get_backend_client(self.local_storage)
|
||||
senses = await backend_client.get_sense_list()
|
||||
self.senses_cards.controls = [
|
||||
await self.render_card(sense)
|
||||
for sense in senses
|
||||
]
|
||||
await self.update_async()
|
||||
|
||||
async def render_card(self, sense: Sense) -> flet.Card:
|
||||
feelings = flet.Container(content=flet.Text(sense.feelings), expand=True)
|
||||
created_datetime = flet.Text(sense.created_at.strftime("%d %b %H:%M"))
|
||||
|
||||
return flet.Card(
|
||||
content=flet.Container(
|
||||
content=flet.Column(controls=[feelings, created_datetime]),
|
||||
padding=10,
|
||||
),
|
||||
width=400,
|
||||
height=100,
|
||||
)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_add_sense(self, event: flet.ControlEvent):
|
||||
await event.page.go_async(SENSE_ADD)
|
||||
|
||||
@callback_error_handle
|
||||
async def callback_logout(self, event: flet.ControlEvent):
|
||||
backend_client = await get_backend_client(local_storage=self.local_storage)
|
||||
async with in_progress(page=event.page):
|
||||
await backend_client.logout()
|
||||
await self.local_storage.clear_shared_data()
|
||||
await event.page.go_async(AUTH)
|
||||
@@ -1,323 +1,70 @@
|
||||
import asyncio
|
||||
from functools import partial
|
||||
from typing import Any
|
||||
|
||||
import flet
|
||||
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.controls.utils import in_progress
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
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
|
||||
from .base import BaseView, view
|
||||
from soul_diary.ui.app.models import BackendType
|
||||
from soul_diary.ui.app.pages.auth.backend import BackendPage
|
||||
from soul_diary.ui.app.pages.auth.login import LoginPage
|
||||
from soul_diary.ui.app.pages.auth.soul_server import SoulServerPage
|
||||
from soul_diary.ui.app.pages.base import BasePage
|
||||
from .base import BaseView
|
||||
|
||||
|
||||
class AuthView(BaseView):
|
||||
def __init__(
|
||||
self,
|
||||
local_storage: LocalStorage,
|
||||
backend: BackendType | None = None,
|
||||
backend_data: dict[str, Any] | None = None,
|
||||
):
|
||||
self.top_container: flet.Container
|
||||
self.center_container: flet.Container
|
||||
self.bottom_container: flet.Container
|
||||
self.backend = backend
|
||||
self.backend_data = backend_data
|
||||
|
||||
self.initial_backend = self.backend = backend
|
||||
self.initial_backend_data = self.backend_data = backend_data or {}
|
||||
self.backend_registration_enabled: bool = True
|
||||
self.username: str | None = None
|
||||
self.password: str | None = None
|
||||
|
||||
super().__init__(local_storage=local_storage)
|
||||
|
||||
async def clear(self):
|
||||
self.top_container.content = None
|
||||
self.center_container.content = None
|
||||
self.bottom_container.content = None
|
||||
|
||||
def clear_data(self):
|
||||
self.backend = self.initial_backend
|
||||
self.backend_data = self.initial_backend_data
|
||||
self.username = None
|
||||
self.password = None
|
||||
|
||||
async def setup(self):
|
||||
self.top_container = flet.Container(alignment=flet.alignment.center)
|
||||
self.center_container = flet.Container(alignment=flet.alignment.center)
|
||||
self.bottom_container = flet.Container(alignment=flet.alignment.center)
|
||||
|
||||
self.container.content = flet.Column(
|
||||
controls=[self.top_container, self.center_container, self.bottom_container],
|
||||
width=300,
|
||||
)
|
||||
self.container.alignment = flet.alignment.center
|
||||
|
||||
self.view.route = AUTH
|
||||
self.view.vertical_alignment = flet.MainAxisAlignment.CENTER
|
||||
|
||||
@view(initial=True)
|
||||
async def entrypoint_view(self, page: flet.Page):
|
||||
if self.initial_backend == BackendType.SOUL:
|
||||
async def connect():
|
||||
async with self.in_progress(page=page):
|
||||
options = await self.connect_to_soul_server()
|
||||
self.backend_registration_enabled = options.registration_enabled
|
||||
await self.credentials_view(page=page)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.create_task(connect())
|
||||
elif self.initial_backend == BackendType.LOCAL:
|
||||
await self.credentials_view(page=page)
|
||||
else:
|
||||
await self.backend_view(page=page)
|
||||
|
||||
@view()
|
||||
async def backend_view(self, page: flet.Page):
|
||||
label = flet.Text("Выберите сервер")
|
||||
self.top_container.content = label
|
||||
|
||||
backend_dropdown = flet.Dropdown(
|
||||
label="Бэкенд",
|
||||
options=[
|
||||
flet.dropdown.Option(text="Локально", key=BackendType.LOCAL.value),
|
||||
flet.dropdown.Option(text="Soul Diary сервер", key=BackendType.SOUL.value),
|
||||
],
|
||||
value=None if self.backend is None else self.backend.value,
|
||||
on_change=self.callback_change_backend,
|
||||
)
|
||||
self.center_container.content = backend_dropdown
|
||||
|
||||
connect_button = flet.ElevatedButton(
|
||||
"Выбрать",
|
||||
width=300,
|
||||
height=50,
|
||||
on_click=partial(self.callback_choose_backend, dropdown=backend_dropdown),
|
||||
)
|
||||
self.bottom_container.content = connect_button
|
||||
|
||||
@view()
|
||||
async def soul_server_data_view(self, page: flet.Page):
|
||||
label = flet.Text("Soul Diary сервер")
|
||||
backend_button = flet.IconButton(
|
||||
icon=flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_go_backend,
|
||||
)
|
||||
self.top_container.content = flet.Row(
|
||||
controls=[backend_button, label],
|
||||
width=300,
|
||||
alignment=flet.MainAxisAlignment.START,
|
||||
)
|
||||
|
||||
url_field = flet.TextField(
|
||||
width=300,
|
||||
label="URL",
|
||||
value=self.backend_data.get("url"),
|
||||
on_change=self.callback_change_soul_server_url,
|
||||
)
|
||||
self.center_container.content = url_field
|
||||
|
||||
connect_button = flet.ElevatedButton(
|
||||
"Подключиться",
|
||||
width=300,
|
||||
height=50,
|
||||
on_click=partial(self.callback_soul_server_connect, url_field=url_field),
|
||||
)
|
||||
self.bottom_container.content = connect_button
|
||||
|
||||
@view()
|
||||
async def credentials_view(self, page: flet.Page):
|
||||
controls = []
|
||||
if self.initial_backend is None:
|
||||
backend_data_button = flet.IconButton(
|
||||
icon=flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_go_backend_data,
|
||||
)
|
||||
controls.append(backend_data_button)
|
||||
self.top_container.content = flet.Row(
|
||||
controls=controls,
|
||||
width=300,
|
||||
alignment=flet.MainAxisAlignment.START,
|
||||
)
|
||||
|
||||
username_field = flet.TextField(
|
||||
label="Логин",
|
||||
on_change=self.callback_change_username,
|
||||
)
|
||||
password_field = flet.TextField(
|
||||
label="Пароль",
|
||||
password=True,
|
||||
can_reveal_password=True,
|
||||
on_change=self.callback_change_password,
|
||||
)
|
||||
self.center_container.content = flet.Column(
|
||||
controls=[username_field, password_field],
|
||||
width=300,
|
||||
)
|
||||
|
||||
signin_button = flet.Container(
|
||||
content=flet.ElevatedButton(
|
||||
text="Войти",
|
||||
width=300,
|
||||
height=50,
|
||||
on_click=partial(
|
||||
self.callback_signin,
|
||||
username_field=username_field,
|
||||
password_field=password_field,
|
||||
),
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
signup_button = flet.Container(
|
||||
content=flet.ElevatedButton(
|
||||
text="Зарегистрироваться",
|
||||
width=300,
|
||||
height=50,
|
||||
disabled=not self.backend_registration_enabled,
|
||||
on_click=partial(
|
||||
self.callback_signup,
|
||||
username_field=username_field,
|
||||
password_field=password_field,
|
||||
),
|
||||
),
|
||||
alignment=flet.alignment.center,
|
||||
)
|
||||
|
||||
self.bottom_container.content = flet.Column(controls=[signin_button, signup_button])
|
||||
|
||||
async def callback_change_backend(self, event: flet.ControlEvent):
|
||||
self.backend = BackendType(event.control.value)
|
||||
event.control.error_text = None
|
||||
await event.page.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 event.page.update_async()
|
||||
|
||||
async def callback_change_soul_server_url(self, event: flet.ControlEvent):
|
||||
try:
|
||||
AnyHttpUrl(event.control.value or "")
|
||||
except:
|
||||
event.control.error_text = "Некорректный URL"
|
||||
self.backend_data["url"] = None
|
||||
else:
|
||||
event.control.error_text = None
|
||||
self.backend_data["url"] = event.control.value
|
||||
await event.page.update_async()
|
||||
|
||||
async def callback_soul_server_connect(
|
||||
self,
|
||||
event: flet.ControlEvent,
|
||||
url_field: flet.TextField,
|
||||
):
|
||||
async def entrypoint(self, page: flet.Page) -> BasePage:
|
||||
local_storage = LocalStorage(client_storage=page.client_storage)
|
||||
if self.backend == BackendType.SOUL:
|
||||
async with self.in_progress(page=event.page):
|
||||
try:
|
||||
options = await self.connect_to_soul_server()
|
||||
except SoulServerIncorrectURL:
|
||||
url_field.error_text = "Некорректный URL"
|
||||
except:
|
||||
url_field.error_text = "Невозможно подключиться к серверу"
|
||||
await event.page.update_async()
|
||||
else:
|
||||
self.backend_registration_enabled = options.registration_enabled
|
||||
else:
|
||||
await self.credentials_view(page=event.page)
|
||||
|
||||
async def connect_to_soul_server(self) -> Options:
|
||||
try:
|
||||
backend_url = AnyHttpUrl(self.backend_data.get("url"))
|
||||
except ValueError:
|
||||
raise SoulServerIncorrectURL()
|
||||
|
||||
backend_client = SoulBackend(
|
||||
local_storage=self.local_storage,
|
||||
url=str(backend_url),
|
||||
return await self.connect_to_soul_server(
|
||||
page=page,
|
||||
local_storage=local_storage,
|
||||
)
|
||||
return await backend_client.get_options()
|
||||
|
||||
async def callback_change_username(self, event: flet.ControlEvent):
|
||||
self.username = event.control.value
|
||||
|
||||
async def callback_change_password(self, event: flet.ControlEvent):
|
||||
self.password = event.control.value
|
||||
|
||||
async def callback_signin(
|
||||
self,
|
||||
event: flet.ControlEvent,
|
||||
username_field: flet.TextField,
|
||||
password_field: flet.TextField,
|
||||
):
|
||||
if not self.username:
|
||||
username_field.error_text = "Заполните имя пользователя"
|
||||
if not self.password:
|
||||
password_field.error_text = "Заполните пароль"
|
||||
if not self.username or not self.password:
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
backend_client_class = self.BACKEND_MAPPING.get(self.backend)
|
||||
if backend_client_class is None:
|
||||
raise
|
||||
backend_client = backend_client_class(
|
||||
local_storage=self.local_storage,
|
||||
**self.backend_data,
|
||||
)
|
||||
|
||||
async with self.in_progress(page=event.page):
|
||||
try:
|
||||
await backend_client.login(username=self.username, password=self.password)
|
||||
except IncorrectCredentialsException:
|
||||
password_field.error_text = "Неверные имя пользователя и пароль"
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
async def callback_signup(
|
||||
self,
|
||||
event: flet.ControlEvent,
|
||||
username_field: flet.TextField,
|
||||
password_field: flet.TextField,
|
||||
):
|
||||
if not self.username:
|
||||
username_field.error_text = "Заполните имя пользователя"
|
||||
if not self.password:
|
||||
password_field.error_text = "Заполните пароль"
|
||||
if not self.username or not self.password:
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
backend_client_class = self.BACKEND_MAPPING.get(self.backend)
|
||||
if backend_client_class is None:
|
||||
raise
|
||||
backend_client = backend_client_class(
|
||||
local_storage=self.local_storage,
|
||||
**self.backend_data,
|
||||
)
|
||||
|
||||
async with self.in_progress(page=event.page):
|
||||
try:
|
||||
await backend_client.registration(username=self.username, password=self.password)
|
||||
except UserAlreadyExistsException:
|
||||
username_field.error_text = "Пользователь с таким именем уже существует"
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
async def callback_go_backend(self, event: flet.ControlEvent):
|
||||
await self.backend_view(page=event.page)
|
||||
|
||||
async def callback_go_backend_data(self, event: flet.ControlEvent):
|
||||
if self.backend == BackendType.SOUL:
|
||||
await self.soul_server_data_view(page=event.page)
|
||||
elif self.backend == BackendType.LOCAL:
|
||||
await self.backend_view(page=event.page)
|
||||
return LoginPage(
|
||||
view=self.view,
|
||||
backend=self.backend,
|
||||
backend_data=self.backend_data,
|
||||
backend_registration_enabled=True,
|
||||
local_storage=local_storage,
|
||||
can_return_back=False,
|
||||
)
|
||||
else:
|
||||
return BackendPage(view=self.view, local_storage=local_storage)
|
||||
|
||||
async def connect_to_soul_server(self, page: flet.Page, local_storage: LocalStorage) -> BasePage:
|
||||
backend_data = await local_storage.get_shared_data("backend_data")
|
||||
if backend_data is None:
|
||||
backend_data = {}
|
||||
soul_backend_client = SoulBackend(
|
||||
local_storage=local_storage,
|
||||
url=self.backend_data.get("url") or backend_data.get("url"),
|
||||
)
|
||||
try:
|
||||
async with in_progress(page=page):
|
||||
options = await soul_backend_client.get_options()
|
||||
except:
|
||||
return SoulServerPage(
|
||||
view=self.view,
|
||||
local_storage=local_storage,
|
||||
url=self.backend_data.get("url") or backend_data.get("url"),
|
||||
)
|
||||
else:
|
||||
return LoginPage(
|
||||
view=self.view,
|
||||
local_storage=local_storage,
|
||||
backend=self.backend,
|
||||
backend_data=self.backend_data,
|
||||
backend_registration_enabled=options.registration_enabled,
|
||||
can_return_back=False,
|
||||
)
|
||||
|
||||
@@ -1,128 +1,17 @@
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any, Callable
|
||||
|
||||
import flet
|
||||
from flet_route import Basket, Params
|
||||
|
||||
from soul_diary.ui.app.backend.base import BaseBackend
|
||||
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.models import BackendType
|
||||
from soul_diary.ui.app.pages.base import BasePage
|
||||
|
||||
|
||||
def view(initial: bool = False, disabled: bool = False):
|
||||
def decorator(function: Callable):
|
||||
async def wrapper(self, page: flet.Page):
|
||||
await self.clear()
|
||||
class BaseView:
|
||||
async def __call__(self, page: flet.Page, params: Params, basket: Basket) -> flet.View:
|
||||
self.view = flet.View(vertical_alignment=flet.MainAxisAlignment.CENTER)
|
||||
|
||||
await function(self, page=page)
|
||||
await page.update_async()
|
||||
|
||||
wrapper._view_enabled = not disabled
|
||||
wrapper._view_initial = initial
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
class MetaView(type):
|
||||
def __init__(cls, name: str, bases: tuple[type], attrs: dict[str, Any]):
|
||||
super().__init__(name, bases, attrs)
|
||||
|
||||
is_abstract = attrs.pop("is_abstract", False)
|
||||
if not is_abstract:
|
||||
cls.setup_class(attrs=attrs)
|
||||
|
||||
def setup_class(cls, attrs: dict[str, Any]):
|
||||
initial_view = None
|
||||
for attr in attrs.values():
|
||||
view_enabled = getattr(attr, "_view_enabled", False)
|
||||
view_initial = getattr(attr, "_view_initial", False)
|
||||
if view_enabled and view_initial:
|
||||
if initial_view is not None:
|
||||
raise ValueError(f"Initial view already defined: {initial_view.__name__}")
|
||||
initial_view = attr
|
||||
if initial_view is None:
|
||||
raise ValueError("Initial view must be defined")
|
||||
|
||||
cls._initial_view = initial_view
|
||||
|
||||
|
||||
class BaseView(metaclass=MetaView):
|
||||
BACKEND_MAPPING = {
|
||||
BackendType.LOCAL: LocalBackend,
|
||||
BackendType.SOUL: SoulBackend,
|
||||
}
|
||||
|
||||
is_abstract = True
|
||||
_initial_view: Callable | None
|
||||
|
||||
def __init__(self, local_storage: LocalStorage):
|
||||
self.local_storage = local_storage
|
||||
|
||||
self.container: flet.Container
|
||||
self.view: flet.View
|
||||
|
||||
async def entrypoint(self, page: flet.Page, params: Params, basket: Basket) -> flet.View:
|
||||
self.container = flet.Container()
|
||||
self.view = flet.View(controls=[self.container])
|
||||
|
||||
await self.setup()
|
||||
await self.clear()
|
||||
self.clear_data()
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.create_task(self.run_initial_view(page=page))
|
||||
page = await self.entrypoint(page=page)
|
||||
self.view.controls = [page]
|
||||
|
||||
return self.view
|
||||
|
||||
async def setup(self):
|
||||
pass
|
||||
|
||||
async def clear(self):
|
||||
pass
|
||||
|
||||
def clear_data(self):
|
||||
pass
|
||||
|
||||
@asynccontextmanager
|
||||
async def in_progress(self, page: flet.Page, tooltip: str | None = None):
|
||||
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.container.disabled = False
|
||||
page.splash = None
|
||||
await page.update_async()
|
||||
|
||||
async def run_initial_view(self, page: flet.Page):
|
||||
if self._initial_view is not None:
|
||||
await self._initial_view(page=page)
|
||||
|
||||
async def get_backend_client(self) -> BaseBackend:
|
||||
auth_data = await self.local_storage.get_auth_data()
|
||||
if auth_data is None:
|
||||
raise NonAuthenticatedException()
|
||||
|
||||
backend_client_class = self.BACKEND_MAPPING.get(auth_data.backend, None)
|
||||
if backend_client_class is None:
|
||||
raise
|
||||
|
||||
return backend_client_class(
|
||||
local_storage=self.local_storage,
|
||||
username=auth_data.username,
|
||||
encryption_key=auth_data.encryption_key,
|
||||
token=auth_data.token,
|
||||
**auth_data.backend_data,
|
||||
)
|
||||
async def entrypoint(self, page: flet.Page) -> BasePage:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
class SoulServerIncorrectURL(Exception):
|
||||
pass
|
||||
@@ -1,235 +1,12 @@
|
||||
from functools import partial
|
||||
|
||||
import flet
|
||||
|
||||
from soul_diary.ui.app.local_storage import LocalStorage
|
||||
from soul_diary.ui.app.models import Emotion
|
||||
from soul_diary.ui.app.routes import SENSE_ADD, SENSE_LIST
|
||||
from .base import BaseView, view
|
||||
|
||||
from soul_diary.ui.app.pages.base import BasePage
|
||||
from soul_diary.ui.app.pages.sense_add.emotions import EmotionsPage
|
||||
from .base import BaseView
|
||||
|
||||
|
||||
class SenseAddView(BaseView):
|
||||
def __init__(
|
||||
self,
|
||||
local_storage: LocalStorage,
|
||||
):
|
||||
self.title: flet.Text
|
||||
self.content_container: flet.Container
|
||||
self.buttons_row: flet.Row
|
||||
|
||||
self.emotions: list[Emotion] = []
|
||||
self.feelings: str | None = None
|
||||
self.body: str | None = None
|
||||
self.desires: str | None = None
|
||||
|
||||
super().__init__(local_storage=local_storage)
|
||||
|
||||
async def clear(self):
|
||||
self.title.value = ""
|
||||
self.content_container.content = None
|
||||
self.buttons_row.controls = []
|
||||
|
||||
def clear_data(self):
|
||||
self.emotions = []
|
||||
self.feelings = None
|
||||
self.body = None
|
||||
self.desires = None
|
||||
|
||||
async def setup(self):
|
||||
# Top
|
||||
self.title = flet.Text()
|
||||
close_button = flet.IconButton(icon=flet.icons.CLOSE, on_click=self.callback_close)
|
||||
top_container = flet.Container(
|
||||
content=flet.Row(
|
||||
[self.title, close_button],
|
||||
alignment=flet.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
margin=10,
|
||||
)
|
||||
|
||||
# Center
|
||||
self.content_container = flet.Container()
|
||||
center_container = flet.Container(content=self.content_container, margin=10)
|
||||
|
||||
# Bottom
|
||||
self.buttons_row = flet.Row()
|
||||
bottom_container = flet.Container(content=self.buttons_row, margin=10)
|
||||
|
||||
# Build
|
||||
self.container.content = flet.Column(
|
||||
controls=[top_container, center_container, bottom_container],
|
||||
width=600,
|
||||
)
|
||||
self.container.alignment = flet.alignment.center
|
||||
|
||||
self.view.route = SENSE_ADD
|
||||
self.view.vertical_alignment = flet.MainAxisAlignment.CENTER
|
||||
|
||||
@view(initial=True)
|
||||
async def emotions_view(self, page: flet.Page):
|
||||
self.title.value = "Что ты чувствуешь?"
|
||||
|
||||
chips = flet.Row(
|
||||
controls=[
|
||||
flet.Chip(
|
||||
label=flet.Text(emotion.value),
|
||||
show_checkmark=False,
|
||||
selected=emotion.value in self.emotions,
|
||||
on_select=partial(self.callback_choose_emotion, emotion=emotion),
|
||||
)
|
||||
for emotion in Emotion
|
||||
],
|
||||
wrap=True,
|
||||
)
|
||||
self.content_container.content = flet.Column(
|
||||
controls=[chips],
|
||||
)
|
||||
|
||||
next_button = flet.IconButton(
|
||||
flet.icons.ARROW_FORWARD,
|
||||
on_click=self.callback_go_feelings_from_emotions,
|
||||
)
|
||||
self.buttons_row.controls = [next_button]
|
||||
self.buttons_row.alignment = flet.MainAxisAlignment.END
|
||||
|
||||
@view()
|
||||
async def feelings_view(self, page: flet.Page):
|
||||
self.title.value = "Опиши свои чувства"
|
||||
|
||||
self.content_container.content = flet.TextField(
|
||||
value=self.feelings,
|
||||
multiline=True,
|
||||
min_lines=10,
|
||||
max_lines=10,
|
||||
on_change=self.callback_change_feelings,
|
||||
)
|
||||
|
||||
previous_button = flet.IconButton(
|
||||
flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_go_emotions_from_feelings,
|
||||
)
|
||||
next_button = flet.IconButton(
|
||||
flet.icons.ARROW_FORWARD,
|
||||
on_click=self.callback_go_body_from_feelings,
|
||||
)
|
||||
self.buttons_row.controls = [previous_button, next_button]
|
||||
self.buttons_row.alignment = flet.MainAxisAlignment.SPACE_BETWEEN
|
||||
|
||||
@view()
|
||||
async def body_view(self, page: flet.Page):
|
||||
self.title.value = "Опиши свои телесные ощущения"
|
||||
|
||||
self.content_container.content = flet.TextField(
|
||||
value=self.body,
|
||||
multiline=True,
|
||||
min_lines=10,
|
||||
max_lines=10,
|
||||
on_change=self.callback_change_body,
|
||||
)
|
||||
|
||||
previous_button = flet.IconButton(
|
||||
flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_go_feelings_from_body,
|
||||
)
|
||||
next_button = flet.IconButton(
|
||||
flet.icons.ARROW_FORWARD,
|
||||
on_click=self.callback_go_desires_from_body,
|
||||
)
|
||||
self.buttons_row.controls = [previous_button, next_button]
|
||||
self.buttons_row.alignment = flet.MainAxisAlignment.SPACE_BETWEEN
|
||||
|
||||
@view()
|
||||
async def desires_view(self, page: flet.Page):
|
||||
self.title.value = "Опиши свои желания на данный момент"
|
||||
|
||||
self.content_container.content = flet.TextField(
|
||||
value=self.desires,
|
||||
multiline=True,
|
||||
min_lines=10,
|
||||
max_lines=10,
|
||||
on_change=self.callback_change_desires,
|
||||
)
|
||||
|
||||
previous_button = flet.IconButton(
|
||||
flet.icons.ARROW_BACK,
|
||||
on_click=self.callback_go_body_from_desires,
|
||||
)
|
||||
apply_button = flet.IconButton(flet.icons.CREATE, on_click=self.callback_add_sense)
|
||||
self.buttons_row.controls = [previous_button, apply_button]
|
||||
self.buttons_row.alignment = flet.MainAxisAlignment.SPACE_BETWEEN
|
||||
|
||||
async def callback_close(self, event: flet.ControlEvent):
|
||||
self.clear_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
|
||||
async def callback_choose_emotion(self, event: flet.ControlEvent, emotion: Emotion):
|
||||
if event.control.selected:
|
||||
self.emotions.append(emotion)
|
||||
emotions_column = self.content_container.content
|
||||
if len(emotions_column.controls) > 1:
|
||||
emotions_column.controls = emotions_column.controls[:1]
|
||||
await event.page.update_async()
|
||||
else:
|
||||
self.emotions.remove(emotion)
|
||||
|
||||
async def callback_change_feelings(self, event: flet.ControlEvent):
|
||||
self.feelings = event.control.value
|
||||
|
||||
async def callback_change_body(self, event: flet.ControlEvent):
|
||||
self.body = event.control.value
|
||||
|
||||
async def callback_change_desires(self, event: flet.ControlEvent):
|
||||
self.desires = event.control.value
|
||||
|
||||
async def callback_go_emotions_from_feelings(self, event: flet.ControlEvent):
|
||||
await self.emotions_view(page=event.page)
|
||||
|
||||
async def callback_go_feelings_from_emotions(self, event: flet.ControlEvent):
|
||||
if not self.emotions:
|
||||
emotions_column = self.content_container.content
|
||||
error_text = flet.Text("Выберите как минимум одну эмоцию", color=flet.colors.RED)
|
||||
emotions_column.controls = [emotions_column.controls[0], error_text]
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
await self.feelings_view(page=event.page)
|
||||
|
||||
async def callback_go_feelings_from_body(self, event: flet.ControlEvent):
|
||||
await self.feelings_view(page=event.page)
|
||||
|
||||
async def callback_go_body_from_feelings(self, event: flet.ControlEvent):
|
||||
if self.feelings is None or not self.feelings.strip():
|
||||
self.content_container.content.error_text = "Коротко опиши свои чувства"
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
await self.body_view(page=event.page)
|
||||
|
||||
async def callback_go_body_from_desires(self, event: flet.ControlEvent):
|
||||
await self.body_view(page=event.page)
|
||||
|
||||
async def callback_go_desires_from_body(self, event: flet.ControlEvent):
|
||||
if self.body is None or not self.body.strip():
|
||||
self.content_container.content.error_text = "Коротко опиши свои телесные ощущения"
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
await self.desires_view(page=event.page)
|
||||
|
||||
async def callback_add_sense(self, event: flet.ControlEvent):
|
||||
if self.desires is None or not self.desires.strip():
|
||||
self.content_container.content.error_text = "Коротко опиши свои желания"
|
||||
await event.page.update_async()
|
||||
return
|
||||
|
||||
backend_client = await self.get_backend_client()
|
||||
async with self.in_progress(page=event.page):
|
||||
await backend_client.create_sense(
|
||||
emotions=self.emotions,
|
||||
feelings=self.feelings,
|
||||
body=self.body,
|
||||
desires=self.desires,
|
||||
)
|
||||
|
||||
self.clear_data()
|
||||
await event.page.go_async(SENSE_LIST)
|
||||
async def entrypoint(self, page: flet.Page) -> BasePage:
|
||||
local_storage = LocalStorage(page.client_storage)
|
||||
return EmotionsPage(view=self.view, local_storage=local_storage)
|
||||
|
||||
@@ -1,99 +1,12 @@
|
||||
import asyncio
|
||||
|
||||
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.models import Sense
|
||||
from soul_diary.ui.app.routes import AUTH, SENSE_ADD, SENSE_LIST
|
||||
from .base import BaseView, view
|
||||
from soul_diary.ui.app.pages.base import BasePage
|
||||
from soul_diary.ui.app.pages.sense_list import SenseListPage
|
||||
from .base import BaseView
|
||||
|
||||
|
||||
class SenseListView(BaseView):
|
||||
def __init__(
|
||||
self,
|
||||
local_storage: LocalStorage,
|
||||
):
|
||||
self.cards: flet.Column
|
||||
|
||||
self.local_storage = local_storage
|
||||
|
||||
super().__init__(local_storage=local_storage)
|
||||
|
||||
async def setup(self):
|
||||
self.cards = flet.Column(alignment=flet.alignment.center, width=400)
|
||||
|
||||
add_button = flet.IconButton(
|
||||
icon=flet.icons.ADD_CIRCLE_OUTLINE,
|
||||
on_click=self.callback_add_sense,
|
||||
)
|
||||
settings_button = flet.IconButton(
|
||||
icon=flet.icons.SETTINGS,
|
||||
)
|
||||
logout_button = flet.IconButton(
|
||||
icon=flet.icons.LOGOUT,
|
||||
on_click=self.callback_logout,
|
||||
)
|
||||
top_container = flet.Container(
|
||||
content=flet.Row(
|
||||
controls=[add_button, settings_button, logout_button],
|
||||
alignment=flet.MainAxisAlignment.END,
|
||||
),
|
||||
)
|
||||
|
||||
self.container.content = flet.Column(
|
||||
controls=[top_container, self.cards],
|
||||
width=400,
|
||||
)
|
||||
self.container.alignment = flet.alignment.center
|
||||
|
||||
self.view.route = SENSE_LIST
|
||||
self.view.vertical_alignment = flet.MainAxisAlignment.CENTER
|
||||
self.view.scroll = flet.ScrollMode.ALWAYS
|
||||
|
||||
async def clear(self):
|
||||
self.cards.controls = []
|
||||
|
||||
@view(initial=True)
|
||||
async def sense_list_view(self, page: flet.Page):
|
||||
self.cards.controls = [
|
||||
flet.Container(
|
||||
content=flet.ProgressRing(),
|
||||
alignment=flet.alignment.center,
|
||||
),
|
||||
]
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
loop.create_task(self.render_sense_list(page=page))
|
||||
|
||||
async def render_sense_list(self, page: flet.Page):
|
||||
auth_data = await self.local_storage.get_auth_data()
|
||||
if auth_data is None:
|
||||
raise NonAuthenticatedException()
|
||||
|
||||
backend_client = await self.get_backend_client()
|
||||
senses = await backend_client.get_sense_list()
|
||||
self.cards.controls = [await self.render_card_from_sense(sense) for sense in senses]
|
||||
await page.update_async()
|
||||
|
||||
async def render_card_from_sense(self, sense: Sense) -> flet.Card:
|
||||
feelings = flet.Container(content=flet.Text(sense.feelings), expand=True)
|
||||
created_datetime = flet.Text(sense.created_at.strftime("%d %b %H:%M"))
|
||||
|
||||
return flet.Card(
|
||||
content=flet.Container(
|
||||
content=flet.Column(controls=[feelings, created_datetime]),
|
||||
padding=10,
|
||||
),
|
||||
width=400,
|
||||
height=100,
|
||||
)
|
||||
|
||||
async def callback_add_sense(self, event: flet.ControlEvent):
|
||||
await event.page.go_async(SENSE_ADD)
|
||||
|
||||
async def callback_logout(self, event: flet.ControlEvent):
|
||||
backend_client = await self.get_backend_client()
|
||||
async with self.in_progress(page=event.page):
|
||||
await backend_client.logout()
|
||||
await event.page.go_async(AUTH)
|
||||
async def entrypoint(self, page: flet.Page) -> BasePage:
|
||||
local_storage = LocalStorage(client_storage=page.client_storage)
|
||||
return SenseListPage(view=self.view, local_storage=local_storage)
|
||||
|
||||
@@ -25,9 +25,8 @@ class WebService(ServiceMixin):
|
||||
|
||||
async def start(self):
|
||||
app = flet_fastapi.app(SoulDiaryApp(
|
||||
# backend=BackendType.SOUL,
|
||||
# backend_data=self._backend_data,
|
||||
# backend=BackendType.LOCAL,
|
||||
backend=BackendType.SOUL,
|
||||
backend_data=self._backend_data,
|
||||
).run)
|
||||
config = uvicorn.Config(app=app, host="0.0.0.0", port=self._port)
|
||||
server = UvicornServer(config)
|
||||
|
||||
Reference in New Issue
Block a user