Yohey's Slow Living

Take a break, have a cup of coffee, and have a nice day.

Python Practice Project #10: Unit Converter

思いっきり備忘録を書くのを忘れていた。

 

というわけでいきなり完成したものをあげる。

今回作ったのは、単位の変換アプリ。KMからMiles、MilesからKMとか。

で、今回からいくつかCLIだけでなくGUIでの実装も目指してみた。

使ったのはflet。


#Day10 Unit Converter単位変換アプリ

###imports###
import flet as ft
from datetime import datetime
from zoneinfo import ZoneInfo

###sub-functions###
#km to mile
def km_to_mile(km):
    miles = km * 0.621371
    return miles

#mile to km
def mile_to_km(miles):
    km = miles / 0.621371
    return km

#celcius to fahrenheit
def celsius_to_fahrenheit(celsius):
    fahrenheit = (celsius * 9/5) + 32
    return fahrenheit

#fahrenheit to celsius
def fahrenheit_to_celsius(fahrenheit):
    celsius = (fahrenheit - 32) * 5/9
    return celsius

#Timezone A to B
def timezone_converter(time, timezone_a, timezone_b):
    timezone_difference = timezone_b - timezone_a
    converted_time = time + timezone_difference
    return converted_time

###main###
def main(page: ft.Page): #ft.pageでは動かなくて、ft.Pageと記述すること!
    page.title = "Unit Converter"
    #dark mode <-> light mode切り替え
    theme_switch = ft.Switch(label="Dark mode", value=False)
    def toggle_theme(e):
        if theme_switch.value:
            page.theme_mode = ft.ThemeMode.DARK
        else:
            page.theme_mode = ft.ThemeMode.LIGHT
        page.update()
    toggle_theme(None) #アプリ起動時は初期設定(ライトモード)で起動。
    theme_switch.on_change = toggle_theme
    #input_fieldは、日時変換とUnit変換では形式が違うから分岐させたほうがいい?Ex. 最初にOptionsを選ばせる→Optionに応じた変換フィールド(input)を作成する?
    input_field = ft.TextField(label="Format will be shown here", width=300)
    #dropdownを追加して、ドロップダウンに従い上記のvalue_inputを変換するex.num=32, dropdown=f to c --> convert to 0 in c
    #dropdownに渡せるのはlabel, options, value, on_changeなどの公式引数のみ(最初convert_menu=[ft.dropdown.Option...]と書いてエラーになっていた)
    #value="Timezone" -->これは、ドロップダウンのデフォルトの選択肢を決めている。
    convert_menu = ft.Dropdown(
        label="Options: ",
        options=[
            ft.dropdown.Option("Timezone"),
            ft.dropdown.Option("km <-> mile"),
            ft.dropdown.Option("Fahrenheit <-> Celsius")],
        value = "Timezone"
    )
    #Timezone変換用のサブオプション
    timezone_from = ft.Dropdown(
        label="From",
        options=[
            ft.dropdown.Option("Asia/Tokyo"),
            ft.dropdown.Option("UTC")],
        value="Asia/Tokyo",
        visible=True
    )

    timezone_to = ft.Dropdown(
        label="To",
        options=[
            ft.dropdown.Option("Asia/Tokyo"),
            ft.dropdown.Option("UTC")],
        value="UTC",
        visible=True
    )

    #conversion_dirのUI用の設定
    conversion_dir = ft.RadioGroup(
        content=ft.Column([
            ft.Radio(value="km_to_mile", label="km -> mile"),
            ft.Radio(value="mile_to_km", label="mile -> km"),
            ft.Radio(value="fahrenheit_to_celsius", label="F -> C"),
            ft.Radio(value="celsius_to_fahrenheit", label="C -> F")
        ]),
        visible=False
    )

    converted_text = ft.Text("Converted: ")

    #選択肢によってUIを切り替える処理
    def handle_menu_change(e):
        if convert_menu.value == "Timezone":
            timezone_from.visible = True
            timezone_to.visible = True
            input_field.label = "Enter time (YYYY-MM-DD HH:MM:SS):" #選択肢に応じて入力フォーマットを提示する
            conversion_dir.visible = False
        elif convert_menu.value == "km <-> mile":
            timezone_from.visible = False
            timezone_to.visible = False
            input_field.label = "Enter distance in kilometers:" #選択肢に応じて入力フォーマットを提示する
            conversion_dir.content.controls = [
                ft.Radio(value="km_to_mile", label="km -> mile"),
                ft.Radio(value="mile_to_km", label="mile -> km")
            ]
            conversion_dir.visible = True
        elif convert_menu.value == "Fahrenheit <-> Celsius":
            timezone_from.visible = False
            timezone_to.visible = False
            input_field.label = "Enter temperature in Fahrenheit:" #選択肢に応じて入力フォーマットを提示する
            conversion_dir.content.controls = [
                ft.Radio(value="fahrenheit_to_celsius", label="F -> C"),
                ft.Radio(value="celsius_to_fahrenheit", label="C -> F")
            ]
            conversion_dir.visible = True
        conversion_dir.update()
        input_field.update() #input_field.update()を行い、ラベル表示をちゃんと切り替える
        page.update()

    convert_menu.on_change = handle_menu_change

    #converter function
    def converter(e):
        try:
            if convert_menu.value == "Timezone":
                #選択したプルダウンがTimezoneの場合、入力した文字列をdatetimeに変換
                input_time = datetime.fromisoformat(input_field.value)
                tz_from = ZoneInfo(timezone_from.value)
                tz_to = ZoneInfo(timezone_to.value)
                converted_time = input_time.replace(tzinfo=tz_from).astimezone(tz_to)
                converted_text.value = f"Converted: {converted_time.strftime('%Y-%m-%d %H:%M:%S')}"
            #最初elif convert_menu == "km -> mile"と書いていて動かなかった。.valueをつけるように注意!
            #convert_menu は Dropdown オブジェクトそのものであり、.value を使って現在の選択値(文字列)を取得しないといけない。
            elif convert_menu.value == "km <-> mile":
                num = float(input_field.value)
                if conversion_dir.value == "km_to_mile": #最初"km -> mile"と記載していて動かなかった。ラベルではなくvalueの値を指定する。
                    km_to_mile(num)
                    converted_text.value = f"Converted: {km_to_mile(num):.2f} miles"
                elif conversion_dir.value == "mile_to_km":
                    mile_to_km(num)
                    converted_text.value = f"Converted: {mile_to_km(num):.2f} km"
            elif convert_menu.value == "Fahrenheit <-> Celsius":
                num = float(input_field.value)
                if conversion_dir.value == "fahrenheit_to_celsius":
                    fahrenheit_to_celsius(num)
                    converted_text.value = f"Converted: {fahrenheit_to_celsius(num):.2f} celsius"
                elif conversion_dir.value == "celsius_to_fahrenheit":
                    celsius_to_fahrenheit(num)
                    converted_text.value = f"Converted: {celsius_to_fahrenheit(num):.2f} °F"
        except Exception as ex:
            converted_text.value = f"Error: {ex}"
        page.update()

#Buttons
    convert_button = ft.ElevatedButton("Convert", on_click=converter)

    #UI設定
    page.add(
        theme_switch,
        convert_menu,
        ft.Row([timezone_from, timezone_to]),
        conversion_dir,
        input_field,
        convert_button,
        converted_text
    )

###app###
ft.app(target=main)


#Learning notes/ideas
#unit変換用の関数が多くて煩雑に見える。。。改善できないかな?
#unit変換用の関数群は別途作成して、その関数群を読み込んで、選択したUnitに対応して関数を呼び出すとか?

#ChatGPTからもらった課題と
#🎯 Day10:単位変換アプリ(Unit Converter)
#
#題材:
#簡単な単位変換ツールを作ってみよう!例として「距離(km ↔︎ mile)」や「温度(℃ ↔︎ °F)」などを切り替えて変換できるGUIを作成してみよう。
#
#機能要件:
#	•	数値入力欄
#	•	単位の種類を選ぶ(例:距離 or 温度)
#	•	変換方向を選べる(例:km → mile、または逆)
#	•	「変換」ボタンで結果を表示
#	•	見やすいレイアウトと結果表示
#
#Flet UIの構成ヒント:
#	•	数値入力用の TextField
#	•	変換タイプを選択する Dropdown
#	•	変換方向を選択する Radio か Dropdown
#	•	結果を表示する Text や Container
#	•	Button で変換実行
#