להורדה | ~~רציתם תוספים לתוכנה? קיבלתם!...~~
-
@יאיר-דניאל לי נראה שכדאי שלספרים האישיים יהיה מיקום נפרד ולא שיהיה באותה תקיה של כל הספרים
עניין של טעם.
-
עניין של טעם.
@יאיר-דניאל לטעמי זה לא עניין של טעם

-
בעקבות בקשות ודיונים שונים בפורום - בנוגע לכך האם אפשר שיהיה תוספים לתוכנה - שדרכם יוכלו להוריד מאגרים חיצוניים כמו שלי ושל @שמואל5 - בניתי (-כמובן בעזרת AI...) סוג של תוסף לתוכנה - היא כעת בהרצה, ועוד מעט תעלה.
פרטים בספויילר הראשון:אין אפשרות להוסיף תוספים חיצוניים לתוכנה שכתובה בפלאטר ודארט, מה שכן ישנם 3 אפשרויות:
**1. שימוש ב-Dynamic Loading (טעינה דינמית)
זו הדרך הנפוצה ביותר בתוכנות שולחניות (Desktop). ניתן לבנות את האפליקציה כך שהיא תחפש קבצי ספרייה חיצוניים (כמו .dll בחלונות או .so בלינוקס) בתיקייה מסוימת בזמן ריצה.איך זה עובד: משתמשים בחבילת dart:ffi כדי לקרוא לפונקציות מתוך קבצי ה-C/C++ החיצוניים.
הוספת כפתורים: התוסף יכול להחזיר נתונים שהתוכנה הראשית מפרשת כהוראה להציג כפתור או פונקציה חדשה.
- הזרקת קוד באמצעות מנוע Scripting
זוהי הדרך הגמישה ביותר להוספת פונקציונליות ושינוי ממשק המשתמש (UI) ללא צורך בקימפול.
שילוב מנוע לואה (Lua) או JavaScript: ניתן להטמיע בתוך אפליקציית ה-Flutter מנוע הרצה שמפרש קבצי טקסט חיצוניים.
דוגמה: התוסף יהיה קובץ .lua המגדיר פונקציות ומיקומים של כפתורים. האפליקציה קוראת את הקובץ ובונה את ה-UI בהתאם.
- ארכיטקטורת Micro-Frontends (באמצעות נתיבים דינמיים)
אם מדובר בתוספים שאתה מפתח בעצמך ורק רוצה לאפשר למשתמש לבחור אם להתקין אותם:
ניתן להשתמש בטכניקה של Deferred Loading. זה מאפשר להוריד חלקי קוד מהרשת רק כשהמשתמש זקוק להם, מה שחוסך במקום ומאפשר הוספת רכיבים בצורה מודולרית.**
עד כאן תשובותו של הבן הרשע, סליחה של ג'מיני...
למעשה מה שעשיתי זה כך:
ישנה תוכנה בשם AutoHotkey שמאפשרת להזריק סקריפטים מעל תוכנות קיימות, וליצור כפתורים צפים עליהם.
כתבתי בעזרת הAI קוד פייתון וקוד AHK (הקוד שאיתו התוכנה הזו עובדת) שמה שהם עושים ביחד הוא כך - התוכנה רצה ברקע במחשב (היא לא לוקחת זיכרון - בקושי 2M) וכשאוצריא עולה - מיד צץ כפתור חדש בסרגל - עם אייקון של הורדה, וטולטיפ של הסבר על הכפתור.
לחיצה עליו פותחת חלון ששואל האם להוריד את המאגר שלי - לתוך אוצריא, ובלחיצה על אישור - נפתח חלון התקדמות שבסופו ההודעה "הספרים הותקנו בהצלחה"
עיינו בספויילר הבא - לתמונות.תמונות:



לאחר שעות ששרפתי על זה - ובאגים בלי סוף - נמאס לי מהעניין והחלטתי שאני פורש.
מצורף הקודים למי שרוצה להמשיך את זה מכאן.
בהצלחה, וסליחה מכל מי שפיתחתי בו ציפיות.
הקודים להורדה:
downloader.py
OtzariaButton (1).ahk
(יש להתקין את הספריה הבאה:)pip install requestsקוד הפייתון (- שעליו רץ הקוד השני)
import requests import sys import zipfile import shutil from pathlib import Path # הגדרות מערכת - נשאר בדיוק לפי ההנחיות שלך API_URL = "https://api.github.com/repos/YairDaniel123/Otzarya-Library/releases/latest" OTZARIA_LIB = Path(r"C:\ProgramData\otzaria\books") TEMP_FILE = Path("download.tmp") HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} CHUNK_SIZE = 1024 * 1024 def get_remote_assets(): try: r = requests.get(API_URL, headers=HEADERS, timeout=15) r.raise_for_status() data = r.json() assets = {} for a in data.get("assets", []): if not a["name"].endswith(".zip"): continue name = a.get("label") or a["name"] assets[name] = { "url": a["browser_download_url"], "updated": a["updated_at"], "is_full": (a["name"] == "full-library.zip") } return assets except Exception as e: print(f"ERROR:LIST|{str(e)}", flush=True) sys.exit(1) def check_updates(): if not OTZARIA_LIB.exists(): try: OTZARIA_LIB.mkdir(parents=True, exist_ok=True) except Exception as e: print(f"ERROR:PERMISSION|{str(e)}", flush=True) return remote = get_remote_assets() results = [] for name, info in remote.items(): folder = name.replace(".zip", "") version_file = OTZARIA_LIB / folder / ".version_info" is_up_to_date = version_file.exists() and version_file.read_text(encoding="utf-8").strip() == info["updated"] if not is_up_to_date: type_id = "0" if info["is_full"] else "1" results.append(f"{type_id}|{name}|{info['url']}|{info['updated']}") if results: results.sort() print(f"LIST:{';'.join(results)}", flush=True) else: print("EMPTY", flush=True) def download_and_extract(url, updated_at, display_name): try: with requests.get(url, stream=True, headers=HEADERS, timeout=(10, None)) as r: r.raise_for_status() total = int(r.headers.get("content-length", 0)) done = 0 with open(TEMP_FILE, "wb") as f: for chunk in r.iter_content(CHUNK_SIZE): if chunk: f.write(chunk) done += len(chunk) if total: print(f"DL_PROGRESS:{int(done/total*100)}", flush=True) print("DL_FINISHED", flush=True) folder_name = display_name.replace(".zip", "") target_dir = OTZARIA_LIB / folder_name temp_extract = OTZARIA_LIB / f"temp_{folder_name}" if temp_extract.exists(): shutil.rmtree(temp_extract) temp_extract.mkdir(parents=True) with zipfile.ZipFile(TEMP_FILE, "r") as z: files = [f for f in z.namelist() if not f.endswith(".version_info")] for i, f in enumerate(files): z.extract(f, temp_extract) print(f"EX_PROGRESS:{int((i+1)/len(files)*100)}", flush=True) if target_dir.exists(): shutil.rmtree(target_dir) temp_extract.rename(target_dir) (target_dir / ".version_info").write_text(updated_at, encoding="utf-8") print("DONE", flush=True) except Exception as e: print(f"ERROR:PROCESS|{str(e)}", flush=True) finally: if TEMP_FILE.exists(): TEMP_FILE.unlink() if __name__ == "__main__": if len(sys.argv) < 2: sys.exit() cmd = sys.argv[1] if cmd == "CHECK": check_updates() elif "|" in cmd: p = cmd.split("|") if len(p) >= 4: download_and_extract(p[2], p[3], p[1])קוד AHK (יש צורך להתקין את התוכנה הזו בשביל להריץ את הקוד, תוכנה קטנה ממש).
#Requires AutoHotkey v2.0 #SingleInstance Force global MainColor := "F8F4E8" global AccentColor := "7D5800" global BgColor := "EDE8D5" global IsHover := false global Queue := [] global Worker := "" global IsCancelled := false global OurWindows := Map() global SelectGui := unset global ProgGui := unset global MsgGui := unset ; ===================================================== ; גרירת חלונות ; ===================================================== OnMessage(0x0201, WM_LBUTTONDOWN) WM_LBUTTONDOWN(wParam, lParam, msg, hwnd) { rootHwnd := DllCall("GetAncestor", "ptr", hwnd, "uint", 2, "ptr") if !OurWindows.Has(rootHwnd) return try { cls := WinGetClass("ahk_id " hwnd) if (cls = "Button" || cls = "Edit") return } PostMessage(0xA1, 2,,, "ahk_id " rootHwnd) } ApplyRounding(guiObj) { guiObj.Opt("+LastFound") DllCall("dwmapi\DwmSetWindowAttribute", "ptr", guiObj.Hwnd, "uint", 33, "ptr*", 2, "uint", 4) } ; ===================================================== ; כפתור הורדה צף ; ===================================================== BtnGui := Gui("+AlwaysOnTop -Caption +ToolWindow +LastFound") BtnGui.BackColor := "EEAA99" WinSetTransColor("EEAA99", BtnGui) BtnGui.SetFont("s22", "Segoe UI Symbol") global BtnTxt := BtnGui.Add("Text", "x0 y0 w60 h50 Center c" AccentColor, "📥") BtnTxt.OnEvent("Click", ShowSelectionMenu) OurWindows[BtnGui.Hwnd] := true TipGui := Gui("-Caption +AlwaysOnTop +ToolWindow") TipGui.BackColor := "2B2B2B" TipGui.SetFont("s9 w800 cWhite", "Segoe UI") TipGui.Add("Text", "x8 y4 w80 h44 Center", "הורדת מאגרי ספרים מותאמים") ApplyRounding(TipGui) OurWindows[TipGui.Hwnd] := true SetTimer(WatchOtzaria, 250) WatchOtzaria() { global IsHover TargetID := WinExist("ahk_exe otzaria.exe") ActiveID := WinActive("A") SelectHwnd := (IsSet(SelectGui) && IsObject(SelectGui)) ? SelectGui.Hwnd : 0 ProgHwnd := (IsSet(ProgGui) && IsObject(ProgGui)) ? ProgGui.Hwnd : 0 MsgHwnd := (IsSet(MsgGui) && IsObject(MsgGui)) ? MsgGui.Hwnd : 0 ; בדיקה האם אנחנו עובדים כרגע על אוצריא או על אחד החלונות שלנו IsOtzariaContext := TargetID && (ActiveID = TargetID || ActiveID = SelectHwnd || ActiveID = BtnGui.Hwnd || ActiveID = ProgHwnd || ActiveID = MsgHwnd) if IsOtzariaContext { WinGetPos(&X, &Y, &W, &H, "ahk_id " TargetID) btnX := X + W - 78 btnY := Y + H - 186 BtnGui.Show("x" btnX " y" btnY " w60 h50 NoActivate") ; החזרת החלונות במידה וחזרנו לאוצריא מתוכנה אחרת if SelectHwnd try SelectGui.Show("NoActivate") if ProgHwnd try ProgGui.Show("NoActivate") MouseGetPos(,, &HoverWin) if (HoverWin = BtnGui.Hwnd) { if !IsHover { TipGui.Show("x" (btnX - 85) " y" (btnY + 5) " NoActivate") IsHover := true } } else if IsHover { TipGui.Hide() IsHover := false } } else { BtnGui.Hide() TipGui.Hide() ; הסתרת חלונות בעת מעבר לתוכנה אחרת (לא סגירה, רק הסתרה) if SelectHwnd try SelectGui.Hide() if ProgHwnd try ProgGui.Hide() } } ; ===================================================== ; חלון בחירת ספרים מעוצב וימין-שמאל (RTL) ; ===================================================== ShowSelectionMenu(*) { BtnTxt.Text := "⏳" try { shell := ComObject("WScript.Shell") exec := shell.Exec("pythonw.exe downloader.py CHECK") res := exec.StdOut.ReadAll() } catch { BtnTxt.Text := "📥" CustomMsgBox("שגיאה: לא ניתן להפעיל את פייתון.", "שגיאת הפעלה") return } BtnTxt.Text := "📥" if !InStr(res, "LIST:") || InStr(res, "EMPTY") { CustomMsgBox("המאגר מעודכן!", "עדכון ספריה") return } global SelectGui := Gui("+AlwaysOnTop -Caption") SelectGui.BackColor := MainColor ApplyRounding(SelectGui) OurWindows[SelectGui.Hwnd] := true SelectGui.SetFont("s15 w700 c" AccentColor, "Segoe UI") SelectGui.Add("Text", "x0 y15 w420 Center", "עדכון ספריה") SelectGui.SetFont("s11 w600 cBlack", "Segoe UI") ; +0x20 ו-Right אחראים על הפיכת הצ'קבוקס לימין שמאלה cbAll := SelectGui.Add("Checkbox", "x20 y+20 w380 Right +0x20", "בחר / בטל הכל (ללא המאגר המלא)") Checkboxes := [] items := StrSplit(StrReplace(res, "LIST:", ""), ";") for item in items { if (Trim(item) = "") { continue } p := StrSplit(item, "|") isFull := (p[1] = "0") SelectGui.SetFont("s11 w400 cBlack", "Segoe UI") cb := SelectGui.Add("Checkbox", (p[1] = "1" ? "Checked " : "") "x20 y+10 w380 Right +0x20", p[2]) Checkboxes.Push({full: item, cb: cb, isFull: isFull}) } cbAll.OnEvent("Click", (self, *) => ToggleAll(self.Value, Checkboxes)) BtnDown := SelectGui.Add("Button", "Default w160 h35 x40 y+25", "הורד") BtnCancel := SelectGui.Add("Button", "w160 h35 x+20", "ביטול") BtnDown.OnEvent("Click", (*) => StartSelectedDownload(SelectGui, Checkboxes)) BtnCancel.OnEvent("Click", (*) => SelectGui.Destroy()) SelectGui.Show("w420 Center") } ToggleAll(val, Checkboxes) { for item in Checkboxes { if !item.isFull item.cb.Value := val } } ; ===================================================== ; הודעה מעוצבת בסגנון אוצריא ; ===================================================== CustomMsgBox(txt, title) { global MsgGui := Gui("-Caption +AlwaysOnTop +Owner") MsgGui.BackColor := MainColor ApplyRounding(MsgGui) OurWindows[MsgGui.Hwnd] := true MsgGui.SetFont("s15 w700 c" AccentColor, "Segoe UI") MsgGui.Add("Text", "x0 y20 w350 Center", title) MsgGui.SetFont("s11 w400 cBlack", "Segoe UI") MsgGui.Add("Text", "x25 y+15 w300 Center +Wrap", txt) BtnOk := MsgGui.Add("Button", "Default w120 h35 x115 y+25", "אישור") BtnOk.OnEvent("Click", (*) => MsgGui.Destroy()) MsgGui.Show("w350 Center") } ; ===================================================== ; חלון התקדמות ; ===================================================== ProgressShow(FileName, Remaining) { global ProgGui, StatusTxt, Bar, PctLabel ProgGui := Gui("-Caption +AlwaysOnTop") ProgGui.BackColor := MainColor ApplyRounding(ProgGui) OurWindows[ProgGui.Hwnd] := true ProgGui.SetFont("s14 w700 c" AccentColor, "Segoe UI") ProgGui.Add("Text", "x0 y20 w400 Center", "הורדה בתהליך") ProgGui.SetFont("s11 w600 c" AccentColor, "Segoe UI") ProgGui.Add("Text", "x25 y+15 w350 Center +Wrap", FileName) ProgGui.SetFont("s12 w700 c" AccentColor, "Segoe UI") StatusTxt := ProgGui.Add("Text", "x0 y+15 w400 Center", "מוריד...") Bar := ProgGui.Add("Progress", "x30 y+12 w340 h18 cGreen Background" BgColor, 0) ProgGui.SetFont("s11 w700 cBlack", "Segoe UI") PctLabel := ProgGui.Add("Text", "x0 y+8 w400 Center", "0%") CancelBtn := ProgGui.Add("Button", "w120 h35 x140 y+20", "ביטול") CancelBtn.OnEvent("Click", CancelDownload) ProgGui.Show("w400 Center") } ; ===================================================== ; ניהול תור הורדות ; ===================================================== StartSelectedDownload(GuiObj, Checkboxes) { global Queue := [], IsCancelled := false for item in Checkboxes { if item.cb.Value Queue.Push(item.full) } GuiObj.Destroy() if Queue.Length > 0 NextDownload() else CustomMsgBox("לא נבחרו ספרים להורדה.", "שים לב") } NextDownload() { global Queue, IsCancelled, Worker if IsCancelled || (Queue.Length = 0) { if !IsCancelled CustomMsgBox("הספריה עודכנה בהצלחה!", "סיום הורדה") return } CurrentItem := Queue.RemoveAt(1) p := StrSplit(CurrentItem, "|") ProgressShow(p[2], Queue.Length) Worker := ComObject("WScript.Shell").Exec('pythonw.exe downloader.py "' CurrentItem '"') SetTimer(CheckStatus, 100) } CheckStatus() { global Worker, ProgGui, StatusTxt, Bar, PctLabel if Worker.StdOut.AtEndOfStream { SetTimer(CheckStatus, 0) try { if IsObject(ProgGui) && (ProgGui != "") { ProgGui.Destroy() ProgGui := unset CustomMsgBox("ההורדה נכשלה.`nבדוק חיבור לאינטרנט.", "שגיאה") } } return } line := Worker.StdOut.ReadLine() if InStr(line, "DL_PROGRESS:") { pct := StrReplace(line, "DL_PROGRESS:", "") Bar.Value := pct PctLabel.Text := pct "%" } else if InStr(line, "DL_FINISHED") { StatusTxt.Text := "מחלץ קבצים..." Bar.Value := 0 } else if InStr(line, "EX_PROGRESS:") { pct := StrReplace(line, "EX_PROGRESS:", "") Bar.Value := pct PctLabel.Text := pct "%" } else if InStr(line, "DONE") { SetTimer(CheckStatus, 0) ProgGui.Destroy() ProgGui := unset NextDownload() } } CancelDownload(*) { global Worker, IsCancelled, ProgGui IsCancelled := true SetTimer(CheckStatus, 0) try Worker.Terminate() ProgGui.Destroy() ProgGui := unset CustomMsgBox("ההורדה בוטלה.", "ביטול") }@יאיר-דניאל התוסף קיים לציבור או "עתיד לבוא" פשוט הסמל הוא

תודה רבה - הזרקת קוד באמצעות מנוע Scripting
-
@יאיר-דניאל התוסף קיים לציבור או "עתיד לבוא" פשוט הסמל הוא

תודה רבה@u88 האמת שזה היה מוכן לגמרי - והייתי אמור להעלות אותו שעה - שעתיים אחרי שפרסמתי את הפוסט, אבל שיפרתי כמה דברים בעיצוב שלו, ואז הגיע @דבש עם הרעיון היפה - לאפשר למשתמש לבחור מה להוריד (בברירת המחדל הראשונית זה הוריד את כל המאגר כולו), מה שסיבך אותי מאוד בביצוע - מבחינת יצירת אוטומציה בגיטאב - שתיצור קובץ זיפ להורדה מכל תיקייה בנפרד, ארך לי יומיים לפתור את זה (!), ואחרי שהצלחתי בסייעתא דשמיא, הייתי צריך לסדר את כל העיצוב = UI מחדש, כך שיתאים לפונקציות שנוספו, ואז כבר הגיע ער"פ, בדיקת חמץ ושות' וזה נתקע - עד אחרי החג, בעז"ה.
-
@u88 האמת שזה היה מוכן לגמרי - והייתי אמור להעלות אותו שעה - שעתיים אחרי שפרסמתי את הפוסט, אבל שיפרתי כמה דברים בעיצוב שלו, ואז הגיע @דבש עם הרעיון היפה - לאפשר למשתמש לבחור מה להוריד (בברירת המחדל הראשונית זה הוריד את כל המאגר כולו), מה שסיבך אותי מאוד בביצוע - מבחינת יצירת אוטומציה בגיטאב - שתיצור קובץ זיפ להורדה מכל תיקייה בנפרד, ארך לי יומיים לפתור את זה (!), ואחרי שהצלחתי בסייעתא דשמיא, הייתי צריך לסדר את כל העיצוב = UI מחדש, כך שיתאים לפונקציות שנוספו, ואז כבר הגיע ער"פ, בדיקת חמץ ושות' וזה נתקע - עד אחרי החג, בעז"ה.
-
@יאיר-דניאל אגב, הרב @י.-פל. עמל על אפשרות מובנית לתוספים, יתכן וכדאי לך לחכות ולא להשקיע כ"כ, או לבקש ממנו את התיעוד ולהתחיל לעבוד על התוסף שיעבוד בצורה רשמית
@י.-פל. מתנצל אם היה אסור לגלות...
@יאיר-דניאל אגב, הרב @י.-פל. עמל על אפשרות מובנית לתוספים, יתכן וכדאי לך לחכות ולא להשקיע כ"כ, או לבקש ממנו את התיעוד ולהתחיל לעבוד על התוסף שיעבוד בצורה רשמית
תודה, אבל זה לא רלוונטי, צורת הבניה של "התוסף" שבניתי לא קשורה בכלל למבנה של תוספים בפלאטר, היא סה"כ תוכנה חיצונית שמוסיפה כפתור מעל החלון של אוצריא - זה לא מוזרק בתוכו.
(יכולתי לעשות את זה, אבל זה דרש הרבה מאמץ וזמן, שלא ראיתי ענין וצורך להשקיע בזה). -
@u88 האמת שזה היה מוכן לגמרי - והייתי אמור להעלות אותו שעה - שעתיים אחרי שפרסמתי את הפוסט, אבל שיפרתי כמה דברים בעיצוב שלו, ואז הגיע @דבש עם הרעיון היפה - לאפשר למשתמש לבחור מה להוריד (בברירת המחדל הראשונית זה הוריד את כל המאגר כולו), מה שסיבך אותי מאוד בביצוע - מבחינת יצירת אוטומציה בגיטאב - שתיצור קובץ זיפ להורדה מכל תיקייה בנפרד, ארך לי יומיים לפתור את זה (!), ואחרי שהצלחתי בסייעתא דשמיא, הייתי צריך לסדר את כל העיצוב = UI מחדש, כך שיתאים לפונקציות שנוספו, ואז כבר הגיע ער"פ, בדיקת חמץ ושות' וזה נתקע - עד אחרי החג, בעז"ה.
-
פוסט זה נמחק!
-
@יאיר-דניאל אגב, הרב @י.-פל. עמל על אפשרות מובנית לתוספים, יתכן וכדאי לך לחכות ולא להשקיע כ"כ, או לבקש ממנו את התיעוד ולהתחיל לעבוד על התוסף שיעבוד בצורה רשמית
תודה, אבל זה לא רלוונטי, צורת הבניה של "התוסף" שבניתי לא קשורה בכלל למבנה של תוספים בפלאטר, היא סה"כ תוכנה חיצונית שמוסיפה כפתור מעל החלון של אוצריא - זה לא מוזרק בתוכו.
(יכולתי לעשות את זה, אבל זה דרש הרבה מאמץ וזמן, שלא ראיתי ענין וצורך להשקיע בזה). -
היא סה"כ תוכנה חיצונית שמוסיפה כפתור מעל החלון של אוצריא - זה לא מוזרק בתוכו.
נכון, וזה מה שחבל, כי הרבה יותר עדיף תוסף מובנה, הוא הרבה יותר יעיל ונקי
פוסט זה נמחק! -
בעקבות בקשות ודיונים שונים בפורום - בנוגע לכך האם אפשר שיהיה תוספים לתוכנה - שדרכם יוכלו להוריד מאגרים חיצוניים כמו שלי ושל @שמואל5 - בניתי (-כמובן בעזרת AI...) סוג של תוסף לתוכנה - היא כעת בהרצה, ועוד מעט תעלה.
פרטים בספויילר הראשון:אין אפשרות להוסיף תוספים חיצוניים לתוכנה שכתובה בפלאטר ודארט, מה שכן ישנם 3 אפשרויות:
**1. שימוש ב-Dynamic Loading (טעינה דינמית)
זו הדרך הנפוצה ביותר בתוכנות שולחניות (Desktop). ניתן לבנות את האפליקציה כך שהיא תחפש קבצי ספרייה חיצוניים (כמו .dll בחלונות או .so בלינוקס) בתיקייה מסוימת בזמן ריצה.איך זה עובד: משתמשים בחבילת dart:ffi כדי לקרוא לפונקציות מתוך קבצי ה-C/C++ החיצוניים.
הוספת כפתורים: התוסף יכול להחזיר נתונים שהתוכנה הראשית מפרשת כהוראה להציג כפתור או פונקציה חדשה.
- הזרקת קוד באמצעות מנוע Scripting
זוהי הדרך הגמישה ביותר להוספת פונקציונליות ושינוי ממשק המשתמש (UI) ללא צורך בקימפול.
שילוב מנוע לואה (Lua) או JavaScript: ניתן להטמיע בתוך אפליקציית ה-Flutter מנוע הרצה שמפרש קבצי טקסט חיצוניים.
דוגמה: התוסף יהיה קובץ .lua המגדיר פונקציות ומיקומים של כפתורים. האפליקציה קוראת את הקובץ ובונה את ה-UI בהתאם.
- ארכיטקטורת Micro-Frontends (באמצעות נתיבים דינמיים)
אם מדובר בתוספים שאתה מפתח בעצמך ורק רוצה לאפשר למשתמש לבחור אם להתקין אותם:
ניתן להשתמש בטכניקה של Deferred Loading. זה מאפשר להוריד חלקי קוד מהרשת רק כשהמשתמש זקוק להם, מה שחוסך במקום ומאפשר הוספת רכיבים בצורה מודולרית.**
עד כאן תשובותו של הבן הרשע, סליחה של ג'מיני...
למעשה מה שעשיתי זה כך:
ישנה תוכנה בשם AutoHotkey שמאפשרת להזריק סקריפטים מעל תוכנות קיימות, וליצור כפתורים צפים עליהם.
כתבתי בעזרת הAI קוד פייתון וקוד AHK (הקוד שאיתו התוכנה הזו עובדת) שמה שהם עושים ביחד הוא כך - התוכנה רצה ברקע במחשב (היא לא לוקחת זיכרון - בקושי 2M) וכשאוצריא עולה - מיד צץ כפתור חדש בסרגל - עם אייקון של הורדה, וטולטיפ של הסבר על הכפתור.
לחיצה עליו פותחת חלון ששואל האם להוריד את המאגר שלי - לתוך אוצריא, ובלחיצה על אישור - נפתח חלון התקדמות שבסופו ההודעה "הספרים הותקנו בהצלחה"
עיינו בספויילר הבא - לתמונות.תמונות:



לאחר שעות ששרפתי על זה - ובאגים בלי סוף - נמאס לי מהעניין והחלטתי שאני פורש.
מצורף הקודים למי שרוצה להמשיך את זה מכאן.
בהצלחה, וסליחה מכל מי שפיתחתי בו ציפיות.
הקודים להורדה:
downloader.py
OtzariaButton (1).ahk
(יש להתקין את הספריה הבאה:)pip install requestsקוד הפייתון (- שעליו רץ הקוד השני)
import requests import sys import zipfile import shutil from pathlib import Path # הגדרות מערכת - נשאר בדיוק לפי ההנחיות שלך API_URL = "https://api.github.com/repos/YairDaniel123/Otzarya-Library/releases/latest" OTZARIA_LIB = Path(r"C:\ProgramData\otzaria\books") TEMP_FILE = Path("download.tmp") HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} CHUNK_SIZE = 1024 * 1024 def get_remote_assets(): try: r = requests.get(API_URL, headers=HEADERS, timeout=15) r.raise_for_status() data = r.json() assets = {} for a in data.get("assets", []): if not a["name"].endswith(".zip"): continue name = a.get("label") or a["name"] assets[name] = { "url": a["browser_download_url"], "updated": a["updated_at"], "is_full": (a["name"] == "full-library.zip") } return assets except Exception as e: print(f"ERROR:LIST|{str(e)}", flush=True) sys.exit(1) def check_updates(): if not OTZARIA_LIB.exists(): try: OTZARIA_LIB.mkdir(parents=True, exist_ok=True) except Exception as e: print(f"ERROR:PERMISSION|{str(e)}", flush=True) return remote = get_remote_assets() results = [] for name, info in remote.items(): folder = name.replace(".zip", "") version_file = OTZARIA_LIB / folder / ".version_info" is_up_to_date = version_file.exists() and version_file.read_text(encoding="utf-8").strip() == info["updated"] if not is_up_to_date: type_id = "0" if info["is_full"] else "1" results.append(f"{type_id}|{name}|{info['url']}|{info['updated']}") if results: results.sort() print(f"LIST:{';'.join(results)}", flush=True) else: print("EMPTY", flush=True) def download_and_extract(url, updated_at, display_name): try: with requests.get(url, stream=True, headers=HEADERS, timeout=(10, None)) as r: r.raise_for_status() total = int(r.headers.get("content-length", 0)) done = 0 with open(TEMP_FILE, "wb") as f: for chunk in r.iter_content(CHUNK_SIZE): if chunk: f.write(chunk) done += len(chunk) if total: print(f"DL_PROGRESS:{int(done/total*100)}", flush=True) print("DL_FINISHED", flush=True) folder_name = display_name.replace(".zip", "") target_dir = OTZARIA_LIB / folder_name temp_extract = OTZARIA_LIB / f"temp_{folder_name}" if temp_extract.exists(): shutil.rmtree(temp_extract) temp_extract.mkdir(parents=True) with zipfile.ZipFile(TEMP_FILE, "r") as z: files = [f for f in z.namelist() if not f.endswith(".version_info")] for i, f in enumerate(files): z.extract(f, temp_extract) print(f"EX_PROGRESS:{int((i+1)/len(files)*100)}", flush=True) if target_dir.exists(): shutil.rmtree(target_dir) temp_extract.rename(target_dir) (target_dir / ".version_info").write_text(updated_at, encoding="utf-8") print("DONE", flush=True) except Exception as e: print(f"ERROR:PROCESS|{str(e)}", flush=True) finally: if TEMP_FILE.exists(): TEMP_FILE.unlink() if __name__ == "__main__": if len(sys.argv) < 2: sys.exit() cmd = sys.argv[1] if cmd == "CHECK": check_updates() elif "|" in cmd: p = cmd.split("|") if len(p) >= 4: download_and_extract(p[2], p[3], p[1])קוד AHK (יש צורך להתקין את התוכנה הזו בשביל להריץ את הקוד, תוכנה קטנה ממש).
#Requires AutoHotkey v2.0 #SingleInstance Force global MainColor := "F8F4E8" global AccentColor := "7D5800" global BgColor := "EDE8D5" global IsHover := false global Queue := [] global Worker := "" global IsCancelled := false global OurWindows := Map() global SelectGui := unset global ProgGui := unset global MsgGui := unset ; ===================================================== ; גרירת חלונות ; ===================================================== OnMessage(0x0201, WM_LBUTTONDOWN) WM_LBUTTONDOWN(wParam, lParam, msg, hwnd) { rootHwnd := DllCall("GetAncestor", "ptr", hwnd, "uint", 2, "ptr") if !OurWindows.Has(rootHwnd) return try { cls := WinGetClass("ahk_id " hwnd) if (cls = "Button" || cls = "Edit") return } PostMessage(0xA1, 2,,, "ahk_id " rootHwnd) } ApplyRounding(guiObj) { guiObj.Opt("+LastFound") DllCall("dwmapi\DwmSetWindowAttribute", "ptr", guiObj.Hwnd, "uint", 33, "ptr*", 2, "uint", 4) } ; ===================================================== ; כפתור הורדה צף ; ===================================================== BtnGui := Gui("+AlwaysOnTop -Caption +ToolWindow +LastFound") BtnGui.BackColor := "EEAA99" WinSetTransColor("EEAA99", BtnGui) BtnGui.SetFont("s22", "Segoe UI Symbol") global BtnTxt := BtnGui.Add("Text", "x0 y0 w60 h50 Center c" AccentColor, "📥") BtnTxt.OnEvent("Click", ShowSelectionMenu) OurWindows[BtnGui.Hwnd] := true TipGui := Gui("-Caption +AlwaysOnTop +ToolWindow") TipGui.BackColor := "2B2B2B" TipGui.SetFont("s9 w800 cWhite", "Segoe UI") TipGui.Add("Text", "x8 y4 w80 h44 Center", "הורדת מאגרי ספרים מותאמים") ApplyRounding(TipGui) OurWindows[TipGui.Hwnd] := true SetTimer(WatchOtzaria, 250) WatchOtzaria() { global IsHover TargetID := WinExist("ahk_exe otzaria.exe") ActiveID := WinActive("A") SelectHwnd := (IsSet(SelectGui) && IsObject(SelectGui)) ? SelectGui.Hwnd : 0 ProgHwnd := (IsSet(ProgGui) && IsObject(ProgGui)) ? ProgGui.Hwnd : 0 MsgHwnd := (IsSet(MsgGui) && IsObject(MsgGui)) ? MsgGui.Hwnd : 0 ; בדיקה האם אנחנו עובדים כרגע על אוצריא או על אחד החלונות שלנו IsOtzariaContext := TargetID && (ActiveID = TargetID || ActiveID = SelectHwnd || ActiveID = BtnGui.Hwnd || ActiveID = ProgHwnd || ActiveID = MsgHwnd) if IsOtzariaContext { WinGetPos(&X, &Y, &W, &H, "ahk_id " TargetID) btnX := X + W - 78 btnY := Y + H - 186 BtnGui.Show("x" btnX " y" btnY " w60 h50 NoActivate") ; החזרת החלונות במידה וחזרנו לאוצריא מתוכנה אחרת if SelectHwnd try SelectGui.Show("NoActivate") if ProgHwnd try ProgGui.Show("NoActivate") MouseGetPos(,, &HoverWin) if (HoverWin = BtnGui.Hwnd) { if !IsHover { TipGui.Show("x" (btnX - 85) " y" (btnY + 5) " NoActivate") IsHover := true } } else if IsHover { TipGui.Hide() IsHover := false } } else { BtnGui.Hide() TipGui.Hide() ; הסתרת חלונות בעת מעבר לתוכנה אחרת (לא סגירה, רק הסתרה) if SelectHwnd try SelectGui.Hide() if ProgHwnd try ProgGui.Hide() } } ; ===================================================== ; חלון בחירת ספרים מעוצב וימין-שמאל (RTL) ; ===================================================== ShowSelectionMenu(*) { BtnTxt.Text := "⏳" try { shell := ComObject("WScript.Shell") exec := shell.Exec("pythonw.exe downloader.py CHECK") res := exec.StdOut.ReadAll() } catch { BtnTxt.Text := "📥" CustomMsgBox("שגיאה: לא ניתן להפעיל את פייתון.", "שגיאת הפעלה") return } BtnTxt.Text := "📥" if !InStr(res, "LIST:") || InStr(res, "EMPTY") { CustomMsgBox("המאגר מעודכן!", "עדכון ספריה") return } global SelectGui := Gui("+AlwaysOnTop -Caption") SelectGui.BackColor := MainColor ApplyRounding(SelectGui) OurWindows[SelectGui.Hwnd] := true SelectGui.SetFont("s15 w700 c" AccentColor, "Segoe UI") SelectGui.Add("Text", "x0 y15 w420 Center", "עדכון ספריה") SelectGui.SetFont("s11 w600 cBlack", "Segoe UI") ; +0x20 ו-Right אחראים על הפיכת הצ'קבוקס לימין שמאלה cbAll := SelectGui.Add("Checkbox", "x20 y+20 w380 Right +0x20", "בחר / בטל הכל (ללא המאגר המלא)") Checkboxes := [] items := StrSplit(StrReplace(res, "LIST:", ""), ";") for item in items { if (Trim(item) = "") { continue } p := StrSplit(item, "|") isFull := (p[1] = "0") SelectGui.SetFont("s11 w400 cBlack", "Segoe UI") cb := SelectGui.Add("Checkbox", (p[1] = "1" ? "Checked " : "") "x20 y+10 w380 Right +0x20", p[2]) Checkboxes.Push({full: item, cb: cb, isFull: isFull}) } cbAll.OnEvent("Click", (self, *) => ToggleAll(self.Value, Checkboxes)) BtnDown := SelectGui.Add("Button", "Default w160 h35 x40 y+25", "הורד") BtnCancel := SelectGui.Add("Button", "w160 h35 x+20", "ביטול") BtnDown.OnEvent("Click", (*) => StartSelectedDownload(SelectGui, Checkboxes)) BtnCancel.OnEvent("Click", (*) => SelectGui.Destroy()) SelectGui.Show("w420 Center") } ToggleAll(val, Checkboxes) { for item in Checkboxes { if !item.isFull item.cb.Value := val } } ; ===================================================== ; הודעה מעוצבת בסגנון אוצריא ; ===================================================== CustomMsgBox(txt, title) { global MsgGui := Gui("-Caption +AlwaysOnTop +Owner") MsgGui.BackColor := MainColor ApplyRounding(MsgGui) OurWindows[MsgGui.Hwnd] := true MsgGui.SetFont("s15 w700 c" AccentColor, "Segoe UI") MsgGui.Add("Text", "x0 y20 w350 Center", title) MsgGui.SetFont("s11 w400 cBlack", "Segoe UI") MsgGui.Add("Text", "x25 y+15 w300 Center +Wrap", txt) BtnOk := MsgGui.Add("Button", "Default w120 h35 x115 y+25", "אישור") BtnOk.OnEvent("Click", (*) => MsgGui.Destroy()) MsgGui.Show("w350 Center") } ; ===================================================== ; חלון התקדמות ; ===================================================== ProgressShow(FileName, Remaining) { global ProgGui, StatusTxt, Bar, PctLabel ProgGui := Gui("-Caption +AlwaysOnTop") ProgGui.BackColor := MainColor ApplyRounding(ProgGui) OurWindows[ProgGui.Hwnd] := true ProgGui.SetFont("s14 w700 c" AccentColor, "Segoe UI") ProgGui.Add("Text", "x0 y20 w400 Center", "הורדה בתהליך") ProgGui.SetFont("s11 w600 c" AccentColor, "Segoe UI") ProgGui.Add("Text", "x25 y+15 w350 Center +Wrap", FileName) ProgGui.SetFont("s12 w700 c" AccentColor, "Segoe UI") StatusTxt := ProgGui.Add("Text", "x0 y+15 w400 Center", "מוריד...") Bar := ProgGui.Add("Progress", "x30 y+12 w340 h18 cGreen Background" BgColor, 0) ProgGui.SetFont("s11 w700 cBlack", "Segoe UI") PctLabel := ProgGui.Add("Text", "x0 y+8 w400 Center", "0%") CancelBtn := ProgGui.Add("Button", "w120 h35 x140 y+20", "ביטול") CancelBtn.OnEvent("Click", CancelDownload) ProgGui.Show("w400 Center") } ; ===================================================== ; ניהול תור הורדות ; ===================================================== StartSelectedDownload(GuiObj, Checkboxes) { global Queue := [], IsCancelled := false for item in Checkboxes { if item.cb.Value Queue.Push(item.full) } GuiObj.Destroy() if Queue.Length > 0 NextDownload() else CustomMsgBox("לא נבחרו ספרים להורדה.", "שים לב") } NextDownload() { global Queue, IsCancelled, Worker if IsCancelled || (Queue.Length = 0) { if !IsCancelled CustomMsgBox("הספריה עודכנה בהצלחה!", "סיום הורדה") return } CurrentItem := Queue.RemoveAt(1) p := StrSplit(CurrentItem, "|") ProgressShow(p[2], Queue.Length) Worker := ComObject("WScript.Shell").Exec('pythonw.exe downloader.py "' CurrentItem '"') SetTimer(CheckStatus, 100) } CheckStatus() { global Worker, ProgGui, StatusTxt, Bar, PctLabel if Worker.StdOut.AtEndOfStream { SetTimer(CheckStatus, 0) try { if IsObject(ProgGui) && (ProgGui != "") { ProgGui.Destroy() ProgGui := unset CustomMsgBox("ההורדה נכשלה.`nבדוק חיבור לאינטרנט.", "שגיאה") } } return } line := Worker.StdOut.ReadLine() if InStr(line, "DL_PROGRESS:") { pct := StrReplace(line, "DL_PROGRESS:", "") Bar.Value := pct PctLabel.Text := pct "%" } else if InStr(line, "DL_FINISHED") { StatusTxt.Text := "מחלץ קבצים..." Bar.Value := 0 } else if InStr(line, "EX_PROGRESS:") { pct := StrReplace(line, "EX_PROGRESS:", "") Bar.Value := pct PctLabel.Text := pct "%" } else if InStr(line, "DONE") { SetTimer(CheckStatus, 0) ProgGui.Destroy() ProgGui := unset NextDownload() } } CancelDownload(*) { global Worker, IsCancelled, ProgGui IsCancelled := true SetTimer(CheckStatus, 0) try Worker.Terminate() ProgGui.Destroy() ProgGui := unset CustomMsgBox("ההורדה בוטלה.", "ביטול") }יאיר-דניאל כתב:
לאחר שעות ששרפתי על זה - ובאגים בלי סוף - נמאס לי מהעניין והחלטתי שאני פורש.
מצורף הקודים בפוסט הראשון = למי שרוצה להמשיך את זה מכאן.
בהצלחה, וסליחה מכל מי שפיתחתי בו ציפיות.
- הזרקת קוד באמצעות מנוע Scripting
-
@u88 האמת שזה היה מוכן לגמרי - והייתי אמור להעלות אותו שעה - שעתיים אחרי שפרסמתי את הפוסט, אבל שיפרתי כמה דברים בעיצוב שלו, ואז הגיע @דבש עם הרעיון היפה - לאפשר למשתמש לבחור מה להוריד (בברירת המחדל הראשונית זה הוריד את כל המאגר כולו), מה שסיבך אותי מאוד בביצוע - מבחינת יצירת אוטומציה בגיטאב - שתיצור קובץ זיפ להורדה מכל תיקייה בנפרד, ארך לי יומיים לפתור את זה (!), ואחרי שהצלחתי בסייעתא דשמיא, הייתי צריך לסדר את כל העיצוב = UI מחדש, כך שיתאים לפונקציות שנוספו, ואז כבר הגיע ער"פ, בדיקת חמץ ושות' וזה נתקע - עד אחרי החג, בעז"ה.
האמת שזה היה מוכן לגמרי
נמאס לי מהעניין והחלטתי שאני פורש.
לפחות תעלה את זה, בלי כל השיפורים.. או שזה כבר לא קיים?
-
האמת שזה היה מוכן לגמרי
נמאס לי מהעניין והחלטתי שאני פורש.
לפחות תעלה את זה, בלי כל השיפורים.. או שזה כבר לא קיים?
-
אולי עכשיו יש סיכוי לחדש את הרעיון? @יאיר-דניאל
-
אולי עכשיו יש סיכוי לחדש את הרעיון? @יאיר-דניאל
-
-
א"א דרך התוכנה - זה רק תוספים אופליין.
אפשר לעשות תוכנה או סקריפט חיצוני שיתחבר לתוסף.
מה דעתך?
הסבר: זה יכול להיות חלון ריק, סקריפט שחור, משהו חסר משמעות, שהתפקיד שלו הוא לגשת לרשת.@י.-פל. אה, חשבתי שזה משהו עקרוני...
א"כ אז באמת יש סיכוי שאעשה את זה, נראה לי אפשר להגדיר שלא יפתח אפילו חלון שחור של טרמינל. יש דבר כזה -
@י.-פל. אה, חשבתי שזה משהו עקרוני...
א"כ אז באמת יש סיכוי שאעשה את זה, נראה לי אפשר להגדיר שלא יפתח אפילו חלון שחור של טרמינל. יש דבר כזה -
אה, חשבתי שזה משהו עקרוני...
אין שום עיקרון... עניין של אבטחה.
אבל אם המשתמש נוקט פעולה אקטיבית - עניינו.@י.-פל. שומע.
אבדוק את זה.
בכ"א יקח יומיומיים = אני עמוס כרגע, אם התוכנה שלי של הPDF, והתוסף הקודם שאני באמצע. ועוד משהו. קיצור מקווה שאגיע לזה מהר. -
א"א דרך התוכנה - זה רק תוספים אופליין.
אפשר לעשות תוכנה או סקריפט חיצוני שיתחבר לתוסף.
מה דעתך?
הסבר: זה יכול להיות חלון ריק, סקריפט שחור, משהו חסר משמעות, שהתפקיד שלו הוא לגשת לרשת.
שלום! נראה שהשיחה הזו מעניינת אותך, אבל עדיין אין לך חשבון.
נמאס לכם לגלול בין אותם הפוסטים בכל ביקור? כשנרשמים לחשבון, תמיד תחזרו בדיוק למקום שבו הייתם קודם, ותוכלו לבחור לקבל התראות על תגובות חדשות (בין אם במייל, ובין אם בהתראת פוש). תוכלו גם לשמור סימניות ולפרגן ב-upvote לפוסטים כדי להביע הערכה לחברי קהילה אחרים.
בעזרת התרומה שלך, הפוסט הזה יכול להיות אפילו טוב יותר 💗
הרשמה התחברות