first commit
This commit is contained in:
commit
f7840ed78a
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
*.egg
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
.venv312/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# py2app build output
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Env files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
"""
|
||||||
|
Currency Converter — customtkinter edition
|
||||||
|
Rounded corners, dark/light mode toggle.
|
||||||
|
Dependencies: pip install requests pyperclip customtkinter
|
||||||
|
"""
|
||||||
|
|
||||||
|
import customtkinter as ctk
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import platform
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
HAS_REQUESTS = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_REQUESTS = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pyperclip
|
||||||
|
HAS_PYPERCLIP = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_PYPERCLIP = False
|
||||||
|
|
||||||
|
IS_MAC = platform.system() == "Darwin"
|
||||||
|
ctk.set_appearance_mode("dark")
|
||||||
|
ctk.set_default_color_theme("blue")
|
||||||
|
|
||||||
|
FONT = "SF Pro Text" if IS_MAC else "Helvetica"
|
||||||
|
FONT_D = "SF Pro Display" if IS_MAC else "Helvetica"
|
||||||
|
ACCENT = "#0A84FF"
|
||||||
|
GREEN = "#30D158"
|
||||||
|
|
||||||
|
# Dynamic colors per mode — (dark, light)
|
||||||
|
def T(dark, light):
|
||||||
|
"""Return a tuple customtkinter uses for dark/light."""
|
||||||
|
return (dark, light)
|
||||||
|
|
||||||
|
# Color pairs
|
||||||
|
C_BG = T("#1C1C1E", "#F2F2F7")
|
||||||
|
C_PANEL = T("#2C2C2E", "#FFFFFF")
|
||||||
|
C_PILL = T("#3A3A3C", "#E5E5EA")
|
||||||
|
C_PILLH = T("#555558", "#D1D1D6")
|
||||||
|
C_WHITE = T("#FFFFFF", "#000000")
|
||||||
|
C_GREY = T("#8E8E93", "#6C6C70")
|
||||||
|
C_GREY2 = T("#636366", "#8E8E93")
|
||||||
|
C_SEP = T("#38383A", "#C6C6C8")
|
||||||
|
|
||||||
|
FALLBACK_RATES = {
|
||||||
|
"USD": 1.0, "VND": 25450.0, "EUR": 0.92, "GBP": 0.79,
|
||||||
|
"JPY": 149.5, "KRW": 1340.0, "CNY": 7.24, "SGD": 1.35,
|
||||||
|
"THB": 35.5, "MYR": 4.72, "AUD": 1.54, "CAD": 1.37,
|
||||||
|
"CHF": 0.90, "HKD": 7.82, "INR": 83.2, "NZD": 1.65,
|
||||||
|
"SEK": 10.5, "NOK": 10.8, "DKK": 6.89, "BRL": 4.97,
|
||||||
|
"MXN": 17.2, "ZAR": 18.6, "TRY": 32.1, "RUB": 90.5,
|
||||||
|
"AED": 3.67, "SAR": 3.75, "ILS": 3.72, "PLN": 3.98,
|
||||||
|
"CZK": 23.1, "HUF": 358.0, "RON": 4.59, "IDR": 15750.0,
|
||||||
|
"PHP": 56.8, "PKR": 278.0, "BDT": 110.0, "NGN": 1550.0,
|
||||||
|
}
|
||||||
|
CURRENCIES = sorted(FALLBACK_RATES.keys())
|
||||||
|
SYMBOLS = {
|
||||||
|
"USD":"$", "VND":"₫", "EUR":"€", "GBP":"£", "JPY":"¥",
|
||||||
|
"KRW":"₩", "CNY":"¥", "SGD":"S$", "THB":"฿", "MYR":"RM",
|
||||||
|
"AUD":"A$", "CAD":"C$", "CHF":"Fr", "HKD":"HK$","INR":"₹",
|
||||||
|
"NZD":"NZ$","SEK":"kr", "NOK":"kr", "DKK":"kr", "BRL":"R$",
|
||||||
|
"MXN":"$", "ZAR":"R", "TRY":"₺", "RUB":"₽", "AED":"د.إ",
|
||||||
|
"SAR":"﷼", "ILS":"₪", "PLN":"zł", "CZK":"Kč", "HUF":"Ft",
|
||||||
|
"RON":"lei","IDR":"Rp", "PHP":"₱", "PKR":"₨", "BDT":"৳",
|
||||||
|
"NGN":"₦",
|
||||||
|
}
|
||||||
|
|
||||||
|
FALLBACK_RATES
|
||||||
|
class RateCache:
|
||||||
|
def __init__(self):
|
||||||
|
self.rates = dict(FALLBACK_RATES)
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
def fetch(self, cb=None):
|
||||||
|
if not HAS_REQUESTS:
|
||||||
|
if cb: cb(False); return
|
||||||
|
try:
|
||||||
|
r = requests.get("https://open.er-api.com/v6/latest/USD", timeout=8)
|
||||||
|
d = r.json()
|
||||||
|
if d.get("result") == "success":
|
||||||
|
with self._lock: self.rates = d["rates"]
|
||||||
|
if cb: cb(True)
|
||||||
|
else:
|
||||||
|
if cb: cb(False)
|
||||||
|
except Exception:
|
||||||
|
if cb: cb(False)
|
||||||
|
|
||||||
|
def convert(self, amt, frm, to):
|
||||||
|
with self._lock: r = self.rates
|
||||||
|
return amt / r[frm] * r[to]
|
||||||
|
|
||||||
|
def rate_str(self, frm, to):
|
||||||
|
with self._lock: r = self.rates
|
||||||
|
v = r[to] / r[frm]
|
||||||
|
return f"1 {frm} = {v:,.4f} {to}" if v >= 1 else f"1 {frm} = {v:.6f} {to}"
|
||||||
|
|
||||||
|
|
||||||
|
# ── App ───────────────────────────────────────────────────────────────────────
|
||||||
|
class App(ctk.CTk):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.cache = RateCache()
|
||||||
|
self._result_str = ""
|
||||||
|
self._build_window()
|
||||||
|
self._build_ui()
|
||||||
|
threading.Thread(target=self.cache.fetch,
|
||||||
|
kwargs={"cb": self._on_rates}, daemon=True).start()
|
||||||
|
self._live_convert()
|
||||||
|
|
||||||
|
def _build_window(self):
|
||||||
|
self.title("Currency")
|
||||||
|
self.configure(fg_color=C_BG)
|
||||||
|
self.resizable(False, False)
|
||||||
|
W, H = 320, 420
|
||||||
|
sw, sh = self.winfo_screenwidth(), self.winfo_screenheight()
|
||||||
|
self.geometry(f"{W}x{H}+{(sw-W)//2}+{(sh-H)//2}")
|
||||||
|
|
||||||
|
def _build_ui(self):
|
||||||
|
self.from_var = ctk.StringVar(value="USD")
|
||||||
|
self.to_var = ctk.StringVar(value="VND")
|
||||||
|
|
||||||
|
# ── Currency selector row ─────────────────────────────────────────
|
||||||
|
top = ctk.CTkFrame(self, fg_color=C_BG)
|
||||||
|
top.pack(fill="x", padx=16, pady=(16, 0))
|
||||||
|
top.columnconfigure(0, weight=1)
|
||||||
|
top.columnconfigure(2, weight=1)
|
||||||
|
|
||||||
|
# FROM dropdown
|
||||||
|
self._from_dd = ctk.CTkOptionMenu(
|
||||||
|
top, variable=self.from_var, values=CURRENCIES,
|
||||||
|
width=110, height=32, corner_radius=8,
|
||||||
|
fg_color=C_PILL, button_color=C_PILL,
|
||||||
|
button_hover_color=C_PILLH,
|
||||||
|
text_color=C_WHITE, dropdown_fg_color=C_PANEL,
|
||||||
|
dropdown_hover_color=C_PILL,
|
||||||
|
font=(FONT, 13, "bold"),
|
||||||
|
command=lambda _: self._on_from_change())
|
||||||
|
self._from_dd.grid(row=0, column=0, sticky="ew")
|
||||||
|
|
||||||
|
# Swap button
|
||||||
|
swap_btn = ctk.CTkButton(
|
||||||
|
top, text="⇄", width=36, height=32,
|
||||||
|
corner_radius=8,
|
||||||
|
fg_color=C_PILL, hover_color=C_PILLH,
|
||||||
|
text_color=C_GREY,
|
||||||
|
font=(FONT, 14, "bold"),
|
||||||
|
command=self._swap)
|
||||||
|
swap_btn.grid(row=0, column=1, padx=8)
|
||||||
|
|
||||||
|
# TO dropdown
|
||||||
|
self._to_dd = ctk.CTkOptionMenu(
|
||||||
|
top, variable=self.to_var, values=CURRENCIES,
|
||||||
|
width=110, height=32, corner_radius=8,
|
||||||
|
fg_color=C_PILL, button_color=C_PILL,
|
||||||
|
button_hover_color=C_PILLH,
|
||||||
|
text_color=C_WHITE, dropdown_fg_color=C_PANEL,
|
||||||
|
dropdown_hover_color=C_PILL,
|
||||||
|
font=(FONT, 13, "bold"),
|
||||||
|
command=lambda _: self._live_convert())
|
||||||
|
self._to_dd.grid(row=0, column=2, sticky="ew")
|
||||||
|
|
||||||
|
# ── Rate label ────────────────────────────────────────────────────
|
||||||
|
self._rate_lbl = ctk.CTkLabel(self, text="",
|
||||||
|
font=(FONT, 11),
|
||||||
|
text_color=C_GREY)
|
||||||
|
self._rate_lbl.pack(pady=(10, 0))
|
||||||
|
|
||||||
|
# Separator
|
||||||
|
ctk.CTkFrame(self, height=1, fg_color=C_SEP, corner_radius=0)\
|
||||||
|
.pack(fill="x", padx=16, pady=(10, 0))
|
||||||
|
|
||||||
|
# ── INPUT card ────────────────────────────────────────────────────
|
||||||
|
in_card = ctk.CTkFrame(self, fg_color=C_PANEL, corner_radius=12)
|
||||||
|
in_card.pack(fill="x", padx=16, pady=(12, 0))
|
||||||
|
|
||||||
|
in_hdr = ctk.CTkFrame(in_card, fg_color=C_PANEL)
|
||||||
|
in_hdr.pack(fill="x", padx=14, pady=(10, 0))
|
||||||
|
|
||||||
|
self._in_tag = ctk.CTkLabel(in_hdr, text="USD $",
|
||||||
|
font=(FONT, 10, "bold"),
|
||||||
|
text_color=ACCENT)
|
||||||
|
self._in_tag.pack(side="left")
|
||||||
|
|
||||||
|
self._copy_in_btn = ctk.CTkButton(
|
||||||
|
in_hdr, text="⎘", width=28, height=22,
|
||||||
|
corner_radius=6,
|
||||||
|
fg_color="transparent", hover_color=C_PILL,
|
||||||
|
text_color=C_GREY2, font=(FONT, 11),
|
||||||
|
command=self._copy_input)
|
||||||
|
self._copy_in_btn.pack(side="right")
|
||||||
|
|
||||||
|
self.amount_var = ctk.StringVar(value="1")
|
||||||
|
self._entry = ctk.CTkEntry(
|
||||||
|
in_card, textvariable=self.amount_var,
|
||||||
|
font=(FONT_D, 38, "bold"),
|
||||||
|
fg_color=C_PANEL, border_width=0,
|
||||||
|
text_color=C_WHITE,
|
||||||
|
justify="center", width=280, height=56)
|
||||||
|
self._entry.pack(pady=(2, 12), padx=14)
|
||||||
|
self._entry.bind("<KeyRelease>", lambda e: self._live_convert())
|
||||||
|
self._entry.bind("<Return>", lambda e: self._live_convert())
|
||||||
|
|
||||||
|
# ── Swap vertical ─────────────────────────────────────────────────
|
||||||
|
mid = ctk.CTkFrame(self, fg_color=C_BG)
|
||||||
|
mid.pack(pady=8)
|
||||||
|
ctk.CTkButton(mid, text="↕", width=32, height=32,
|
||||||
|
corner_radius=16,
|
||||||
|
fg_color=C_PILL, hover_color=C_PILLH,
|
||||||
|
text_color=C_GREY, font=(FONT, 13),
|
||||||
|
command=self._swap).pack()
|
||||||
|
|
||||||
|
# ── OUTPUT card ───────────────────────────────────────────────────
|
||||||
|
out_card = ctk.CTkFrame(self, fg_color=C_PANEL, corner_radius=12)
|
||||||
|
out_card.pack(fill="x", padx=16, pady=(0, 12))
|
||||||
|
|
||||||
|
out_hdr = ctk.CTkFrame(out_card, fg_color=C_PANEL)
|
||||||
|
out_hdr.pack(fill="x", padx=14, pady=(10, 0))
|
||||||
|
|
||||||
|
self._out_tag = ctk.CTkLabel(out_hdr, text="VND ₫",
|
||||||
|
font=(FONT, 10, "bold"),
|
||||||
|
text_color=ACCENT)
|
||||||
|
self._out_tag.pack(side="left")
|
||||||
|
|
||||||
|
self._copy_out_btn = ctk.CTkButton(
|
||||||
|
out_hdr, text="⎘", width=28, height=22,
|
||||||
|
corner_radius=6,
|
||||||
|
fg_color="transparent", hover_color=C_PILL,
|
||||||
|
text_color=C_GREY2, font=(FONT, 11),
|
||||||
|
command=self._copy_output)
|
||||||
|
self._copy_out_btn.pack(side="right")
|
||||||
|
|
||||||
|
self._result_lbl = ctk.CTkLabel(
|
||||||
|
out_card, text="—",
|
||||||
|
font=(FONT_D, 38, "bold"),
|
||||||
|
text_color=C_WHITE)
|
||||||
|
self._result_lbl.pack(pady=(2, 12))
|
||||||
|
|
||||||
|
# ── Status bar ────────────────────────────────────────────────────
|
||||||
|
bot = ctk.CTkFrame(self, fg_color=C_BG)
|
||||||
|
bot.pack(fill="x", padx=16, pady=(0, 12))
|
||||||
|
|
||||||
|
self._status_lbl = ctk.CTkLabel(bot, text="● connecting…",
|
||||||
|
font=(FONT, 9), text_color=C_GREY2)
|
||||||
|
self._status_lbl.pack(side="left")
|
||||||
|
|
||||||
|
# Dark / Light toggle
|
||||||
|
self._mode = "dark"
|
||||||
|
self._mode_btn = ctk.CTkButton(
|
||||||
|
bot, text="☀", width=28, height=22,
|
||||||
|
corner_radius=6,
|
||||||
|
fg_color="transparent", hover_color=C_PILL,
|
||||||
|
text_color=C_GREY2, font=(FONT, 13),
|
||||||
|
command=self._toggle_mode)
|
||||||
|
self._mode_btn.pack(side="right", padx=(6, 0))
|
||||||
|
|
||||||
|
ctk.CTkLabel(bot, text="Currency Converter",
|
||||||
|
font=(FONT, 9), text_color=C_GREY2).pack(side="right")
|
||||||
|
|
||||||
|
# ── Logic ─────────────────────────────────────────────────────────────────
|
||||||
|
def _toggle_mode(self):
|
||||||
|
if self._mode == "dark":
|
||||||
|
self._mode = "light"
|
||||||
|
ctk.set_appearance_mode("light")
|
||||||
|
self._mode_btn.configure(text="🌙")
|
||||||
|
else:
|
||||||
|
self._mode = "dark"
|
||||||
|
ctk.set_appearance_mode("dark")
|
||||||
|
self._mode_btn.configure(text="☀")
|
||||||
|
|
||||||
|
def _on_from_change(self):
|
||||||
|
self._refresh_tags()
|
||||||
|
self._live_convert()
|
||||||
|
|
||||||
|
def _refresh_tags(self):
|
||||||
|
f, t = self.from_var.get(), self.to_var.get()
|
||||||
|
self._in_tag.configure(text=f"{f} {SYMBOLS.get(f,'')}")
|
||||||
|
self._out_tag.configure(text=f"{t} {SYMBOLS.get(t,'')}")
|
||||||
|
|
||||||
|
def _get_amount(self):
|
||||||
|
raw = self.amount_var.get().strip().replace(",", "")
|
||||||
|
m = re.findall(r"[\d.]+", raw)
|
||||||
|
if not m: return None
|
||||||
|
try: return float(m[0])
|
||||||
|
except ValueError: return None
|
||||||
|
|
||||||
|
def _fmt(self, v):
|
||||||
|
if v >= 1000: return f"{v:,.2f}"
|
||||||
|
if v >= 1: return f"{v:.4f}"
|
||||||
|
return f"{v:.6f}"
|
||||||
|
|
||||||
|
def _live_convert(self):
|
||||||
|
self._refresh_tags()
|
||||||
|
amt = self._get_amount()
|
||||||
|
if amt is None: return
|
||||||
|
frm, to = self.from_var.get(), self.to_var.get()
|
||||||
|
try:
|
||||||
|
result = self.cache.convert(amt, frm, to)
|
||||||
|
self._result_str = self._fmt(result)
|
||||||
|
self._result_lbl.configure(text=self._result_str, text_color=C_WHITE)
|
||||||
|
self._rate_lbl.configure(text=self.cache.rate_str(frm, to))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _swap(self):
|
||||||
|
a, b = self.from_var.get(), self.to_var.get()
|
||||||
|
res = self._result_str.replace(",", "") if self._result_str else None
|
||||||
|
self.from_var.set(b)
|
||||||
|
self.to_var.set(a)
|
||||||
|
if res:
|
||||||
|
try: self.amount_var.set(res)
|
||||||
|
except Exception: pass
|
||||||
|
self._refresh_tags()
|
||||||
|
self._live_convert()
|
||||||
|
|
||||||
|
def _do_copy(self, btn, value):
|
||||||
|
if not value: return
|
||||||
|
try:
|
||||||
|
if HAS_PYPERCLIP: pyperclip.copy(value)
|
||||||
|
else: self.clipboard_clear(); self.clipboard_append(value)
|
||||||
|
except Exception: pass
|
||||||
|
btn.configure(text="✓", text_color=GREEN)
|
||||||
|
self.after(1200, lambda: btn.configure(text="⎘", text_color=C_GREY2))
|
||||||
|
|
||||||
|
def _copy_input(self):
|
||||||
|
self._do_copy(self._copy_in_btn, self.amount_var.get())
|
||||||
|
|
||||||
|
def _copy_output(self):
|
||||||
|
self._do_copy(self._copy_out_btn, self._result_str)
|
||||||
|
|
||||||
|
def _on_rates(self, live):
|
||||||
|
def _u():
|
||||||
|
self._status_lbl.configure(
|
||||||
|
text="● live rates" if live else "● offline",
|
||||||
|
text_color=GREEN if live else C_GREY2)
|
||||||
|
self._live_convert()
|
||||||
|
self.after(0, _u)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("💱 Currency | ⌘+Shift+T")
|
||||||
|
App().mainloop()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
# 💱 Currency Converter — Desktop App
|
||||||
|
|
||||||
|
Cross-platform desktop app (Windows & macOS) viết bằng Python.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Tính năng
|
||||||
|
|
||||||
|
| Tính năng | Mô tả |
|
||||||
|
| --------------------- | ---------------------------------------------------------------- |
|
||||||
|
| 🔄 Chuyển đổi tiền tệ | 40+ loại tiền, tỉ giá thực từ open.er-api.com |
|
||||||
|
| ⌨️ Global Hotkey | `Ctrl+Shift+C` — bôi đen số bất kỳ → nhấn hotkey → tự chuyển đổi |
|
||||||
|
| 📋 Copy nhanh | Sao chép kết quả 1 click |
|
||||||
|
| 🔃 Swap | Đổi chiều nhanh (From ↔ To) |
|
||||||
|
| 📜 Lịch sử | 4 lần chuyển đổi gần nhất |
|
||||||
|
| 🌐 Offline fallback | Nếu không có mạng, dùng tỉ giá offline gần đúng |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Cài đặt
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Cài Python 3.10+ (nếu chưa có)
|
||||||
|
|
||||||
|
# 2. Cài dependencies
|
||||||
|
pip install requests pyperclip keyboard
|
||||||
|
|
||||||
|
# 3. Chạy app
|
||||||
|
python currency_converter.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖱️ Cách dùng tính năng Hotkey (highlight text)
|
||||||
|
|
||||||
|
1. Bôi đen / highlight một đoạn text có chứa số (VD: "Price: 299.99")
|
||||||
|
2. Nhấn `Ctrl+C` để copy (như bình thường)
|
||||||
|
3. Nhấn **`Ctrl+Shift+C`** → App tự bật lên, điền số và chuyển đổi
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Lưu ý theo hệ điều hành
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
- Chạy bình thường, không cần quyền admin (trừ khi app khác chặn hotkey)
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
- Cần cấp quyền **Accessibility**:
|
||||||
|
- System Settings → Privacy & Security → Accessibility
|
||||||
|
- Thêm Terminal hoặc Python interpreter của bạn
|
||||||
|
- Nếu dùng `Cmd` thay `Ctrl`, sửa dòng `HOTKEY = "ctrl+shift+c"` thành `"cmd+shift+c"`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Tuỳ chỉnh
|
||||||
|
|
||||||
|
Mở file `currency_converter.py`, tìm các hằng số ở đầu file:
|
||||||
|
|
||||||
|
```python
|
||||||
|
HOTKEY = "ctrl+shift+c" # Đổi phím tắt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📡 Nguồn tỉ giá
|
||||||
|
|
||||||
|
- **Live**: [open.er-api.com](https://open.er-api.com) (miễn phí, cập nhật hàng ngày)
|
||||||
|
- **Offline**: Tỉ giá xấp xỉ được nhúng sẵn trong code
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
requests>=2.28.0
|
||||||
|
pyperclip>=1.8.2
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
APP = ['main.py']
|
||||||
|
DATA_FILES = []
|
||||||
|
OPTIONS = {
|
||||||
|
'argv_emulation': False,
|
||||||
|
'packages': ['customtkinter', 'requests', 'pyperclip'],
|
||||||
|
'iconfile': None,
|
||||||
|
'plist': {
|
||||||
|
'CFBundleName': 'Currency',
|
||||||
|
'CFBundleDisplayName': 'Currency Converter',
|
||||||
|
'CFBundleIdentifier': 'com.currency.converter',
|
||||||
|
'CFBundleVersion': '1.0.0',
|
||||||
|
'CFBundleShortVersionString': '1.0',
|
||||||
|
'LSMinimumSystemVersion': '10.13',
|
||||||
|
'NSHighResolutionCapable': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(
|
||||||
|
app=APP,
|
||||||
|
data_files=DATA_FILES,
|
||||||
|
options={'py2app': OPTIONS},
|
||||||
|
setup_requires=['py2app'],
|
||||||
|
)
|
||||||
Loading…
Reference in New Issue