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)**: thats-the-power-of-flet ================================================ FILE: Flet-Utils/assets/index.html ================================================ Flet Utilities
Loading indicator...
================================================ 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 ![capture](https://github.com/ndonkoHenri/Flet-Samples/assets/98978078/27cdd031-5d20-4b63-95a3-4cd2d6a1eeb4) ### 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)**: thats-the-power-of-flet ================================================ 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). ![image](https://user-images.githubusercontent.com/98978078/214576043-5c9d11d0-b615-4075-87b3-ade0607e9f5f.png) ### 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)**: thats-the-power-of-flet 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
Loading...
================================================ 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). --> ![short jokes GIF](https://user-images.githubusercontent.com/98978078/213294369-634e2d21-c4f1-4933-b021-9c017489946a.gif) ### 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)**: thats-the-power-of-flet 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:** ![flutter startup name gen](https://user-images.githubusercontent.com/98978078/210209500-02e3cacc-1692-44ad-bf55-3ea10302a692.gif) - **Made with Flet:** ![startup name gen GIF](https://user-images.githubusercontent.com/98978078/210209505-26c6664f-2d7b-4751-9758-7fee96be4696.gif) ================================================ 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
Loading indicator...
================================================ 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