diff --git a/fletched/__init__.py b/fletched/__init__.py index 2704619..249c9df 100644 --- a/fletched/__init__.py +++ b/fletched/__init__.py @@ -1,19 +1 @@ -from fletched.mvp_utils import ( - ErrorMessage, - MvpDataSource, - MvpModel, - MvpPresenter, - MvpPresenterProtocol, - MvpView, - MvpViewProtocol, - Observable, -) -from fletched.routed_app import ( - CustomAppState, - MvpViewBuilder, - RoutedApp, - ViewBuilder, - group_required, - login_required, - route, -) +from fletched import mvp, routed_app diff --git a/fletched/mvp/__init__.py b/fletched/mvp/__init__.py new file mode 100644 index 0000000..16598de --- /dev/null +++ b/fletched/mvp/__init__.py @@ -0,0 +1,7 @@ +from fletched.mvp.datasource import MvpDataSource +from fletched.mvp.error import ErrorMessage +from fletched.mvp.model import MvpModel +from fletched.mvp.observable import Observable +from fletched.mvp.presenter import MvpPresenter +from fletched.mvp.protocols import MvpPresenterProtocol, MvpViewProtocol +from fletched.mvp.view import MvpView, MvpViewBuilder, ViewConfig diff --git a/fletched/mvp_utils/datasource.py b/fletched/mvp/datasource.py similarity index 89% rename from fletched/mvp_utils/datasource.py rename to fletched/mvp/datasource.py index f90c738..21a0633 100644 --- a/fletched/mvp_utils/datasource.py +++ b/fletched/mvp/datasource.py @@ -1,9 +1,9 @@ from abstractcp import Abstract, abstract_class_property -from fletched.mvp_utils.error import ErrorMessage -from fletched.mvp_utils.observable import Observable from pydantic import BaseModel, ValidationError -from fletched.routed_app.app import RoutedApp +from fletched.mvp.error import ErrorMessage +from fletched.mvp.observable import Observable +from fletched.routed_app import RoutedApp class MvpDataSource(Abstract, Observable): diff --git a/fletched/mvp_utils/error.py b/fletched/mvp/error.py similarity index 100% rename from fletched/mvp_utils/error.py rename to fletched/mvp/error.py diff --git a/fletched/mvp_utils/model.py b/fletched/mvp/model.py similarity index 100% rename from fletched/mvp_utils/model.py rename to fletched/mvp/model.py diff --git a/fletched/mvp_utils/observable.py b/fletched/mvp/observable.py similarity index 100% rename from fletched/mvp_utils/observable.py rename to fletched/mvp/observable.py diff --git a/fletched/mvp_utils/presenter.py b/fletched/mvp/presenter.py similarity index 80% rename from fletched/mvp_utils/presenter.py rename to fletched/mvp/presenter.py index dbdc995..6473127 100644 --- a/fletched/mvp_utils/presenter.py +++ b/fletched/mvp/presenter.py @@ -1,7 +1,7 @@ from dataclasses import dataclass -from fletched.mvp_utils.datasource import MvpDataSource -from fletched.mvp_utils.protocols import MvpViewProtocol +from fletched.mvp.datasource import MvpDataSource +from fletched.mvp.protocols import MvpViewProtocol @dataclass diff --git a/fletched/mvp_utils/protocols.py b/fletched/mvp/protocols.py similarity index 100% rename from fletched/mvp_utils/protocols.py rename to fletched/mvp/protocols.py diff --git a/fletched/mvp/view.py b/fletched/mvp/view.py new file mode 100644 index 0000000..79a44d4 --- /dev/null +++ b/fletched/mvp/view.py @@ -0,0 +1,91 @@ +from abc import abstractmethod +from dataclasses import asdict, dataclass +from typing import List, Optional, Type + +import flet as ft +from abstractcp import Abstract, abstract_class_property +from pydantic import BaseModel + +from fletched.mvp.datasource import MvpDataSource +from fletched.mvp.error import ErrorMessage +from fletched.mvp.presenter import MvpPresenter +from fletched.mvp.protocols import MvpPresenterProtocol +from fletched.routed_app import ViewBuilder + + +@dataclass +class ViewConfig: + route: Optional[str] = None + controls: Optional[List[ft.Control]] = None + appbar: Optional[ft.AppBar] = None + floating_action_button: Optional[ft.FloatingActionButton] = None + navigation_bar: Optional[ft.NavigationBar] = None + vertical_alignment: ft.MainAxisAlignment = ft.MainAxisAlignment.NONE + horizontal_alignment: ft.CrossAxisAlignment = ft.CrossAxisAlignment.NONE + spacing: ft.OptionalNumber = None + padding: ft.PaddingValue = None + bgcolor: Optional[str] = None + scroll: Optional[ft.ScrollMode] = None + auto_scroll: Optional[bool] = None + + +class MvpView(Abstract, ft.View): + ref_map = abstract_class_property(dict[str, ft.Ref]) + config = abstract_class_property(ViewConfig) + + def __init__(self) -> None: + super().__init__(**asdict(self.config)) + + def render(self, model: BaseModel) -> None: + page: ft.Page | None = None + model_map = model.dict() + + for variable_name, ref in self.ref_map.items(): + + model_field_content = model_map[variable_name] + control_attribute_name = "value" + if not hasattr(ref.current, control_attribute_name): + control_attribute_name = "text" + if isinstance(model_field_content, ErrorMessage): + control_attribute_name = "error_text" + model_field_content = model_field_content.message + + control_attribute_content = getattr(ref.current, control_attribute_name) + + if model_field_content == control_attribute_content: + continue + setattr(ref.current, control_attribute_name, model_field_content) + + if not page: + page = ref.current.page + + if page: + page.update() + + @abstractmethod + def build(self, presenter: MvpPresenterProtocol) -> None: + ... + + +class MvpViewBuilder(ViewBuilder): + data_source_class: Type[MvpDataSource] + view_class: Type[MvpView] + presenter_class: Type[MvpPresenter] + + def build_view(self, route_params: dict[str, str]) -> ft.View: + + if not hasattr(self, "data_source"): + self.data_source = self.data_source_class( + app=self.app, route_params=route_params + ) + + self.view_class.config.route = self.route + self.view: ft.View = self.view_class() + self.presenter = self.presenter_class( + data_source=self.data_source, + view=self.view, + ) + self.presenter.build() + self.view.render(self.data_source.current_model) + + return self.view diff --git a/fletched/mvp_utils/__init__.py b/fletched/mvp_utils/__init__.py deleted file mode 100644 index cab7dfd..0000000 --- a/fletched/mvp_utils/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from fletched.mvp_utils.datasource import MvpDataSource -from fletched.mvp_utils.error import ErrorMessage -from fletched.mvp_utils.model import MvpModel -from fletched.mvp_utils.observable import Observable -from fletched.mvp_utils.presenter import MvpPresenter -from fletched.mvp_utils.protocols import MvpPresenterProtocol, MvpViewProtocol -from fletched.mvp_utils.view import MvpView diff --git a/fletched/mvp_utils/view.py b/fletched/mvp_utils/view.py deleted file mode 100644 index 761e333..0000000 --- a/fletched/mvp_utils/view.py +++ /dev/null @@ -1,74 +0,0 @@ -from abc import abstractmethod -from typing import List, Optional - -import flet as ft -from abstractcp import Abstract, abstract_class_property -from pydantic import BaseModel - -from fletched.mvp_utils.error import ErrorMessage -from fletched.mvp_utils.protocols import MvpPresenterProtocol - - -class MvpView(Abstract, ft.View): - ref_map = abstract_class_property(dict[str, ft.Ref]) - - def __init__( - self, - *, - route: Optional[str] = None, - controls: Optional[List[ft.Control]] = None, - appbar: Optional[ft.AppBar] = None, - floating_action_button: Optional[ft.FloatingActionButton] = None, - navigation_bar: Optional[ft.NavigationBar] = None, - vertical_alignment: ft.MainAxisAlignment = ft.MainAxisAlignment.NONE, - horizontal_alignment: ft.CrossAxisAlignment = ft.CrossAxisAlignment.NONE, - spacing: ft.OptionalNumber = None, - padding: ft.PaddingValue = None, - bgcolor: Optional[str] = None, - scroll: Optional[ft.ScrollMode] = None, - auto_scroll: Optional[bool] = None, - ) -> None: - super().__init__( - route, - controls, - appbar, - floating_action_button, - navigation_bar, - vertical_alignment, - horizontal_alignment, - spacing, - padding, - bgcolor, - scroll, - auto_scroll, - ) - - def render(self, model: BaseModel) -> None: - page: ft.Page | None = None - model_map = model.dict() - - for variable_name, ref in self.ref_map.items(): - - model_field_content = model_map[variable_name] - control_attribute_name = "value" - if not hasattr(ref.current, control_attribute_name): - control_attribute_name = "text" - if isinstance(model_field_content, ErrorMessage): - control_attribute_name = "error_text" - model_field_content = model_field_content.message - - control_attribute_content = getattr(ref.current, control_attribute_name) - - if model_field_content == control_attribute_content: - continue - setattr(ref.current, control_attribute_name, model_field_content) - - if not page: - page = ref.current.page - - if page: - page.update() - - @abstractmethod - def build(self, presenter: MvpPresenterProtocol) -> None: - ... diff --git a/fletched/routed_app/__init__.py b/fletched/routed_app/__init__.py index 287d7eb..5817c8b 100644 --- a/fletched/routed_app/__init__.py +++ b/fletched/routed_app/__init__.py @@ -2,4 +2,4 @@ from fletched.routed_app.auth import group_required, login_required from fletched.routed_app.routing import route from fletched.routed_app.state import CustomAppState -from fletched.routed_app.view_builder import MvpViewBuilder, ViewBuilder +from fletched.routed_app.view_builder import ViewBuilder diff --git a/fletched/routed_app/view_builder.py b/fletched/routed_app/view_builder.py index 3fb464a..8686ad7 100644 --- a/fletched/routed_app/view_builder.py +++ b/fletched/routed_app/view_builder.py @@ -1,10 +1,8 @@ from abc import ABC, abstractmethod -from typing import Any, Callable, Type +from typing import Any, Callable import flet as ft -from fletched.mvp_utils import MvpDataSource, MvpPresenter, MvpView - class ViewBuilder(ABC): route: str | None = None @@ -49,22 +47,3 @@ def _build_unauthorized_view(self) -> ft.View: vertical_alignment=ft.MainAxisAlignment.CENTER, horizontal_alignment=ft.CrossAxisAlignment.CENTER, ) - - -class MvpViewBuilder(ViewBuilder): - data_source_class: Type[MvpDataSource] - view_class: Type[MvpView] - presenter_class: Type[MvpPresenter] - - def build_view(self, route_params: dict[str, str]) -> ft.View: - self.data_source = self.data_source_class( - app=self.app, route_params=route_params - ) - self.view: ft.View = self.view_class(route=self.route) - self.presenter = self.presenter_class( - data_source=self.data_source, - view=self.view, - ) - self.presenter.build() - - return self.view