Flet - アイコンブラウザの改良

# はじめに

Fletの公式サイトにアイコン検索ができるサイトがあるのですが、いつの間にか使えなくなっていました。（2/2現在）
恐らくメモリ不足になっているのではないかと思います。
サイト：https://flet-icons-browser.fly.dev/#/

そこで、公開されているソースをもとに少し改良したものを作ってみたので、その内容をまとめておきます。
公式のソース：https://github.com/flet-dev/examples/tree/main/python/apps/icons-browser

ちなみにアイコンの検索は↓できます。（たぶんFlutter用を探せば使える）
https://fonts.google.com/icons?selected=Material+Icons&icon.platform=flutter&icon.set=Material+Icons

::: note
2023/2/4 に公式サイトの変更があったようです。
いまはアイコン検索のURLが↓になっていて動きます。
https://flet-icons-browser.pages.dev/
:::

# 作ったもの

作ったアイコンブラウザはこちら。良ければ試してみてください。

https://flet-my-icon-browser.pages.dev/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273897/438516c4-abd5-7c38-5208-d739df7cc5f4.png)

## 改良ポイント

- googleを参考に4分類して検索するようにした
    - 確かに4種類を一緒に出す必要もないと思うのでそうした
    - これにより、全アイコン表示しても2千ほどになって軽くなった
    - fly.ioでメモリ不足にもならなくなった
- DARKモードの表示も見れるようにSwitchを追加した
    - page.theme_modeを変えてるだけ
- 検索文字列なしの時は全部のアイコンを表示するようにした
    - サンプルだとこれができなかった
    - 目視になるけど全体を見れるのは個人的には必要

# 経緯まとめ

- 公式サイトのアイコンブラウザを使おうとしたら使えなかった（応答がない状況）
- しょうがないので、公開されているソースをローカルで動かしてみる
- なんか使い勝手がいまいちなので改良してみることに
- サンプルソースがyieldを使ってややこしいつくりになっているのを確認
- 全部のアイコンが見れないのが嫌だったのでその辺を変更
- アイコンを大量に表示すると結構遅いけどローカルでは動くのを確認
- fly.ioにアップしたらメモリ不足で動かないことを確認（サンプルでyieldを使っているのはこのためか？）
- 中身はFlutterなので、Flutterのアイコンを検索
- これを発見 https://fonts.google.com/icons?selected=Material+Icons&icon.platform=flutter&icon.set=Material+Icons
- ↑を参考にさらに改良
- 改良によってfly.ioでも動くようになった

# ソース

ソースはこちら。
150行程度でここまでできるのは楽だと思います。

```python
import flet as ft


class IconBrowser(ft.UserControl):
    def __init__(self, expand=False, height=500):
        super().__init__()
        if expand:
            self.expand = expand
        else:
            self.height = height

    def build(self):

        def copy_to_clipboard(e):
            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(type: str, search_term: str):
            if self.page != None:
                search_results.clean()

            count = 0
            for icon_name in icons_list:
                icon_key = f"icons.{icon_name.upper()}"
                if type == "" \
                    and ("OUTLINED" in icon_key or
                         "ROUNDED" in icon_key or
                         "SHARP" in icon_key):
                    # Filledは↑の文字列を含む場合はスキップ
                    continue
                elif type not in icon_key:
                    # タイプの文字列を含まない場合はスキップ
                    continue
                if search_term.upper() not in icon_key:
                    # 入力値を含まない場合はスキップ
                    continue

                search_results.controls.append(
                    ft.TextButton(
                        content=ft.Container(ft.Column(
                            [
                                ft.Icon(name=icon_name),
                                ft.Text(
                                    value=f"{icon_name}",
                                    size=12,
                                    width=120,
                                    no_wrap=True,
                                    text_align="center",
                                    color=ft.colors.ON_SURFACE_VARIANT,
                                ),
                            ],
                            spacing=0,
                            alignment="center",
                            horizontal_alignment="center",
                        ),
                            alignment=ft.alignment.center,
                        ),
                        tooltip=f"{icon_key}\nClick to copy to a clipboard",
                        on_click=copy_to_clipboard,
                        data=icon_key,
                    )
                )
                count += 1

            if self.page != None:
                message = f"{count} icons found"
                if count == 0:
                    message = "No icons found"
                self.page.show_snack_bar(
                    ft.SnackBar(ft.Text(message), open=True))
                self.update()

        def change_theme_mode(e: ft.UserControl):
            value = e.control.value
            if value:
                self.page.theme_mode = ft.ThemeMode.DARK
            else:
                self.page.theme_mode = ft.ThemeMode.LIGHT
            self.page.update()

        # 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)

        # I/O Controls
        search_txt = ft.TextField(
            expand=1,
            hint_text="Enter keyword and press search button",
            autofocus=True,
            on_submit=lambda _: search_icons(
                select_type_group.value, search_txt.value),
        )
        search_query = ft.Row([
            search_txt,
            ft.IconButton(icon=ft.icons.SEARCH,
                          on_click=lambda _: search_icons(
                              select_type_group.value, search_txt.value))
        ])
        select_type_group = ft.RadioGroup(
            ft.Row([
                ft.Radio(label="Outlined", value="OUTLINED"),
                ft.Radio(label="Filled", value=""),
                ft.Radio(label="Rounded", value="ROUNDED"),
                ft.Radio(label="Sharp", value="SHARP"),
            ]),
            value="OUTLINED",
            on_change=lambda _: search_icons(
                select_type_group.value, search_txt.value),
        )
        select_type = ft.Row([
            select_type_group
        ])
        select_theme_mode = ft.Row([
            ft.Switch(
                label="DARK",
                on_change=change_theme_mode
            )
        ])
        search_results = ft.GridView(
            expand=1,
            runs_count=10,
            max_extent=120,
            run_spacing=0,
            child_aspect_ratio=1,
        )
        status_bar = ft.Text()

        search_icons(select_type_group.value, search_txt.value)

        return ft.Column(
            [
                search_query,
                select_type,
                select_theme_mode,
                search_results,
                status_bar,
            ],
            expand=True,
        )


def main(page: ft.Page):
    page.theme_mode = ft.ThemeMode.LIGHT
    page.title = "My Flet icons browser"
    page.add(IconBrowser(expand=True))


ft.flet.app(target=main)
```