Mini Project: Theme Switcher 🎨
Xây dựng ứng dụng demo chuyển đổi Light/Dark theme. ⏱️ 20 phút
🎯 Mục tiêu
Tạo ứng dụng với:
- Toggle Light/Dark mode
- Custom color scheme
- Preview các UI components với theme
🚀 Bắt đầu
Bước 1: Theme Mode cơ bản
import flet as ft
def main(page: ft.Page):
page.title = "Theme Switcher"
page.theme_mode = ft.ThemeMode.LIGHT
def toggle_theme(_):
if page.theme_mode == ft.ThemeMode.LIGHT:
page.theme_mode = ft.ThemeMode.DARK
else:
page.theme_mode = ft.ThemeMode.LIGHT
page.update()
page.add(
ft.Switch(label="Dark Mode", on_change=toggle_theme),
ft.Text("Hello World!"),
)
ft.run(main)Bước 2: Thêm các UI Components
import flet as ft
def main(page: ft.Page):
page.title = "Theme Switcher"
page.theme_mode = ft.ThemeMode.LIGHT
page.padding = 20
def toggle_theme(e):
page.theme_mode = ft.ThemeMode.DARK if e.control.value else ft.ThemeMode.LIGHT
page.update()
page.add(
ft.Row([
ft.Text("Theme Switcher", size=24, weight=ft.FontWeight.BOLD),
ft.Container(expand=True),
ft.Switch(label="Dark Mode", on_change=toggle_theme),
]),
ft.Divider(),
ft.Text("Buttons", weight=ft.FontWeight.BOLD),
ft.Row([
ft.ElevatedButton("Elevated"),
ft.FilledButton("Filled"),
ft.OutlinedButton("Outlined"),
ft.TextButton("Text"),
], wrap=True),
ft.Text("Inputs", weight=ft.FontWeight.BOLD),
ft.TextField(label="Text Field"),
ft.Checkbox(label="Checkbox"),
ft.Switch(label="Switch"),
ft.Slider(value=50),
)
ft.run(main)Bước 3: Custom Color Scheme
import flet as ft
# Custom themes
LIGHT_THEME = ft.Theme(
color_scheme=ft.ColorScheme(
primary=ft.Colors.BLUE,
secondary=ft.Colors.ORANGE,
surface=ft.Colors.WHITE,
background=ft.Colors.GREY_100,
),
)
DARK_THEME = ft.Theme(
color_scheme=ft.ColorScheme(
primary=ft.Colors.CYAN,
secondary=ft.Colors.AMBER,
surface=ft.Colors.GREY_900,
background=ft.Colors.BLACK,
),
)
def main(page: ft.Page):
page.title = "Theme Switcher"
page.theme = LIGHT_THEME
page.dark_theme = DARK_THEME
page.theme_mode = ft.ThemeMode.LIGHT
page.padding = 20
def toggle_theme(e):
page.theme_mode = ft.ThemeMode.DARK if e.control.value else ft.ThemeMode.LIGHT
page.update()
page.add(
ft.Switch(label="Dark Mode", on_change=toggle_theme),
)
ft.run(main)Bước 4: Code hoàn chỉnh với Preview
import flet as ft
@ft.observable
class ThemeState:
is_dark: bool = False
primary_color: str = "blue"
colors = {
"blue": ft.Colors.BLUE,
"green": ft.Colors.GREEN,
"purple": ft.Colors.PURPLE,
"orange": ft.Colors.ORANGE,
"red": ft.Colors.RED,
}
def toggle_mode(self):
self.is_dark = not self.is_dark
def set_color(self, color: str):
self.primary_color = color
def get_primary(self):
return self.colors.get(self.primary_color, ft.Colors.BLUE)
state = ThemeState()
@ft.component
def ColorPicker():
colors = ["blue", "green", "purple", "orange", "red"]
color_map = {
"blue": ft.Colors.BLUE,
"green": ft.Colors.GREEN,
"purple": ft.Colors.PURPLE,
"orange": ft.Colors.ORANGE,
"red": ft.Colors.RED,
}
return ft.Row([
ft.Container(
width=40,
height=40,
bgcolor=color_map[c],
border_radius=20,
border=ft.border.all(3, ft.Colors.WHITE) if state.primary_color == c else None,
shadow=ft.BoxShadow(blur_radius=5, color=ft.Colors.BLACK26) if state.primary_color == c else None,
on_click=lambda _, color=c: state.set_color(color),
)
for c in colors
], spacing=10)
@ft.component
def ComponentsPreview():
return ft.Column([
ft.Text("Preview Components", size=20, weight=ft.FontWeight.BOLD),
ft.Divider(height=20),
# Buttons
ft.Text("Buttons", weight=ft.FontWeight.W_500),
ft.Row([
ft.ElevatedButton("Elevated"),
ft.FilledButton("Filled"),
ft.OutlinedButton("Outlined"),
], wrap=True),
# Form Elements
ft.Text("Form Elements", weight=ft.FontWeight.W_500),
ft.TextField(label="Username", prefix_icon=ft.Icons.PERSON),
ft.TextField(label="Password", password=True, prefix_icon=ft.Icons.LOCK),
ft.Row([
ft.Checkbox(label="Remember me", value=True),
ft.Switch(label="Notifications"),
]),
# Slider
ft.Text("Slider", weight=ft.FontWeight.W_500),
ft.Slider(value=60, min=0, max=100),
# Progress
ft.Text("Progress", weight=ft.FontWeight.W_500),
ft.ProgressBar(value=0.7),
# Card
ft.Text("Card", weight=ft.FontWeight.W_500),
ft.Card(
content=ft.Container(
content=ft.Column([
ft.ListTile(
leading=ft.Icon(ft.Icons.PERSON),
title=ft.Text("John Doe"),
subtitle=ft.Text("[email protected]"),
),
ft.Row([
ft.TextButton("EDIT"),
ft.TextButton("DELETE"),
], alignment=ft.MainAxisAlignment.END),
]),
padding=10,
),
),
], spacing=15, scroll=ft.ScrollMode.AUTO)
@ft.component
def ThemeSettings():
return ft.Card(
content=ft.Container(
content=ft.Column([
ft.Text("Theme Settings", size=20, weight=ft.FontWeight.BOLD),
ft.Divider(),
# Mode toggle
ft.Row([
ft.Icon(ft.Icons.LIGHT_MODE if not state.is_dark else ft.Icons.DARK_MODE),
ft.Text("Dark Mode"),
ft.Container(expand=True),
ft.Switch(
value=state.is_dark,
on_change=lambda _: state.toggle_mode(),
),
]),
# Color picker
ft.Text("Primary Color", weight=ft.FontWeight.W_500),
ColorPicker(),
]),
padding=20,
),
)
@ft.component
def ThemeSwitcherApp():
return ft.Row([
ft.Container(
content=ThemeSettings(),
width=300,
),
ft.VerticalDivider(),
ft.Container(
content=ComponentsPreview(),
expand=True,
padding=20,
),
], expand=True)
def main(page: ft.Page):
page.title = "Theme Switcher"
page.padding = 20
# Apply dynamic theme
def update_theme():
page.theme = ft.Theme(
color_scheme=ft.ColorScheme(primary=state.get_primary())
)
page.dark_theme = ft.Theme(
color_scheme=ft.ColorScheme(primary=state.get_primary())
)
page.theme_mode = ft.ThemeMode.DARK if state.is_dark else ft.ThemeMode.LIGHT
page.update()
# Watch for state changes
page.add(ThemeSwitcherApp())
# Simple approach - rebuild on theme change
def on_state_change():
update_theme()
update_theme()
ft.run(main)✅ Code đơn giản hơn (Imperative)
import flet as ft
def main(page: ft.Page):
page.title = "Theme Switcher"
page.padding = 30
# Theme colors
colors = [ft.Colors.BLUE, ft.Colors.GREEN, ft.Colors.PURPLE, ft.Colors.ORANGE]
def toggle_theme(e):
page.theme_mode = ft.ThemeMode.DARK if e.control.value else ft.ThemeMode.LIGHT
page.update()
def change_color(color):
def handler(_):
page.theme = ft.Theme(color_scheme=ft.ColorScheme(primary=color))
page.dark_theme = ft.Theme(color_scheme=ft.ColorScheme(primary=color))
page.update()
return handler
page.add(
ft.Text("🎨 Theme Switcher", size=28, weight=ft.FontWeight.BOLD),
ft.Divider(),
# Dark mode toggle
ft.Switch(label="Dark Mode", on_change=toggle_theme),
# Color picker
ft.Text("Primary Color:", weight=ft.FontWeight.W_500),
ft.Row([
ft.Container(
width=50, height=50,
bgcolor=c,
border_radius=25,
on_click=change_color(c),
)
for c in colors
], spacing=10),
ft.Divider(),
# Preview
ft.Text("Preview:", weight=ft.FontWeight.W_500),
ft.ElevatedButton("Elevated Button"),
ft.FilledButton("Filled Button"),
ft.TextField(label="Sample Input"),
ft.Slider(value=50),
ft.ProgressBar(value=0.6),
)
ft.run(main)🎯 Thử thách mở rộng
- Save preference - Lưu theme vào client storage
- System theme - Theo theme của hệ điều hành
- Custom fonts - Đổi font family
- More colors - Color picker đầy đủ hơn
📝 Kiến thức ôn tập
page.theme_mode- Light/Dark modepage.themevàpage.dark_theme- Custom themesft.ColorScheme- Bảng màu- Dynamic UI updates
⏭️ Tiếp theo
Tuyệt! Tiếp tục với Navigation & Routing để học điều hướng.
Last updated on