mirror of
https://git.srhtnin.garden/sarah/SaraTune.git
synced 2026-10-13 18:18:11 +02:00
92 lines
3.5 KiB
Python
92 lines
3.5 KiB
Python
"""What both modes share: the App context (config, notifications, dialogs,
|
|
the file picker, switching modes) and small reusable controls."""
|
|
|
|
import os
|
|
import subprocess
|
|
from typing import Callable
|
|
|
|
import flet as ft
|
|
|
|
from Core import AppConfig, ChildEnvironment, ChipButton, IsWindows, Note, NotificationStyle, Palette
|
|
|
|
|
|
class App:
|
|
def __init__(self, Page: ft.Page, Config: AppConfig):
|
|
self.Page = Page
|
|
self.Config = Config
|
|
self.Picker = ft.FilePicker() # registers itself with the page
|
|
# Set by Main.
|
|
self.SwitchMode: Callable[[str], None] = lambda Name: None
|
|
self.OpenInAudio: Callable[[list[str]], None] = lambda Paths: None
|
|
|
|
def Notify(self, Text: str, Kind: str = "info") -> None:
|
|
Background, Accent = NotificationStyle(Kind)
|
|
self.Page.show_dialog(ft.SnackBar(
|
|
content=ft.Text(Text, color=Accent),
|
|
bgcolor=Background,
|
|
shape=ft.RoundedRectangleBorder(radius=4, side=ft.BorderSide(1, Accent)),
|
|
open=True,
|
|
))
|
|
|
|
def ShowDialog(self, Title: str, Content: ft.Control, Actions: list[ft.Control], Width: int = 480) -> ft.AlertDialog:
|
|
Dialog = ft.AlertDialog(title=Title, content=ft.Container(Content, width=Width), actions=Actions)
|
|
self.Page.show_dialog(Dialog)
|
|
return Dialog
|
|
|
|
def Close(self) -> None:
|
|
self.Page.pop_dialog()
|
|
|
|
def Confirm(self, Title: str, Text: str, OnYes: Callable[[], None], YesText: str = "Delete") -> None:
|
|
def Yes(e) -> None:
|
|
self.Close()
|
|
OnYes()
|
|
|
|
self.ShowDialog(Title, ft.Text(Text), [
|
|
ChipButton("Cancel", lambda e: self.Close()),
|
|
ChipButton(YesText, Yes, Accent=Palette.Red),
|
|
], Width=420)
|
|
|
|
def Prompt(self, Title: str, Label: str, OnOk: Callable[[str], None], Value: str = "", OkText: str = "OK") -> None:
|
|
"""Asks for one line of text; OnOk gets it stripped (never empty)."""
|
|
Field = ft.TextField(label=Label, value=Value, autofocus=True)
|
|
|
|
def Ok(e) -> None:
|
|
Text = (Field.value or "").strip()
|
|
if Text:
|
|
self.Close()
|
|
OnOk(Text)
|
|
|
|
Field.on_submit = Ok
|
|
self.ShowDialog(Title, Field, [ChipButton("Cancel", lambda e: self.Close()), ChipButton(OkText, Ok)], Width=380)
|
|
|
|
def OpenFolder(self, PathText: str) -> None:
|
|
try:
|
|
if IsWindows:
|
|
os.startfile(PathText) # Explorer
|
|
else:
|
|
subprocess.Popen(["xdg-open", PathText], env=ChildEnvironment(), start_new_session=True)
|
|
except OSError as Error:
|
|
self.Notify(f"Couldn't open {PathText}: {Error}", "error")
|
|
|
|
|
|
def EmptyState(Text: str, Detail: str = "", Icon=ft.Icons.MUSIC_OFF_OUTLINED) -> ft.Control:
|
|
return ft.Container(
|
|
ft.Column([
|
|
ft.Icon(Icon, color=Palette.Surface1, size=56),
|
|
ft.Text(Text, color=Palette.Overlay0, size=16),
|
|
Note(Detail),
|
|
], horizontal_alignment=ft.CrossAxisAlignment.CENTER, tight=True, spacing=8),
|
|
alignment=ft.Alignment.CENTER, expand=True,
|
|
)
|
|
|
|
|
|
def InfoTable(Rows: list[tuple[str, str]], LabelWidth: int = 110) -> ft.Column:
|
|
"""Label/value rows; values are selectable so tags can be copied."""
|
|
return ft.Column([
|
|
ft.Row([
|
|
ft.Text(Label, color=Palette.Overlay0, size=13, width=LabelWidth),
|
|
ft.Text(Value, color=Palette.Text, size=13, selectable=True, expand=True),
|
|
], vertical_alignment=ft.CrossAxisAlignment.START)
|
|
for Label, Value in Rows
|
|
], spacing=4)
|