Repository: ndonkoHenri/Flet-Samples
Branch: master
Commit: a25c6aefa01e
Files: 83
Total size: 379.7 KB
Directory structure:
gitextract_f_v8w953/
├── .gitattributes
├── .gitignore
├── Flet-Utils/
│ ├── Dockerfile
│ ├── README.md
│ ├── assets/
│ │ ├── index.html
│ │ └── manifest.json
│ ├── fly.toml
│ ├── main.py
│ ├── requirements.txt
│ └── utils/
│ ├── alignment_utils.py
│ ├── blur_utils.py
│ ├── border_radius_utils.py
│ ├── border_utils.py
│ ├── circle_avatar_utils.py
│ ├── colors_utils.py
│ ├── divider_utils.py
│ ├── gradient_utils.py
│ ├── icon_utils.py
│ ├── icons_browser_utils.py
│ ├── padding_utils.py
│ ├── progress_bar_utils.py
│ ├── progress_ring_utils.py
│ ├── shadermask_utils.py
│ ├── shadow_utils.py
│ ├── shape_utils.py
│ ├── tooltip_utils.py
│ └── vertical_divider_utils.py
├── Forms/
│ ├── README.md
│ ├── login_utils.py
│ ├── login_with_captcha.py
│ └── requirements.txt
├── IP Revealer/
│ ├── README.md
│ ├── assets/
│ │ └── fonts/
│ │ ├── Plus_Jakarta_Sans/
│ │ │ └── OFL.txt
│ │ └── Stick/
│ │ └── OFL.txt
│ ├── main.py
│ ├── main_with_ipify.py
│ └── requirements.txt
├── LICENSE
├── Markdown Editor/
│ ├── Dockerfile
│ ├── README.md
│ ├── assets/
│ │ ├── index.html
│ │ └── manifest.json
│ ├── fly.toml
│ ├── main.py
│ ├── requirements.txt
│ └── utils.py
├── QR Code Generator/
│ ├── Dockerfile
│ ├── README.md
│ ├── assets/
│ │ ├── index.html
│ │ └── manifest.json
│ ├── fly.toml
│ ├── main.py
│ └── requirements.txt
├── README.md
├── Random Image Viewer/
│ ├── README.md
│ ├── assets/
│ │ ├── fonts/
│ │ │ ├── JetBrains_Mono/
│ │ │ │ └── OFL.txt
│ │ │ ├── Kalam/
│ │ │ │ └── OFL.txt
│ │ │ └── Space_Grotesk/
│ │ │ └── OFL.txt
│ │ ├── index.html
│ │ └── manifest.json
│ ├── main.py
│ ├── requirements.txt
│ └── utils.py
├── Short Jokes/
│ ├── README.md
│ ├── assets/
│ │ ├── fonts/
│ │ │ ├── JetBrains_Mono/
│ │ │ │ └── OFL.txt
│ │ │ ├── Kalam/
│ │ │ │ └── OFL.txt
│ │ │ └── Space_Grotesk/
│ │ │ └── OFL.txt
│ │ ├── index.html
│ │ └── manifest.json
│ ├── joke.py
│ ├── main.py
│ ├── requirements.txt
│ └── utils.py
├── StartUp Name Generator/
│ ├── README.md
│ ├── main.py
│ ├── requirements.txt
│ └── utils.py
└── URL shortener/
├── Dockerfile
├── README.md
├── assets/
│ └── index.html
├── fly.toml
├── main.py
└── requirements.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
================================================
FILE: .gitignore
================================================
/.idea
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/main.py
/URL shortener/assets/manifest.json
================================================
FILE: Flet-Utils/Dockerfile
================================================
FROM python:3-alpine
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "./main.py"]
================================================
FILE: Flet-Utils/README.md
================================================
# Flet-Utils (Flutils)
**Link to Online Demo [here](https://flutils.pages.dev/)!**
When developing with Flet([website](https://flet.dev), [gitHub](https://github.com/flet-dev/flet)), It's not that easy to work with classes that take alot of parameters.
Choosing the perfect values for the Padding, Alignment, Border Radius... of our Controls becomes very difficult, time-consuming and/or very annoying.
I then decided to build this tool - Flet Utils - to help out all those using Flet to build applications, to efficiently and rapidly choose parameters when working with Controls.
## Captures (to be added)
See the [website](https://flutils.pages.dev/) itself till I add please.
## Issues and Contribution
If you have any error/issue/problem using this app, please raise one on the [Issues section of this repo](https://github.com/ndonkoHenri/Flet-Samples/issues).
Also, feel free to a drop a pull request in case you wish to contribute.
**Flet-ty MEME by [@Hololeo](https://github.com/hololeo)**:
================================================
FILE: Flet-Utils/assets/index.html
================================================
Flet Utilities
================================================
FILE: Flet-Utils/assets/manifest.json
================================================
{
"name": "Flet Utilities",
"short_name": "Flet Utils",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0175C2",
"description": "A collection of utility tools for Flet developers.",
"orientation": "natural",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
================================================
FILE: Flet-Utils/fly.toml
================================================
app = "flutils"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
FLET_SERVER_PORT = "8080"
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
================================================
FILE: Flet-Utils/main.py
================================================
import flet as ft
from utils.padding_utils import TabContentPadding
from utils.alignment_utils import TabContentAlignment
from utils.border_utils import TabContentBorder
from utils.border_radius_utils import TabContentBorderRadius
from utils.colors_utils import TabContentColors1, TabContentColors2
from utils.icons_browser_utils import TabContentIconsBrowser
from utils.gradient_utils import TabContentLinearGradient, TabContentSweepGradient, TabContentRadialGradient
from utils.shadermask_utils import TabContentShaderMask
from utils.shape_utils import TabContentShape
from utils.tooltip_utils import TabContentTooltip
from utils.icon_utils import TabContentIcon
from utils.progress_ring_utils import TabContentProgressRing
from utils.progress_bar_utils import TabContentProgressBar
from utils.divider_utils import TabContentDivider
from utils.vertical_divider_utils import TabContentVerticalDivider
from utils.circle_avatar_utils import TabContentCircleAvatar
from utils.shadow_utils import TabContentShadow
from utils.blur_utils import TabContentBlur
def main(page: ft.Page):
page.title = "Flet Utilities"
page.theme_mode = "light"
# page.window_always_on_top = True
page.vertical_alignment = "start"
# set the width and height of the window.
page.window_width = 640
page.window_height = 750
# page.horizontal_alignment = "center"
def change_theme(e):
"""
Changes the app's theme_mode, from dark to light or light to dark.
:param e: The event that triggered the function
:type e: ControlEvent
"""
page.theme_mode = "light" if page.theme_mode == "dark" else "dark" # changes the page's theme_mode
theme_icon_button.selected = not theme_icon_button.selected # changes the icon
page.update()
theme_icon_button = ft.IconButton(
ft.icons.DARK_MODE,
selected=False,
selected_icon=ft.icons.LIGHT_MODE,
icon_size=35,
tooltip="change theme",
on_click=change_theme,
style=ft.ButtonStyle(color={"": ft.colors.BLACK, "selected": ft.colors.WHITE}, ),
)
page.appbar = ft.AppBar(
title=ft.Text(
"Flet Utilities",
color="white"
),
center_title=True,
bgcolor="blue",
actions=[theme_icon_button],
leading=ft.IconButton(
icon=ft.icons.CODE,
icon_color=ft.colors.YELLOW_ACCENT,
on_click=lambda e: page.launch_url(
"https://github.com/ndonkoHenri/Flet-Samples/tree/master/Flet-Utils"),
tooltip="View Code"
),
)
icon_content = TabContentIcon()
tooltip_content = TabContentTooltip()
progress_ring_content = TabContentProgressRing()
progress_bar_content = TabContentProgressBar()
divider_content = TabContentDivider()
vertical_divider_content = TabContentVerticalDivider()
circle_avatar_content = TabContentCircleAvatar()
border_radius_content = TabContentBorderRadius()
padding_content = TabContentPadding()
icons_browser_content = TabContentIconsBrowser()
colors1_content = TabContentColors1()
colors2_content = TabContentColors2(page)
alignment_content = TabContentAlignment()
shape_content = TabContentShape()
shadow_content = TabContentShadow()
blur_content = TabContentBlur()
border_content = TabContentBorder()
linear_gradient_content = TabContentLinearGradient()
radial_gradient_content = TabContentRadialGradient()
sweep_gradient_content = TabContentSweepGradient()
shader_mask_content = TabContentShaderMask()
page.add(
ft.Tabs(
expand=True,
selected_index=0,
tabs=[
ft.Tab(
text="Icon",
content=icon_content
),
ft.Tab(
text="Tooltip",
content=tooltip_content
),
ft.Tab(
text="ProgressRing",
content=progress_ring_content
),
ft.Tab(
text="ProgressBar",
content=progress_bar_content
),
ft.Tab(
text="Divider",
content=divider_content
),
ft.Tab(
text="VerticalDivider",
content=vertical_divider_content
),
ft.Tab(
text="CircleAvatar",
content=circle_avatar_content
),
ft.Tab(
text="Shadow",
content=shadow_content
),
ft.Tab(
text="Blur",
content=blur_content
),
ft.Tab(
text="BorderRadius",
content=border_radius_content
),
ft.Tab(
text="Padding",
content=padding_content
),
ft.Tab(
text="Icons Browser",
content=icons_browser_content
),
ft.Tab(
text="Colors V1",
content=colors1_content
),
ft.Tab(
text="Colors V2",
content=colors2_content
),
ft.Tab(
text="Alignment",
content=alignment_content
),
ft.Tab(
text="Shape",
content=shape_content
),
ft.Tab(
text="Border",
content=border_content
),
ft.Tab(
text="Linear Gradient",
content=linear_gradient_content
),
ft.Tab(
text="Radial Gradient",
content=radial_gradient_content
),
ft.Tab(
text="Sweep Gradient",
content=sweep_gradient_content
),
ft.Tab(
text="Shader Mask",
content=shader_mask_content
),
]
),
ft.Text(
"Made with ❤ by @ndonkoHenri aka TheEthicalBoy!",
style=ft.TextThemeStyle.LABEL_SMALL,
weight=ft.FontWeight.BOLD,
italic=True,
color=ft.colors.BLUE_900,
)
)
ft.app(
target=main,
route_url_strategy="path",
assets_dir="assets",
view=ft.WEB_BROWSER
)
================================================
FILE: Flet-Utils/requirements.txt
================================================
flet==0.9.0
================================================
FILE: Flet-Utils/utils/alignment_utils.py
================================================
import flet as ft
# the content of the alignment tab
class TabContentAlignment(ft.UserControl):
def __init__(self):
super().__init__()
# slider for x parameter of the Alignment object
self.slider_x = ft.Slider(
label="x",
value=0,
on_change=self.update_alignment,
min=-1,
max=1,
divisions=100,
)
# slider for y parameter of the Alignment object
self.slider_y = ft.Slider(
label="y",
value=0,
on_change=self.update_alignment,
min=-1,
max=1,
divisions=100,
)
self.container_button = ft.Ref[ft.FilledTonalButton]()
self.container_obj = ft.Ref[ft.Container]()
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the alignment used by the container to the clipboard.
:param e: The event object
"""
# update the text in the clipboard
e.page.set_clipboard(f"{self.container_obj.current.alignment}")
# show a snackbar to account for the changes
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.container_obj.current.alignment}"), open=True))
def update_alignment(self, e: ft.ControlEvent):
"""
It updates the alignment of the container object.
:param e: The event object
"""
# round the values from the sliders to 2 decimals to avoid long values, and store result in variables
x = round(float(self.slider_x.value), 2)
y = round(float(self.slider_y.value), 2)
# update container's alignment
self.container_obj.current.alignment = ft.Alignment(x, y)
# update the text of the button in the container
self.container_button.current.text = f"Pos: {x},{y}"
self.update()
# show a snackbar to account for the changes
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Alignment!"), open=True))
def build(self):
all_sliders = ft.Row(
controls=[
self.slider_x,
self.slider_y
],
wrap=True
)
return ft.Column(
[
ft.Column(
[
ft.Text("Alignment Builder:", weight=ft.FontWeight.BOLD, size=21),
ft.Text(
"CheatSheet:\ntopLeft = (-1,-1) | topCenter = (0,-1) | topRight = (1,-1)\ncenterLeft = ("
"-1,0) | Center = (0,0) | centerRight = (1,0)\nbottomLeft = (-1,1) | bottomCenter = ("
"0,1) | bottomRight = (1,1)", italic=True, ),
all_sliders
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.Container(
content=ft.FilledTonalButton(
content=ft.Text(
"Pos: 0.0,0.0",
weight=ft.FontWeight.BOLD
),
ref=self.container_button,
disabled=True
),
expand=True,
ref=self.container_obj,
bgcolor=ft.colors.DEEP_PURPLE_ACCENT_700,
width=160,
height=160,
alignment=ft.Alignment(0, 0), # align its contents in the center
)
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container/#alignment"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentAlignment())
ft.app(main)
================================================
FILE: Flet-Utils/utils/blur_utils.py
================================================
import random
from flet import *
import flet as ft
# todo: change link to docs
# the content of the blur tab
class TabContentBlur(ft.UserControl):
def __init__(self):
super().__init__()
self.blur_sigma_y = None
self.blur_sigma_x = None
self.blur_tile_mode = None
self.blur_obj = ft.Ref[ft.Container]()
# text field for the sigma x property of the Blur object
self.field_sigma_x = ft.TextField(
label="sigma_x",
helper_text="Union[int, float]",
value="",
on_change=self.update_blur,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the sigma_y property of the Blur object
self.field_sigma_y = ft.TextField(
label="sigma_y",
value="",
helper_text="Union[int, float]",
on_change=self.update_blur,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# radio buttons for the tile_mode parameter
self.tile_mode_radio_group = ft.RadioGroup(
ft.Row(
[
ft.Radio(value="clamp", label="clamp"),
ft.Radio(value="decal", label="decal"),
ft.Radio(value="mirror", label="mirror"),
ft.Radio(value="repeated", label="repeated"),
],
alignment=ft.MainAxisAlignment.CENTER
),
value="clamp",
on_change=self.update_blur,
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_sigma_x, self.field_sigma_y]
),
self.tile_mode_radio_group,
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11,
)
w, h = 300, 300
return ft.Column(
[
ft.Text("Blur Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields,
ft.Row(
[
ft.Stack(
[
ft.Container(
ref=self.blur_obj,
# bgcolor=ft.colors.AMBER,
image_src=f"https://picsum.photos/300/300?random={random.randint(1, 500)}",
alignment=ft.alignment.center,
tooltip="reload page to get a new image",
width=w,
height=h,
),
ft.Container(
ref=self.blur_obj,
# bgcolor=ft.colors.AMBER,
alignment=ft.alignment.center,
width=w,
height=h,
),
]
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/verticaldivider/"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=30,
expand=True
)
def update_blur(self, e: ft.ControlEvent):
"""It updates the blur object."""
self.blur_sigma_x, self.blur_sigma_y = (
int(self.field_sigma_x.value.strip()) if self.field_sigma_x.value.strip().isnumeric() else None,
int(self.field_sigma_y.value.strip()) if self.field_sigma_y.value.strip().isnumeric() else None,
)
self.blur_tile_mode = self.tile_mode_radio_group.value
# sigma_y
try:
if self.field_sigma_y.value:
self.blur_sigma_y = eval(self.field_sigma_y.value)
assert isinstance(self.blur_sigma_y,
(int, float)), "`sigma_y` must be either of type float or int !"
else:
self.blur_sigma_y = None
except Exception as x:
print(f"Sigma Y Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# sigma_x
try:
if self.field_sigma_x.value:
self.blur_sigma_x = eval(self.field_sigma_x.value)
assert isinstance(self.blur_sigma_x,
(int, float)), "`sigma_x` must be either of type float or int !"
else:
self.blur_sigma_x = None
except Exception as x:
print(f"Sigma X Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
self.blur_obj.current.blur = ft.Blur(self.blur_sigma_x, self.blur_sigma_y, self.blur_tile_mode)
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Blur!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the blur object/instance to the clipboard."""
val = self.blur_obj.current.blur
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}"), open=True))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
page.window_always_on_top = True
page.add(TabContentBlur())
ft.app(main, view=ft.WEB_BROWSER)
================================================
FILE: Flet-Utils/utils/border_radius_utils.py
================================================
import flet as ft
# the content of the border radius tab
class TabContentBorderRadius(ft.UserControl):
def __init__(self):
super().__init__()
# text field for topLeft(tl) property of the BorderRadius object
self.field_tl = ft.TextField(
label="topLeft",
value="",
width=120,
height=50,
on_change=self.update_border_radius,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for topRight(tr) property of the BorderRadius object
self.field_tr = ft.TextField(
label="topRight",
value="",
width=120,
height=50,
on_change=self.update_border_radius,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for bottomLeft(bl) property of the BorderRadius object
self.field_bl = ft.TextField(
label="bottomLeft",
value="",
width=120,
height=50,
on_change=self.update_border_radius,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for bottomRight(br) property of the BorderRadius object
self.field_br = ft.TextField(
label="bottomRight",
value="",
width=120,
height=50,
on_change=self.update_border_radius,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for the width property of the Container object
self.field_width = ft.TextField(
label="Width",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size
)
# text field for the height property of the Container object
self.field_height = ft.TextField(
label="Height",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size
)
self.container_obj = ft.Ref[ft.Container]()
self.container_text = ft.Ref[ft.Text]()
def update_border_radius(self, e: ft.ControlEvent):
"""
It updates the border radius of the container object.
:param e: The event object
"""
if e.control.value.strip().isnumeric() or not e.control.value.strip():
# if the value of the text field in focus is numeric or if it is empty...
self.container_obj.current.border_radius = ft.border_radius.BorderRadius(
int(self.field_tl.value.strip()) if self.field_tl.value.strip().isnumeric() else 0,
int(self.field_tr.value.strip()) if self.field_tr.value.strip().isnumeric() else 0,
int(self.field_bl.value.strip()) if self.field_bl.value.strip().isnumeric() else 0,
int(self.field_br.value.strip()) if self.field_br.value.strip().isnumeric() else 0, )
self.container_text.current.value = f"{int(self.field_tl.value.strip()) if self.field_tl.value.strip().isnumeric() else 0}, {int(self.field_tr.value.strip()) if self.field_tr.value.strip().isnumeric() else 0}, {int(self.field_bl.value.strip()) if self.field_bl.value.strip().isnumeric() else 0}, {int(self.field_br.value.strip()) if self.field_br.value.strip().isnumeric() else 0} "
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated BorderRadius!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def update_container_size(self, e: ft.ControlEvent):
"""
The function updates the container size when the width or height values are changed.
:param e: The event object
"""
if e.control.value.strip().isnumeric():
# if the value of the text field in focus is numeric...
self.container_obj.current.height = int(
self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else 160
self.container_obj.current.width = int(
self.field_width.value.strip()) if self.field_width.value.strip().isnumeric() else 160
self.container_obj.current.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Container Size!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the border radius of the container to the clipboard.
:param e: The event object
"""
e.page.set_clipboard(f"{self.container_obj.current.border_radius}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.container_obj.current.border_radius}"), open=True))
def build(self):
all_fields = ft.Row(
controls=[
self.field_tl, self.field_tr, self.field_bl, self.field_br
],
alignment=ft.MainAxisAlignment.CENTER,
)
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Size:", weight=ft.FontWeight.BOLD, size=21),
ft.Row(
[self.field_width, self.field_height],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Divider(height=2, thickness=2),
ft.Text("BorderRadius Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.Container(
content=ft.Text(
"0, 0, 0, 0",
ref=self.container_text,
weight=ft.FontWeight.BOLD,
size=18,
color="black"),
ref=self.container_obj,
bgcolor=ft.colors.RED_ACCENT_700,
padding=ft.Padding(15, 0, 15, 0),
width=160,
height=160,
alignment=ft.Alignment(0, 0),
border_radius=ft.BorderRadius(0, 0, 0, 0),
)
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container#border_radius"
),
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentBorderRadius())
ft.app(main)
================================================
FILE: Flet-Utils/utils/border_utils.py
================================================
import flet as ft
# todo: add respective colors to each
# the content of the border tab
class TabContentBorder(ft.UserControl):
# border = border.only(BorderSide(2, "blue"), BorderSide(2, "blue"), BorderSide(2, "blue"), BorderSide(2, "blue"),)
def __init__(self):
super().__init__()
self.container_obj = ft.Ref[ft.Container]()
# text field for left property of the Border object
self.field_left = ft.TextField(
label="left",
value="",
width=120,
height=50,
content_padding=9,
on_change=self.update_border,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for right property of the Border object
self.field_right = ft.TextField(
label="right",
value="",
width=120,
height=50,
content_padding=9,
on_change=self.update_border,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for top property of the Border object
self.field_top = ft.TextField(
label="top",
value="",
width=120,
height=50,
content_padding=9,
on_change=self.update_border,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for bottom property of the Border object
self.field_bottom = ft.TextField(
label="bottom",
value="",
width=120,
height=50,
content_padding=9,
on_change=self.update_border,
keyboard_type=ft.KeyboardType.NUMBER
)
# text field for the width property of the Container object
self.field_width = ft.TextField(
label="Width",
hint_text=f"default=160",
value="160",
width=120,
height=50,
content_padding=9,
on_submit=self.update_container_size
)
# text field for the height property of the Container object
self.field_height = ft.TextField(
label="Height",
hint_text=f"default=160",
value="160",
width=120,
height=50,
content_padding=9,
on_submit=self.update_container_size
)
def update_border(self, e: ft.ControlEvent):
"""
It updates the border radius of the container object.
:param e: The event object
"""
left, right, top, bottom = int(
self.field_left.value.strip()) if self.field_left.value.strip().isnumeric() else 0, int(
self.field_right.value.strip()) if self.field_right.value.strip().isnumeric() else 0, int(
self.field_top.value.strip()) if self.field_top.value.strip().isnumeric() else 0, int(
self.field_bottom.value.strip()) if self.field_bottom.value.strip().isnumeric() else 0
if e.control.value.strip().isnumeric() or not e.control.value.strip():
# if the value of the text field in focus is numeric or if it is empty...
self.container_obj.current.border = ft.border.only(
ft.BorderSide(left, ft.colors.GREEN_700),
ft.BorderSide(top, ft.colors.GREEN_700),
ft.BorderSide(right, ft.colors.GREEN_700),
ft.BorderSide(bottom, ft.colors.GREEN_700)
)
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Border!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def update_container_size(self, e: ft.ControlEvent):
"""
The function updates the container size when the width or height values are changed.
:param e: The event object
"""
if e.control.value.strip().isnumeric():
# if the value of the text field in focus is numeric...
self.container_obj.current.height = int(
self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else 160
self.container_obj.current.width = int(
self.field_width.value.strip()) if self.field_width.value.strip().isnumeric() else 160
self.container_obj.current.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Container Size!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the border radius of the container to the clipboard.
:param e: The event object
"""
e.page.set_clipboard(f"{self.container_obj.current.border}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.container_obj.current.border}"), open=True))
def build(self):
all_fields = ft.Row(
controls=[
self.field_left, self.field_right, self.field_top, self.field_bottom
],
alignment=ft.MainAxisAlignment.CENTER,
)
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Size:", weight=ft.FontWeight.BOLD, size=21),
ft.Row(
[self.field_width, self.field_height],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Divider(height=2, thickness=2),
ft.Text("Container's Border:", weight=ft.FontWeight.BOLD, size=21),
all_fields
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.Container(
ref=self.container_obj,
bgcolor=ft.colors.RED_ACCENT_700,
padding=ft.Padding(15, 0, 15, 0),
width=160,
height=160,
alignment=ft.Alignment(0, 0),
border=ft.border.all(0, ft.colors.TRANSPARENT),
)
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container/#border"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentBorder())
ft.app(main)
================================================
FILE: Flet-Utils/utils/circle_avatar_utils.py
================================================
from flet import *
import flet as ft
# todo: "rotate" from icon_utils
# the content of the circle avatar tab
class TabContentCircleAvatar(ft.UserControl):
def __init__(self):
super().__init__()
self.avatar_background_image_url = None
self.avatar_foreground_image_url = None
self.avatar_content = None
self.avatar_radius = None
self.avatar_color = None
self.avatar_bgcolor = None
self.avatar_tooltip = None
self.avatar_min_radius = None
self.avatar_max_radius = None
self.avatar_width = None
self.avatar_height = None
self.avatar_opacity = None
self.avatar_rotate = None
self.avatar_scale = None
self.avatar_offset = None
self.avatar_obj = ft.Ref[ft.CircleAvatar]()
# text field for tooltip property of the CircleAvatar object
self.field_tooltip = ft.TextField(
label="tooltip",
value="",
helper_text="Optional[str]",
on_change=self.update_avatar,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for color property of the CircleAvatar object
self.field_color = ft.TextField(
label="color",
value="",
helper_text="Optional[str]",
on_submit=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for the bgcolor property of the CircleAvatar object
self.field_bgcolor = ft.TextField(
label="bgcolor",
value="",
helper_text="Optional[str]",
hint_text="colors.RED_50 or red50",
on_submit=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1,
# width=170,
)
# text field for background_image_url property of the CircleAvatar object
self.field_background_image_url = ft.TextField(
label="background_image_url",
value="",
helper_text="Optional[str]",
on_submit=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="",
expand=1,
text_style=ft.TextStyle(color=ft.colors.BLUE)
)
# text field for foreground_image_url property of the CircleAvatar object
self.field_foreground_image_url = ft.TextField(
label="foreground_image_url",
value="https://avatars.githubusercontent.com/u/5041459?s=88&v=4",
helper_text="Optional[str]",
on_submit=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="",
expand=1,
text_style=ft.TextStyle(color=ft.colors.BLUE)
)
# text field for the radius property of the CircleAvatar object
self.field_radius = ft.TextField(
label="radius",
helper_text="Union[int, float]",
# value="4",
on_change=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the max_radius property of the CircleAvatar object
self.field_max_radius = ft.TextField(
label="max_radius",
helper_text="Union[int, float]",
# value="4",
on_change=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the min_radius property of the CircleAvatar object
self.field_min_radius = ft.TextField(
label="min_radius",
helper_text="Union[int, float]",
# value="4",
on_change=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the content property of the CircleAvatar object
self.field_content = ft.TextField(
label="content",
value="",
helper_text="Optional[Control]",
hint_text="Text('Hello')",
on_submit=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
# width=80,
expand=1
)
# text field for the width property of the CircleAvatar object
self.field_width = ft.TextField(
label="width",
value="",
helper_text="Union[int, float]",
on_submit=self.update_avatar,
on_blur=self.update_avatar,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the height property of the CircleAvatar object
self.field_height = ft.TextField(
label="height",
value="",
helper_text="Union[int, float]",
on_submit=self.update_avatar,
on_blur=self.update_avatar,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the opacity property of the CircleAvatar object
self.field_opacity = ft.TextField(
label="opacity",
value="",
helper_text="Union[int, float]",
on_change=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
# width=170,
expand=1
)
# text field for the offset property of the CircleAvatar object
self.field_offset = ft.TextField(
label="offset",
value="",
helper_text="Optional[Offset, tuple]",
on_submit=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for the scale property of the CircleAvatar object
self.field_scale = ft.TextField(
label="scale",
value="",
helper_text="Union[int, float, Scale]",
on_submit=self.update_avatar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
# width=110,
expand=1
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_content, self.field_foreground_image_url, self.field_background_image_url],
),
ft.Row(
[self.field_bgcolor, self.field_color, self.field_radius, self.field_min_radius, self.field_max_radius, self.field_opacity]
),
ft.Row(
[self.field_height, self.field_width, self.field_offset, self.field_scale, self.field_tooltip, ],
),
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11
)
return ft.Column(
[
ft.Text("Circle Avatar Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields,
ft.Row(
[
ft.CircleAvatar(
ref=self.avatar_obj,
foreground_image_url="https://avatars.githubusercontent.com/u/5041459?s=88&v=4"
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/circleavatar"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=30
)
def update_avatar(self, e: ft.ControlEvent):
"""It updates the CircleAvatar object."""
self.avatar_opacity, self.avatar_radius, self.avatar_min_radius, self.avatar_max_radius = (
int(self.field_opacity.value.strip()) if self.field_opacity.value.strip().isnumeric() else None,
int(self.field_radius.value.strip()) if self.field_radius.value.strip().isnumeric() else None,
int(self.field_min_radius.value.strip()) if self.field_min_radius.value.strip().isnumeric() else None,
int(self.field_max_radius.value.strip()) if self.field_max_radius.value.strip().isnumeric() else None
)
self.avatar_foreground_image_url, self.avatar_background_image_url = self.field_foreground_image_url.value, self.field_background_image_url.value
self.avatar_color = self.field_color.value.strip() if self.field_color.value.strip() else None
self.avatar_bgcolor = self.field_bgcolor.value.strip() if self.field_bgcolor.value.strip() else None
self.avatar_tooltip = self.field_tooltip.value.strip() if self.field_tooltip.value.strip() else None
self.avatar_offset = self.field_offset.value.strip() if self.field_offset.value.strip() else "None"
self.avatar_scale = self.field_scale.value.strip() if self.field_scale.value.strip() else "None"
# content
try:
if self.field_content.value:
self.avatar_content = eval(self.field_content.value)
assert isinstance(self.avatar_content, ft.Control), "`content` must be a flet Control !"
else:
self.avatar_content = None
except Exception as x:
print(f"Content Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# radius
try:
if self.field_radius.value:
self.avatar_radius = eval(self.field_radius.value)
assert isinstance(self.avatar_radius,
(int, float)), "`radius` must be either of type float or int !"
else:
self.avatar_radius = None
except Exception as x:
print(f"Radius Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# min_radius
try:
if self.field_min_radius.value:
self.avatar_min_radius = eval(self.field_min_radius.value)
assert isinstance(self.avatar_min_radius,
(int, float)), "`min_radius` must be either of type float or int !"
else:
self.avatar_min_radius = None
except Exception as x:
print(f"Min Radius Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# max_radius
try:
if self.field_max_radius.value:
self.avatar_max_radius = eval(self.field_max_radius.value)
assert isinstance(self.avatar_max_radius,
(int, float)), "`max_radius` must be either of type float or int !"
else:
self.avatar_max_radius = None
except Exception as x:
print(f"Max Radius Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# width
try:
if self.field_width.value:
self.avatar_width = eval(self.field_width.value)
assert isinstance(self.avatar_width, (int, float)), "`width` must be either of type float or int !"
else:
self.avatar_width = None
except Exception as x:
print(f"Width Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# height
try:
if self.field_height.value:
self.avatar_height = eval(self.field_height.value)
assert isinstance(self.avatar_height, (int, float)), "`height` must be either of type float or int !"
else:
self.avatar_height = None
except Exception as x:
print(f"Height Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# color
try:
if self.avatar_color is not None:
self.avatar_color = eval(self.avatar_color) if '.' in self.avatar_color else self.avatar_color.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.avatar_color not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Color Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# bgcolor
try:
if self.avatar_bgcolor is not None:
self.avatar_bgcolor = eval(
self.avatar_bgcolor) if '.' in self.avatar_bgcolor else self.avatar_bgcolor.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.avatar_bgcolor not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Bgcolor Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# offset
try:
self.avatar_offset = eval(self.avatar_offset)
if not isinstance(self.avatar_offset, ft.Offset) \
and not isinstance(self.avatar_offset, tuple) \
and self.avatar_offset is not None:
raise ValueError("Wrong Value!")
elif isinstance(self.avatar_offset, tuple) and len(self.avatar_offset) == 2:
self.avatar_offset = eval(f"Offset({self.avatar_offset[0]}, {self.avatar_offset[1]})")
except Exception as x:
print(f"Offset Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `offset` must be an Offset object or in the form x,y. Please check your input."),
open=True))
return
# scale
try:
self.avatar_scale = eval(self.avatar_scale)
if not isinstance(self.avatar_scale, ft.Scale) \
and not isinstance(self.avatar_scale, (int, float)) \
and self.avatar_scale is not None:
raise ValueError("Wrong Value!")
except Exception as x:
print(f"Scale Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: `scale` must be an Scale object. Please check your input."), open=True))
return
# opacity
try:
if self.field_opacity.value:
self.avatar_opacity = eval(self.field_opacity.value)
assert isinstance(self.avatar_opacity, (int, float)), "`opacity` must be either of type float or int !"
else:
self.avatar_opacity = None
except Exception as x:
print(f"Opacity Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
self.avatar_obj.current.color = self.avatar_color
self.avatar_obj.current.bgcolor = self.avatar_bgcolor
self.avatar_obj.current.tooltip = self.avatar_tooltip
self.avatar_obj.current.foreground_image_url = self.avatar_foreground_image_url
self.avatar_obj.current.background_image_url = self.avatar_background_image_url
self.avatar_obj.current.content = self.avatar_content
self.avatar_obj.current.radius = self.avatar_radius
self.avatar_obj.current.min_radius = self.avatar_min_radius
self.avatar_obj.current.max_radius = self.avatar_max_radius
self.avatar_obj.current.width = self.avatar_width
self.avatar_obj.current.height = self.avatar_height
self.avatar_obj.current.opacity = self.avatar_opacity
self.avatar_obj.current.scale = self.avatar_scale
self.avatar_obj.current.offset = self.avatar_offset
# todo self.avatar_obj.current.rotate = self.avatar_rotate
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated CircleAvatar!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the tooltip object/instance to the clipboard."""
o = f", opacity={self.avatar_opacity}"
s = f", scale={self.avatar_scale}"
off = f", offset={self.avatar_offset}"
t = f", tooltip='{self.avatar_tooltip}'"
bg = f", bgcolor='{self.avatar_bgcolor}'"
c = f", color='{self.avatar_color}'"
w = f", width={self.avatar_width}"
h = f", height={self.avatar_height}"
r = f", radius={self.avatar_radius}"
minr = f", min_radius={self.avatar_min_radius}"
maxr = f", max_radius={self.avatar_max_radius}"
burl = f", background_image_url='{self.avatar_background_image_url}'"
furl = f", foreground_image_url='{self.avatar_foreground_image_url}'"
others = f"{maxr if self.avatar_max_radius is not None else ''}{minr if self.avatar_min_radius is not None else ''}{furl if self.avatar_foreground_image_url else ''}{burl if self.avatar_background_image_url else ''}{r if self.avatar_radius is not None else ''}{w if self.avatar_width is not None else ''}{h if self.avatar_height is not None else ''}{c if self.avatar_color is not None else ''}{bg if self.avatar_bgcolor is not None else ''}{t if self.avatar_tooltip else ''}{o if self.avatar_opacity is not None else ''}{s if self.avatar_scale is not None else ''}{off if self.avatar_offset is not None else ''}"
val = f"CircleAvatar(radius={self.avatar_radius}{others if others else ''})"
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}"), open=True))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
page.add(TabContentCircleAvatar())
ft.app(main)
================================================
FILE: Flet-Utils/utils/colors_utils.py
================================================
import flet as ft
from itertools import islice
# the content of the ColorV1 tab
class TabContentColors1(ft.UserControl):
# all this below was obtained from https://github.com/ndonkoHenri/Flet-Color-Browser
def __init__(self, expand=True):
"""
If the expand parameter is set to True, then the expand attribute of the object is set to True. Otherwise, the
height attribute of the object is set to the value of the height parameter.
:param expand: If True, the widget will expand to fill its parent, defaults to True (optional)
"""
super().__init__(expand=expand)
def build(self):
def batches(iterable, batch_size):
"""
It takes an iterable and a batch size, and returns an iterator that yields batches of the iterable
:param iterable: An iterable object (e.g. a list)
:param batch_size: The number of items to process in each batch
"""
iterator = iter(iterable)
while batch := list(islice(iterator, batch_size)):
yield batch
# fetch all icon constants from colors.py module and store them in a dict(colors_dict)
colors_dict = dict()
list_started = False
for key, value in vars(ft.colors).items():
if key == "PRIMARY":
# 'PRIMARY' is the first color-variable (our starting point)
list_started = True
if list_started:
# when this list_started is True, we create new key-value pair in our dictionary
colors_dict[key] = value
# Creating a text field
search_txt = ft.TextField(
expand=1,
hint_text="Enter keyword and press search button",
autofocus=True,
on_submit=lambda e: display_colors(e.control.value),
tooltip="search field",
label="Color Search Field"
)
def search_click(_):
"""
Called when the search button is pressed, it displays the colors.
"""
display_colors(search_txt.value)
# Creating a row with a search text field and a search button.
search_query = ft.Row(
[search_txt, ft.FloatingActionButton(icon=ft.icons.SEARCH, on_click=search_click, tooltip="search")]
)
# Creating a grid view with 10 columns and 150 pixels as the maximum extent of each column.
search_results = ft.GridView(
expand=1, runs_count=10, max_extent=150, spacing=5, run_spacing=5, child_aspect_ratio=1,
)
status_bar = ft.Text()
def copy_to_clipboard(e):
"""
When the user clicks on a color, copy the color key to the clipboard
:param e: The event object
"""
color_key = e.control.data
print("Copied to clipboard:", color_key)
self.page.set_clipboard(e.control.data)
self.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {color_key}"), open=True))
def search_colors(search_term: str):
"""
It takes a search term as an argument, and then loops through the colors_dict dictionary,
checking if the search term is in the color name or the color value. If it is, it yields the color key
:param search_term: The search term that the user entered
:return color_key: str
"""
for color_key, color_value in colors_dict.items():
# the color_key has underscores while the color_value doesn't. We take this into consideration
if search_term and (search_term in color_value or search_term in color_key.lower()):
yield color_key
def display_colors(search_term: str):
"""
It takes a search term, disables the search box, cleans the search results(grid view),
and then loops through the search results in batches of 40, adding each color to the search results
:param search_term: str
"""
# disable the text field and the search button, and clean search results
search_query.disabled = True
self.update()
search_results.clean()
# Adding the colors to the grid view in batches of 40.
for batch in batches(search_colors(search_term.lower()), 40):
for color_key in batch:
flet_color_key = f"colors.{color_key}"
search_results.controls.append(
ft.TextButton(
content=ft.Container(
content=ft.Column(
[
ft.Icon(name=ft.icons.RECTANGLE, size=40, color=colors_dict[color_key], ),
ft.Text(
value=f"{colors_dict[color_key]}", size=14, width=100,
no_wrap=True, text_align=ft.TextAlign.CENTER, color=colors_dict[color_key],
),
],
spacing=5,
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
),
alignment=ft.alignment.center,
),
tooltip=f"{flet_color_key}\nClick to copy to a clipboard",
on_click=copy_to_clipboard,
data=flet_color_key,
)
)
status_bar.value = f"Colors found: {len(search_results.controls)}"
self.update()
# It checks if the search results are empty, and if they are, it shows a snack bar some message
if len(search_results.controls) == 0:
# if no color was found containing the user's search term
self.page.show_snack_bar(ft.SnackBar(ft.Text("No colors found"), open=True))
search_query.disabled = False
self.update()
return ft.Column(
[
search_query,
search_results,
status_bar,
],
expand=True,
)
# the tiles used in the ColorV2 tab
class Tile(ft.Container):
# all this below was obtained from https://github.com/ndonkoHenri/Flet-Color-Browser
def __init__(self, tile_text, color, page):
super().__init__()
self.text = ft.Text(tile_text, text_align=ft.TextAlign.CENTER, weight=ft.FontWeight.BOLD, italic=True, )
self.color_text = f"colors.{tile_text}"
self.bgcolor = color
self.expand = True
self.height = 40
self.content = self.text
self.page = page
self.tooltip = "Click to copy to Clipboard"
def _build(self):
def click_event(_):
"""
It copies the color's text to the clipboard.
:param _: The event that triggered the function
"""
print("Copied to clipboard:", self.color_text)
self.page.set_clipboard(self.color_text)
self.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.color_text}!"), open=True))
self.on_click = click_event
return self
# the content of the ColorV2 tab
class TabContentColors2(ft.UserControl):
# all this below was obtained from https://github.com/ndonkoHenri/Flet-Color-Browser
def __init__(self, page):
"""
The function creates a reference to the Tabs object that will be created later, and creates a list of colors
that will be used to create the tabs.
:param page: This is a reference to the page object that is passed to the constructor of the class
"""
super().__init__(expand=True)
# A reference to the page object that is passed to the constructor of the class.
self.page = page
# Creating a reference to the Tabs object that will be created later.
self.displayed_tabs = ft.Ref[ft.Tabs]()
# A list of colors that will be used to create the tabs.
self.original_tab_names = ['RED', "BLACK", "WHITE", 'PINK', 'PURPLE', 'DEEP_PURPLE', 'INDIGO', 'BLUE',
'LIGHT_BLUE', 'CYAN', 'TEAL', 'GREEN', 'LIGHT_GREEN', 'LIME', 'YELLOW',
'AMBER', "ORANGE", 'DEEP_ORANGE', 'BROWN', "GREY", 'BLUE_GREY']
def build(self):
# Getting all the colors from the colors' module.
list_started = False
all_flet_colors = list()
for key in vars(ft.colors).keys():
if key == "PRIMARY":
list_started = True
if list_started:
all_flet_colors.append(key)
def create_tabs(tab_names: list) -> list:
"""
It takes a list of strings where each string represents the name of a tab to be shown, and returns a list
of tabs, each tab containing a list of tiles(containers) having a specific background color associated
with the text in it.
:param tab_names: list of strings that will be used to create the tabs
:type tab_names: list
:return: A list of tabs
"""
created_tabs = []
found = []
# iterate over the tab_names(list containing the tabs to be shown)
for tab_name in tab_names:
tab_content = ft.ListView()
for color in all_flet_colors:
tile_bgcolor = color.lower().replace("_", "")
tile_content = Tile(color, tile_bgcolor, self.page)
# Checking if the color starts with the tab_name and if the tab_name is in the color.
if (tab_name in color) and color.startswith(tab_name):
tab_content.controls.append(tile_content)
found.append(color)
# Add a tab with the name of the color and the content of the tab is a list of tiles.
# Also remove underscores from the tab's name.
created_tabs.append(ft.Tab(tab_name.replace("_", " "), content=tab_content, ))
# Creating a tab called "OTHERS" and adding all the colors that were not added to any other tab to it.
others = [i for i in all_flet_colors if i not in found]
others_content = ft.ListView(controls=[Tile(x, x.lower().replace("_", ""), self.page) for x in others])
created_tabs.append(ft.Tab("OTHERS", content=others_content))
return created_tabs
# self.displayed_tabs.current.tabs = create_tabs(self.original_tab_names)
def filter_tabs(_):
"""
If the text in the search field is "ALL", show all tabs. If the search field is not empty, show only the
tabs that contain the search term.
"""
filtered_tab_names = []
if search_field.value and search_field.value.lower().strip() == "all":
filtered_tab_names = self.original_tab_names
else:
for tab_name in self.original_tab_names:
if search_field.value and search_field.value.lower().strip() in tab_name.lower().replace("_", " "):
filtered_tab_names.append(tab_name)
if filtered_tab_names:
# Removing all the tabs from the Tabs object.
self.displayed_tabs.current.clean()
self.page.update()
# Updating the tabs of the Tabs object.
self.displayed_tabs.current.tabs = create_tabs(filtered_tab_names)
self.displayed_tabs.current.update()
return
# creating a field which will t=help the user search for specific tabs
search_field = ft.TextField(
label="Search Tabs...",
prefix_icon=ft.icons.SEARCH,
on_submit=filter_tabs,
border_radius=50,
suffix=ft.IconButton(
icon=ft.icons.CHECK,
bgcolor=ft.colors.INVERSE_PRIMARY,
icon_color=ft.colors.ERROR,
on_click=filter_tabs
),
helper_text="Tip: Enter 'ALL' to show all the tabs", height=70, width=450,
keyboard_type=ft.KeyboardType.TEXT,
capitalization=ft.TextCapitalization.CHARACTERS,
)
return ft.Column(
controls=[
search_field,
ft.Tabs(ref=self.displayed_tabs, expand=True,
tabs=create_tabs(self.original_tab_names))
]
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentColors1())
ft.app(main)
================================================
FILE: Flet-Utils/utils/divider_utils.py
================================================
from flet import *
import flet as ft
# the content of the divider tab
class TabContentDivider(ft.UserControl):
def __init__(self):
super().__init__()
self.divider_color = "green"
self.divider_tooltip = None
self.divider_thickness = 4
self.divider_height = None
self.divider_opacity = None
self.top_con_obj = ft.Ref[ft.Container]()
self.divider_obj = ft.Ref[ft.Divider]()
self.bottom_con_obj = ft.Ref[ft.Container]()
# text field for tooltip property of the Divider object
self.field_tooltip = ft.TextField(
label="tooltip",
value="",
helper_text="Optional[str]",
on_change=self.update_divider,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for color property of the Divider object
self.field_color = ft.TextField(
label="color",
value="green",
helper_text="Optional[str]",
on_submit=self.update_divider,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for the thickness property of the Divider object
self.field_thickness = ft.TextField(
label="thickness",
helper_text="Union[int, float]",
value="4",
on_change=self.update_divider,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the height property of the Divider object
self.field_height = ft.TextField(
label="height",
value="",
helper_text="Union[int, float]",
on_change=self.update_divider,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the opacity property of the Divider object
self.field_opacity = ft.TextField(
label="opacity",
value="",
helper_text="Union[int, float]",
on_change=self.update_divider,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# checkbox
self.containers_checkbox = ft.Checkbox(
label="Don't show top and bottom containers.",
on_change=self.update_divider,
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_thickness, self.field_height, self.field_opacity]
),
ft.Row(
[self.field_color, self.field_tooltip, ],
),
self.containers_checkbox
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11,
)
w, h = 300, 50
return ft.Column(
[
ft.Row([ft.Text("Divider Builder:", weight=ft.FontWeight.BOLD, size=21)]),
all_fields,
ft.Column(
[
ft.Container(
ref=self.top_con_obj,
bgcolor=ft.colors.AMBER,
alignment=ft.alignment.center,
width=w,
height=h,
),
ft.Divider(
ref=self.divider_obj,
thickness=4,
color="green"
),
ft.Container(
ref=self.bottom_con_obj,
bgcolor=ft.colors.AMBER,
alignment=ft.alignment.center,
width=w,
height=h,
),
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
spacing=0,
width=w
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/divider/"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=30,
expand=True
)
def update_divider(self, e: ft.ControlEvent):
"""It updates the Divider object."""
self.divider_opacity, self.divider_thickness, self.divider_height = (
int(self.field_opacity.value.strip()) if self.field_opacity.value.strip().isnumeric() else None,
int(self.field_thickness.value.strip()) if self.field_thickness.value.strip().isnumeric() else None,
int(self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else None,
)
self.divider_color = self.field_color.value.strip() if self.field_color.value.strip() else None
self.divider_tooltip = self.field_tooltip.value.strip() if self.field_tooltip.value.strip() else None
# thickness
try:
if self.field_thickness.value:
self.divider_thickness = eval(self.field_thickness.value)
assert isinstance(self.divider_thickness,
(int, float)), "`thickness` must be either of type float or int !"
else:
self.divider_thickness = None
except Exception as x:
print(f"Thickness Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# height
try:
if self.field_height.value:
self.divider_height = eval(self.field_height.value)
assert isinstance(self.divider_height, (int, float)), "`height` must be either of type float or int !"
else:
self.divider_height = None
except Exception as x:
print(f"Height Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# color
try:
if self.divider_color is not None:
self.divider_color = eval(self.divider_color) if '.' in self.divider_color else self.divider_color.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.divider_color not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Color Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# opacity
try:
if self.field_opacity.value:
self.divider_opacity = eval(self.field_opacity.value)
assert isinstance(self.divider_opacity, (int, float)), "`opacity` must be either of type float or int !"
else:
self.divider_opacity = None
except Exception as x:
print(f"Opacity Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
self.divider_obj.current.color = self.divider_color
self.divider_obj.current.tooltip = self.divider_tooltip
self.divider_obj.current.thickness = self.divider_thickness
self.divider_obj.current.height = self.divider_height
self.divider_obj.current.opacity = self.divider_opacity
self.top_con_obj.current.visible = not self.containers_checkbox.value
self.bottom_con_obj.current.visible = not self.containers_checkbox.value
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Divider!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the tooltip object/instance to the clipboard."""
o = f", opacity={self.divider_opacity}"
t = f", tooltip='{self.divider_tooltip}'"
c = f", color='{self.divider_color}'"
th = f", thickness={self.divider_thickness}"
others = f"{th if self.divider_thickness is not None else ''}{c if self.divider_color is not None else ''}{t if self.divider_tooltip else ''}{o if self.divider_opacity is not None else ''}"
val = f"Divider(height={self.divider_height}{others if others else ''})"
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}"), open=True))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
page.window_always_on_top = True
page.add(TabContentDivider())
ft.app(main)
================================================
FILE: Flet-Utils/utils/gradient_utils.py
================================================
import math
from flet import *
import flet as ft
# the content of the LinearGradient tab
class TabContentLinearGradient(ft.UserControl):
def __init__(self):
super().__init__()
self.container_obj = ft.Ref[ft.Container]()
# field for begin parameter of the LinearGradient object
self.field_begin = ft.TextField(
label="begin",
value='-1, 0.5',
width=200,
on_submit=self.update_gradient,
hint_text="ex: -1, 0.5",
helper_text="Alignment object or x,y",
keyboard_type=ft.KeyboardType.TEXT,
)
# field for end parameter of the LinearGradient object
self.field_end = ft.TextField(
label="end",
value='Alignment(0, 1)',
on_submit=self.update_gradient,
width=200,
hint_text="ex: Alignment(0, 1)",
helper_text="Alignment object or x,y",
keyboard_type=ft.KeyboardType.TEXT,
)
# radio buttons for the tile_mode parameter
self.tile_mode_radio_group = ft.RadioGroup(
ft.Row(
[
ft.Radio(value="clamp", label="clamp"),
ft.Radio(value="decal", label="decal"),
ft.Radio(value="mirror", label="mirror"),
ft.Radio(value="repeated", label="repeated"),
],
alignment=ft.MainAxisAlignment.CENTER,
),
value="clamp",
on_change=self.update_gradient,
)
# text field for colors property of the LinearGradient object
self.field_colors = ft.TextField(
label="colors",
value="colors.RED_ACCENT\nYellow",
width=190,
on_submit=self.update_gradient,
keyboard_type=ft.KeyboardType.TEXT,
multiline=True,
shift_enter=True,
min_lines=2,
hint_text="colors.RED_50\npurple"
)
# text field for stops property of the LinearGradient object
self.field_stops = ft.TextField(
label="stops",
value="0.2\n0.7",
width=90,
on_submit=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
shift_enter=True,
min_lines=2,
hint_text="0.2\n0.7"
)
# text field for rotation property of the LinearGradient object
self.field_rotation = ft.TextField(
label="rotation",
value="0",
width=110,
on_change=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
suffix_text="°",
helper_text="In degrees",
hint_text="ex: 180",
)
# text field for the width property of the Container object
self.field_width = ft.TextField(
label="Width",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size,
)
# text field for the height property of the Container object
self.field_height = ft.TextField(
label="Height",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size,
)
def update_gradient(self, e: ft.ControlEvent):
"""
It updates the gradient of the container object.
:param e: The event object
"""
begin = self.field_begin.value.strip() if self.field_begin.value.strip() else "alignment.center_left"
end = self.field_end.value.strip() if self.field_end.value.strip() else "alignment.center_right"
clrs = self.field_colors.value.strip().split("\n") if self.field_colors.value.strip() else []
stops = self.field_stops.value.strip().split("\n") if self.field_stops.value.strip() else []
rotation = self.field_rotation.value.strip() if self.field_rotation.value.strip() else None
# tile_mode - How this gradient should tile the plane beyond in the region before begin and after end.
tile_mode = self.tile_mode_radio_group.value
# end - An instance of Alignment class. The offset at which stop 1.0 of the gradient is placed.
try:
end = eval(end)
if not isinstance(end, ft.Alignment) and not isinstance(end, tuple):
raise ValueError("Wrong Value!")
elif isinstance(end, tuple) and len(end) == 2:
end = eval(f"Alignment({end[0]}, {end[1]})")
except Exception as x:
print(f"End Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `end` must be an Alignment object or in the form x,y. Please check your input."),
open=True))
return
# begin - An instance of Alignment class. The offset at which stop 0.0 of the gradient is placed.
try:
begin = eval(begin)
if not isinstance(begin, ft.Alignment) and not isinstance(begin, tuple):
raise ValueError("Wrong Value!")
elif isinstance(begin, tuple) and len(begin) == 2:
begin = eval(f"Alignment({begin[0]}, {begin[1]})")
except Exception as x:
print(f"Begin Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `begin` must be an Alignment object or in the form x,y. Please check your input."),
open=True))
return
# rotation: rotation - rotation for the gradient, in radians, around the center-point of its bounding box.
try:
if rotation is not None:
rotation = round((math.pi * float(rotation)) / 180, 3) # convert to rads
except Exception as x:
print(f"Rotation Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: For simplicity, `rotation` must be in degrees! It will be "
"converted to radians internally."),
open=True))
return
# colors: must have at least two colors in it (otherwise, it's not a gradient!).
if len(clrs) < 2:
e.page.show_snack_bar(
ft.SnackBar(ft.Text(
"ERROR: `colors` must have at least two colors. This could be gotten from the Colors V1/V2 "
"Tab here."),
open=True))
return
elif len(clrs) >= 2:
try:
clrs = [eval(c) if '.' in c else c.lower() for c in clrs]
# Getting all the colors from the flet.colors module.
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
for i in clrs:
if i not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Colors Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# stops: must have the same length as colors.
if stops and len(stops) >= 2:
try:
stops = [eval(s) for s in stops]
is_not_range = True if list(filter(lambda a: not 0.0 <= a <= 1.0, stops)) else False
if is_not_range: raise ValueError("Some values are out of the specified range(0.0 - 1.0)!")
except Exception as x:
print(f"Stops Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: There seems to be an error with your stops. Please check your entries and "
"make sure they are between 0.0 and 1.0!"),
open=True))
return
elif stops and len(stops) < 2:
e.page.show_snack_bar(
ft.SnackBar(ft.Text(
"ERROR: `stops` is either empty or has a number of values(between 0.0 and 1.0) equal to those "
"in `colors`. This could be gotten from the Colors V1/V2 Tab here."),
open=True))
return
# compare colors and stops
if stops and len(clrs) != len(stops):
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: The number of values in `colors` must be equal to that in `stops`!"),
open=True))
return
# make the gradient visible
try:
self.container_obj.current.gradient = ft.LinearGradient(
colors=clrs,
tile_mode=tile_mode,
rotation=rotation,
stops=stops,
begin=begin,
end=end
)
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Gradient!"), open=True))
except Exception as x:
print(f"Display Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text("ERROR: Display error!"), open=True))
return
def update_container_size(self, e: ft.ControlEvent):
"""
The function updates the container size when the width or height values are changed.
:param e: The event object
"""
if e.control.value.strip().isnumeric():
# if the value of the text field in focus is numeric...
self.container_obj.current.height = int(
self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else 160
self.container_obj.current.width = int(
self.field_width.value.strip()) if self.field_width.value.strip().isnumeric() else 160
self.container_obj.current.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Container Size!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the gradient of the container to the clipboard.
:param e: The event object
"""
e.page.set_clipboard(f"{self.container_obj.current.gradient}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.container_obj.current.gradient}"), open=True))
def build(self):
begin_stop_fields = ft.Row(
controls=[
self.field_begin, self.field_end
],
alignment=ft.MainAxisAlignment.CENTER,
)
all_textfields = ft.Row(
controls=[
self.field_colors, self.field_stops, self.field_rotation
],
alignment=ft.MainAxisAlignment.CENTER
)
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Size:", weight=ft.FontWeight.BOLD, size=21),
ft.Row(
[self.field_width, self.field_height],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Divider(height=2, thickness=2),
ft.Text("Linear Gradient Builder:", weight=ft.FontWeight.BOLD, size=21),
all_textfields,
self.tile_mode_radio_group,
begin_stop_fields,
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Row(
[
ft.Container(
ref=self.container_obj,
bgcolor=ft.colors.RED_ACCENT_700,
padding=ft.Padding(15, 0, 15, 0),
width=160,
height=160,
alignment=ft.Alignment(0, 0),
gradient=ft.LinearGradient(
colors=['redaccent', 'yellow'],
tile_mode=ft.GradientTileMode.MIRROR,
stops=[0.2, 0.7],
begin=ft.Alignment(x=-1, y=0.5),
rotation=0,
end=ft.Alignment(x=0, y=1), type='linear'
)
),
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard,
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container#lineargradient"
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
)
# the content of the RadialGradient tab
class TabContentRadialGradient(ft.UserControl):
def __init__(self):
super().__init__()
self.container_obj = ft.Ref[ft.Container]()
# field for center parameter of the RadialGradient object
self.field_center = ft.TextField(
label="center",
value='0,0',
width=200,
on_submit=self.update_gradient,
hint_text="ex: -1, 0.5",
helper_text="Alignment object or x,y",
keyboard_type=ft.KeyboardType.TEXT,
)
# text field for focal property of the RadialGradient object
self.field_focal = ft.TextField(
label="focal",
value='0,0',
width=200,
on_submit=self.update_gradient,
hint_text="ex: -1, 0.5",
helper_text="Alignment object or x,y",
keyboard_type=ft.KeyboardType.TEXT,
)
# text field for rotation property of the LinearGradient object
self.field_rotation = ft.TextField(
label="rotation",
value="0",
width=110,
on_change=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
suffix_text="°",
helper_text="In degrees",
hint_text="ex: 180",
)
# radio buttons for the tile_mode parameter
self.tile_mode_radio_group = ft.RadioGroup(
ft.Row(
[
ft.Radio(value="clamp", label="clamp"),
ft.Radio(value="decal", label="decal"),
ft.Radio(value="mirror", label="mirror"),
ft.Radio(value="repeated", label="repeated"),
],
alignment=ft.MainAxisAlignment.CENTER
),
value="clamp",
on_change=self.update_gradient,
)
# text field for colors property of the RadialGradient object
self.field_colors = ft.TextField(
label="colors",
value="colors.RED_ACCENT\nYellow",
width=190,
on_submit=self.update_gradient,
keyboard_type=ft.KeyboardType.TEXT,
multiline=True,
shift_enter=True,
min_lines=2,
hint_text="colors.RED_50\npurple"
)
# text field for stops property of the RadialGradient object
self.field_stops = ft.TextField(
label="stops",
value="0.2\n0.7",
width=90,
on_submit=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
shift_enter=True,
min_lines=2,
hint_text="0.2\n0.7"
)
# text field for radius property of the RadialGradient object
self.field_radius = ft.TextField(
label="radius",
value="",
width=90,
on_change=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
hint_text="ex: 0.5",
)
# text field for focal radius property of the RadialGradient object
self.field_focal_radius = ft.TextField(
label="focal radius",
value="0.3",
width=90,
on_change=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
hint_text="ex: 0.5",
)
# text field for the width property of the Container object
self.field_width = ft.TextField(
label="Width",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size,
)
# text field for the height property of the Container object
self.field_height = ft.TextField(
label="Height",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size,
)
def update_gradient(self, e: ft.ControlEvent):
"""
It updates the gradient of the container object.
:param e: The event object
"""
center = self.field_center.value.strip() if self.field_center.value.strip() else "0,0"
clrs = self.field_colors.value.strip().split("\n") if self.field_colors.value.strip() else []
stops = self.field_stops.value.strip().split("\n") if self.field_stops.value.strip() else []
radius = self.field_radius.value.strip() if self.field_radius.value.strip() else "0.5"
focal = self.field_focal.value.strip() if self.field_focal.value.strip() else "None"
focal_radius = self.field_focal_radius.value.strip() if self.field_focal_radius.value.strip() else "0.0"
rotation = self.field_rotation.value.strip() if self.field_rotation.value.strip() else None
tile_mode = self.tile_mode_radio_group.value
# center - An instance of Alignment class.
try:
center = eval(center)
if not isinstance(center, ft.Alignment) and not isinstance(center, tuple):
raise ValueError("Wrong Value!")
elif isinstance(center, tuple) and len(center) == 2:
center = eval(f"Alignment({center[0]}, {center[1]})")
except Exception as x:
print(f"center Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `center` must be an Alignment object or in the form x,y. Please check your input."),
open=True))
return
# radius: The radius of the gradient, as a fraction of the shortest side of the paint box.
try:
radius = float(radius)
except Exception as x:
print(f"Radius Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: There seems to be an error. Please check your entry for `radius`!"),
open=True))
return
# colors: must have at least two colors in it (otherwise, it's not a gradient!).
if len(clrs) < 2:
e.page.show_snack_bar(
ft.SnackBar(ft.Text(
"ERROR: `colors` must have at least two colors. This could be gotten from the Colors V1/V2 "
"Tab here."),
open=True))
return
elif len(clrs) >= 2:
try:
clrs = [eval(c) if '.' in c else c.lower() for c in clrs]
# Getting all the colors from the flet.colors module.
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
for i in clrs:
if i not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Colors Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# stops: must have the same length as colors.
if stops and len(stops) >= 2:
try:
stops = [eval(s) for s in stops]
is_not_range = True if list(filter(lambda a: not 0.0 <= a <= 1.0, stops)) else False
print(f"{stops=}")
if is_not_range: raise ValueError("Some values are out of the specified range(0.0 - 1.0)!")
except Exception as x:
print(f"Stops Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: There seems to be an error with your stops. Please check your entries and "
"make sure they are between 0.0 and 1.0!"),
open=True))
return
elif stops and len(stops) < 2:
e.page.show_snack_bar(
ft.SnackBar(ft.Text(
"ERROR: `stops` is either empty or has a number of values(between 0.0 and 1.0) equal to those "
"in `colors`. This could be gotten from the Colors V1/V2 "
"Tab here."),
open=True))
return
# focal - The focal point of the gradient.
try:
focal = eval(focal)
if not isinstance(focal, ft.Alignment) and not isinstance(focal, tuple):
raise ValueError("Wrong Value!")
elif isinstance(focal, tuple) and len(focal) == 2:
focal = eval(f"Alignment({focal[0]}, {focal[1]})")
except Exception as x:
print(f"Focal Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `focal` must be an Alignment object or in the form x,y. Please check your input."),
open=True))
return
# focal_radius - The radius of the focal point of gradient.
try:
focal_radius = float(focal_radius)
except Exception as x:
print(f"Focal Radius Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: There seems to be an error. Please check your entry for `focal_radius`!"),
open=True))
return
# rotation: rotation - rotation for the gradient, in radians, around the center-point of its bounding box.
try:
if rotation is not None:
rotation = round((math.pi * float(rotation)) / 180, 3) # convert to rads
except Exception as x:
print(f"Rotation Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: For simplicity, `rotation` must be in degrees! It will be "
"converted to radians internally."),
open=True))
return
# compare colors and stops
if stops and len(clrs) != len(stops):
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: The number of values in `colors` must be equal to that in `stops`!"),
open=True))
return
# make the gradient visible
try:
self.container_obj.current.gradient = ft.RadialGradient(
colors=clrs,
tile_mode=tile_mode,
radius=radius,
stops=stops,
center=center,
focal=focal,
focal_radius=focal_radius,
rotation=rotation
)
print(self.container_obj.current.gradient)
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Gradient!"), open=True))
except Exception as x:
print(f"Display Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text("ERROR: Display error!"), open=True))
return
def update_container_size(self, e: ft.ControlEvent):
"""
The function updates the container size when the width or height values are changed.
:param e: The event object
"""
if e.control.value.strip().isnumeric():
# if the value of the text field in focus is numeric...
self.container_obj.current.height = int(
self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else 160
self.container_obj.current.width = int(
self.field_width.value.strip()) if self.field_width.value.strip().isnumeric() else 160
self.container_obj.current.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Container Size!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the gradient of the container to the clipboard.
:param e: The event object
"""
e.page.set_clipboard(f"{self.container_obj.current.gradient}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.container_obj.current.gradient}"), open=True))
def build(self):
# a row containing all the fields created above
center_focal_rotation_fields = ft.Row(
controls=[
self.field_center, self.field_focal, self.field_rotation
],
alignment=ft.MainAxisAlignment.CENTER,
)
# a row containing all the fields created above
all_textfields = ft.Row(
controls=[
self.field_colors, self.field_stops, self.field_radius, self.field_focal_radius
],
alignment=ft.MainAxisAlignment.CENTER,
)
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Size:", weight=ft.FontWeight.BOLD, size=21),
ft.Row(
[self.field_width, self.field_height],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Divider(height=2, thickness=2),
ft.Text("Radial Gradient Builder:", weight=ft.FontWeight.BOLD, size=21),
all_textfields,
self.tile_mode_radio_group,
center_focal_rotation_fields
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.Container(
ref=self.container_obj,
bgcolor=ft.colors.RED_ACCENT_700,
padding=ft.padding.Padding(15, 0, 15, 0),
width=160,
height=160,
gradient=ft.RadialGradient(colors=['redaccent', 'yellow'], stops=[0.2, 0.7],
focal_radius=0.3)
)
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container#radialgradient"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN
)
# the content of the SweepGradient tab
class TabContentSweepGradient(ft.UserControl):
def __init__(self):
super().__init__()
self.container_obj = ft.Ref[ft.Container]()
# field for center parameter of the SweepGradient object
self.field_center = ft.TextField(
label="center",
value='0,0',
width=200,
on_submit=self.update_gradient,
hint_text="ex: -1, 0.5",
helper_text="Alignment object or x,y",
keyboard_type=ft.KeyboardType.TEXT,
)
# text field for start_angle property of the SweepGradient object
self.field_start_angle = ft.TextField(
label="start angle",
value="0",
width=110,
on_change=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
suffix_text="°",
helper_text="In degrees",
hint_text="ex: 180",
)
# text field for end_angle property of the SweepGradient object
self.field_end_angle = ft.TextField(
label="end angle",
value="",
width=110,
on_change=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
suffix_text="°",
helper_text="In degrees",
hint_text="ex: 320",
)
# text field for colors property of the SweepGradient object
self.field_colors = ft.TextField(
label="colors",
value="colors.RED_ACCENT\nYellow",
width=190,
on_submit=self.update_gradient,
keyboard_type=ft.KeyboardType.TEXT,
multiline=True,
shift_enter=True,
min_lines=2,
hint_text="colors.RED_50\npurple"
)
# text field for stops property of the SweepGradient object
self.field_stops = ft.TextField(
label="stops",
value="0.2\n0.7",
width=90,
on_submit=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
shift_enter=True,
min_lines=2,
hint_text="0.2\n0.7"
)
# text field for rotation property of the SweepGradient object
self.field_rotation = ft.TextField(
label="rotation",
value="",
width=110,
on_change=self.update_gradient,
keyboard_type=ft.KeyboardType.NUMBER,
suffix_text="°",
helper_text="In degrees",
hint_text="ex: 180",
)
# radio buttons for the tile_mode parameter
self.tile_mode_radio_group = ft.RadioGroup(
ft.Row(
[
ft.Radio(value="clamp", label="clamp"),
ft.Radio(value="decal", label="decal"),
ft.Radio(value="mirror", label="mirror"),
ft.Radio(value="repeated", label="repeated"),
],
alignment=ft.MainAxisAlignment.CENTER
),
value="clamp",
on_change=self.update_gradient,
)
# text field for the width property of the Container object
self.field_width = ft.TextField(
label="Width",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size,
)
# text field for the height property of the Container object
self.field_height = ft.TextField(
label="Height",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_container_size,
)
def update_gradient(self, e: ft.ControlEvent):
"""
It updates the gradient of the container object.
:param e: The event object
"""
center = self.field_center.value.strip() if self.field_center.value.strip() else "0,0"
clrs = self.field_colors.value.strip().split("\n") if self.field_colors.value.strip() else []
stops = self.field_stops.value.strip().split("\n") if self.field_stops.value.strip() else []
start_angle = self.field_start_angle.value.strip() if self.field_start_angle.value.strip() else "0"
end_angle = self.field_end_angle.value.strip() if self.field_end_angle.value.strip() else "180"
rotation = self.field_rotation.value.strip() if self.field_rotation.value.strip() else None
tile_mode = self.tile_mode_radio_group.value
# center - An instance of Alignment class.
try:
center = eval(center)
if not isinstance(center, ft.Alignment) and not isinstance(center, tuple):
raise ValueError("Wrong Value!")
elif isinstance(center, tuple) and len(center) == 2:
center = eval(f"Alignment({center[0]}, {center[1]})")
except Exception as x:
print(f"center Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `center` must be an Alignment object or in the form x,y. Please check your input."),
open=True))
return
# colors: must have at least two colors in it (otherwise, it's not a gradient!).
if len(clrs) < 2:
e.page.show_snack_bar(
ft.SnackBar(ft.Text(
"ERROR: `colors` must have at least two colors. This could be gotten from the Colors V1/V2 "
"Tab here."),
open=True))
return
elif len(clrs) >= 2:
try:
clrs = [eval(c) if '.' in c else c.lower() for c in clrs]
# Getting all the colors from the flet.colors module.
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
for i in clrs:
if i not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Colors Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# stops: must have the same length as colors.
if stops and len(stops) >= 2:
try:
stops = [eval(s) for s in stops]
is_not_range = True if list(filter(lambda a: not 0.0 <= a <= 1.0, stops)) else False
print(f"{stops=}")
if is_not_range: raise ValueError("Some values are out of the specified range(0.0 - 1.0)!")
except Exception as x:
print(f"Stops Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: There seems to be an error with your stops. Please check your entries and "
"make sure they are between 0.0 and 1.0!"),
open=True))
return
elif stops and len(stops) < 2:
e.page.show_snack_bar(
ft.SnackBar(ft.Text(
"ERROR: `stops` is either empty or has a number of values(between 0.0 and 1.0) equal to those "
"in `colors`. This could be gotten from the Colors V1/V2 "
"Tab here."),
open=True))
return
# rotation: rotation - rotation for the gradient, in radians, around the center-point of its bounding box.
try:
if rotation is not None:
rotation = round((math.pi * float(rotation)) / 180, 3) # convert to rads
except Exception as x:
print(f"Rotation Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: For simplicity, `rotation` must be in degrees! It will be "
"converted to radians internally."),
open=True))
return
# start_angle : The angle in radians at which stop 0.0 of the gradient is placed. Defaults to 0.0.
try:
if start_angle is not None:
start_angle = round((math.pi * float(start_angle)) / 180, 3) # convert to rads
except Exception as x:
print(f"Start_angle Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: For simplicity, `start_angle` must be in degrees! It will be "
"converted to radians internally."),
open=True))
return
# end_angle : The angle in radians at which stop 1.0 of the gradient is placed. Defaults to math.pi * 2.
try:
if end_angle is not None:
end_angle = round((math.pi * float(end_angle)) / 180, 3) # convert to rads
except Exception as x:
print(f"End_angle Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: For simplicity, `end_angle` must be in degrees! It will be "
"converted to radians internally."),
open=True))
return
# compare colors and stops
if stops and len(clrs) != len(stops):
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: The number of values in `colors` must be equal to that in `stops`!"),
open=True))
return
# make the gradient visible
try:
self.container_obj.current.gradient = ft.SweepGradient(
colors=clrs,
tile_mode=tile_mode,
start_angle=start_angle,
end_angle=end_angle,
stops=stops,
center=center,
rotation=rotation
)
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Gradient!"), open=True))
except Exception as x:
print(f"Display Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: Display error!"),
open=True))
return
def update_container_size(self, e: ft.ControlEvent):
"""
The function updates the container size when the width or height values are changed.
:param e: The event object
"""
if e.control.value.strip().isnumeric():
# if the value of the text field in focus is numeric...
self.container_obj.current.height = int(
self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else 160
self.container_obj.current.width = int(
self.field_width.value.strip()) if self.field_width.value.strip().isnumeric() else 160
self.container_obj.current.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Container Size!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the gradient of the container to the clipboard.
:param e: The event object
"""
e.page.set_clipboard(f"{self.container_obj.current.gradient}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.container_obj.current.gradient}"), open=True))
def build(self):
center_focal_rotation_fields = ft.Row(
controls=[
self.field_center, self.field_start_angle, self.field_end_angle
],
alignment=ft.MainAxisAlignment.CENTER,
)
all_textfields = ft.Row(
controls=[
self.field_colors, self.field_stops, self.field_rotation,
],
alignment=ft.MainAxisAlignment.CENTER,
)
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Size:", weight=ft.FontWeight.BOLD, size=21),
ft.Row(
[self.field_width, self.field_height],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Divider(height=2, thickness=2),
ft.Text("Sweep Gradient Builder:", weight=ft.FontWeight.BOLD, size=21),
all_textfields,
self.tile_mode_radio_group,
center_focal_rotation_fields
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.Container(
ref=self.container_obj,
bgcolor=ft.colors.RED_ACCENT_700,
padding=ft.padding.Padding(15, 0, 15, 0),
width=160,
height=160,
gradient=ft.SweepGradient(colors=['redaccent', 'yellow'], )
)
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container#sweepgradient"
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN
)
if __name__ == "__main__":
def main(page: ft.Page):
page.scroll = ft.ScrollMode.HIDDEN
page.add(
TabContentLinearGradient(),
TabContentRadialGradient(),
TabContentSweepGradient()
)
ft.app(main)
================================================
FILE: Flet-Utils/utils/icon_utils.py
================================================
import math
from flet import *
import flet as ft
# the content of the icon tab
class TabContentIcon(ft.UserControl):
def __init__(self):
super().__init__()
self.icon_color = "red900"
self.icon_name = "cake_rounded"
self.icon_size = 65
self.icon_tooltip = None
self.icon_opacity = None
self.icon_rotate = None
self.icon_scale = None
self.icon_offset = None
self.icon_obj = ft.Ref[ft.Icon]()
# text field for tooltip property of the Icon object
self.field_tooltip = ft.TextField(
label="tooltip",
value="",
helper_text="Optional[str]",
on_change=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for color property of the Icon object
self.field_color = ft.TextField(
label="color",
value="red900",
on_submit=self.update_icon,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for the size property of the Icon object
self.field_size = ft.TextField(
label="size",
value="65",
helper_text="Union[int, float]",
on_submit=self.update_icon,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
# expand=1
width=170,
)
# text field for the name property of the Icon object
self.field_name = ft.TextField(
label="name",
hint_text="ft.icons.COPY or COPY or copy",
value="cake_rounded",
on_submit=self.update_icon,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=2
)
# text field for the opacity property of the Icon object
self.field_opacity = ft.TextField(
label="opacity",
value="",
helper_text="Union[int, float]",
on_change=self.update_icon,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
width=170,
# expand=1
)
# text field for the rotate property of the Icon object
self.field_rotate = ft.TextField(
label="rotate | angle in degrees",
value="",
helper_text="Union[int, float, Rotate]",
on_submit=self.update_icon,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for the offset property of the Icon object
self.field_offset = ft.TextField(
label="offset",
value="",
helper_text="Optional[Offset, tuple]",
on_submit=self.update_icon,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for the scale property of the Icon object
self.field_scale = ft.TextField(
label="scale",
value="",
helper_text="Union[int, float, Scale]",
on_submit=self.update_icon,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
# width=110,
expand=1
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_name, self.field_color],
),
ft.Row(
[self.field_tooltip, self.field_scale],
),
ft.Row(
[self.field_rotate, self.field_offset],
),
ft.Row(
[self.field_size, self.field_opacity],
alignment=ft.MainAxisAlignment.CENTER
)
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11
)
return ft.Column(
[
ft.Text("Icon Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields,
ft.Row(
[
ft.Icon(
ref=self.icon_obj,
name="cake_rounded",
size=65,
color="red900"
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/icon"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=25
)
def update_icon(self, e: ft.ControlEvent):
"""
It updates the Icon object.
:param e: The event object
"""
self.icon_size= int(self.field_size.value.strip()) if self.field_size.value.strip().isnumeric() else 65
self.icon_color = self.field_color.value.strip() if self.field_color.value.strip() else None
self.icon_name = self.field_name.value.strip() if self.field_name.value.strip() else "cake_rounded"
self.icon_tooltip = self.field_tooltip.value.strip() if self.field_tooltip.value.strip() else None
self.icon_offset = self.field_offset.value.strip() if self.field_offset.value.strip() else "None"
self.icon_rotate = self.field_rotate.value.strip() if self.field_rotate.value.strip() else "None"
self.icon_scale = self.field_scale.value.strip() if self.field_scale.value.strip() else "None"
# name
try:
if self.icon_name is not None:
self.icon_name = eval(self.icon_name) if '.' in self.icon_name else self.icon_name.lower()
# Getting all the icons from flet's icons module
list_started = False
all_flet_icons = list()
for value in vars(ft.icons).values():
if value == "ten_k":
list_started = True
if list_started:
all_flet_icons.append(value)
# checking if all the entered icons exist in flet
if self.icon_name not in all_flet_icons:
raise ValueError("Wrong Value!")
except Exception as x:
print(f"Name Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: There seems to be an error with your icon's name. See the Icon tabs for "
f"help with choosing an icon name!"),
open=True))
return
# color
try:
if self.icon_color is not None:
self.icon_color = eval(self.icon_color) if '.' in self.icon_color else self.icon_color.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.icon_color not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Color Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# offset
try:
self.icon_offset = eval(self.icon_offset)
if not isinstance(self.icon_offset, ft.Offset) \
and not isinstance(self.icon_offset, tuple) \
and self.icon_offset is not None:
raise ValueError("Wrong Value!")
elif isinstance(self.icon_offset, tuple) and len(self.icon_offset) == 2:
self.icon_offset = eval(f"Offset({self.icon_offset[0]}, {self.icon_offset[1]})")
except Exception as x:
print(f"Offset Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `offset` must be an Offset object or in the form x,y. Please check your input."),
open=True))
return
# rotate - input is assumed to be in degrees (which is in turn converted to rads internally)
try:
self.icon_rotate = eval(self.icon_rotate)
deg_to_rads = lambda d: round((math.pi * float(d)) / 180, 3)
if not isinstance(self.icon_rotate, ft.Rotate) \
and not isinstance(self.icon_rotate, (int, float)) \
and self.icon_rotate is not None:
raise ValueError("Wrong Value!")
elif isinstance(self.icon_rotate, ft.Rotate):
self.icon_rotate.angle = deg_to_rads(self.icon_rotate.angle)
elif isinstance(self.icon_rotate, (int, float)):
self.icon_rotate = deg_to_rads(self.icon_rotate)
elif isinstance(self.icon_rotate, tuple) and len(self.icon_rotate) == 2:
self.icon_rotate = eval(f"Rotate({deg_to_rads(self.icon_rotate[0])}, {self.icon_rotate[1]})")
except Exception as x:
print(f"Rotate Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `rotate` must be an Rotate object or in the form angle,alignment. Please check your input."),
open=True))
return
# scale
try:
self.icon_scale = eval(self.icon_scale)
if not isinstance(self.icon_scale, ft.Scale) \
and not isinstance(self.icon_scale, (int, float)) \
and self.icon_scale is not None:
raise ValueError("Wrong Value!")
except Exception as x:
print(f"Scale Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `scale` must be an Scale object. Please check your input."),
open=True))
return
# opacity
try:
if self.field_opacity.value:
self.icon_opacity = eval(self.field_opacity.value)
assert isinstance(self.icon_opacity, (int, float)), "`opacity` must be either of type float or int !"
else:
self.icon_opacity = None
except Exception as x:
print(f"Opacity Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
self.icon_obj.current.color = self.icon_color
self.icon_obj.current.tooltip = self.icon_tooltip
self.icon_obj.current.name = self.icon_name
self.icon_obj.current.size = self.icon_size
self.icon_obj.current.opacity = self.icon_opacity
self.icon_obj.current.scale = self.icon_scale
self.icon_obj.current.rotate = self.icon_rotate
self.icon_obj.current.offset = self.icon_offset
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Icon!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the tooltip object/instance to the clipboard."""
o = f", opacity={self.icon_opacity}"
s = f", scale={self.icon_scale}"
r = f", rotate={self.icon_rotate}"
off = f", offset={self.icon_offset}"
t = f", tooltip='{self.icon_tooltip}'"
c = f", color='{self.icon_color}'"
others = f"{c if self.icon_color is not None else ''}{t if self.icon_tooltip is not None else ''}{o if self.icon_opacity is not None else ''}{s if self.icon_scale is not None else ''}{off if self.icon_offset is not None else ''}{r if self.icon_rotate is not None else ''}"
val = f"Icon(name='{self.icon_name}', size={self.icon_size}{others if others else ''})"
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}"), open=True))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentIcon())
ft.app(main)
================================================
FILE: Flet-Utils/utils/icons_browser_utils.py
================================================
from itertools import islice
import flet as ft
# the content of the Icons tab
class TabContentIconsBrowser(ft.UserControl):
# all this below was obtained from https://github.com/flet-dev/examples/tree/main/python/apps/icons-browser
def __init__(self):
super().__init__()
def build(self):
def batches(iterable, batch_size):
"""
It takes an iterable and a batch size, and returns an iterator that yields batches of the iterable
:param iterable: An iterable object (e.g. a list) that you want to split into batches
:param batch_size: The number of items to process in each batch
"""
iterator = iter(iterable)
while batch := list(islice(iterator, batch_size)):
yield batch
# fetch all icon constants from icons.py module
icons_list = []
list_started = False
for key, value in vars(ft.icons).items():
if key == "TEN_K":
list_started = True
if list_started:
icons_list.append(value)
# search field
search_txt = ft.TextField(
expand=1,
hint_text="Enter keyword and press search button",
autofocus=True,
on_submit=lambda e: display_icons(e.control.value),
)
def search_click(_):
"""
It takes the value of the search box and passes it as parameter to the display_icons function
:param _:
:type _: The event that triggered the function
"""
display_icons(search_txt.value)
search_query = ft.Row(
[search_txt, ft.IconButton(icon=ft.icons.SEARCH, on_click=search_click)]
)
# the grid in which the results will be displayed
search_results = ft.GridView(
expand=1,
runs_count=10,
max_extent=150,
spacing=5,
run_spacing=5,
child_aspect_ratio=1,
)
status_bar = ft.Text()
def copy_to_clipboard(e):
"""
When the user clicks on an icon, the icon's value is copied to the clipboard,
and a snackbar is shown to account for the changes.
:param e: The event object
"""
icon_key = e.control.data
print("Copy to clipboard:", icon_key)
self.page.set_clipboard(e.control.data)
self.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {icon_key}"), open=True))
def search_icons(search_term: str):
"""
It takes a search term and returns a generator object that yields the icon names that
contain the search term
:param search_term: The search term that the user entered
:type search_term: str
"""
for icon_name in icons_list:
if search_term != "" and search_term in icon_name:
yield icon_name
def display_icons(search_term: str):
"""
It takes a search term, disables the search box, cleans the search results, and then loops through the
search results in batches of 200, adding each icon to the search results(the displayed grid).
:param search_term: str - the search term to use
:type search_term: str
"""
# clean search results
search_query.disabled = True
self.update()
search_results.clean()
for batch in batches(search_icons(search_term.lower()), 200):
for icon_name in batch:
icon_key = f"icons.{icon_name.upper()}"
search_results.controls.append(
ft.TextButton(
content=ft.Container(
content=ft.Column(
[
ft.Icon(name=icon_name, size=35),
ft.Text(
value=f"{icon_name}",
size=12,
width=100,
no_wrap=True,
text_align=ft.TextAlign.CENTER,
color=ft.colors.ON_SURFACE_VARIANT,
),
],
spacing=5,
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
),
alignment=ft.alignment.center,
),
tooltip=f"{icon_key}\nClick to copy to a clipboard",
on_click=copy_to_clipboard,
data=icon_key,
)
)
status_bar.value = f"Icons found: {len(search_results.controls)}"
self.update()
# if there are no results, a snackbar shows up to let the user be aware
if len(search_results.controls) == 0:
self.page.show_snack_bar(ft.SnackBar(ft.Text("No icons found"), open=True))
search_query.disabled = False
self.update()
return ft.Column(
[
search_query,
search_results,
status_bar,
],
expand=True,
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentIconsBrowser())
ft.app(main)
================================================
FILE: Flet-Utils/utils/padding_utils.py
================================================
import flet as ft
from flet import padding
# the content of the padding tab
class TabContentPadding(ft.UserControl):
def __init__(self):
super().__init__()
self.container_front = ft.Ref[ft.Container]()
self.container_back = ft.Ref[ft.Container]()
self.container_text = ft.Ref[ft.Text]()
# text field for left parameter of the Padding object
self.field_left = ft.TextField(
label="left",
value="",
width=120,
height=50,
on_change=self.update_front_container_padding,
keyboard_type=ft.KeyboardType.NUMBER,
)
# text field for top parameter of the Padding object
self.field_top = ft.TextField(
label="top",
value="",
width=120,
height=50,
on_change=self.update_front_container_padding,
keyboard_type=ft.KeyboardType.NUMBER,
)
# text field for right parameter of the Padding object
self.field_right = ft.TextField(
label="right",
value="",
width=120,
height=50,
on_change=self.update_front_container_padding,
keyboard_type=ft.KeyboardType.NUMBER,
)
# text field for bottom parameter of the Padding object
self.field_bottom = ft.TextField(
label="bottom",
value="",
width=120,
height=50,
on_change=self.update_front_container_padding,
keyboard_type=ft.KeyboardType.NUMBER,
)
# text field for the width property of the Container object
self.field_width = ft.TextField(
label="Width",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_front_container_size,
)
# text field for the height property of the Container object
self.field_height = ft.TextField(
label="Height",
hint_text=f"default=160",
value="160",
width=120,
height=50,
on_submit=self.update_front_container_size,
)
def update_front_container_padding(self, e: ft.ControlEvent):
"""
It updates the padding of the container object.
:param e: The event object
"""
# if the value of the text field in focus is numeric or if it is empty...
if e.control.value.strip().isnumeric() or not e.control.value.strip():
# update the container's padding values
self.container_back.current.padding = ft.padding.Padding(
int(self.field_left.value.strip()) if self.field_left.value.strip().isnumeric() else 0,
int(self.field_top.value.strip()) if self.field_top.value.strip().isnumeric() else 0,
int(self.field_right.value.strip()) if self.field_right.value.strip().isnumeric() else 0,
int(self.field_bottom.value.strip()) if self.field_bottom.value.strip().isnumeric() else 0,
)
# update the text in the container
self.container_text.current.value = f"{int(self.field_left.value.strip()) if self.field_left.value.strip().isnumeric() else 0}, {int(self.field_top.value.strip()) if self.field_top.value.strip().isnumeric() else 0}, {int(self.field_right.value.strip()) if self.field_right.value.strip().isnumeric() else 0}, {int(self.field_bottom.value.strip()) if self.field_bottom.value.strip().isnumeric() else 0} "
self.update()
# show a snackbar to account for the changes
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Padding!"), open=True))
else:
# Show a snackbar with an error message, in case the above condition is not met.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def update_front_container_size(self, e: ft.ControlEvent):
"""
The function updates the container size when the width or height values are changed.
:param e: The event object
"""
if e.control.value.strip().isnumeric():
# if the value of the text field in focus is numeric...
self.container_front.current.height = int(
self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else 160
self.container_front.current.width = int(
self.field_width.value.strip()) if self.field_width.value.strip().isnumeric() else 160
self.container_front.current.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Container's Size!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the padding of the container to the clipboard.
:param e: The event object
"""
e.page.set_clipboard(f"{self.container_back.current.padding}")
# show a snackbar to account for the changes
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {self.container_back.current.padding}"), open=True))
def build(self):
all_fields = ft.Row(
controls=[
self.field_left, self.field_top, self.field_right, self.field_bottom
],
alignment=ft.MainAxisAlignment.CENTER,
)
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Size:", weight=ft.FontWeight.BOLD, size=21),
ft.Row(
[self.field_width, self.field_height],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Divider(height=2),
ft.Text("Container's Padding:", weight=ft.FontWeight.BOLD, size=21),
all_fields
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.Container(
content=ft.Container(
ft.Text(
"0, 0, 0, 0",
ref=self.container_text,
weight=ft.FontWeight.BOLD,
size=18,
color="black"
),
ref=self.container_front,
bgcolor=ft.colors.BLUE_700,
padding=ft.Padding(0, 0, 0, 0),
alignment=ft.Alignment(0, 0),
width=float(self.field_width.value),
height=float(self.field_height.value),
),
expand=True,
height=250,
ref=self.container_back,
bgcolor=ft.colors.RED_ACCENT_700,
padding=ft.Padding(0, 0, 0, 0),
alignment=ft.Alignment(0, 0), # align its contents in the center
border_radius=ft.border_radius.BorderRadius(0, 0, 0, 0),
)
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container/#padding"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentPadding())
ft.app(main)
================================================
FILE: Flet-Utils/utils/progress_bar_utils.py
================================================
from flet import *
import flet as ft
# the content of the progress bar tab
class TabContentProgressBar(ft.UserControl):
def __init__(self):
super().__init__()
self.bar_color = "green"
self.bar_bgcolor = None
self.bar_bar_height = None
self.bar_tooltip = None
self.bar_value = None
self.bar_width = None
self.bar_height = None
self.bar_opacity = None
self.bar_rotate = None
self.bar_scale = None
self.bar_offset = None
self.bar_obj = ft.Ref[ft.ProgressBar]()
# text field for tooltip property of the Progress Bar object
self.field_tooltip = ft.TextField(
label="tooltip",
value="",
helper_text="Optional[str]",
on_change=self.update_bar,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for color property of the Progress Bar object
self.field_color = ft.TextField(
label="color",
value="green",
helper_text="Optional[str]",
on_submit=self.update_bar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for the bgcolor property of the Progress Bar object
self.field_bgcolor = ft.TextField(
label="bgcolor",
value="",
helper_text="Optional[str]",
hint_text="colors.RED_50 or red50",
on_submit=self.update_bar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1,
# width=170,
)
# text field for the bar_height property of the Progress Bar object
self.field_bar_height = ft.TextField(
label="bar_height",
helper_text="Union[int, float]",
# value="4",
on_change=self.update_bar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the value property of the Progress Bar object
self.field_value = ft.TextField(
label="value",
value="",
helper_text="Union[int, float]",
on_change=self.update_bar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the width property of the Progress Bar object
self.field_width = ft.TextField(
label="width",
value="420",
helper_text="Union[int, float]",
on_submit=self.update_bar,
on_blur=self.update_bar,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the height property of the Progress Bar object
self.field_height = ft.TextField(
label="height",
value="",
helper_text="Union[int, float]",
on_submit=self.update_bar,
on_blur=self.update_bar,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the opacity property of the Progress Bar object
self.field_opacity = ft.TextField(
label="opacity",
value="",
helper_text="Union[int, float]",
on_change=self.update_bar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
# width=170,
expand=1
)
# text field for the offset property of the Progress Bar object
self.field_offset = ft.TextField(
label="offset",
value="",
helper_text="Optional[Offset, tuple]",
on_submit=self.update_bar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for the scale property of the Progress Bar object
self.field_scale = ft.TextField(
label="scale",
value="",
helper_text="Union[int, float, Scale]",
on_submit=self.update_bar,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
# width=110,
expand=1
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_value, self.field_bar_height, self.field_opacity],
),
ft.Row(
[self.field_bgcolor, self.field_color, self.field_tooltip],
),
ft.Row(
[self.field_offset, self.field_scale, self.field_height, self.field_width],
),
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11
)
return ft.Column(
[
ft.Text("Progress Bar Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields,
ft.Row(
[
ft.ProgressBar(
ref=self.bar_obj,
color="green",
# bar_height=4,
width=420
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/progressbar"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=30
)
def update_bar(self, e: ft.ControlEvent):
"""It updates the Progress Bar object."""
self.bar_opacity = int(self.field_value.value.strip()) if self.field_value.value.strip().isnumeric() else None
self.bar_color = self.field_color.value.strip() if self.field_color.value.strip() else None
self.bar_bgcolor = self.field_bgcolor.value.strip() if self.field_bgcolor.value.strip() else None
self.bar_tooltip = self.field_tooltip.value.strip() if self.field_tooltip.value.strip() else None
self.bar_offset = self.field_offset.value.strip() if self.field_offset.value.strip() else "None"
self.bar_scale = self.field_scale.value.strip() if self.field_scale.value.strip() else "None"
# value
try:
if self.field_value.value:
self.bar_value = eval(self.field_value.value)
assert isinstance(self.bar_value, (int, float)), "`value` must be either of type float or int !"
else:
self.bar_value = None
except Exception as x:
print(f"Value Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"),open=True))
return
# bar_height
try:
if self.field_bar_height.value:
self.bar_bar_height = eval(self.field_bar_height.value)
assert isinstance(self.bar_bar_height, (int, float)), "`bar_height` must be either of type float or int !"
else:
self.bar_bar_height = None
except Exception as x:
print(f"Bar Height Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# width
try:
if self.field_width.value:
self.bar_width = eval(self.field_width.value)
assert isinstance(self.bar_width, (int, float)), "`width` must be either of type float or int !"
else:
self.bar_width = None
except Exception as x:
print(f"Width Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# height
try:
if self.field_height.value:
self.bar_height = eval(self.field_height.value)
assert isinstance(self.bar_height, (int, float)), "`height` must be either of type float or int !"
else:
self.bar_height = None
except Exception as x:
print(f"Height Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# color
try:
if self.bar_color is not None:
self.bar_color = eval(self.bar_color) if '.' in self.bar_color else self.bar_color.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.bar_color not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Color Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# bgcolor
try:
if self.bar_bgcolor is not None:
self.bar_bgcolor = eval(self.bar_bgcolor) if '.' in self.bar_bgcolor else self.bar_bgcolor.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.bar_bgcolor not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Bgcolor Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# offset
try:
self.bar_offset = eval(self.bar_offset)
if not isinstance(self.bar_offset, ft.Offset) \
and not isinstance(self.bar_offset, tuple) \
and self.bar_offset is not None:
raise ValueError("Wrong Value!")
elif isinstance(self.bar_offset, tuple) and len(self.bar_offset) == 2:
self.bar_offset = eval(f"Offset({self.bar_offset[0]}, {self.bar_offset[1]})")
except Exception as x:
print(f"Offset Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `offset` must be an Offset object or in the form x,y. Please check your input."),
open=True))
return
# scale
try:
self.bar_scale = eval(self.bar_scale)
if not isinstance(self.bar_scale, ft.Scale) \
and not isinstance(self.bar_scale, (int, float)) \
and self.bar_scale is not None:
raise ValueError("Wrong Value!")
except Exception as x:
print(f"Scale Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `scale` must be an Scale object. Please check your input."),
open=True))
return
# opacity
try:
if self.field_opacity.value:
self.bar_opacity = eval(self.field_opacity.value)
assert isinstance(self.bar_opacity, (int, float)), "`opacity` must be either of type float or int !"
else:
self.bar_opacity = None
except Exception as x:
print(f"Opacity Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
self.bar_obj.current.color = self.bar_color
self.bar_obj.current.bgcolor = self.bar_bgcolor
self.bar_obj.current.tooltip = self.bar_tooltip
self.bar_obj.current.value = self.bar_value
self.bar_obj.current.bar_height = self.bar_bar_height
self.bar_obj.current.width = self.bar_width
self.bar_obj.current.height = self.bar_height
self.bar_obj.current.opacity = self.bar_opacity
self.bar_obj.current.scale = self.bar_scale
self.bar_obj.current.offset = self.bar_offset
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Progress Bar!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the tooltip object/instance to the clipboard."""
o = f", opacity={self.bar_opacity}"
s = f", scale={self.bar_scale}"
off = f", offset={self.bar_offset}"
t = f", tooltip='{self.bar_tooltip}'"
bg= f", bgcolor='{self.bar_bgcolor}'"
c = f", color='{self.bar_color}'"
w = f", width={self.bar_width}"
h = f", height={self.bar_height}"
others = f"{w if self.bar_width is not None else ''}{h if self.bar_height is not None else ''}{c if self.bar_color is not None else ''}{bg if self.bar_bgcolor is not None else ''}{t if self.bar_tooltip is not None else ''}{o if self.bar_opacity is not None else ''}{s if self.bar_scale is not None else ''}{off if self.bar_offset is not None else ''}"
val = f"Progressbar(value={self.bar_value}, bar_height={self.bar_bar_height}{others if others else ''})"
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}"), open=True))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
page.add(TabContentProgressBar())
ft.app(main)
================================================
FILE: Flet-Utils/utils/progress_ring_utils.py
================================================
from flet import *
import flet as ft
# todo: width, height
# the content of the progress ring tab
class TabContentProgressRing(ft.UserControl):
def __init__(self):
super().__init__()
self.ring_color = None
self.ring_bgcolor = None
self.ring_stroke_width = None
self.ring_tooltip = None
self.ring_value = None
self.ring_width = None
self.ring_height = None
self.ring_opacity = None
self.ring_rotate = None
self.ring_scale = None
self.ring_offset = None
self.ring_obj = ft.Ref[ft.ProgressRing]()
# text field for tooltip property of the Progress Ring object
self.field_tooltip = ft.TextField(
label="tooltip",
value="",
helper_text="Optional[str]",
on_change=self.update_ring,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for color property of the Progress Ring object
self.field_color = ft.TextField(
label="color",
value="",
helper_text="Optional[str]",
on_submit=self.update_ring,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for the bgcolor property of the Progress Ring object
self.field_bgcolor = ft.TextField(
label="bgcolor",
value="",
helper_text="Optional[str]",
hint_text="colors.RED_50 or red50",
on_submit=self.update_ring,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1,
# width=170,
)
# text field for the stroke_width property of the Progress Ring object
self.field_stroke_width = ft.TextField(
label="stroke_width",
helper_text="Union[int, float]",
value="",
on_change=self.update_ring,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the value property of the Progress Ring object
self.field_value = ft.TextField(
label="value",
value="",
helper_text="Union[int, float]",
on_change=self.update_ring,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the width property of the Progress Bar object
self.field_width = ft.TextField(
label="width",
value="",
helper_text="Union[int, float]",
on_submit=self.update_ring,
on_blur=self.update_ring,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the height property of the Progress Bar object
self.field_height = ft.TextField(
label="height",
value="",
helper_text="Union[int, float]",
on_submit=self.update_ring,
on_blur=self.update_ring,
keyboard_type=ft.KeyboardType.NUMBER,
# width=80,
expand=1
)
# text field for the opacity property of the Progress Ring object
self.field_opacity = ft.TextField(
label="opacity",
value="",
helper_text="Union[int, float]",
on_change=self.update_ring,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.NUMBER,
# width=170,
expand=1
)
# text field for the offset property of the Progress Ring object
self.field_offset = ft.TextField(
label="offset",
value="",
helper_text="Optional[Offset, tuple]",
on_submit=self.update_ring,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for the scale property of the Progress Ring object
self.field_scale = ft.TextField(
label="scale",
value="",
helper_text="Union[int, float, Scale]",
on_submit=self.update_ring,
# on_blur=self.update_icon,
keyboard_type=ft.KeyboardType.TEXT,
# width=110,
expand=1
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_value, self.field_stroke_width, self.field_opacity],
),
ft.Row(
[self.field_bgcolor, self.field_color, self.field_tooltip],
),
ft.Row(
[self.field_offset, self.field_scale, self.field_width, self.field_height],
),
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11
)
return ft.Column(
[
ft.Text("Progress Ring Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields,
ft.Row(
[
ft.ProgressRing(
ref=self.ring_obj,
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/progressring"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=30
)
def update_ring(self, e: ft.ControlEvent):
"""It updates the Progress Ring object."""
self.ring_opacity = int(self.field_value.value.strip()) if self.field_value.value.strip().isnumeric() else None
self.ring_color = self.field_color.value.strip() if self.field_color.value.strip() else None
self.ring_bgcolor = self.field_bgcolor.value.strip() if self.field_bgcolor.value.strip() else None
self.ring_tooltip = self.field_tooltip.value.strip() if self.field_tooltip.value.strip() else None
self.ring_offset = self.field_offset.value.strip() if self.field_offset.value.strip() else "None"
self.ring_scale = self.field_scale.value.strip() if self.field_scale.value.strip() else "None"
# value
try:
if self.field_value.value:
self.ring_value = eval(self.field_value.value)
assert isinstance(self.ring_value, (int, float)), "`value` must be either of type float or int !"
else:
self.ring_value = None
except Exception as x:
print(f"Value Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# stroke_width
try:
if self.field_stroke_width.value:
self.ring_stroke_width = eval(self.field_stroke_width.value)
assert isinstance(self.ring_stroke_width,
(int, float)), "`stroke_width` must be either of type float or int !"
else:
self.ring_stroke_width = None
except Exception as x:
print(f"Stroke Width Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# width
try:
if self.field_width.value:
self.ring_width = eval(self.field_width.value)
assert isinstance(self.ring_width, (int, float)), "`width` must be either of type float or int !"
else:
self.ring_width = None
except Exception as x:
print(f"Width Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# height
try:
if self.field_height.value:
self.ring_height = eval(self.field_height.value)
assert isinstance(self.ring_height, (int, float)), "`height` must be either of type float or int !"
else:
self.ring_height = None
except Exception as x:
print(f"Height Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# color
try:
if self.ring_color is not None:
self.ring_color = eval(self.ring_color) if '.' in self.ring_color else self.ring_color.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.ring_color not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Color Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# bgcolor
try:
if self.ring_bgcolor is not None:
self.ring_bgcolor = eval(self.ring_bgcolor) if '.' in self.ring_bgcolor else self.ring_bgcolor.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.ring_bgcolor not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Bgcolor Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# offset
try:
self.ring_offset = eval(self.ring_offset)
if not isinstance(self.ring_offset, ft.Offset) \
and not isinstance(self.ring_offset, tuple) \
and self.ring_offset is not None:
raise ValueError("Wrong Value!")
elif isinstance(self.ring_offset, tuple) and len(self.ring_offset) == 2:
self.ring_offset = eval(f"Offset({self.ring_offset[0]}, {self.ring_offset[1]})")
except Exception as x:
print(f"Offset Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `offset` must be an Offset object or in the form x,y. Please check your input."),
open=True))
return
# scale
try:
self.ring_scale = eval(self.ring_scale)
if not isinstance(self.ring_scale, ft.Scale) \
and not isinstance(self.ring_scale, (int, float)) \
and self.ring_scale is not None:
raise ValueError("Wrong Value!")
except Exception as x:
print(f"Scale Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `scale` must be an Scale object. Please check your input."),
open=True))
return
# opacity
try:
if self.field_opacity.value:
self.ring_opacity = eval(self.field_opacity.value)
assert isinstance(self.ring_opacity, (int, float)), "`opacity` must be either of type float or int !"
else:
self.ring_opacity = None
except Exception as x:
print(f"Opacity Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
self.ring_obj.current.color = self.ring_color
self.ring_obj.current.bgcolor = self.ring_bgcolor
self.ring_obj.current.tooltip = self.ring_tooltip
self.ring_obj.current.value = self.ring_value
self.ring_obj.current.stroke_width = self.ring_stroke_width
self.ring_obj.current.width = self.ring_width
self.ring_obj.current.height = self.ring_height
self.ring_obj.current.opacity = self.ring_opacity
self.ring_obj.current.scale = self.ring_scale
self.ring_obj.current.offset = self.ring_offset
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Progress Ring!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the tooltip object/instance to the clipboard."""
o = f", opacity={self.ring_opacity}"
s = f", scale={self.ring_scale}"
off = f", offset={self.ring_offset}"
t = f", tooltip='{self.ring_tooltip}'"
bg = f", bgcolor='{self.ring_bgcolor}'"
c = f", color='{self.ring_color}'"
w = f", width={self.ring_width}"
h = f", height={self.ring_height}"
sw = f", stroke_width={self.ring_stroke_width}"
others = f"{sw if self.ring_stroke_width is not None else ''}{w if self.ring_width is not None else ''}{h if self.ring_height is not None else ''}{c if self.ring_color is not None else ''}{bg if self.ring_bgcolor is not None else ''}{t if self.ring_tooltip is not None else ''}{o if self.ring_opacity is not None else ''}{s if self.ring_scale is not None else ''}{off if self.ring_offset is not None else ''}"
val = f"ProgressRing(value={self.ring_value}{others if others else ''})"
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}"), open=True))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
page.add(TabContentProgressRing())
ft.app(main)
================================================
FILE: Flet-Utils/utils/shadermask_utils.py
================================================
# import all the controls: if the shader mask requires controls which are not imported, an error is raised
from flet import *
import flet as ft
# the content of the ShaderMask tab
class TabContentShaderMask(ft.UserControl):
def __init__(self):
super().__init__()
self.shader_mask_obj = ft.Ref[ft.ShaderMask]()
# dropdown values for the blend_mode parameter
self.bm_dropdown = ft.Dropdown(
options=[
ft.dropdown.Option("modulate"),
ft.dropdown.Option("clear"),
ft.dropdown.Option("color"),
ft.dropdown.Option("colorBurn"),
ft.dropdown.Option("colorDodge"),
ft.dropdown.Option("darken"),
ft.dropdown.Option("difference"),
ft.dropdown.Option("dst"),
ft.dropdown.Option("dstATop"),
ft.dropdown.Option("dstIn"),
ft.dropdown.Option("dstOut"),
ft.dropdown.Option("dstOver"),
ft.dropdown.Option("exclusion"),
ft.dropdown.Option("hardLight"),
ft.dropdown.Option("hue"),
ft.dropdown.Option("lighten"),
ft.dropdown.Option("luminosity"),
ft.dropdown.Option("multiply"),
ft.dropdown.Option("overlay"),
ft.dropdown.Option("plus"),
ft.dropdown.Option("saturation"),
ft.dropdown.Option("screen"),
ft.dropdown.Option("softLight"),
ft.dropdown.Option("src"),
ft.dropdown.Option("srcATop"),
ft.dropdown.Option("srcIn"),
ft.dropdown.Option("srcOut"),
ft.dropdown.Option("srcOver"),
ft.dropdown.Option("values"),
ft.dropdown.Option("xor"),
],
value="modulate",
on_change=self.update_mask,
width=150,
helper_text="blend mode",
)
# text field for gradient property of the ShaderMask object
self.field_shader = ft.TextField(
label="shader",
value="LinearGradient(begin=alignment.top_center, end=alignment.bottom_center, colors=[colors.BLUE_100,"
"colors.TRANSPARENT], stops=[0.5, 1.0])",
on_submit=self.update_mask,
keyboard_type=ft.KeyboardType.TEXT,
on_blur=self.update_mask,
hint_text="LinearGradient(.....)",
helper_text="Linear/Radial/Sweep Gradient object"
)
# text field for the border radius property of the ShaderMask object
self.field_border_radius = ft.TextField(
label="border radius",
value="border_radius.all(10)",
on_submit=self.update_mask,
on_blur=self.update_mask,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="5,10,2,3",
helper_text="BorderRadius object or (left, top, right, bottom)"
)
# text field for content property of the ShaderMask object
self.field_content = ft.TextField(
label="content",
value="Image(src='https://picsum.photos/100/200?2')",
on_submit=self.update_mask,
on_blur=self.update_mask,
keyboard_type=ft.KeyboardType.TEXT,
helper_text="A control for the content",
hint_text="Image(src='https://picsum.photos/100/200?2')",
)
self.border_radius = 10
self.shader = ft.LinearGradient(
begin=ft.alignment.top_center,
end=ft.alignment.bottom_center,
colors=[colors.BLACK, colors.TRANSPARENT],
stops=[0.5, 1.0],
)
self.blend_mode = "dstIn"
self.content = ft.Image(src="https://picsum.photos/100/200?2")
def update_mask(self, e: ft.ControlEvent):
"""
It updates the gradient of the container object.
:param e: The event object
"""
content = self.field_content.value.strip() if self.field_content.value.strip() else None
b_radius = self.field_border_radius.value.strip() if self.field_border_radius.value.strip() else None
shader = self.field_shader.value.strip() if self.field_shader.value.strip() else None
blend_mode = self.bm_dropdown.value
# border radius
try:
b_radius = eval(b_radius)
except Exception as x:
print(f"BorderRadius Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `border_radius` must be an BorderRadius object or in the form (left, top, right, "
"bottom). Please check your input."),
open=True)
)
return
else:
if not isinstance(b_radius, ft.BorderRadius) and \
not isinstance(b_radius, tuple):
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `border_radius` must be an BorderRadius object or in the form (left, top, "
"right, bottom). This could be gotten from the BorderRadius Tab here."
),
open=True
)
)
return
elif isinstance(b_radius, tuple) and len(b_radius) == 4:
b_radius = eval(
f"BorderRadius({b_radius[0]}, {b_radius[1]},{b_radius[2]}, {b_radius[3]})")
# shader
try:
shader = eval(shader)
if shader is not None and not isinstance(shader, (ft.LinearGradient, ft.RadialGradient, ft.SweepGradient)):
raise ValueError("Wrong Value")
except Exception as x:
print(f"Shader Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `shader` must be a Gradient(LinearGradient,RadialGradient,SweepGradient) "
"object. Please check your input."),
open=True)
)
return
# content
try:
content = eval(content)
self.content = self.shader_mask_obj.current.content = content
except Exception as x:
print(f"Content Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `content` must be a valid Control object. Please check your input."),
open=True))
return
self.blend_mode = self.shader_mask_obj.current.blend_mode = blend_mode
self.border_radius = self.shader_mask_obj.current.border_radius = b_radius
self.shader = self.shader_mask_obj.current.shader = shader
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated ShaderMask!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the gradient of the container to the clipboard.
:param e: The event object
"""
e.page.set_clipboard(
f"ShaderMask(content={self.field_content.value.strip()}, blend_mode={self.blend_mode}, shader={self.shader}, border_radius={self.border_radius})")
e.page.show_snack_bar(ft.SnackBar(ft.Text(
f"Copied: ShaderMask(content={self.field_content.value.strip()}, blend_mode={self.blend_mode}, shader={self.shader}, border_radius={self.border_radius})"),
open=True))
def build(self):
return ft.Column(
[
self.field_content,
self.field_shader,
ft.Row(
[self.field_border_radius, self.bm_dropdown],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.ShaderMask(
ft.Image(src="https://picsum.photos/100/200?2"),
blend_mode=ft.BlendMode.DST_IN,
shader=ft.LinearGradient(
begin=ft.alignment.top_center,
end=ft.alignment.bottom_center,
colors=[colors.BLACK, colors.TRANSPARENT],
stops=[0.5, 1.0],
),
border_radius=10,
ref=self.shader_mask_obj
)
],
alignment=ft.MainAxisAlignment.CENTER),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=icons.COPY,
on_click=self.copy_to_clipboard,
),
ft.FilledTonalButton(
"Go to Docs",
icon=icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/shadermask"
)
],
alignment=ft.MainAxisAlignment.CENTER,
),
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentShaderMask())
ft.app(main)
================================================
FILE: Flet-Utils/utils/shadow_utils.py
================================================
from flet import *
import flet as ft
# todo: change link to docs
# the content of the shadow tab
class TabContentShadow(ft.UserControl):
def __init__(self):
super().__init__()
self.shadow_color = None
self.shadow_offset = None
self.shadow_blur_radius = None
self.shadow_spread_radius = None
self.shadow_blur_style = None
self.container_bgcolor = None
self.shadow_obj = ft.Ref[ft.Container]()
# text field for offset property of the BoxShadow object
self.field_offset = ft.TextField(
label="offset",
value="",
helper_text="Optional[Offset, tuple]",
on_submit=self.update_shadow,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for color property of the BoxShadow object
self.field_color = ft.TextField(
label="color",
value="",
helper_text="Optional[str]",
on_submit=self.update_shadow,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for the spread_radius property of the BoxShadow object
self.field_spread_radius = ft.TextField(
label="spread_radius",
helper_text="Union[int, float]",
value="",
on_change=self.update_shadow,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the blur_radius property of the BoxShadow object
self.field_blur_radius = ft.TextField(
label="blur_radius",
value="",
helper_text="Union[int, float]",
on_change=self.update_shadow,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# radio buttons for the blur_style parameter
self.blur_style_radio_group = ft.RadioGroup(
ft.Row(
[
ft.Radio(value="normal", label="normal"),
ft.Radio(value="solid", label="solid"),
ft.Radio(value="outer", label="outer"),
ft.Radio(value="inner", label="inner"),
],
alignment=ft.MainAxisAlignment.CENTER
),
value="normal",
on_change=self.update_shadow,
)
# text field for bgcolor property of the shown container
self.field_container_bgcolor = ft.TextField(
label="bgcolor",
value="amber",
helper_text="Optional[str]",
on_submit=self.update_shadow,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_spread_radius, self.field_blur_radius]
),
self.blur_style_radio_group,
ft.Row(
[self.field_color, self.field_offset, self.field_container_bgcolor],
),
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11,
)
return ft.Column(
[
ft.Text("BoxShadow Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields,
ft.Row(
[
ft.Container(
ref=self.shadow_obj,
bgcolor=ft.colors.AMBER,
alignment=ft.alignment.center,
width=150,
height=150,
),
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/verticaldivider/"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=30,
expand=True
)
def update_shadow(self, e: ft.ControlEvent):
"""It updates the Shadow object."""
self.shadow_blur_radius, self.shadow_spread_radius = (
int(self.field_spread_radius.value.strip()) if self.field_spread_radius.value.strip().isnumeric() else None,
int(self.field_blur_radius.value.strip()) if self.field_blur_radius.value.strip().isnumeric() else None,
)
self.shadow_color = self.field_color.value.strip() if self.field_color.value.strip() else None
self.shadow_offset = self.field_offset.value.strip() if self.field_offset.value.strip() else None
self.shadow_blur_style = self.blur_style_radio_group.value
self.container_bgcolor = self.field_container_bgcolor.value.strip() if self.field_container_bgcolor.value.strip() else None
# spread_radius
try:
if self.field_spread_radius.value:
self.shadow_blur_radius = eval(self.field_spread_radius.value)
assert isinstance(self.shadow_blur_radius,
(int, float)), "`spread_radius` must be either of type float or int !"
else:
self.shadow_blur_radius = None
except Exception as x:
print(f"Spread Radius Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# blur_radius
try:
if self.field_blur_radius.value:
self.shadow_spread_radius = eval(self.field_blur_radius.value)
assert isinstance(self.shadow_spread_radius,
(int, float)), "`blur_radius` must be either of type float or int !"
else:
self.shadow_spread_radius = None
except Exception as x:
print(f"Blur Radius Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# color
try:
if self.shadow_color is not None:
self.shadow_color = eval(self.shadow_color) if '.' in self.shadow_color else self.shadow_color.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.shadow_color not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Color Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# container bgcolor
try:
if self.container_bgcolor is not None:
self.container_bgcolor = eval(
self.container_bgcolor) if '.' in self.container_bgcolor else self.container_bgcolor.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.container_bgcolor not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"BgColor Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
# offset
try:
if self.shadow_offset is not None:
self.shadow_offset = eval(self.shadow_offset)
if not isinstance(self.shadow_offset, ft.Offset) \
and not isinstance(self.shadow_offset, tuple) \
and self.shadow_offset is not None:
raise ValueError("Wrong Value!")
elif isinstance(self.shadow_offset, tuple) and len(self.shadow_offset) == 2:
self.shadow_offset = eval(f"Offset({self.shadow_offset[0]}, {self.shadow_offset[1]})")
except Exception as x:
print(f"Offset Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `offset` must be an Offset object or in the form x,y. Please check your input."),
open=True))
return
self.shadow_obj.current.shadow = ft.BoxShadow(self.shadow_spread_radius, self.shadow_blur_radius, self.shadow_color, self.shadow_offset, self.shadow_blur_style)
self.shadow_obj.current.bgcolor = self.container_bgcolor
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated BoxShadow!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the Shadow object/instance to the clipboard."""
val = self.shadow_obj.current.shadow
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}"), open=True))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
page.window_always_on_top = True
page.add(TabContentShadow())
ft.app(main)
================================================
FILE: Flet-Utils/utils/shape_utils.py
================================================
import flet as ft
# the content of the Shape tab
class TabContentShape(ft.UserControl):
def __init__(self):
super().__init__()
self.container_obj = ft.Ref[ft.Container]()
self.radios = ft.RadioGroup(
ft.Row(
[
ft.Radio(value="rectangle", label="Rectangle"),
ft.Radio(value="circle", label="Circle")
],
alignment=ft.MainAxisAlignment.CENTER,
),
value="rectangle",
on_change=self.update_shape,
)
def update_shape(self, e: ft.ControlEvent):
"""
It updates the Shape of the container object.
:param e: The event object
"""
_shape = self.radios.value
# update container's shape
self.container_obj.current.shape = ft.BoxShape(_shape)
self.update()
# show a snackbar to account for the changes
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Shape!"), open=True))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the shape used by the container to the clipboard.
:param e: The event object
"""
# update the text in the clipboard
e.page.set_clipboard(f"BoxShape('{self.container_obj.current.shape}')")
# show a snackbar to account for the changes
e.page.show_snack_bar(
ft.SnackBar(ft.Text(f"Copied: BoxShape('{self.container_obj.current.shape}')"), open=True))
def build(self):
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Shape:", weight=ft.FontWeight.BOLD, size=21),
self.radios
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
),
ft.Row(
[
ft.Container(
ref=self.container_obj,
bgcolor=ft.colors.GREEN,
width=180,
height=180,
shape=ft.BoxShape("rectangle"),
)
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/container/#shape"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN
)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentShape())
ft.app(main)
================================================
FILE: Flet-Utils/utils/tooltip_utils.py
================================================
import math
import flet as ft
from flet import *
from flet import border_radius, border, padding, margin, alignment
# the content of the tooltip tab
class TabContentTooltip(ft.UserControl):
def __init__(self):
super().__init__()
self.vertical_offset = None
self.wait_duration = None
self.show_duration = None
self.text_align = None
self.prefer_below = None
self.message = "This is tooltip"
self.shape = None
self.enable_feedback = None
self.content_property = ft.Text("Hover me to see tooltip")
self.margin_property = margin.all(0)
self.padding_property = padding.all(10)
self.gradient_property = ft.LinearGradient(
begin=ft.Alignment(-1, -1),
end=ft.Alignment(0.8, 1),
colors=[
"red",
"yellow",
],
tile_mode=ft.GradientTileMode.MIRROR,
rotation=math.pi / 3,
)
self.border_property = None
self.bgcolor = None
self.border_radius_property = border_radius.all(10)
self.text_style_property = ft.TextStyle(size=20, color=ft.colors.WHITE)
self.container_obj = ft.Ref[ft.Container]()
self.tooltip_obj = ft.Ref[ft.Tooltip]()
# text field for the width property of the Container object
self.container_width = ft.TextField(
label="Width",
hint_text="default=200",
value="200",
width=120,
height=50,
content_padding=9,
on_submit=self.update_container_size
)
# text field for the height property of the Container object
self.container_height = ft.TextField(
label="Height",
hint_text="default=200",
value="200",
width=120,
height=50,
content_padding=9,
on_submit=self.update_container_size
)
# text field for message property of the Tooltip object
self.field_message = ft.TextField(
label="message",
value="This is tooltip",
on_change=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
expand=2
)
# text field for bgcolor property of the Tooltip object
self.field_bgcolor = ft.TextField(
label="bgcolor",
value="",
on_submit=self.update_tooltip,
on_blur=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for text_style property of the Tooltip object
self.field_text_style = ft.TextField(
label="text_style",
value="TextStyle(size=20, color=colors.WHITE)",
helper_text="TextStyle instance",
on_submit=self.update_tooltip,
on_blur=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for the border radius property of the Tooltip object
self.field_border_radius = ft.TextField(
label="border radius",
value="BorderRadius(10, 10, 10, 10)",
on_submit=self.update_tooltip,
on_blur=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="5,10,2,3",
helper_text="BorderRadius instance or (left, top, right, bottom)",
expand=1
)
# text field for the border property of the Tooltip object
self.field_border = ft.TextField(
label="border",
value="",
on_submit=self.update_tooltip,
on_blur=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="5,10,2,3",
helper_text="Border instance or (left, top, right, bottom)",
expand=1
)
# text field for gradient property of the Tooltip object
self.field_gradient = ft.TextField(
label="gradient",
value="LinearGradient(begin=Alignment(-1, -1), end=Alignment(0.8, 1), colors=['red','yellow',], tile_mode=GradientTileMode.MIRROR, rotation=math.pi / 3)",
on_submit=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="LinearGradient(.....)",
helper_text="Linear, Radial or Sweep Gradient instance",
expand=2
)
# text field for the height property of the Tooltip object
self.field_height = ft.TextField(
label="height",
hint_text="160",
value="",
width=120,
height=50,
content_padding=9,
on_change=self.update_tooltip,
keyboard_type=ft.KeyboardType.NUMBER,
# on_blur=update_tooltip,
)
# text field for the margin property of the Tooltip object
self.field_margin = ft.TextField(
label="margin",
value="margin.all(0)",
on_submit=self.update_tooltip,
on_blur=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="margin.all(10)",
helper_text="Margin instance or (left, top, right, bottom)",
expand=1
)
# text field for the padding property of the Tooltip object
self.field_padding = ft.TextField(
label="padding",
value="padding.all(10)",
on_submit=self.update_tooltip,
on_blur=self.update_tooltip,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="padding.symmetric(horizontal=10)",
helper_text="Padding instance or (left, top, right, bottom)",
expand=1
)
# text field for the show_duration property of the Tooltip object
self.field_show_duration = ft.TextField(
label="show_duration",
value="",
on_change=self.update_tooltip,
# on_blur=update_tooltip,
keyboard_type=ft.KeyboardType.NUMBER,
hint_text="2000",
width=125,
height=60,
content_padding=9
)
# text field for the vertical_offset property of the Tooltip object
self.field_vertical_offset = ft.TextField(
label="vertical_offset",
hint_text="160",
value="",
width=127,
height=60,
on_change=self.update_tooltip,
content_padding=9,
keyboard_type=ft.KeyboardType.NUMBER,
# on_blur=update_tooltip,
)
# text field for the wait_duration property of the Tooltip object
self.field_wait_duration = ft.TextField(
label="wait_duration",
value="",
on_change=self.update_tooltip,
# on_blur=update_tooltip,
keyboard_type=ft.KeyboardType.NUMBER,
hint_text="1000",
width=120,
height=60,
content_padding=9
)
# dropdown values for the prefer_below parameter
self.prefer_below_dropdown = ft.Dropdown(
options=[
ft.dropdown.Option("True"),
ft.dropdown.Option("False"),
],
value="True",
on_change=self.update_tooltip,
width=100,
label="prefer_below",
content_padding=9,
height=60
)
# dropdown values for the shape parameter
self.shape_dropdown = ft.Dropdown(
options=[
ft.dropdown.Option("circle"),
ft.dropdown.Option("rectangle"),
],
value="rectangle",
on_change=self.update_tooltip,
width=100,
label="shape",
content_padding=9,
height=60
)
# dropdown values for the enable_feedback parameter
self.enable_feedback_dropdown = ft.Dropdown(
options=[
ft.dropdown.Option("True"),
ft.dropdown.Option("False"),
],
value="True",
on_change=self.update_tooltip,
width=115,
label="enable_feedback",
content_padding=9,
height=60
)
# dropdown values for the text_align parameter
self.text_align_dropdown = ft.Dropdown(
options=[
ft.dropdown.Option("left"),
ft.dropdown.Option("right"),
ft.dropdown.Option("center"),
ft.dropdown.Option("justify"),
ft.dropdown.Option("start"),
ft.dropdown.Option("end")
],
value="left",
on_change=self.update_tooltip,
width=100,
label="text_align",
content_padding=9,
height=60
)
def build(self):
all_fields = ft.Row(
controls=[
ft.Row(
[self.field_message, self.field_bgcolor],
),
ft.Row(
[self.field_border, self.field_border_radius],
),
ft.Row(
[self.field_margin, self.field_padding],
),
ft.Row(
[self.field_gradient, self.field_text_style],
),
self.field_vertical_offset, self.field_show_duration, self.field_wait_duration,
self.text_align_dropdown,
self.prefer_below_dropdown, self.shape_dropdown, self.enable_feedback_dropdown
],
alignment=ft.MainAxisAlignment.CENTER,
wrap=True
)
return ft.Column(
[
ft.Column(
[
ft.Text("Container's Size:", weight=ft.FontWeight.BOLD, size=21),
ft.Row(
[self.container_width, self.container_height],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Divider(height=2, thickness=2),
ft.Text("Tooltip Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.Container(
ft.Tooltip(
ref=self.tooltip_obj,
message="This is tooltip",
content=ft.Text("Hover me to see tooltip"),
padding=padding.all(10),
border_radius=10,
margin=margin.all(10),
text_style=ft.TextStyle(size=20, color=ft.colors.WHITE),
gradient=ft.LinearGradient(
begin=ft.Alignment(-1, -1),
end=ft.Alignment(0.8, 1),
colors=[
'red',
'yellow',
],
tile_mode=ft.GradientTileMode.MIRROR,
rotation=math.pi / 3,
),
),
ref=self.container_obj,
bgcolor=ft.colors.RED_ACCENT_700,
padding=padding.Padding(15, 0, 15, 0),
width=200,
height=200,
alignment=ft.Alignment(0, 0),
border=border.all(0, ft.colors.TRANSPARENT),
)
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/tooltip"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN
)
def update_tooltip(self, e: ft.ControlEvent):
"""
It updates the tooltip object.
:param e: The event object
"""
self.wait_duration, self.show_duration, self.height, self.vertical_offset = (
int(self.field_wait_duration.value.strip()) if self.field_wait_duration.value.strip().isnumeric() else None,
int(self.field_show_duration.value.strip()) if self.field_show_duration.value.strip().isnumeric() else None,
int(self.field_height.value.strip()) if self.field_height.value.strip().isnumeric() else None,
int(self.field_vertical_offset.value.strip()) if self.field_vertical_offset.value.strip().isnumeric() else None
)
self.enable_feedback = self.enable_feedback_dropdown.value
self.shape = self.shape_dropdown.value
self.message = self.field_message.value.strip()
self.prefer_below = self.prefer_below_dropdown.value
self.text_align = self.text_align_dropdown.value
self.bgcolor = self.field_bgcolor.value.strip() if self.field_bgcolor.value.strip() else None
self.border_radius_property = self.field_border_radius.value.strip() if self.field_border_radius.value.strip() else "None"
self.border_property = self.field_border.value.strip() if self.field_border.value.strip() else "None"
self.text_style_property = self.field_text_style.value.strip() if self.field_text_style.value.strip() else "None"
self.margin_property = self.field_margin.value.strip() if self.field_margin.value.strip() else "None"
self.padding_property = self.field_padding.value.strip() if self.field_padding.value.strip() else "None"
self.gradient_property = self.field_gradient.value.strip() if self.field_gradient.value.strip() else "None"
# border
try:
self.border_property = eval(self.border_property)
if self.border_property is not None and \
not isinstance(self.border_property, ft.Border) and \
not isinstance(self.border_property, tuple):
raise ValueError("Wrong Value")
elif isinstance(self.border_property, tuple) and len(self.border_property) == 4:
border_property = eval(
f"Border({self.border_property[0]}, {self.border_property[1]},{self.border_property[2]}, {self.border_property[3]})")
except Exception as x:
print(f"BorderRadius Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `border` must be an Border object or in the form (left, top, right, "
"bottom). Please check your input."),
open=True)
)
return
# border radius
try:
self.border_radius_property = eval(self.border_radius_property)
if self.border_radius_property is not None and \
not isinstance(self.border_radius_property, (border_radius.BorderRadius, ft.BorderRadius)) and \
not isinstance(self.border_radius_property, tuple):
raise ValueError("Wrong Value")
elif isinstance(self.border_radius_property, tuple) and len(self.border_radius_property) == 4:
self.border_radius_property = eval(
f"BorderRadius({self.border_radius_property[0]}, {self.border_radius_property[1]},{self.border_radius_property[2]}, {self.border_radius_property[3]})")
except Exception as x:
print(f"BorderRadius Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `border_radius` must be an BorderRadius object or in the form (left, top, right, "
"bottom). Please check your input."),
open=True)
)
return
# margin
try:
self.margin_property = eval(self.margin_property)
if self.margin_property is not None and \
not isinstance(self.margin_property, ft.Margin) and \
not isinstance(self.margin_property, tuple):
raise ValueError("Wrong Value")
elif isinstance(self.margin_property, tuple) and len(self.margin_property) == 4:
self.margin_property = eval(
f"Margin({self.margin_property[0]}, {self.margin_property[1]},{self.margin_property[2]}, {self.margin_property[3]})")
except Exception as x:
print(f"Margin Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `margin` must be an Margin object or in the form (left, top, right, "
"bottom). Please check your input."),
open=True))
return
# padding
try:
self.padding_property = eval(self.padding_property)
if self.padding_property is not None and \
not isinstance(self.padding_property, ft.Padding) and \
not isinstance(self.padding_property, tuple):
raise ValueError("Wrong Value")
elif isinstance(self.padding_property, tuple) and len(self.padding_property) == 4:
self.padding_property = eval(
f"Padding({self.padding_property[0]}, {self.padding_property[1]},{self.padding_property[2]}, {self.padding_property[3]})")
except Exception as x:
print(f"Padding Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(
"ERROR: `padding` must be an Padding object or in the form (left, top, right, "
"bottom). Please check your input."),
open=True)
)
return
# gradient
try:
self.gradient_property = eval(self.gradient_property)
if self.gradient_property is not None and not isinstance(self.gradient_property,
(ft.LinearGradient, ft.RadialGradient,
ft.SweepGradient)):
raise ValueError("Wrong Value")
except Exception as x:
print(f"Gradient Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `gradient` must be a Gradient(LinearGradient,RadialGradient,SweepGradient) "
"object. Please check your input."),
open=True)
)
return
# text style
try:
self.text_style_property = eval(self.text_style_property)
if self.text_style_property is not None and \
not isinstance(self.text_style_property, ft.TextStyle):
raise ValueError("Wrong Value")
except Exception as x:
print(f"TextStyle Error: {x}")
e.page.show_snack_bar(
ft.SnackBar(
ft.Text("ERROR: `text_style` must be a TextStyle object. Please check your input."),
open=True)
)
return
# bgcolor
try:
if self.bgcolor is not None:
self.bgcolor = eval(self.bgcolor) if '.' in self.bgcolor else self.bgcolor.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.bgcolor not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Bgcolor Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}"), open=True))
return
self.tooltip_obj.current.bgcolor = self.bgcolor
self.tooltip_obj.current.text_style = self.text_style_property
self.tooltip_obj.current.enable_feedback = self.enable_feedback
self.tooltip_obj.current.message = self.message
self.tooltip_obj.current.wait_duration = self.wait_duration
self.tooltip_obj.current.show_duration = self.show_duration
self.tooltip_obj.current.height = self.height
self.tooltip_obj.current.vertical_offset = self.vertical_offset
self.tooltip_obj.current.prefer_below = self.prefer_below
self.tooltip_obj.current.shape = ft.BoxShape(self.shape)
self.tooltip_obj.current.text_align = self.text_align
self.tooltip_obj.current.border_radius = self.border_radius_property
self.tooltip_obj.current.border = self.border_property
self.tooltip_obj.current.margin = self.margin_property
self.tooltip_obj.current.padding = self.padding_property
self.tooltip_obj.current.gradient = self.gradient_property
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Tooltip!"), open=True))
def update_container_size(self, e: ft.ControlEvent):
"""
The function updates the container size when the width or height values are changed.
:param e: The event object
"""
if e.control.value.strip().isnumeric():
# if the value of the text field in focus is numeric...
self.container_obj.current.height = int(
self.container_height.value.strip()) if self.container_height.value.strip().isnumeric() else 160
self.container_obj.current.width = int(
self.container_width.value.strip()) if self.container_width.value.strip().isnumeric() else 160
self.container_obj.current.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated Container Size!"), open=True))
else:
# Show a snackbar with the error message.
e.page.show_snack_bar(
ft.SnackBar(ft.Text("ERROR: The value(ex: non-integer) entered is not valid!"), open=True)
)
def copy_to_clipboard(self, e: ft.ControlEvent):
"""
It copies the tooltip object/instance to the clipboard.
:param e: The event object
"""
t = f"Tooltip(enable_feedback={self.enable_feedback}, height={self.height}, vertical_offset={self.vertical_offset}, margin={self.margin_property}, padding={self.padding_property}, bgcolor={self.bgcolor}, gradient={self.gradient_property}, border={self.border_property}, border_radius={self.border_radius_property}, shape=BoxShape('{self.shape}'), message='{self.message}', text_style={self.text_style_property}, text_align={self.text_align}, prefer_below={self.prefer_below}, show_duration={self.show_duration}, wait_duration={self.wait_duration})"
e.page.set_clipboard(f"{t}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {t}"), open=True))
print(t)
if __name__ == "__main__":
def main(page: ft.Page):
page.add(TabContentTooltip())
ft.app(main)
================================================
FILE: Flet-Utils/utils/vertical_divider_utils.py
================================================
from flet import *
import flet as ft
# the content of the vertical_divider tab
class TabContentVerticalDivider(ft.UserControl):
def __init__(self):
super().__init__()
self.vertical_divider_color = "green"
self.vertical_divider_tooltip = None
self.vertical_divider_thickness = 4
self.vertical_divider_width = None
self.vertical_divider_opacity = None
self.left_con_obj = ft.Ref[ft.Container]()
self.vertical_divider_obj = ft.Ref[ft.VerticalDivider]()
self.right_con_obj = ft.Ref[ft.Container]()
# text field for tooltip property of the VerticalDivider object
self.field_tooltip = ft.TextField(
label="tooltip",
value="",
helper_text="Optional[str]",
on_change=self.update_vertical_divider,
keyboard_type=ft.KeyboardType.TEXT,
expand=1
)
# text field for color property of the VerticalDivider object
self.field_color = ft.TextField(
label="color",
value="green",
helper_text="Optional[str]",
on_submit=self.update_vertical_divider,
keyboard_type=ft.KeyboardType.TEXT,
hint_text="colors.RED_50 or red50",
expand=1
)
# text field for the thickness property of the VerticalDivider object
self.field_thickness = ft.TextField(
label="thickness",
helper_text="Union[int, float]",
value="4",
on_change=self.update_vertical_divider,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the width property of the VerticalDivider object
self.field_width = ft.TextField(
label="width",
value="10",
helper_text="Union[int, float]",
on_change=self.update_vertical_divider,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# text field for the opacity property of the VerticalDivider object
self.field_opacity = ft.TextField(
label="opacity",
value="",
helper_text="Union[int, float]",
on_change=self.update_vertical_divider,
keyboard_type=ft.KeyboardType.NUMBER,
expand=1
)
# checkbox
self.containers_checkbox = ft.Checkbox(
label="Don't show left and right containers.",
on_change=self.update_vertical_divider,
)
def build(self):
all_fields = ft.Column(
controls=[
ft.Row(
[self.field_thickness, self.field_width, self.field_opacity]
),
ft.Row(
[self.field_color, self.field_tooltip],
),
self.containers_checkbox
],
alignment=ft.MainAxisAlignment.CENTER,
spacing=11,
)
w, h = 125, 200
return ft.Column(
[
ft.Text("VerticalDivider Builder:", weight=ft.FontWeight.BOLD, size=21),
all_fields,
ft.Row(
[
ft.Container(
ref=self.left_con_obj,
bgcolor=ft.colors.AMBER,
alignment=ft.alignment.center,
width=w,
height=h,
),
ft.VerticalDivider(
ref=self.vertical_divider_obj,
width=10,
thickness=4,
color="green"
),
ft.Container(
ref=self.right_con_obj,
bgcolor=ft.colors.AMBER,
alignment=ft.alignment.center,
width=w,
height=h,
),
],
alignment=ft.MainAxisAlignment.CENTER,
# spacing=0,
height=h,
),
ft.Row(
[
ft.FilledButton(
"Copy Value to Clipboard",
icon=ft.icons.COPY,
on_click=self.copy_to_clipboard
),
ft.FilledTonalButton(
"Go to Docs",
icon=ft.icons.DATASET_LINKED_OUTLINED,
url="https://flet.dev/docs/controls/verticaldivider/"
)
],
alignment=ft.MainAxisAlignment.CENTER,
)
],
alignment=ft.MainAxisAlignment.CENTER,
# horizontal_alignment=ft.CrossAxisAlignment.CENTER,
scroll=ft.ScrollMode.HIDDEN,
spacing=30,
expand=True
)
def update_vertical_divider(self, e: ft.ControlEvent):
"""It updates the VerticalDivider object."""
self.vertical_divider_opacity, self.vertical_divider_thickness, self.vertical_divider_width = (
int(self.field_opacity.value.strip()) if self.field_opacity.value.strip().isnumeric() else None,
int(self.field_thickness.value.strip()) if self.field_thickness.value.strip().isnumeric() else None,
int(self.field_width.value.strip()) if self.field_width.value.strip().isnumeric() else None,
)
self.vertical_divider_color = self.field_color.value.strip() if self.field_color.value.strip() else None
self.vertical_divider_tooltip = self.field_tooltip.value.strip() if self.field_tooltip.value.strip() else None
# thickness
try:
if self.field_thickness.value:
self.vertical_divider_thickness = eval(self.field_thickness.value)
assert isinstance(self.vertical_divider_thickness,
(int, float)), "`thickness` must be either of type float or int !"
else:
self.vertical_divider_thickness = None
except Exception as x:
print(f"Thickness Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}")))
return
# width
try:
if self.field_width.value:
self.vertical_divider_width = eval(self.field_width.value)
assert isinstance(self.vertical_divider_width, (int, float)), "`width` must be either of type float or int !"
else:
self.vertical_divider_width = None
except Exception as x:
print(f"Height Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}")))
return
# color
try:
if self.vertical_divider_color is not None:
self.vertical_divider_color = eval(self.vertical_divider_color) if '.' in self.vertical_divider_color else self.vertical_divider_color.lower()
# Getting all the colors from flet's colors module
list_started = False
all_flet_colors = list()
for value in vars(ft.colors).values():
if value == "primary":
list_started = True
if list_started:
all_flet_colors.append(value)
# checking if all the entered colors exist in flet
if self.vertical_divider_color not in all_flet_colors:
raise ValueError("Entered color was not found! See the colors browser for help!")
except Exception as x:
print(f"Color Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}")))
return
# opacity
try:
if self.field_opacity.value:
self.vertical_divider_opacity = eval(self.field_opacity.value)
assert isinstance(self.vertical_divider_opacity, (int, float)), "`opacity` must be either of type float or int !"
else:
self.vertical_divider_opacity = None
except Exception as x:
print(f"Opacity Error: {x}")
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"ERROR: {x}")))
return
self.vertical_divider_obj.current.color = self.vertical_divider_color
self.vertical_divider_obj.current.tooltip = self.vertical_divider_tooltip
self.vertical_divider_obj.current.thickness = self.vertical_divider_thickness
self.vertical_divider_obj.current.width = self.vertical_divider_width
self.vertical_divider_obj.current.opacity = self.vertical_divider_opacity
self.left_con_obj.current.visible = not self.containers_checkbox.value
self.right_con_obj.current.visible = not self.containers_checkbox.value
self.update()
e.page.show_snack_bar(ft.SnackBar(ft.Text("Updated VerticalDivider!")))
def copy_to_clipboard(self, e: ft.ControlEvent):
"""It copies the tooltip object/instance to the clipboard."""
o = f", opacity={self.vertical_divider_opacity}"
t = f", tooltip='{self.vertical_divider_tooltip}'"
c = f", color='{self.vertical_divider_color}'"
th = f", thickness={self.vertical_divider_thickness}"
others = f"{th if self.vertical_divider_thickness is not None else ''}{c if self.vertical_divider_color is not None else ''}{t if self.vertical_divider_tooltip else ''}{o if self.vertical_divider_opacity is not None else ''}"
val = f"VerticalDivider(width={self.vertical_divider_width}{others if others else ''})"
e.page.set_clipboard(val)
e.page.show_snack_bar(ft.SnackBar(ft.Text(f"Copied: {val}")))
print(val)
if __name__ == "__main__":
def main(page: ft.Page):
page.theme_mode = ft.ThemeMode.LIGHT
# page.horizontal_alignment = "center"
page.window_always_on_top = True
page.add(TabContentVerticalDivider())
ft.app(main)
================================================
FILE: Forms/README.md
================================================
# Forms
Login and Registration Forms. Could be customized for re-use.
## Content (WIP)
- [x] Login Form with Captcha;
- [ ] Registration Form with Captcha;
- [ ] App with login and registration views connected to an external service (firebase, supabase ...)
## Captures
- Login Form with Captcha:
https://github.com/ndonkoHenri/Flet-Samples/assets/98978078/d568daa3-e73c-41c1-9cf1-a1861a5ee6f6
================================================
FILE: Forms/login_utils.py
================================================
import base64
import os
import random
import string
from typing import Callable
import flet as ft
from captcha.audio import AudioCaptcha
from captcha.image import ImageCaptcha
class LoginWithCaptcha(ft.Column):
def __init__(self, on_success: Callable = None, on_error: Callable = None, width=500):
"""
:param on_success: Callable: Handle success
:param on_error: Callable: Handle errors
"""
super().__init__()
self.width = width
self.on_success = on_success
self.on_error = on_error
self.audio_state = "completed"
self.captcha_text = None
# create a captcha string of random digits - you might want to encrypt the returned value :)
self.generate_captcha_text = lambda length: ''.join(random.choices(string.digits, k=length))
self.email_field_ref = ft.Ref[ft.TextField]()
self.pwd_field_ref = ft.Ref[ft.TextField]()
self.captcha_field_ref = ft.Ref[ft.TextField]()
self.captcha_image_ref = ft.Ref[ft.Image]()
self.audio = ft.Audio(
src=f"/captcha-audio-{self.captcha_text}.wav",
autoplay=False,
release_mode=ft.audio.ReleaseMode.STOP,
volume=1,
balance=0,
# on_loaded=lambda _: print("Audio Control Loaded"),
on_state_changed=self.handle_audio_state_change,
)
self.controls = [
ft.SafeArea(
content=ft.Column(
controls=[
ft.Container(
content=ft.Image(
src=r"/images/login.png",
error_content=ft.ProgressRing()
),
height=160,
alignment=ft.alignment.top_center,
),
ft.Column(
alignment=ft.MainAxisAlignment.START,
horizontal_alignment=ft.CrossAxisAlignment.START,
controls=[
ft.Container(
padding=ft.padding.symmetric(horizontal=16),
content=ft.Column(
spacing=0,
alignment=ft.MainAxisAlignment.START,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.TextField(
ref=self.email_field_ref,
label="E-mail",
hint_text="example@xyz.com",
border=ft.InputBorder.UNDERLINE,
on_change=self.handle_textfield_change
),
ft.TextField(
ref=self.pwd_field_ref,
label="Password",
hint_text="ex: D/s4-YcG#5",
password=True,
border=ft.InputBorder.UNDERLINE,
on_change=self.handle_textfield_change
),
ft.Divider(height=15, color=ft.colors.TRANSPARENT),
ft.Column(
spacing=5,
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.Row(
alignment=ft.MainAxisAlignment.SPACE_AROUND,
tight=True,
controls=[
ft.Image(
ref=self.captcha_image_ref,
src="assets/images/captcha-image-test.png",
error_content=ft.ProgressRing(),
gapless_playback=True
),
ft.Column(
alignment=ft.MainAxisAlignment.CENTER,
spacing=0,
controls=[
ft.IconButton(
ft.icons.AUDIOTRACK,
tooltip="play/stop audio",
icon_color=ft.colors.ORANGE_ACCENT_700,
on_click=self.handle_audio
),
ft.IconButton(
ft.icons.REFRESH,
tooltip="new captcha",
icon_color=ft.colors.GREEN,
on_click=self.generate_new_captcha
)
]
)
]
),
ft.Row(
alignment=ft.MainAxisAlignment.CENTER,
vertical_alignment=ft.CrossAxisAlignment.CENTER,
controls=[
ft.TextField(
ref=self.captcha_field_ref,
height=40,
width=110,
content_padding=ft.Padding(7, 7, 7, 7),
text_align=ft.TextAlign.CENTER
),
]
)
]
),
ft.Divider(height=25, color=ft.colors.TRANSPARENT),
ft.Row(
alignment=ft.MainAxisAlignment.SPACE_AROUND,
controls=[
ft.ElevatedButton(
"LOGIN",
icon=ft.icons.EMAIL,
width=185,
height=40,
color=ft.colors.WHITE,
bgcolor=ft.colors.BLUE_700,
style=ft.ButtonStyle(
shape=ft.CountinuosRectangleBorder(radius=20),
),
on_click=self.handle_login
)
]
),
]
)
)
]
),
],
),
)
]
def update(self):
"""
Updates the page, and this control itself.
Calling self.update() becomes a "one stone two birds" operation.
"""
self.page.update()
super().update()
def did_mount(self):
"""
Called when this control is added to the page.
It adds the Audio control to the page's overlay, then generates a new captcha.
"""
self.page.overlay.append(self.audio)
self.generate_new_captcha()
def will_unmount(self):
"""
Called when this control is about to be removed from the page.
It removes/unloads the Audio control from the page's overlay and
deletes any existing audio files in the 'assets/audios' directory.
"""
# remove the Audio from the page's overlay
self.page.overlay.remove(self.audio)
# Delete all existing audio files in the assets directory
for file_name in os.listdir("./assets/audios"):
if file_name.endswith(".wav"):
os.remove(os.path.join("assets", "audios", file_name))
print("Deleted file: ", file_name)
def handle_textfield_change(self, e):
"""
Called when the textfield value changes (user is typing).
If the field's value is empty, the appropriate error message is shown.
"""
e.control.error_text = "Required!" if not e.data else None
self.update()
def handle_fields_validation(self):
"""
Checks if the email and password fields are empty.
If they are not empty, proceed by checking the captcha field too.
:return: True if the email and password fields are not empty,
:rtype: bool
"""
if "@" in self.email_field_ref.current.value.strip() \
and self.pwd_field_ref.current.value.strip():
if self.captcha_field_ref.current.value.strip():
self.show_snackbar_message("No field is empty!!")
return True
else:
self.show_snackbar_message("Error: Please solve the captcha!")
else:
self.show_snackbar_message("Error: Check your entries!")
return False
def handle_login(self, e):
"""
Called when the user clicks on the login button.
It checks if all fields are valid(contain some text), and also if the captcha was correctly solved.
"""
if not self.handle_fields_validation():
if self.on_error:
self.on_error()
return
if self.captcha_field_ref.current.value.strip() == self.captcha_text:
self.show_snackbar_message("SUCCESS!")
if self.on_success:
self.on_success()
else:
self.show_snackbar_message("Captcha is incorrect!")
if self.on_error:
self.on_error()
def generate_captcha(self, length: int = 4):
"""
Generates a random captcha text, generates a corresponding image, converts the image to base64 encoding,
and returns the base64 string. Using Base64 strings makes it possible to avoid saving the images locally.
An audio captcha of the random text is also created.
:return: a base64 string of the Captcha image
:rtype: str
"""
image_captcha = ImageCaptcha()
audio_captcha = AudioCaptcha()
# try to delete the lastly created audio file (not more needed, as we will be creating a new one)
if self.captcha_text:
try:
os.remove(os.path.join("assets", "audios", f"captcha-audio-{self.captcha_text}.wav"))
except FileNotFoundError as ex:
self.show_snackbar_message(str(ex))
self.captcha_text = self.generate_captcha_text(length)
audio_path = f"/audios/captcha-audio-{self.captcha_text}.wav"
try:
audio_captcha.write(self.captcha_text, f'./assets{audio_path}')
except Exception as ex:
self.show_snackbar_message(str(ex))
# Generate the CAPTCHA image as bytes
image_bytes = image_captcha.generate(self.captcha_text)
# Convert bytes to base64-encoded string
image_base64 = base64.b64encode(image_bytes.read()).decode('utf-8')
return image_base64, audio_path
def generate_new_captcha(self, e=None):
"""
Stop/Release any playing audio, then generates a new captcha code and
updates the image and audio files accordingly.
"""
if self.audio_state == "playing":
self.audio.release()
self.captcha_image_ref.current.src_base64, self.audio.src = self.generate_captcha()
self.update()
def handle_audio_state_change(self, e):
"""
Called when the audio state changes.
It updates the audio_state variable to reflect this change.
:return: The audio state of the device
"""
self.audio_state = e.data
def handle_audio(self, e):
"""
The handle_audio function is called when the user clicks on the audio-icon-button.
Depending on the audio state at that moment, the captcha audio file will either be played or released/stopped.
"""
if self.audio_state in ["completed", "stopped", "disposed"]:
self.audio.play()
elif self.audio_state == "playing":
self.audio.release()
def show_snackbar_message(self, text: str = "Message:", duration: int = 6000):
"""
Helper function that displays a snackbar message to the user.
:param text: str: the text of the snackbar message
:param duration: int: the duration of the snackbar message
"""
self.page.show_snack_bar(
ft.SnackBar(
ft.Text(text),
duration=duration,
show_close_icon=True,
behavior=ft.SnackBarBehavior.FLOATING,
dismiss_direction=ft.DismissDirection.DOWN
)
)
# print(text)
================================================
FILE: Forms/login_with_captcha.py
================================================
import flet as ft
from login_utils import LoginWithCaptcha
def main(page: ft.Page):
page.title = "Forms"
# page.window_always_on_top = True
page.theme_mode = "light"
page.horizontal_alignment = page.vertical_alignment = "center"
page.window_width, page.window_height = 425, 700
page.window_center()
page.window_visible = True
page.on_error = lambda e: print("Error: ", e.data)
page.appbar = ft.AppBar(
title=ft.Text("Login Form + Captcha", color=ft.colors.WHITE),
center_title=True,
bgcolor=ft.colors.BLUE,
elevation=5,
)
def handle_success():
print("Handling Success...")
def handle_error():
print("Handling Error...")
page.add(
LoginWithCaptcha(
on_success=handle_success,
on_error=handle_error
)
)
if __name__ == "__main__":
ft.app(target=main, view=ft.FLET_APP_HIDDEN)
================================================
FILE: Forms/requirements.txt
================================================
flet>=0.9.0
captcha>=0.5.0
pandas>=10.0.0
================================================
FILE: IP Revealer/README.md
================================================
# IP Revealer
Helps you know the public ip address of your device.
Try it from [here](https://ip-revealer.henrindonko.repl.co/).
## Capture

### Note
The first version of this app was making requests to the ipify service to get the ip address. The file for it is named `main_with_ipify.py`.
The new/actual version in `main.py`, simply grabs the value obtained by [Flet](https://flet.dev)(the python framework I used to build the app).
================================================
FILE: IP Revealer/assets/fonts/Plus_Jakarta_Sans/OFL.txt
================================================
Copyright 2020 The Plus Jakarta Sans Project Authors (https://github.com/tokotype/PlusJakartaSans)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: IP Revealer/assets/fonts/Stick/OFL.txt
================================================
Copyright 2020 The Stick Project Authors (https://github.com/fontworks-fonts/Stick)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: IP Revealer/main.py
================================================
import time
import flet as ft
def main(page: ft.Page):
page.title = "IP Revealer"
page.theme_mode = ft.ThemeMode.LIGHT
page.vertical_alignment = page.horizontal_alignment = "center"
page.fonts = {
"PJS": "/fonts/Plus_Jakarta_Sans/PlusJakartaSans-VariableFont_wght.ttf",
"Stick": "/fonts/Stick/Stick-Regular.ttf"
}
page.theme = ft.Theme(font_family="PJS")
# in desktop mode
if not page.web:
page.window_center()
# page.window_always_on_top = True
page.window_min_width, page.window_min_height = 312, 124
page.window_width, page.window_height = 386, 201
def reveal_ip(e):
"""
The reveal_ip function is called when the user clicks on the eye icon.
It hides both the eye and blur containers, which makes it so that only the IP address is visible.
"""
eye_container.visible = False
blur_container.visible = False
page.update()
def copy_ip(e):
"""
Copies the IP value to clipboard.
"""
page.set_clipboard(page.client_ip)
copy_button.selected = True
page.update()
time.sleep(2)
copy_button.selected = False
page.update()
page.add(
ft.Container(
content=ft.Column(
controls=[
ft.Text("", weight=ft.FontWeight.BOLD, size=25, selectable=True),
ft.Stack(
[
ft.Container(
ft.Row(
[
ft.Text(
page.client_ip if page.client_ip else "No IP found :(",
weight=ft.FontWeight.BOLD,
font_family="Stick",
size=40,
selectable=True
),
copy_button := ft.IconButton(
ft.icons.COPY_ROUNDED,
icon_color=ft.colors.BLUE,
icon_size=15,
on_click=copy_ip,
visible=page.client_ip is not None,
selected_icon=ft.icons.CHECK_CIRCLE_ROUNDED,
selected_icon_color=ft.colors.GREEN
)
],
spacing=0,
alignment=ft.MainAxisAlignment.CENTER,
vertical_alignment=ft.CrossAxisAlignment.END
),
alignment=ft.alignment.center,
),
blur_container := ft.Container(
width=410,
height=65,
blur=ft.Blur(15, 15),
),
eye_container := ft.Container(
content=ft.IconButton(
ft.icons.REMOVE_RED_EYE,
on_click=reveal_ip,
tooltip="click to reveal ip :)"
),
alignment=ft.alignment.bottom_center,
padding=ft.Padding(0, 7.5, 0, 0)
),
],
),
ft.Container(
content=ft.Text(
"Made with ❤ by the ",
spans=[
ft.TextSpan(
"@TheEthicalBoy",
ft.TextStyle(
color=ft.colors.BLUE,
decoration=ft.TextDecoration.UNDERLINE,
decoration_color=ft.colors.BLUE,
decoration_thickness=0.5,
),
url="https://github.com/ndonkoHenri",
)
],
selectable=True,
size=10
)
)
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
spacing=20
),
width=500,
)
)
ft.app(
main,
view=ft.WEB_BROWSER,
# web_renderer="html",
assets_dir="assets",
# use_color_emoji=True
)
================================================
FILE: IP Revealer/main_with_ipify.py
================================================
import time
import flet as ft
import requests
def main(page: ft.Page):
page.title = "IP Revealer"
page.window_center()
page.window_always_on_top = True
page.vertical_alignment = page.horizontal_alignment = "center"
page.window_min_width, page.window_min_height = 312, 124
page.window_width, page.window_height = 386, 201
ip_text = ft.Ref[ft.Text]()
def fetch_ip():
"""
Query the ipify service (https://www.ipify.org) to retrieve this device's public IP address.
:rtype: string
:returns: The public IP address of the requesting device as a string.
:raises: RequestError if the web request failed
"""
error = ""
try:
resp = requests.get('https://api.ipify.org')
except requests.RequestException:
error = "The request failed, most likely due to an networking error. Check you Internet connection."
else:
if resp.status_code != 200:
error = 'Received an invalid status code from ipify:' + str(
resp.status_code) + '. The service might be experiencing issues.'
else:
ip_text.current.value = resp.text
page.update()
if error:
for i in [error, "Retrying in some few seconds..."]:
print(i)
page.show_snack_bar(
ft.SnackBar(
content=ft.Text(i),
open=True
)
)
time.sleep(9)
# retry again
fetch_ip()
page.add(
ft.Container(
content=ft.Column(
controls=[
ft.Text("", weight=ft.FontWeight.BOLD, size=15),
ft.Text("000.000.000", ref=ip_text, weight=ft.FontWeight.BOLD, size=25, color=ft.colors.WHITE)
],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
expand=True
),
# I came up with this gradient using Flutils - https://flutils.pages.dev
gradient=ft.LinearGradient(
colors=['white38', 'white12'],
tile_mode=ft.GradientTileMode.CLAMP,
rotation=4.712,
stops=[0.01, 0.6],
begin=ft.Alignment(x=-1, y=0),
end=ft.Alignment(x=1, y=0),
type='linear'
),
width=270
)
)
# go get the IP
fetch_ip()
ft.app(main)
================================================
FILE: IP Revealer/requirements.txt
================================================
flet>=0.7.4
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 ndonkoHenri
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Markdown Editor/Dockerfile
================================================
FROM python:3-alpine
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "./main.py"]
================================================
FILE: Markdown Editor/README.md
================================================
# Markdown Editor
[Markdown](https://en.wikipedia.org/wiki/Markdown) is a lightweight markup language for creating formatted text using a plain-text editor.
The creator of Markdown(John Gruber) describes it as follows:
> Markdown is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).
> -- http://daringfireball.net/projects/markdown/
This app will serve as an editor/previewer for your Markdown.
Here is the link to the **online version** still in development: [md-editor.fly.dev](https://md-editor.fly.dev/)
I also created a **Medium blog post**. Check it out [here](https://medium.com/@ndonkohenri/building-a-markdown-editor-previewer-with-flet-7d9b06d6dc4b) and let me have a feedback.
### Features:
- Preview markdown output in realtime
- Import/Load any text file to start with
- Export/Save your input in well-known formats (`*.md`, `*.html`, `*.txt`) - `*.pdf` will soon be supported too.
### Captures
https://user-images.githubusercontent.com/98978078/197305750-5f922239-d5b2-4ee0-8b50-46e2ce55608f.mp4
### Note
The `markdown2` python library([github](https://github.com/trentm/python-markdown2), [pypi](http://pypi.python.org/pypi/markdown2)) must be installed when saving with `.html` extension.
You are free to use any other library of your choice, or completely remove support for html-output.
### Tip
Markdown has its own _special syntax_, which could be used in the editor. A cheatsheet could help increase your speed, efficiency, and productivity when dealing with Markdown.
[Markdown Cheatsheet by John Gruber](https://daringfireball.net/projects/markdown/syntax).
## Issues and Contribution
If you have any error/issue/problem using this app, please raise one on the [Issues section of this repo]().
Also, feel free to a drop a pull request in case you wish to contribute.
**Flet-ty MEME by [@Hololeo](https://github.com/hololeo)**:
================================================
FILE: Markdown Editor/assets/index.html
================================================
Markdown Editor
================================================
FILE: Markdown Editor/assets/manifest.json
================================================
{
"name": "Markdown Editor",
"short_name": "Markdown Editor",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0175C2",
"description": "Edit your Markdown text, have a live preview of the output, download and share.",
"orientation": "natural",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
================================================
FILE: Markdown Editor/fly.toml
================================================
# fly.toml file generated for md-editor on 2022-10-23T17:37:00+02:00
app = "md-editor"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
FLET_SERVER_PORT = "8080"
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
================================================
FILE: Markdown Editor/main.py
================================================
import flet as ft
import utils
# todo: output as pdf | add error dialog to handle errors
def main(page: ft.Page):
"""
App's entry point.
:param page: The page object
:type page: Page
"""
page.title = "Markdown Editor"
# page.window_always_on_top = True
page.theme_mode = "dark"
# set the minimum width and height of the window.
page.window_min_width = 478
page.window_min_height = 389
# set the width and height of the window.
page.window_width = 620
page.window_height = 720
# set the splash (a progress bar)
page.splash = ft.ProgressBar(visible=False, color="yellow")
page.file_picker = ft.FilePicker(on_result=utils.file_picker_result_import, on_upload=utils.on_upload_progress)
# hide dialog in a overlay
page.overlay.append(page.file_picker)
# a dialog to be shown when saving/exporting (on web only)
web_export_dialog = ft.AlertDialog(
title=ft.Text("Save as..."),
content=ft.Text("Choose a format for your file.\nTip: Press CANCEL to abort."),
modal=True,
actions_alignment=ft.MainAxisAlignment.CENTER,
actions=[
ft.ElevatedButton(".md", on_click=lambda e: get_file_format(".md")), # .md = Markdown file format
ft.ElevatedButton(".txt", on_click=lambda e: get_file_format(".txt")), # .txt = ft.Text file format
ft.ElevatedButton(".html", on_click=lambda e: get_file_format(".html")), # .html = HTML file format
# ft.TextButton(".pdf", on_click=lambda e: get_file_format(".pdf")),
ft.TextButton("CANCEL", on_click=lambda e: get_file_format(None)),
],
)
def on_error(e):
# page.dialog = utils.error_dialog
# page.dialog.open = True
# page.update()
page.show_snack_bar(
ft.SnackBar(ft.Text("Humm, seems like an error suddenly occurred! Please try again."), open=True),
)
page.on_error = on_error
def get_file_format(file_format: str | None):
"""
Closes the dialog, and calls the md_save with the file format specified by the user(in the alertdialog)
:param file_format: The file format selected in the AlertDialog.
Note:
file_format=None, when the CANCEL button of the AlertDialog is triggered.
"""
page.dialog.open = False
page.update()
if file_format is not None:
md_save(file_format)
else:
page.show_snack_bar(ft.SnackBar(ft.Text("Operation cancelled successfully!"), open=True))
def md_update(e):
"""
Updates the markdown(preview) when the text in the Textfield changes.
:param e: the event that triggered the function
"""
page.md.value = page.text_field.value
page.update()
def export_markdown_to_file(e):
if page.web:
page.dialog = web_export_dialog
page.dialog.open = True
page.update()
else:
page.file_picker.save_file(
dialog_title="Save As...",
file_type=ft.FilePickerFileType.CUSTOM,
file_name="untitled.md",
allowed_extensions=["txt", "md", 'html']
)
def md_save(file_format):
"""
It takes the Text from the textarea (Left hand side section), saves it as a file in the assets' folder
with the specified file_format, and opens the saved file in a new browser tab using a rel-path to the assets.
:param file_format: The file format to be used when saving
"""
try:
file_name = "untitled"
# to save as HTML file, we convert the Markdown to html using 'markdown2' library
with open(f"assets/untitled{file_format}", "w") as f: # save it in the assets folder
if file_format == ".html":
import markdown2 # pip install markdown2
f.write(markdown2.markdown(page.text_field.value))
else:
f.write(page.text_field.value)
page.launch_url(f"/{file_name}{file_format}") # open the file (already in the assets folder)
page.show_snack_bar(ft.SnackBar(ft.Text(f"Success: File was saved to assets as '{file_name}'!"),
open=True if not page.web else False))
except ImportError as exc:
print(exc)
print("To create an HTML output, install the markdown2 python library, using `pip install markdown2!`")
except Exception as exc:
print(exc)
page.show_snack_bar(ft.SnackBar(ft.Text(f"Error: {exc}!"), open=True))
def change_theme(e):
"""
When the button(to change theme) is clicked, the theme is changed, and the page is updated.
:param e: The event that triggered the function
"""
page.theme_mode = "light" if page.theme_mode == "dark" else "dark"
theme_icon_button.selected = not theme_icon_button.selected
page.update()
# button to change theme_mode (from dark to light mode, or the reverse)
theme_icon_button = ft.IconButton(
icon=ft.icons.LIGHT_MODE,
selected_icon=ft.icons.DARK_MODE,
icon_color=ft.colors.WHITE,
selected_icon_color=ft.colors.BLACK,
selected=False,
icon_size=35,
tooltip="change theme",
on_click=change_theme,
)
page.appbar = ft.AppBar(
title=ft.Text("Markdown Editor", color=ft.colors.WHITE),
center_title=True,
bgcolor=ft.colors.BLUE,
actions=[theme_icon_button],
elevation=5,
leading=ft.IconButton(
icon=ft.icons.CODE,
icon_color=ft.colors.YELLOW_ACCENT,
on_click=lambda e: page.launch_url("https://github.com/ndonkoHenri/Flet-Samples/tree/master/Markdown%20Editor")
)
)
# you can move it to a file if you wish.
md_test_string = """# Markdown
The following provides a quick reference to the most commonly used Markdown syntax.
Gotten from https://ashki23.github.io/markdown-latex.html
## Headers
### H3
#### H4
##### H5
###### H6
*Italic* and **Bold**
~~Scratched Text~~
## Lists
- Item 1
- Item 2
- Item 2a (2 tabs)
- Item 2b
- Item 2b-1 (4 tabs)
- Item 2b-2
Link: [Github](http://www.github.com/)
Quote:
> Imagination is more important than knowledge.
>
> Albert Einstein
## Tables
1st Header|2nd Header|3rd Header
---|:---:|---:
col 1 is|left-aligned|1
col 2 is|center-aligned|2
col 3 is|right-aligned|3
"""
# the LHS of the editor
page.text_field = ft.TextField(
value=md_test_string,
multiline=True,
on_change=md_update,
expand=True,
height=page.window_height,
keyboard_type=ft.KeyboardType.TEXT,
border_color=ft.colors.TRANSPARENT,
hint_text="# Heading\n\n- Use bulleted lists\n- To better clarify\n- Your points",
)
# the RHS of the editor
page.md = ft.Markdown(
value=md_test_string,
selectable=True,
extension_set=ft.MarkdownExtensionSet.GITHUB_WEB,
on_tap_link=lambda e: page.launch_url(e.data),
)
page.add(
ft.Row(
[
ft.Text("Markdown", style=ft.TextThemeStyle.TITLE_LARGE),
ft.FilledButton(
"Import",
on_click=lambda _: page.file_picker.pick_files(
dialog_title="Import File...",
file_type=ft.FilePickerFileType.CUSTOM,
allow_multiple=False,
allowed_extensions=["txt", "md", 'html']
),
tooltip="load a file",
icon=ft.icons.UPLOAD_FILE_ROUNDED
),
ft.FilledButton(
"Export",
on_click=export_markdown_to_file,
tooltip="save as ft.Text file",
icon=ft.icons.SIM_CARD_DOWNLOAD_ROUNDED
),
ft.Text("Preview", style=ft.TextThemeStyle.TITLE_LARGE)
],
alignment=ft.MainAxisAlignment.SPACE_AROUND
),
ft.Divider(thickness=1, color=ft.colors.RED_ACCENT_700),
ft.Row(
[
page.text_field,
ft.VerticalDivider(color=ft.colors.RED_ACCENT_700),
ft.Container(
ft.Column(
[
page.md
],
scroll=ft.ScrollMode.HIDDEN
),
expand=True,
alignment=ft.alignment.top_left,
padding=ft.padding.Padding(0, 12, 0, 0),
)
],
expand=True,
),
ft.Text(
"Made with ❤ by @ndonkoHenri aka TheEthicalBoy!",
style=ft.TextThemeStyle.LABEL_SMALL,
weight=ft.FontWeight.BOLD,
italic=True,
color=ft.colors.BLUE_900,
)
)
# (running the app)
if __name__ == "__main__":
ft.app(target=main, assets_dir="assets", upload_dir='assets/uploads')
================================================
FILE: Markdown Editor/requirements.txt
================================================
flet>=0.3.2
markdown2>=2.4.6
================================================
FILE: Markdown Editor/utils.py
================================================
import flet as ft
file_path = None
error_code = None
# dialog
def close_dialog(e):
"""Closes the Alert Dialog."""
e.page.dialog.open = False
e.page.update()
# dialog
def open_dialog(e):
"""Opens the Alert Dialog."""
e.page.dialog.open = True
e.page.update()
# progress bar / page.splash
def show_progress(e):
"""makes the progress bar visible, indicating we are loading up some stuffs"""
e.page.splash.visible = True
e.page.update()
# progress bar / page.splash
def unshow_progress(e):
e.page.splash.visible = False
e.page.update()
# import
def file_picker_result_import(e: ft.FilePickerResultEvent):
# Note: e.path is None when using pick_files or when FilePicker is closed by user
global file_path
# IMPORT / FILE LOAD (WEB and DESKTOP)
if e.files is not None and e.path is None:
if not e.page.web:
file_path = e.files[0].path
e.page.dialog = import_dialog
else:
e.page.dialog = upload_dialog
open_dialog(e)
# SAVE / FILE EXPORT (DESKTOP only)
if e.path is not None: # e.path has a value only when using save_files or get_directory_path
with open(e.path if e.path.split('\\')[-1].split(".")[-1] in ["html", "md", 'txt'] else f'{e.path}.md', 'wt') as f:
if e.path.split('\\')[-1].split(".")[-1] == 'html':
import markdown2 # pip install markdown2
f.write(markdown2.markdown(e.page.text_field.value))
else:
f.write(e.page.text_field.value)
e.page.show_snack_bar(
ft.SnackBar(ft.Text("File saved successfully!"), open=True),
)
# web upload
def on_upload_progress(e: ft.FilePickerUploadEvent):
if e.error:
e.page.show_snack_bar(
ft.SnackBar(ft.Text("Sorry, but an error occurred! Try again."), open=True),
)
return
show_progress(e)
if e.progress == 1:
overwrite_textfield_and_md(e.page, e.page.file_picker.result.files[0].name)
unshow_progress(e)
# web upload
def upload_file(e):
if e.page.file_picker.result is not None and e.page.file_picker.result.files is not None:
file_name = e.page.file_picker.result.files[0].name
e.page.file_picker.upload(
[
ft.FilePickerUploadFile(
file_name,
upload_url=e.page.get_upload_url(file_name, 600),
)
]
)
close_dialog(e)
show_progress(e)
# textfield and markdown
def overwrite_textfield_and_md(page, file_name_or_path, is_web=True):
"""updates the content of the LHS and RHS"""
global file_path
file_path = f'assets/uploads/{file_name_or_path}' if is_web else file_name_or_path
try:
with open(file_path, 'rt') as f:
text = f.read()
except Exception as exc:
# page.dialog = error_dialog
# page.dialog.open = True
# page.update()
print(exc)
page.show_snack_bar(
ft.SnackBar(ft.Text(f"Humm, seems like an error suddenly occurred! Please try again."), open=True),
)
return
page.text_field.value = text # update the content of the Textfield (LHS)
page.md.value = text # update the content of the Markdown (RHS)
page.update()
page.dialog.open = False
page.show_snack_bar(
ft.SnackBar(ft.Text(f"File uploaded successfully!"), open=True),
)
# a dialog to be shown when uploading a file (on WEB only)
upload_dialog = ft.AlertDialog(
title=ft.Text("Confirm file upload"),
content=ft.Column(
[
ft.Text("Do you really want me to upload this file?\n"
"Remember, the content of the input box will be deleted!\n"
"Tip: Press CANCEL to abort."),
]
),
modal=True,
actions_alignment="center",
actions=[
ft.ElevatedButton("UPLOAD", on_click=upload_file),
ft.TextButton("CANCEL", on_click=close_dialog),
],
)
# a dialog to be shown when importing a file on DESKTOP only
import_dialog = ft.AlertDialog(
title=ft.Text("Confirm file import"),
content=ft.Text("Do you really want me to import this file?\n"
"Remember, the current Markdown will be deleted/overwritten!\n"
"Tip: Press CANCEL to abort."),
modal=True,
actions_alignment="center",
actions=[
ft.ElevatedButton("IMPORT", on_click=lambda e: overwrite_textfield_and_md(e.page, file_path, is_web=False)),
ft.TextButton("CANCEL", on_click=close_dialog),
],
)
# a dialog to be shown when there is an error
error_dialog = ft.AlertDialog(
title=ft.Text("An Error occurred!"),
content=ft.Text("Please try again, and if it persists, report on GitHub.\n"
"I will take an immediate look at it!"),
modal=True,
actions_alignment="end",
actions=[
ft.ElevatedButton("REPORT", on_click=lambda e: e.page.launch_url("https://github.com/ndonkoHenri/Flet-Samples/issues")),
ft.TextButton("CANCEL", on_click=close_dialog),
],
)
================================================
FILE: QR Code Generator/Dockerfile
================================================
FROM python:3-alpine
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "./main.py"]
================================================
FILE: QR Code Generator/README.md
================================================
# QR Code Generator
A 'quick response' code also known as [a QR Code](https://en.wikipedia.org/wiki/QR_code) has widely grown in popularity. It may be used to display text to the user, to open a webpage on the user's device, to add a vCard contact to the user's device, to open a Uniform Resource Identifier (URI), to connect to a wireless network, or to compose an email or text message.
This tool will help you easily generate and save these codes.
Have fun using it, and don't forget to share.
_**Here is the link to the online version still in development:**_ [qrcode-gen.fly.dev](https://qrcode-gen.fly.dev/)
## Captures
================================================
FILE: QR Code Generator/assets/index.html
================================================
QR Code Generator
================================================
FILE: QR Code Generator/assets/manifest.json
================================================
{
"name": "QR Code Generator",
"short_name": "QR Code Generator",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0175C2",
"description": "Convert any text/url to a QR code, download, and share!",
"orientation": "natural",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
================================================
FILE: QR Code Generator/fly.toml
================================================
# fly.toml file generated for qrcode-gen on 2022-10-23T17:49:38+02:00
app = "qrcode-gen"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
FLET_SERVER_PORT = "8080"
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
================================================
FILE: QR Code Generator/main.py
================================================
import os
import flet as ft
import pyqrcode
# todo: add filepicker for image save on desktop
def main(page: ft.Page):
page.title = "QRcode Generator"
page.theme_mode = "light"
# page.window_always_on_top = True
page.splash = ft.ProgressBar(visible=False)
page.horizontal_alignment = "center"
page.count = len(os.listdir("assets/generated-codes"))
page.window_min_height = 658
page.window_width = 521
page.scroll = "hidden"
def save(e):
"""saves the QR code as an SVG file(for PC) or opens a new tab for download(web)."""
cancel_dialog(e)
if qr_text.current.value.strip() and qr_image.current.src_base64:
if page.web:
page.launch_url(f"data:application/octet-stream;base64,{qr_image.current.src_base64}")
return
else:
qr_code = pyqrcode.create(qr_text.current.value)
qr_code.svg(f"assets/generated-codes/output_{page.count}.svg", scale=10, debug=True)
page.show_snack_bar(
ft.SnackBar(
ft.Text(
f"The file was saved successfully as 'assets/generated-codes/output_{page.count}.svg'!"),
open=True
)
)
page.count += 1
else:
page.show_snack_bar(
ft.SnackBar(
ft.Text("Did you forget to enter your Text/URL?"),
open=True
)
)
def cancel_dialog(e):
"""closes the dialog box"""
page.dialog.open = False
page.update()
def open_dialog(e):
"""opens the dialog box"""
page.dialog = web_alert_dialog if page.web else pc_alert_dialog
page.dialog.open = True
page.update()
pc_alert_dialog = ft.AlertDialog(
modal=True,
title=ft.Text("Confirm"),
content=ft.Text("You are about to download the generated QR Code.\nDo you want to proceed?"),
actions=[
ft.TextButton("Yeah", on_click=save),
ft.TextButton("Abort", on_click=cancel_dialog),
],
actions_alignment=ft.MainAxisAlignment.END,
)
web_alert_dialog = ft.AlertDialog(
modal=True,
title=ft.Text("Note"),
content=ft.Text("You are about to download the generated QR Code.\n"
"Remember to save it with common image file formats such as: .png, .jpg, .jpeg, .bmp etc!"),
actions=[
ft.TextButton("Alright", on_click=save),
ft.TextButton("Abort", on_click=cancel_dialog),
],
actions_alignment=ft.MainAxisAlignment.END,
)
page.dialog = web_alert_dialog
def change_theme(e):
"""
Changes the app's theme_mode, from dark to light or light to dark. A splash(progress bar) is also shown.
:param e: The event that triggered the function
:type e: ControlEvent
"""
page.theme_mode = "light" if page.theme_mode == "dark" else "dark" # changes the page's theme_mode
theme_icon_button.selected = not theme_icon_button.selected # changes the icon
page.update()
def generate_qr(e):
try:
if qr_text.current.value.strip():
page.splash.visible = True
page.update()
# generating the code as base64, and setting the src_base64 property of the image to it
qr_code = pyqrcode.create(qr_text.current.value)
qr_image.current.src = None
qr_image.current.src_base64 = qr_code.png_as_base64_str(scale=10)
qr_image.current.update()
page.splash.visible = False
save_btn.current.disabled = False
page.update()
page.show_snack_bar(
ft.SnackBar(
ft.Text(f"Updated QR Code!"),
open=True
)
)
else:
save_btn.current.disabled = True
page.show_snack_bar(
ft.SnackBar(
ft.Text("No Text was entered!"),
open=False if qr_image.current.src_base64 else True
)
)
except Exception as e:
page.show_snack_bar(
ft.SnackBar(
ft.Text(f"An Error occurred: {e}"),
open=True
)
)
theme_icon_button = ft.IconButton(
ft.icons.DARK_MODE,
selected=False,
selected_icon=ft.icons.LIGHT_MODE,
icon_size=35,
tooltip="change theme",
on_click=change_theme,
style=ft.ButtonStyle(color={"": ft.colors.BLACK, "selected": ft.colors.WHITE}, ),
)
page.appbar = ft.AppBar(
title=ft.Text(
"QRcode Generator",
color="white"
),
center_title=True,
bgcolor="blue",
actions=[theme_icon_button],
leading=ft.IconButton(
icon=ft.icons.CODE,
icon_color=ft.colors.YELLOW_ACCENT,
tooltip="View Code",
on_click=lambda e: page.launch_url(
"https://github.com/ndonkoHenri/Flet-Samples/tree/master/QR%20Code%20Generator")
)
)
qr_image = ft.Ref[ft.Image]()
qr_text = ft.Ref[ft.TextField]()
save_btn = ft.Ref[ft.FilledButton]()
page.add(
ft.TextField(
ref=qr_text,
max_length=250,
hint_text="enter text here..",
label="QR Text Field",
width=520,
height=90,
suffix=ft.FilledButton("Generate", on_click=generate_qr),
on_change=generate_qr,
autofocus=True
),
ft.Divider(),
ft.Container(
ft.Image(
ref=qr_image,
src="/enter_text_meme.png",
width=370,
height=370,
),
alignment=ft.Alignment(0, 0),
),
ft.FilledButton("Save/Download", save_btn, icon=ft.icons.DOWNLOAD, on_click=open_dialog, disabled=True)
)
if __name__ == "__main__":
# flet.app(target=main, assets_dir="assets")
ft.app(target=main, assets_dir="assets", view=ft.WEB_BROWSER)
================================================
FILE: QR Code Generator/requirements.txt
================================================
flet>=0.3.2
pyqrcode>=1.2.1
pypng>=0.20220715.0
================================================
FILE: README.md
================================================
# Flet-Samples
Showcasing applications I made with the Flet([website](https://flet.dev), [github](https://github.com/flet-dev/flet)) python framework.
_I use free deployment services to host my apps online, so it may happen that some links get broken with time. Let me then please know if this is the case._
- Flet-Utils (Flutils) - [Online Demo](https://flutils.pages.dev/)
- URL Shortener - [Online Demo](https://url-shorten.fly.dev/) | [Blog Post](https://medium.com/@ndonkohenri/building-a-url-shortener-flutter-app-with-flet-python-framework-fffa1d98a53e)
- Markdown Editor - [Online Demo](https://md-editor.fly.dev/) | [Blog Post](https://medium.com/@ndonkohenri/building-a-markdown-editor-previewer-with-flet-7d9b06d6dc4b)
- QR Code Generator - [Online Demo](https://qrcode-gen.fly.dev/)
- IP Revealer - [Online Demo](https://ip-revealer.henrindonko.repl.co/)
- Short Jokes - [Online Demo](https://short-jokes.henrindonko.repl.co/)
- Random Image Viewer - [Online Demo](https://random-image-generator.henrindonko.repl.co) | [Blog Post](https://ndonkohenri.medium.com/building-a-random-image-generator-flutter-app-with-the-flet-python-framework-ecfe8b5daaf8)
- StartUp Name Generator Clone
- Forms (Login and Registration)
[//]: # (- [Online Demo](https://startup-name-generator.henrindonko.repl.co))
_More are still to come..._ (feel free to suggest)
================================================
FILE: Random Image Viewer/README.md
================================================
# Random Image Viewer/Generator
I deployed an online version of this application [here](https://random-image-generator.henrindonko.repl.co). Feel free to try it out anytime, and drop your feedbacks (issues, improvements etc).

### How to build this project?
I wrote an article on Medium explaining step by step, how I built this application. Check it out at [here](https://ndonkohenri.medium.com/building-a-random-image-generator-flutter-app-with-the-flet-python-framework-ecfe8b5daaf8).
### Where are the images from?
All the images you will see while using this app are served by [picsum.photos](https://picsum.photos).
### Why this project?
To be honest, JUST FOR FUN :)
### Contribution/Issues
If you face any issue or have any question, please drop it in the issue tracker, and I will reply as soon as possible.
On the other hand, if you wish to contribute(_ex: typos, UI/UX improvement_) to this project, please fork this repo, and make a pull request, which after review and discussion will be possibly merged.
**Flet-ty MEME by [@Hololeo](https://github.com/hololeo)**:
Best regards,
**TheEthicalBoy**
================================================
FILE: Random Image Viewer/assets/fonts/JetBrains_Mono/OFL.txt
================================================
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: Random Image Viewer/assets/fonts/Kalam/OFL.txt
================================================
Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: Random Image Viewer/assets/fonts/Space_Grotesk/OFL.txt
================================================
Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: Random Image Viewer/assets/index.html
================================================
Random Image Generator
================================================
FILE: Random Image Viewer/assets/manifest.json
================================================
{
"name": "Random Image Generator",
"short_name": "Random Image Generator",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0175C2",
"description": "Someone once said, 'an image is worth a thousand words'",
"orientation": "natural",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
================================================
FILE: Random Image Viewer/main.py
================================================
import flet as ft
from utils import ImageCard
def main(page: ft.Page):
# little configurations
page.title = "Random Image Generator"
page.horizontal_alignment = "center" # center the controls in the page - just for a beautiful UI
page.theme_mode = "light"
page.img_id_counter = 1
# some custom fonts found in the assets folder
page.fonts = {
"Kalam": "/fonts/Kalam/Kalam-Regular.ttf",
"JetBrainsMono": "/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf",
"SpaceGrotesk": "/fonts/Space_Grotesk/SpaceGrotesk-VariableFont_wght.ttf"
}
# set the default font_family for light and dark theme modes
page.theme = ft.Theme(font_family="JetBrainsMono")
page.dark_theme = ft.Theme(font_family="JetBrainsMono")
def change_theme(e):
"""
When the button(to change theme) is clicked, the theme is changed, and the page is updated.
:param e: The event that triggered the function
"""
page.theme_mode = "light" if page.theme_mode == "dark" else "dark"
theme_icon_button.selected = not theme_icon_button.selected
page.update()
# button to change theme_mode (from dark to light mode, or the reverse)
def generate_image(e):
# make sure the banner is closed
page.close_banner()
img_gen_btn.disabled = True
page.update()
try:
page.img_id_counter += 1
page.images_row.controls.append(ImageCard(img_id=page.img_id_counter))
except Exception as error:
print(error)
page.show_banner(page.banner)
img_gen_btn.disabled = False
page.update()
page.banner = ft.Banner(
bgcolor=ft.colors.AMBER_100,
leading=ft.Icon(ft.icons.WARNING_AMBER_ROUNDED, color=ft.colors.AMBER, size=40),
content=ft.Text(
"Oops, there were some errors while trying to fetch the Image. Please make sure you are connected to the "
"internet. What would you like me to do? "
),
actions=[
ft.TextButton("Retry", on_click=generate_image),
ft.TextButton("Ignore", on_click=lambda e: page.close_banner()),
],
)
theme_icon_button = ft.IconButton(
icon=ft.icons.DARK_MODE,
selected_icon=ft.icons.LIGHT_MODE,
icon_color=ft.colors.BLACK,
selected_icon_color=ft.colors.WHITE,
selected=False,
icon_size=35,
tooltip="change theme",
on_click=change_theme,
)
page.appbar = ft.AppBar(
title=ft.Text("Random Image Viewer"),
center_title=True,
bgcolor="blue",
color="yellow",
actions=[theme_icon_button],
leading=ft.IconButton(
icon=ft.icons.CODE,
icon_color=ft.colors.YELLOW_ACCENT,
tooltip="View Code",
on_click=lambda e: page.launch_url(
"https://github.com/ndonkoHenri/Flet-Samples/tree/master/Random%20Image%Viewer")
)
)
page.images_row = ft.ResponsiveRow([ImageCard(page.img_id_counter)])
img_gen_btn = ft.ElevatedButton(
text="Generate a Random Image",
on_click=generate_image
)
page.add(
img_gen_btn,
ft.Column(
[page.images_row],
scroll=ft.ScrollMode.HIDDEN,
expand=True
),
ft.Text(
"Made with ❤ by @ndonkoHenri aka TheEthicalBoy!",
style=ft.TextThemeStyle.LABEL_SMALL,
weight=ft.FontWeight.BOLD,
color=ft.colors.BLUE_900,
)
)
ft.app(target=main, assets_dir="assets", view=ft.WEB_BROWSER)
================================================
FILE: Random Image Viewer/requirements.txt
================================================
flet>=0.3.2
================================================
FILE: Random Image Viewer/utils.py
================================================
import random
import flet as ft
# todo: add grayscale and blur
class ImageCard(ft.Card):
# elevation when this card is not hovered
NORMAL_ELEVATION = 3
# elevation when this card is hovered
HOVERED_ELEVATION = 7
def __init__(self, img_id: int):
super(ImageCard, self).__init__(elevation=self.NORMAL_ELEVATION)
self.img_id = str(img_id)
self.random_id = random.randint(0, 1050) # get a random number/id
# images are gotten from https://picsum.photos | I hardcoded a size of 200x300
self.random_img_url = f"https://picsum.photos/id/{self.random_id}/200/300"
# defines the responsiveness of this card (what amount of space should be occupied) on different window sizes
# see docs https://flet.dev/docs/controls/responsiverow/
self.col = {"xs": 12, "sm": 6, "md": 5, "lg": 4, "xl": 3, "xxl": 2.4}
# the content of this card
self.content = ft.Container(
content=ft.Column(
[
ft.Row(
controls=[
ft.Text(
f"Image #{self.img_id} 📸",
weight=ft.FontWeight.BOLD,
font_family="SpaceGrotesk"
)
],
alignment=ft.MainAxisAlignment.END
),
ft.Row(
controls=[
ft.Image(
src=self.random_img_url,
)
],
alignment=ft.MainAxisAlignment.CENTER
),
ft.Row(
[
ft.IconButton(
icon=ft.icons.COPY, on_click=self.copy_img_url,
icon_color=ft.colors.BLUE, tooltip="Copy URL"
),
ft.IconButton(
icon=ft.icons.DOWNLOAD, on_click=self.launch_img_url,
icon_color=ft.colors.GREEN, tooltip="Download image"
),
ft.IconButton(
icon=ft.icons.DELETE, on_click=self.delete_img_card,
icon_color=ft.colors.RED, tooltip="Delete card"
),
],
alignment=ft.MainAxisAlignment.SPACE_EVENLY
),
],
),
padding=10,
on_hover=self.hover_elevation,
# bgcolor=ft.colors.BLUE_GREY_100,
border_radius=18,
)
def launch_img_url(self, e):
"""Opens the image in the browser, so the user downloads from there. See it as a way to avoid copyright issues."""
self.page.launch_url(self.random_img_url)
self.page.show_snack_bar(
ft.SnackBar(ft.Text("Opened original Image. Download from there please.", font_family="SpaceGrotesk"))
)
def copy_img_url(self, e):
"""Copies the url of this image to the clipboard, and then shows a snackbar to notify the user."""
self.page.set_clipboard(self.random_img_url)
self.page.show_snack_bar(ft.SnackBar(ft.Text("Image URL copied to clipboard.")))
def delete_img_card(self, e):
"""When the 'delete' btn is clicked, this card is deleted, and then shows a snackbar to notify the user."""
self.page.images_row.controls.remove(self)
self.update()
self.page.update()
self.page.show_snack_bar(ft.SnackBar(ft.Text("Deletion successful!")))
def hover_elevation(self, e):
"""When the card is hovered, increase the elevation and vice-versa."""
self.elevation = self.HOVERED_ELEVATION if self.elevation == self.NORMAL_ELEVATION else self.NORMAL_ELEVATION
self.update()
================================================
FILE: Short Jokes/README.md
================================================
# Short Jokes
I deployed a live version of this application [here](https://short-jokes.henrindonko.repl.co/). Feel free to try it out anytime, and drop your feedbacks (issues, improvements etc). -->

### Where are the jokes from?
All the jokes you will see while using this app are served by [JokeAPI](https://v2.jokeapi.dev/).
To make the user experience good for everyone, I decided to blacklist some flags(_nsfw, racist, sexist, explicit_) and exposed only two(_programming and christmas_) from the six available categories.
Feel free to fork/clone this project and modify the parameters to your likings.
### Why this project?
I wanted to expose the possibility of communicating with APIs while using Flet for the frontend.
### Project Architecture/Structure
There are three main files: `main.py`, `utils.py` and `joke.py`.
The first two files contain the frontend/UI of the app, while the last file contains the backend communication with the API (fetching, parsing).
### Contribution/Issues
If you face any issue or have any question, please drop it in the issue tracker, and I will reply as soon as possible.
On the other hand, if you wish to contribute(_ex: typos, UI/UX improvement_) to this project, please fork this repo, and make a pull request, which after review and discussion will be possibly merged.
**Flet-ty MEME by [@Hololeo](https://github.com/hololeo)**:
Best regards,
**TheEthicalBoy**
================================================
FILE: Short Jokes/assets/fonts/JetBrains_Mono/OFL.txt
================================================
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: Short Jokes/assets/fonts/Kalam/OFL.txt
================================================
Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: Short Jokes/assets/fonts/Space_Grotesk/OFL.txt
================================================
Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: Short Jokes/assets/index.html
================================================
Short Jokes
================================================
FILE: Short Jokes/assets/manifest.json
================================================
{
"name": "Short Jokes",
"short_name": "Short Jokes",
"start_url": ".",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0175C2",
"description": "If you're ready for some good laughs, these short jokes will do the trick.",
"orientation": "natural",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
================================================
FILE: Short Jokes/joke.py
================================================
import json
from typing import Optional
import urllib3
# documentation available here : https://v2.jokeapi.dev/
JOKE_URL = 'https://v2.jokeapi.dev/joke/Programming,Christmas?blacklistFlags=nsfw,racist,sexist,explicit&safe-mode'
def fetch_joke() -> Optional[dict]:
""" We fetch the joke, while taking into consideration possible errors. """
try:
http = urllib3.PoolManager()
r = http.request('GET', JOKE_URL)
except Exception as ex:
print(ex)
return None
return json.loads(r.data.decode('utf-8'))
def validate_joke(joke: dict) -> None:
""" We want to validate the joke before rendering it"""
assert isinstance(joke, dict), "Humm! Seems like the the joke is not of type Dict."
assert joke['error'] is False, "An error occurred while fetching the joke!"
def render_joke(joke: dict) -> Optional[str]:
try:
validate_joke(joke)
if joke.get('type') == 'single':
joke_str = joke['joke']
elif joke.get('type') == 'twopart':
joke_str = f">> {joke['setup']} \n\n>> {joke['delivery']}"
else:
raise Exception(f'Unknown joke format {joke}')
except Exception as ex:
print(ex)
return None
return joke_str
def return_joke():
return render_joke(
fetch_joke()
)
if __name__ == '__main__':
print(render_joke(fetch_joke()))
================================================
FILE: Short Jokes/main.py
================================================
import flet as ft
from utils import JokeCard, ethical_signature
from joke import return_joke
def main(page: ft.Page):
page.title = "Short Jokes"
page.theme_mode = "light"
# page.window_always_on_top = True
page.jokes_id_counter = 0
page.splash = ft.ProgressBar(visible=False)
page.fonts = {
"Kalam": "/fonts/Kalam/Kalam-Regular.ttf",
"JetBrainsMono": "/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf",
"SpaceGrotesk": "/fonts/Space_Grotesk/SpaceGrotesk-VariableFont_wght.ttf"
}
page.theme = ft.Theme(font_family="JetBrainsMono")
page.dark_theme = ft.Theme(font_family="JetBrainsMono")
def close_dlg(e):
page.dialog.open = False
page.update()
def close_banner(e):
"""Close the banner."""
page.banner.open = False
page.update()
def open_banner(e):
"""Open the banner."""
page.banner.open = True
page.update()
def change_theme(e):
"""
When the button(to change theme) is clicked, the theme is changed, and the page is updated.
:param e: The event that triggered the function
"""
page.theme_mode = "light" if page.theme_mode == "dark" else "dark"
theme_icon_button.selected = not theme_icon_button.selected
page.update()
def generate(e):
# make sure the banner is closed
close_banner(None)
# make the progress bar visible and disable the btn: indicating to the user, that we are trying to get the joke
page.splash.visible = True
joke_gen_btn.disabled = True
page.update()
# if the returned value is a valid Joke(not None), then we add a new card to the Row of jokes
if (fetched_joke := return_joke()) is not None:
page.jokes_id_counter += 1
page.jokes_row.controls.append(JokeCard(joke_id=page.jokes_id_counter, joke=fetched_joke))
else:
open_banner(None)
# make the progress bar invisible and enable the button: indicating
page.splash.visible = False
joke_gen_btn.disabled = False
page.update()
page.dialog = ft.AlertDialog(
modal=True,
title=ft.Text("Disclaimer 📝", weight=ft.FontWeight.BOLD),
content=ft.Text("This app fetches Joke's from an API and I tried my best to make the fetched jokes safe for "
"everyone. Nevertheless, it might still happen that a joke isn't of your likings. Please, "
"feel free to use the 'Delete' button at your disposal.\nThanks for your comprehension. 😉"),
actions=[
ft.TextButton("Alright!", on_click=close_dlg),
],
actions_alignment=ft.MainAxisAlignment.END
)
page.banner = ft.Banner(
bgcolor=ft.colors.AMBER_100,
leading=ft.Icon(ft.icons.WARNING_AMBER_ROUNDED, color=ft.colors.AMBER, size=40),
content=ft.Text(
"Oops, there were some errors while trying to fetch the Joke. Please make sure you are connected to the "
"internet. What would you like me to do? "
),
actions=[
ft.TextButton("Retry", on_click=generate),
ft.TextButton("Ignore", on_click=close_banner),
],
)
theme_icon_button = ft.IconButton(
icon=ft.icons.DARK_MODE,
selected_icon=ft.icons.LIGHT_MODE,
icon_color=ft.colors.BLACK,
selected_icon_color=ft.colors.WHITE,
selected=False,
icon_size=35,
tooltip="change theme",
on_click=change_theme,
)
page.appbar = ft.AppBar(
title=ft.Text("Short Jokes"),
center_title=True,
bgcolor="blue",
color="yellow",
actions=[theme_icon_button],
leading=ft.IconButton(
icon=ft.icons.CODE,
icon_color=ft.colors.YELLOW_ACCENT,
tooltip="View Code",
on_click=lambda e: page.launch_url(
"https://github.com/ndonkoHenri/Flet-Samples/tree/master/Short%20Jokes")
)
)
page.jokes_row = ft.ResponsiveRow(
[
JokeCard(
page.jokes_id_counter,
">> why do python programmers wear glasses? \n\n>> Because they can't C."
),
],
)
page.add(
ft.Row(
controls=[
joke_gen_btn := ft.FilledButton(
on_click=generate,
content=ft.Row(
[
ft.Icon(ft.icons.RESTART_ALT),
ft.Text("Generate a Joke!", font_family="SpaceGrotesk", weight=ft.FontWeight.BOLD)
]
)
),
],
alignment=ft.MainAxisAlignment.CENTER,
),
ft.Column(
[page.jokes_row],
scroll=ft.ScrollMode.HIDDEN,
expand=True
),
ethical_signature
)
# open the disclaimer dialog
page.dialog.open = True
page.update()
ft.app(target=main, assets_dir="assets", view=ft.WEB_BROWSER)
================================================
FILE: Short Jokes/requirements.txt
================================================
flet>=0.3.2
================================================
FILE: Short Jokes/utils.py
================================================
import flet as ft
class JokeCard(ft.Card):
# elevation when this card is not hovered
NORMAL_ELEVATION = 3
# elevation when this card is hovered
HOVERED_ELEVATION = 7
def __init__(self, joke_id: int, joke: str = "This is a Joke bro. Haha!"):
super(JokeCard, self).__init__(elevation=self.NORMAL_ELEVATION)
self.joke_id = str(joke_id)
self.joke = joke
self.col = {"sm": 6, "md": 4}
def _build(self):
self.content = ft.Container(
content=ft.Column(
[
ft.Text(
f"Joke #{self.joke_id} 😂",
weight=ft.FontWeight.BOLD,
font_family="SpaceGrotesk"
),
ft.Text(
self.joke,
weight=ft.FontWeight.BOLD,
font_family="Kalam",
),
ft.Row(
[
ft.TextButton(
on_click=self.copy_joke,
content=ft.Row(
[
ft.Icon(ft.icons.COPY, color=ft.colors.GREEN_ACCENT_700),
ft.Text("Copy", weight=ft.FontWeight.BOLD, color=ft.colors.GREEN_ACCENT_700)
],
)
),
ft.TextButton(
on_click=self.delete_joke_card,
content=ft.Row(
[
ft.Icon(ft.icons.DELETE, color=ft.colors.RED_ACCENT_700),
ft.Text("Delete", color=ft.colors.RED_ACCENT_700)
],
),
),
],
alignment=ft.MainAxisAlignment.END,
),
],
),
width=400,
padding=10,
on_hover=self.hover_elevation,
# bgcolor=ft.colors.BLUE_GREY_100,
border_radius=18,
)
def copy_joke(self, e):
"""When the 'copy' btn is clicked, copy the joke."""
self.page.set_clipboard(self.joke)
self.page.show_snack_bar(
ft.SnackBar(
ft.Text(f"Copied Joke to Clipboard!", font_family="SpaceGrotesk"),
open=True
)
)
def delete_joke_card(self, e):
"""When the 'delete' btn is clicked, delete this card."""
self.page.jokes_row.controls.remove(self)
self.update()
self.page.update()
self.page.show_snack_bar(
ft.SnackBar(
ft.Text(f"Joke was successfully deleted!", font_family="SpaceGrotesk"),
open=True
)
)
def hover_elevation(self, e):
"""When the card is hovered, increase the elevation."""
self.elevation = self.HOVERED_ELEVATION if self.elevation == self.NORMAL_ELEVATION else self.NORMAL_ELEVATION
self.update()
ethical_signature = ft.Text(
"Made with ❤ by @ndonkoHenri aka TheEthicalBoy!",
style=ft.TextThemeStyle.LABEL_SMALL,
weight=ft.FontWeight.BOLD,
color=ft.colors.BLUE_900,
)
================================================
FILE: StartUp Name Generator/README.md
================================================
# StartUp Name Generator Clone
I made this after watching an [interview](https://www.youtube.com/watch?v=kxsLRRY2xZA&t=3512s) of Flet with it's creator and core dev Feodor Fitsner.
The idea was to reproduce apps made with Flutter with Flet, exposing how easy and cool it is to build UIs with Flet.
To be brief, this "Startup Name Generator" comes from [Flutter's CodeLab tutorial](https://codelabs.developers.google.com/codelabs/first-flutter-app-pt2#0) made mainly for beginners starting with the framework.
This program doesn't generate true Startup names as compared to the Flutter one.
I left this out because I didn't find a python package to do that, and also wanted to leave out some extra complexity and keep things simple.
### Captures
- **Made with Flutter:**

- **Made with Flet:**

================================================
FILE: StartUp Name Generator/main.py
================================================
# made by @ndonkoHenri aka TheEthicalBoy
import flet as ft
import utils
def main(page: ft.Page):
def tile_clicked(e):
"""
When a tile is clicked, if it's not a favorite, add it to the favorites page, otherwise remove it from the
favorites page.
"""
# tile item added to the favorites page (precisely into the list of favorites)
if e.control.trailing.name == ft.icons.FAVORITE_BORDER:
# change the icon and its color to reflect the event
e.control.trailing.name = ft.icons.FAVORITE
e.control.trailing.color = ft.colors.RED
# addition to favorites page
utils.favorites_view.controls[0].controls.append(
ft.ListTile(
title=e.control.title
)
)
# tile item removed from the favorites page (precisely from list of favorites)
else:
# change the icon and its color to reflect the event
e.control.trailing.name = ft.icons.FAVORITE_BORDER
e.control.trailing.color = None
# removal from favorites page
for fav_tile in utils.favorites_view.controls[0].controls:
# check the type and content --- please use debug or add prints to better understand
if isinstance(fav_tile, ft.ListTile) and fav_tile.title == e.control.title:
utils.favorites_view.controls[0].controls.remove(fav_tile)
# update the page to reflect the current UI state
page.update()
def route_change(route):
"""
Called whenever there's a change in the routes. (see https://flet.dev/docs/guides/python/navigation-and-routing)
It clears the page's views and appends the main view (root view).
If the route is /favorites, it appends the favorites view, and updates the page.
"""
page.views.clear()
page.views.append(main_view)
if page.route == "/favorites":
page.views.append(
utils.favorites_view
)
page.update()
def view_pop(view):
"""
When the user clicks the back button, the current view is popped off the stack, and the previous view is loaded.
"""
page.views.pop()
top_view = page.views[-1]
page.go(top_view.route)
# basic page settings
page.title = "startup_namer"
page.window_always_on_top = True
page.fonts = {"San Fransisco": "/fonts/San-Francisco/SFUIDisplay-Light.ttf"}
page.theme_mode = "light"
page.theme = ft.Theme(
font_family="San Fransisco", # set as default font family
use_material3=False, # use material 2: this setting is mainly for the app-bar's elevation
)
# change the transition from one view to another to better mimic the Flutter example
page.theme.page_transitions.windows = ft.PageTransitionTheme.CUPERTINO
page.theme.page_transitions.macos = ft.PageTransitionTheme.CUPERTINO
# set the callbacks for the navigation and routing of the app
page.on_route_change = route_change
page.on_view_pop = view_pop
# Creating a list of tiles.
for i in range(20):
item_text = ft.Text(f"Name {i}")
item_tile = ft.ListTile(
title=item_text,
on_click=tile_clicked,
trailing=ft.Icon(
ft.icons.FAVORITE_BORDER
),
)
# add the currently created tile to the listview (list of tiles)
utils.main_listview.controls.append(item_tile)
main_view = ft.View(
"/",
controls=page.controls,
)
page.add(
utils.main_appbar,
utils.main_listview
)
ft.app(
target=main,
assets_dir="assets", # https://flet.dev/docs/controls/image/#src
route_url_strategy="path", # https://flet.dev/docs/guides/python/navigation-and-routing#url-strategy-for-web
# view=ft.WEB_BROWSER # opens the app in the browser
)
================================================
FILE: StartUp Name Generator/requirements.txt
================================================
flet==0.3.2
================================================
FILE: StartUp Name Generator/utils.py
================================================
# I pushed some stuffs into here to make th main.py file cleaner
import flet as ft
# the list view for the main page
main_listview = ft.ListView(
expand=True,
spacing=1,
divider_thickness=0.2,
first_item_prototype=True
)
# the appbar to be shown on the main/entry page
main_appbar = ft.AppBar(
title=ft.Text(
"Startup Name Generator",
weight=ft.FontWeight.BOLD,
color=ft.colors.BLACK,
size=18
),
bgcolor=ft.colors.WHITE,
color=ft.colors.BLACK,
center_title=True,
actions=[
ft.IconButton(
ft.icons.LIST,
tooltip='Saved Suggestions',
icon_color=ft.colors.BLACK,
on_click=lambda e: e.page.go("/favorites"), # if pressed, move to the favorites page
)
],
elevation=4 # will only be seen when Theme.use_material3=False
)
# the appbar to be shown on the favorites page
favorites_appbar = ft.AppBar(
title=ft.Text(
"Saved Suggestions",
weight=ft.FontWeight.BOLD,
color=ft.colors.BLACK,
size=18
),
bgcolor=ft.colors.WHITE,
color=ft.colors.BLACK,
center_title=True,
elevation=4 # will only be seen when Theme.use_material3=False
)
favorites_view = ft.View(
"/favorites",
controls=[
ft.ListView(
expand=True,
spacing=1,
divider_thickness=0.2,
first_item_prototype=True
)
],
appbar=favorites_appbar,
bgcolor=ft.colors.WHITE
)
================================================
FILE: URL shortener/Dockerfile
================================================
FROM python:3-alpine
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8080
CMD ["python", "./main.py"]
================================================
FILE: URL shortener/README.md
================================================
# Flet URL shortener
Using long URLs is sometimes annoying, boring and tedious.
This tool is here to help shorten these URLs, making them more beautiful to see, and also easy to memorize.
## Captures
https://user-images.githubusercontent.com/98978078/197305959-6dfad1c8-e6d8-4b71-9137-4975bbbc49f5.mp4
================================================
FILE: URL shortener/assets/index.html
================================================
URL Shortener
================================================
FILE: URL shortener/fly.toml
================================================
# fly.toml file generated for url-shorten on 2022-10-23T17:54:34+02:00
app = "url-shorten"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
FLET_SERVER_PORT = "8080"
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
================================================
FILE: URL shortener/main.py
================================================
import flet as ft
import pyshorteners # pip install pyshorteners
shortener = pyshorteners.Shortener()
class ShortLinkRow(ft.Row):
# a row containing the shortened url, and two buttons ('copy', and 'open in browser')
def __init__(self, shortened_link, link_source):
"""
We create a new class called `ShortenedLinkRow` that inherits from `ft.Row`.
The constructor takes two arguments/parameters: `shortened_link` and `source`.
:param shortened_link: the shortened link
:param link_source: the service hosting the shortened_link
"""
super().__init__() # required when overwriting the constructor
self.tooltip = link_source # set the tooltip of the row itself to the link provider/source
self.alignment = "center" # center the contents of this row
# the controls/content of our Row
self.controls = [
ft.Text(value=shortened_link, size=16, selectable=True, italic=True),
ft.IconButton(
icon=ft.icons.COPY, # the icon to be showed
on_click=lambda e: self.copy(shortened_link), # when this button is clicked, call the `copy` method, passing the shortened link as parameter
bgcolor=ft.colors.BLUE_700,
tooltip="copy" # to be showed when hovering on this button
),
ft.IconButton(
icon=ft.icons.OPEN_IN_BROWSER_OUTLINED, # the icon to be showed
tooltip="open in browser", # to be showed when hovering on this button
on_click=lambda e: e.page.launch_url(shortened_link) # when this button is clicked, open a browser tab with that shortened link
)
]
def copy(self, value):
"""
It copies the given value to the clipboard, and opens a Snackbar to inform the user.
:param value: The value to be copied to the clipboard
"""
self.page.set_clipboard(value)
self.page.show_snack_bar(ft.SnackBar(ft.Text("Link copied to clipboard!")))
def main(page: ft.Page):
page.title = "URL Shortener" # title of application/page
page.theme_mode = "light" # by default, page.theme_mode=None
page.splash = ft.ProgressBar(visible=False)
page.horizontal_alignment = "center" # center our page's content
# set the width and height of the window on desktop
page.window_width = 522
page.window_height = 620
page.scroll = "hidden"
# use the custom fonts in the assets folder
page.fonts = {
"sf-simple": "/fonts/San-Francisco/SFUIDisplay-Light.ttf",
"sf-bold": "/fonts/San-Francisco/SFUIDisplay-Bold.ttf"
}
page.theme = ft.Theme(font_family="sf-simple")
def change_theme(e):
"""
Changes the app's theme_mode, from dark to light or light to dark. A splash(progress bar) is also shown.
:param e: The event that triggered the function
:type e: ControlEvent
"""
page.theme_mode = "light" if page.theme_mode == "dark" else "dark" # changes the page's theme_mode
theme_icon_button.selected = not theme_icon_button.selected # changes the icon
page.update()
def shorten(e: ft.ControlEvent):
"""Grabs the URL in the textfield, and displays shortened versions of it."""
user_link = text_field.value # retrieve the content of the textfield
if user_link: # if the textfield is not empty
# if the entered text in the textfield is not a valid URl, the program may break,
# hence the need to catch that in a try-except
page.splash.visible = True
page.update()
page.add(ft.Text(f"Long URL: {text_field.value}", italic=False, weight=ft.FontWeight.BOLD))
try:
page.add(ShortLinkRow(shortener.tinyurl.short(text_field.value), "Source: tinyurl.com"))
page.add(ShortLinkRow(shortener.chilpit.short(text_field.value), "Source: chilp.it"))
page.add(ShortLinkRow(shortener.clckru.short(text_field.value), "Source: clck.ru"))
page.add(ShortLinkRow(shortener.dagd.short(text_field.value), "Source: da.dg"))
page.add(ShortLinkRow(shortener.isgd.short(text_field.value), "Source: is.gd"))
page.add(ShortLinkRow(shortener.osdb.short(text_field.value), "Source: os.db"))
# page.add(ShortLinkRow(shortener.gitio.short(url_field.value), "Source: git.io"),
# page.add(ShortLinkRow(shortener.owly.short(url_field.value), "Source: ow.ly"),
# page.add(ShortLinkRow(shortener.qpsru.short(url_field.value), "Source: qps.ru"),
except Exception as exception:
print(exception)
# inform the user that an error has occurred
e.page.show_snack_bar(
ft.SnackBar(
ft.Text(f"An error occurred. Please retry, or refresh the page.")))
page.splash.visible = False
page.update()
else: # inform the user if the textfield is empty (no text)
e.page.show_snack_bar(ft.SnackBar(ft.Text("Please enter a URL in the field!")))
theme_icon_button = ft.IconButton(
ft.icons.DARK_MODE,
selected=False,
selected_icon=ft.icons.LIGHT_MODE,
icon_size=35,
tooltip="change theme",
on_click=change_theme,
style=ft.ButtonStyle(color={"": ft.colors.BLACK, "selected": ft.colors.WHITE}),
)
page.appbar = ft.AppBar(
title=ft.Text(
"URL Shortener",
color="white"
),
center_title=True,
bgcolor="blue",
actions=[theme_icon_button],
leading=ft.IconButton(
icon=ft.icons.CODE,
icon_color=ft.colors.YELLOW_ACCENT,
tooltip="View Code",
on_click=lambda e: page.launch_url("https://github.com/ndonkoHenri/Flet-Samples/tree/master/URL%20shortener")
)
)
page.add(
text_field := ft.TextField(
value='https://github.com/ndonkoHenri', # a test link
label="Long URL",
hint_text="type long url here",
max_length=200,
width=800,
keyboard_type=ft.KeyboardType.URL,
# 'shorten' is the function to be called on occurrence of some events
suffix=ft.FilledButton("Shorten!", on_click=shorten), # event: button clicked
on_submit=shorten # event: 'enter' key pressed
),
ft.Text("Generated URLs:", weight=ft.FontWeight.BOLD, size=23, font_family="sf-bold")
)
ft.app(target=main, assets_dir="assets", view=ft.WEB_BROWSER)
================================================
FILE: URL shortener/requirements.txt
================================================
flet>=0.4.0
pyshorteners>=1.0.1