Files
news_all_spo/web/views/news_feed.py
2026-05-13 19:05:07 +05:00

241 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import flet as ft
import designer as d
import api_client as api
class NewsFeedView:
def __init__(self, page: ft.Page):
self.page = page
self._articles: list = []
self._offset = 0
self._limit = 20
self._has_more = True
self._loading = False
self._category: str | None = None
self._categories: list = []
self._list_col = ft.Column(spacing=16)
self._loader = ft.Container(
content=d.loader(36),
alignment=ft.alignment.center,
padding=20,
visible=True,
)
self._load_more_btn = ft.ElevatedButton(
"Загрузить ещё",
icon=ft.icons.EXPAND_MORE,
on_click=lambda e: self.page.run_task(self._load_more),
visible=False,
style=ft.ButtonStyle(
bgcolor=d.SURFACE,
color=d.PRIMARY,
shape=ft.RoundedRectangleBorder(radius=8),
),
)
self._cat_row = ft.Row(scroll=ft.ScrollMode.AUTO, spacing=8)
def build_page_view(self) -> ft.View:
header = ft.Container(
content=ft.Row(
[
ft.Row([
ft.Icon(ft.icons.NEWSPAPER, color=ft.colors.WHITE, size=28),
ft.Text("Новости", size=20, weight=ft.FontWeight.BOLD, color=ft.colors.WHITE),
], spacing=8),
ft.Row([
ft.TextButton(
"Войти",
icon=ft.icons.ADMIN_PANEL_SETTINGS_OUTLINED,
style=ft.ButtonStyle(color=ft.colors.WHITE70),
on_click=lambda e: self.page.go("/admin/login"),
)
]),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
bgcolor=d.ADMIN_SIDEBAR,
padding=ft.padding.symmetric(horizontal=24, vertical=14),
)
cat_section = ft.Container(
content=ft.Column([
ft.Container(content=self._cat_row, padding=ft.padding.symmetric(horizontal=24, vertical=8)),
]),
bgcolor=d.SURFACE,
border=ft.Border(bottom=ft.BorderSide(1, d.DIVIDER)),
)
main_content = ft.Container(
content=ft.Column(
[
self._list_col,
self._loader,
ft.Container(
content=self._load_more_btn,
alignment=ft.alignment.center,
padding=16,
),
],
spacing=0,
),
padding=ft.padding.symmetric(horizontal=24, vertical=20),
alignment=ft.alignment.top_center,
)
return ft.View(
route="/",
bgcolor=d.BACKGROUND,
controls=[
ft.Column(
[
header,
cat_section,
ft.Container(
content=main_content,
alignment=ft.alignment.top_center,
expand=True,
),
],
spacing=0,
expand=True,
scroll=ft.ScrollMode.AUTO,
)
],
)
async def load(self):
self._categories = await api.get_categories()
self._render_categories()
await self._load_articles()
async def _load_more(self):
if self._loading or not self._has_more:
return
self._loading = True
self._load_more_btn.disabled = True
self.page.update()
self._offset += self._limit
await self._load_articles()
self._loading = False
self._load_more_btn.disabled = False
self.page.update()
async def _load_articles(self):
self._loader.visible = True
self.page.update()
data = await api.get_news(offset=self._offset, limit=self._limit, category=self._category)
self._loader.visible = False
if not data:
self.page.update()
return
items = data.get("items", [])
self._articles.extend(items)
self._has_more = data.get("has_more", False)
for a in items:
self._list_col.controls.append(self._build_card(a))
self._load_more_btn.visible = self._has_more
self.page.update()
def _render_categories(self):
self._cat_row.controls = [
self._cat_chip(None, "Все")
] + [
self._cat_chip(c["slug"], c["name"]) for c in self._categories
]
self.page.update()
def _cat_chip(self, slug: str | None, label: str) -> ft.Chip:
is_active = self._category == slug
return ft.Chip(
label=ft.Text(label, size=13),
bgcolor=d.PRIMARY if is_active else d.SURFACE,
label_style=ft.TextStyle(color=ft.colors.WHITE if is_active else d.TEXT_PRIMARY),
on_click=lambda e, s=slug: self.page.run_task(self._filter_category, s),
padding=ft.padding.symmetric(horizontal=12, vertical=6),
side=ft.BorderSide(1, d.PRIMARY if is_active else d.DIVIDER),
)
async def _filter_category(self, slug: str | None):
self._category = slug
self._articles = []
self._offset = 0
self._has_more = True
self._list_col.controls.clear()
self._render_categories()
await self._load_articles()
def _build_card(self, article: dict) -> ft.Container:
pub_date = ""
if article.get("published_at"):
pub_date = article["published_at"][:10]
cat = article.get("category")
cat_chip = ft.Container(
content=ft.Text(cat["name"], size=11, color=ft.colors.WHITE),
bgcolor=d.PRIMARY,
border_radius=4,
padding=ft.padding.symmetric(horizontal=8, vertical=3),
visible=bool(cat),
) if cat else ft.Container(visible=False)
cover = ft.Image(
src=article["cover_url"],
width=260,
height=160,
fit=ft.ImageFit.COVER,
border_radius=ft.BorderRadius(0, 8, 8, 0),
) if article.get("cover_url") else ft.Container(visible=False)
text_part = ft.Column(
[
ft.Row([cat_chip, ft.Text(pub_date, size=11, color=d.TEXT_SECONDARY)], spacing=8),
ft.Text(
article["title"],
size=18,
weight=ft.FontWeight.BOLD,
color=d.TEXT_PRIMARY,
max_lines=3,
overflow=ft.TextOverflow.ELLIPSIS,
),
ft.Text(
article.get("excerpt", ""),
size=13,
color=d.TEXT_SECONDARY,
max_lines=3,
overflow=ft.TextOverflow.ELLIPSIS,
),
ft.Row(
[
ft.Row([
ft.Icon(ft.icons.REMOVE_RED_EYE_OUTLINED, size=14, color=d.TEXT_SECONDARY),
ft.Text(str(article.get("view_count", 0)), size=12, color=d.TEXT_SECONDARY),
], spacing=4),
ft.TextButton(
"Читать →",
style=ft.ButtonStyle(color=d.PRIMARY),
on_click=lambda e, s=article["slug"]: self.page.go(f"/news/{s}"),
),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
],
spacing=8,
expand=True,
)
return ft.Container(
content=ft.Row([ft.Container(content=text_part, expand=True, padding=20), cover], spacing=0),
bgcolor=d.SURFACE,
border_radius=12,
border=ft.border.all(1, d.DIVIDER),
shadow=ft.BoxShadow(blur_radius=6, color=ft.colors.with_opacity(0.05, ft.colors.BLACK)),
clip_behavior=ft.ClipBehavior.HARD_EDGE,
on_click=lambda e, s=article["slug"]: self.page.go(f"/news/{s}"),
ink=True,
)