From a4f52bfa383f766e065cc17eba095fb1253bfeb2 Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Thu, 29 May 2025 21:10:14 +0200 Subject: [PATCH] Revert "Remove flatpak folder" This reverts commit 18ac4562a26b9d16014f502232045e68f742b547. --- flatpak/app-icon-grid.svg | 1 + flatpak/de.wger.flutter.desktop | 11 + flatpak/de.wger.flutter.metainfo.xml | 95 +++++++ flatpak/flatpak_meta.json | 23 ++ flatpak/logo128.png | Bin 0 -> 2650 bytes flatpak/logo512.png | Bin 0 -> 10572 bytes flatpak/logo64.png | Bin 0 -> 1607 bytes flatpak/scripts/README.md | 29 +++ flatpak/scripts/flatpak_packager.dart | 185 +++++++++++++ flatpak/scripts/flatpak_shared.dart | 330 ++++++++++++++++++++++++ flatpak/scripts/manifest_generator.dart | 167 ++++++++++++ flatpak/scripts/pubspec.lock | 93 +++++++ flatpak/scripts/pubspec.yaml | 8 + 13 files changed, 942 insertions(+) create mode 100644 flatpak/app-icon-grid.svg create mode 100755 flatpak/de.wger.flutter.desktop create mode 100755 flatpak/de.wger.flutter.metainfo.xml create mode 100644 flatpak/flatpak_meta.json create mode 100644 flatpak/logo128.png create mode 100644 flatpak/logo512.png create mode 100644 flatpak/logo64.png create mode 100644 flatpak/scripts/README.md create mode 100644 flatpak/scripts/flatpak_packager.dart create mode 100644 flatpak/scripts/flatpak_shared.dart create mode 100644 flatpak/scripts/manifest_generator.dart create mode 100644 flatpak/scripts/pubspec.lock create mode 100644 flatpak/scripts/pubspec.yaml diff --git a/flatpak/app-icon-grid.svg b/flatpak/app-icon-grid.svg new file mode 100644 index 00000000..33451bd1 --- /dev/null +++ b/flatpak/app-icon-grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/flatpak/de.wger.flutter.desktop b/flatpak/de.wger.flutter.desktop new file mode 100755 index 00000000..5e4cf795 --- /dev/null +++ b/flatpak/de.wger.flutter.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=wger +Comment=Fitness/workout, nutrition and weight tracker +Categories=Education;Utility;Sports; +Icon=de.wger.flutter +Exec=wger +StartupWMClass=wger +X-Purism-FormFactor=Workstation;Mobile; +X-KDE-FormFactors=desktop;tablet;handset;mediacenter; diff --git a/flatpak/de.wger.flutter.metainfo.xml b/flatpak/de.wger.flutter.metainfo.xml new file mode 100755 index 00000000..fbcca043 --- /dev/null +++ b/flatpak/de.wger.flutter.metainfo.xml @@ -0,0 +1,95 @@ + + + de.wger.flutter + wger + Fitness and nutrition tracker + +

wger is a free and open-source fitness application designed to help you achieve your + fitness goals. +

+ +

Workout Management

+
    +
  • Create and customize workout routines tailored to your fitness level and + goals +
  • +
  • Track your progress with detailed exercise logs
  • +
  • Access a vast library of exercises
  • +
+ +

Nutrition Tracking

+
    +
  • Log your food intake using the Open Food Facts database
  • +
  • Calculate your calorie intake and macronutrient breakdown
  • +
  • Set personalized dietary goals and track your progress
  • +
+ +

Body Measurement Tracking

+
    +
  • Monitor your body weight, body fat percentage, and other measurements
  • +
  • Visualize your progress with charts and graphs
  • +
+
+ + CC0-1.0 + AGPL-3.0-or-later + + touch + pointing + keyboard + + wger + https://wger.de/ + https://fosstodon.org/@wger + https://github.com/wger-project/flutter/issues + https://buymeacoffee.com/wger + https://github.com/wger-project/flutter + https://hosted.weblate.org/engage/wger/ + + + + #a2aedd + #687bd9 + + + + + wger's dashboard + + https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/01%20-%20dashboard.png + + + + Workout detail + + https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/02%20-%20workout%20detail.png + + + + Measurements + + https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/04%20-%20measurements.png + + + + Nutritional plan + + https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/05%20-%20nutritional%20plan.png + + + + Body weight + + https://raw.githubusercontent.com/wger-project/flutter/master/fastlane/metadata/android/en-US/images/phoneScreenshots/06%20-%20weight.png + + + + + + + + + + + de.wger.flutter.desktop +
diff --git a/flatpak/flatpak_meta.json b/flatpak/flatpak_meta.json new file mode 100644 index 00000000..f3c1a6a3 --- /dev/null +++ b/flatpak/flatpak_meta.json @@ -0,0 +1,23 @@ +{ + "appId": "de.wger.flutter", + "lowercaseAppName": "wger", + "githubReleaseOrganization": "wger-project", + "githubReleaseProject": "flutter", + "localLinuxBuildDir": "../build/linux", + "appStreamPath": "de.wger.flutter.metainfo.xml", + "desktopPath": "de.wger.flutter.desktop", + "icons": { + "64x64": "logo64.png", + "128x126": "logo128.png", + "512x512": "logo512.png" + }, + "freedesktopRuntime": "24.08", + "finishArgs": [ + "--share=ipc", + "--share=network", + "--socket=fallback-x11", + "--socket=wayland", + "--socket=pulseaudio", + "--device=dri" + ] +} \ No newline at end of file diff --git a/flatpak/logo128.png b/flatpak/logo128.png new file mode 100644 index 0000000000000000000000000000000000000000..726d5c09e465801ebeeb3c9331a8ef63f15b3982 GIT binary patch literal 2650 zcmZ`*dpMNa8vnjAX548Qxos(oglUF7T~O|fj60Q@MB~mdF=enZh{#4nR4&tnXlE3; zG{{u$+L^s+h+@*nm^xC?iNg8p^PK&h^T&DC^ZtI%TI+q+?_IZNtsDbFvScTrj#w>Ne`0 zN@62}ZeysnXxoenOeLxoSJE>be7hJSml;45qx|oXrBs9*HBQ#J+LI+aVNYaKhyTY9 z&!1P8q*NW|KQG^i>HTM=h2ql|YDT|*t&|>sPJ`={jo2T2Zhj7S1R*UsfaKsyaxtV-ibkY`>W z%=ic*JD$U%ze*lj)3eT+Ddj6(;T-x&KTAPX@~5ogD@bxV-5 zLW+R3W~#%^emEb`Yc)Ig4cUjRkh_gyg`*Wlh3$s{ z<)vZuT8hf$OQI0$i}q>7;*=o*OepoOr{co?Q4(OdwdIY+Ad?EAyf>xS-+#9bXjY#YE*P}RY>brZI^ zrZ#>A^s8!=)QQA9Bi>Ib<5TCVLaI*y z8$TPpYp+jo?{41hOX!0#(#eG%?W&(zA6g$yte%f`d4T(v${58a#6=e8O)7@o1vFc? z<-aWbr+&o7UhIy?>XrFeGWIwHy=M^x6|-J4HLBMh4EldxjQ_n4Mgj5YcRz^yG3S*7rH6 z{?}N%27uaX{-H$ky_fA|y#k6$g)zdFh?_!OYss*Wq6e)D2MSRW7Us@?2E1rfevuYp z;ukr5LbYWMD?4dYPtBL1>3$A|WZyXoU7RGt!l}9I{JZ**b!bw30E+qVJ^h>d>7f6~ z);{dXCbL6N>ty{5tRch`7|E}-IrwwYa@(O3&>-XSDCBnMu8xKyoN4cq$5RJW<}xHa zBNPZBcLBnA4a(Q*mUW+JO$w`boK~(@(3AGP)^>w(ez}92>%yPlPg*2aw`;kU9{*s{ zUf@*6rV`tM7tQkc$7HM4gSDB;~2j4E0lFrgb zs+d#NWrg|O-v*|n2S8Al?((5r=PurPYXCWId7ng7&5riUFK=ZoIE}k?m}Y@|kF*mc zz~YKWZq(%Uc*JQgS$T2VCu-~w(bV#yCDNElokUdK@yCJlXBHP^H{Pa|uPUo@Td2AQ ztkeOx|MeT~!GhZb-$ReO8x#dU`B**+u9ZuVTZ6YMk~?@B6=C@qiC>mR9!pLOVMpR} z>Q(FqABLo_+oe2O76q7+1}#JnyK_k5pX8RPGQFo4@O26YGz}vF2M6#VXKz6n@3>lkMW)8y4T|mgl~L3?`oVlzr4u9+ zUru>Z=jPb3l`#8Ag&b{bDZjwG#AOhEk2;Anj;xgy2O}6+u!1*63Jg61R>&>Wf?`=- zX6omeLb5m(Q+F*aef>MUE|sFzdyyVgs0wm$vUbxH*xoW`q49^;Rm0$#hT^fOBUfyW zNoKQ6n1g$(r+VOL6F0JtY%Sx(|rZl$o#Y73re#2z*Ed$x7SjSBnhmMp=?3%78oNWeP zYdVu>svdT?{JQUj#MCTGakWM~AKgH8l;ZU?d94+L3xOMc&NH)4E0bi7fuYiDNm773@9`lJ&j=O&Ui>+?tR3}(lAYbQnrF$(7nOr6t735VFWIJZY zjlh2L-4OQZHq~ML^fzzCUsvNl;GhH6O1*rx0d|Tnjqg9jt%Ckl`gDh*^q@d`usJ0* z7$U$FXG$=^5lrxVeDDPGpZA;N%#3h2a~#g&F8hB2s4+p|AqoFKVEZRkT}VNFQ^7kX pgdQIl8w|w9$792zBjYH6v|wyZZ0NNGOAW{gaCUIBXWIs({|mU}h9Up} literal 0 HcmV?d00001 diff --git a/flatpak/logo512.png b/flatpak/logo512.png new file mode 100644 index 0000000000000000000000000000000000000000..e84a976dc1a490f7c6f133f6d8bb71b5f7fa9b45 GIT binary patch literal 10572 zcmcI~3p~_Y*Z-bN?x8ECLe5ciF>))n5uH*UrpEn<89GuCQbWQR5h~F+85OxyE@x1o z+%Gd7%4sOd{XV6kTr+5l8ROlfPW62L&-1*u|NBXw-*5I_d+oK>Uca^1Uf+G`koCT$ zQfs9E0895KphN;7 zBB8tofP?{Hm~W(-{DaWJUiG4aIjN z0NAXtf6s2)b6sP-!Id6%ZXJ`-$h#~x)0o3|)#7v>wO8x7XVo6hbKMf&;NnGKOsnh7 zN9sP@f5K~THx1QSRNSqunVz;*??etoTm5Ej2*WmalB!Z{dRNj;clj&1%ki7#%~Ts2 zLQ{pOwN9?|iz#yY|}bTHfDS(Q>BI}y9YTPXDSUR^y3ZKm*wjx+6x3emb^@o zcUXbXu~a8q;HJ5XaA?_vxHIL~bF$_y<~;Y6q=l$uyq31AaLg{mX82<=Dp%q^gx=s? zV}`@O9E~fgMOW@-pXmyC{s+S%`rrqsIPo5~^61fhR}FZvhSV(%)ld7IOZct26`YlCiUvGi2>#$g zJLRX+Ki~rEF185qF+6Ei>QleS{^^P62vsUila1{#q?Vk=LAAY(kF;D-#%ES$AK(pM zV}`_d&7E2}MBcbl(cM+5pfcN92Rm+pM1Z_uM!$)EEhop2T4ApFNNLym{_7P(Yi%_M zb_P0))2Yw~aenTERrOxx*N4oR;`~!AdYycXhlcK0>x?aL|AeXpU}0|i&s9fXbab`P zE~KxegfH}&r(3fl22KyI>9dgsj8O7G*b~%7NTbPImoN&Ij@S3w*9$=OiXWXi@qa7(OB$LS*V#0~} z6DotgxYa9>DuB3oZ0FGl$(eTzJk>;$YGTAaBTUge^G2BF#{AiH$+ zZortc`gnymt;wbnoa*Vn68q#IiM)Y&u-KXuPja~cD0((&tryERCUTPsO9k6n%z8Gd zVwBv;y|XO`$sHsqB(Q&xs5@1fqj4j$WqaF+$AKS5M&x7IX04AEQqKo){o1!lfSqq8 z$s$X2KJg`*r)va8wFD#wlH^G1BLKxBtOeVm#U9|{Tz?uDqs6_)bi*_7?5tz4Pp-6&jU zujg@km#_=hMmrT^3VUPY=Qeq!9@68zzSi}V4xiU2@7PAZ8#i_)#1;0U^(V?%uk220 zxth8b>wTPxh*NQuvI-{kCNzhTVSh~gT;u45!`UH|B_)^jN>A_HBIfRzV-vLR_<^yh zNI*gO4R`j|Se&eY-&-Dghpl0yI`qGz+oIS&@SmC-oI zd;~oxH>;{C+(H64()Z2RwHA!Z(t7X)PYucX)7jzW8vS`k`-T{*XplF_AY}VL@)b!B}eN~OxmG@ciQn6Mnzn9 zTZxbA-G$3_vs`0gdj^NfCUuCB#R7xh7*gY7>u!0@daD7JshIFu7eUI3 zSNJ?s#!8~LQr_KZF(R=wOh+U@DSzW=`8^BoYzrnqs-QthSF!7i7^Kjkhj#y3^3+oq zLFu2hDSO3elsKhr?4akNoe@$tc5xX{oORQ^A^XySc4UX?%;Ib(ItEJ}8*{RfR%O!j<=GqPse;F}K+< zgY_rQKSA{#lm{|xjQ#||eH-2|)8BMt_Kh%attGFX)NC4-Hxd`%yBjpMqry_U2-38W z7hE{`6O=!{Eqj}9yQg<;9RAW1U?yECHyT%e?7wjrPxh zq5KIV`nWz+#4xer(@Ln&ACV zcvz0$*A(VH7`5#+v`WJf`%Dk#yU5VC+8504>1{A>uCfPBIMl+gfhSWq!4_K&CQc)~ z?znbZsMHD`Oy1nWbwx15x&i`9G&201EXUmabhYwd21|GwRdl~|-3lD{83m9wIarlt z9PoIx+l28fPjR!jh2|qWO@$OufaM-TFn%EqV23X3v!@+diN94uz&06d5liyV&Wu1( zigw+Zs6FpLD1cETCqb_K4M>#T2%>nA)mg!QR8HzO=GcZA6pDDl3Ap7K8gQ!w>_jY}R`IfuK{{E1d4P2BOo7nrf#6e@?*3I{E+UgRN!%nXQ zdzcMEBa5xjmiN3(Yd>9q-gFdZKx@>{3_sy-&?lq{QXb%iIlcnW?_}s`5TvThPCDs7 z==4k)fjMF{o5`8hq|%*uSNqXeN^r>9Ly5z-57wp)2Phh$8D`xu8ET^)S>x`y{fM$3 zg}=JKhm-L>gx5wJFL<^bfjM3`Wtw$)Olf@^$`8BE_kzBXdr;Xb6f%7Q_rVC}2CD2E zQI4{Tvk3dK*zoS#+A3gfO0I*n&w+MZDe2Q{6qeC7s_B>+5}_l8HtYYx-L&ceZ^Gj7 zB{{}g=e(ps#iUQehSY4u2sGna!)UyRrzP7WQ2G3u2+91oDU`jsRNM95qp|yu#8C23 ze{0G*eA(8FfP!L@7Gp1NrLm*l*u|N%fp@hJfV@j%lLF-&eA#h!f~nCgV*1sdCx+By ztCcH3?n%+^*|r$ltMSRE-XOH}9*c9#i`P7$fm5WT8Nb~h!kt=?#9>o?=S_(q)W^fR z4WGiayQ5K5@H1G~*C0GLxXIk+0nNtAaaUHmxHORwuo6ly?;bQ?d}jN(i~=jr!$s3Y zX|Osy$fL>Ni5zjK{_?qO`Kd5kcEvy}3CT(|;9O!m+cDK=5i*_X=e4ev<%X(72se{-b-4byy7XZS2D^upi_2|=|1L+1+?km zEK$DTVq!bjLcIoCTs)?C~gC%jV8<8~&e}(0ALu;F6jQ)lN z*yBZv`!LP{#px#tdE4Q>s#mW}v2fXzAM)C(`fS~rV4OUND7{A*eP*Fq5;Q8uaPwk% zAMc8nJ%g5EFW4SG;F9fMp%#qTc{dH6#0d-3?~Pc(PzjmBPW^~oj^~y83PIjpbc+U| z2rBFfrl%&WthlVE1TS;V;^`&CnN)Nw$Dw95qGk%a!(^T!KSk(ZiBpP#mLC#&-+Z}T zF?HVpj0kr-A)8ygYn+2i%&=B-SH{k^Cj6wZ33!#CXyPn?@@R<}eJl40+sxAA`BO>g ze^Rubf*)TPTLbTOk@LWx?_XiTNYxHv(-2B#_TBXz)jFawRE6$>7`_NeY<0(`iV(f@b`33M4Rpf(Ji@is+yTPn+_h zV<4IW{(Tc9fCo$ofKXW+VJ141qA8zQK$lILa&71TdBb)I4{G*7J<9m@j?EMJ50~zBGiyXV3T42M<-U0^3{ruJi{u8oWyOt-8l8 z(;^SVgb1G{qZu*cj3M934>1q#!JdJ9;fpE-`l((l0mm=9QDF)vV~KZ9UB-^{eGe-T zKBcM&7eby*5j4PFV~{#2aM&#KK>mawCJBm${~` z{0U!j|FT^@N+p1J2*rX%AC19kO;irsM9=tEFQ}!taQS~}jaJx-%*E<{Di=_ijgSIj zdcb}_m!VM2!R>vkk@B4mU$ptQK8Sx%=$mNYOAKky|C=6)=~Bv+>!;|dA>r%XcuTI) z#G)87hrwC;RGeD1)@^Pfb|pZXf$1|K@7W8s9}cw=T16;JY30QVafmPTLmmH?6;>^zCiYIsY@}kP-TSV+ZgZgYQ{=$Mk#l|9^!3%$!Iup65M$ zKmWqB`s`oToSAO4SsI#QRnm9babo?*M3ysLlF;RVz?(Z2J=13bY2oa|QWUFm$hF70 z{swTY03X*@FFtqnv_ERBaum6oNQ)caS-0}@Y>RefEe4J+N@y*Tex^Jp3BLYf9Q>ON z>~lqaU65sm{&_o?O>GyV9#tj#R7IE;FEH}HPR_!pL?gcA{FT*LitkPSRe*m<_CIwt zj2Xi3g{SXm)tuEtX1vQ(r_z$Ii<*r%|DA;YO85U0gMyFCfylg+Wipg8&#K2<9r`3z zwlle3=!R(8g!uR%_n}E1H*SUtUsj1+!I#XBmK&++Cbu1g3Mh^P#mM9K8))Zf|oU#KzV>$}MT z;+x#x-u^*@@0p|jPkv1Mf)GKM(s||C_nz^86{Ybni?krdAX#0Fq3oCmX=VB+F_EBV zSaHVQN*Kt}=_sPw*N^5qTLa`bMZdlMmtx#^y8SaPWga+GPxe-Imhnh>Sh}C1@-*CQ z?{iy*szzf3f<6N&uy-RMlP8qoXQv9eZ%fv<(RX3#`QzN*MB_waQFfmxjYcdTE|Es4 z0R4?XY(``KXK_XIeO?p*8@{Z=|9qAHUwg3JSBP?xMb1P!EBZ(7d57wSB+`t7a>aPQ z&+!RiQ+6<~$QQd&E3BqsF|Vla|M)^N+oHHKB#T zgdp;lX+;!>mg{pK^tTpY<`Sue{$foP5f$U2x@aR3b$=Q2&o%!#=I>og)Zl%sIV&ue zyM^~6benL_tI8pCJACkkhK8r+N-7ua3X%U?PRsVFte&%?!Xiw$1>*5n*RM+Qw@N95kb;Hsd~}Ox zpuG4(xD3Vhp}}fAzb8j~jXdRQ@~nb;P;0uZ5TyjFTz&K6#`xY+XvSW_xH6tMTe~MP z7IXmT*9`;a%)rQ-H;%-Dy!c9619q~gk|D=oVVe+5BQwu(XrHnLD*@xYaGZb}OxrO1 zB#eb3^0Fy~&Je8FPUmL}F1slTqBnv>uGr0>OPS#yk3AtIHIx0C!nn%iz%h;@7%ta1 zvmFoY7m~xu$cNhba-p!g1hv4Eo0&@N3rkort+cHzv>sz}TSL5TGYf0GIJdfb`O#t9 zz7s(zAeTsk#|F)Sfg1f;ITtVu%M(^BdM8zjl$RsGx;s3>;>I=4o`h{iv5d0C2Dwpt z(ZFvO7JH(y1f%(~LW@Nkc#96xQI0}g-9gy>K7Jn7nIk)_<)&EJmcsYZ@i9d$vc56OR<@1SP{BZy4s{BH7F5#4H-Yh|v$iE_ z8Jr)lqY$&zB%u|r?tGyU$@W?8Z&IRFv4?mQtotl?cj7?wZVN#QyGtgTFF~=`wP$m& zc7}g$HUiuebYQh26Bdp)?+Fxaj=QL~Mzn0+@U(og@t>}cx=zu z;f#BEn|@|!{OHwCBr6TK{Pw8zQmZocJ<9-Qs}jFOLn|^%!2Z;c;-0qxaD#G)R3^mK zy3Lv7)j)A{jj+UycH1jt1&A*B6;r&hEn|mrlDm-`~ZJ7SoF5B6=0;`7nZhaV!ioE1&6kTIeBCrzeH#ZixAqnY|px9 z&*FBNN>XNwh?@sBYdPc!Nn~Db1ZnGHDf~1YbaZS&YU3_ug=lBAH6nqRdSjc$#6$P( zMlU;pFHG(O({(--qOlQbQoZr&)lGre1E=MP)XJ6&WU!EwY3U<(?(Ok z)^@0Xz06|AfHqo4?QRQ(SO%dJhi9b}%uOuIk2o|TOthCYQZQsJ1%5nqH^`GtU3 zxX`)z|X_>bLm_HzjWNMPZDI=1>N0j~~EvfL3 z9{cz6g~74>Y(SLw`RB8BwpKhjSn^+kFAf7AO}amc3SL{D1+q7lc}jt(CYooUoSZiO zJTF~!#S*vL`vT(*VcO*LTVZcDXaL1a^QiK=c5S*|?P$8H;)@?c&!|!xqdTlCHo;6~ zGX2hT&f}aD*n-03^sA9Xbn!TZO>FB5gT2qsA;w{S`fxRX-xe&^%4#n}%%g}g;=-&R z2tN42SXCLUjHLJ)E$(bb`#jZD6|+qE^-tLQ$vjeaYOhI8l`Qm;uGeSdAqr%g5%6la z1*Wua3VtFv>V$%^QJ?Fw?3)at)j1}%iM5Qasj?nOZI@+(+4{W#yh+{+IqfDGs zfgmWxgLV?S-bkn;E`{l0*We+lXZhjGLB4PbrS6$(m-)%YUYc?KxTnLXv9S3;7X{-k z6A6SF!TSf>Oo81d*+18AXuHm&&e)c8hKeK1!cw#_z3Uw^D>r5MXJ5LIe>r*vBIe!h zogYjiKsw!m)iAzHgdz%^7_WDiVh_&QlkF?|+)N~Z*vsPQJ_ljcuwEBgW12TKB2oi0 zyJAW5tuzT*NTv1t72#J|9A$#{4C|cf|4Ji!BKRhM2@=3X?zl1i z6kbuN4jHlKI&#I)KyByYrdKtw?|!e&Ew)M=8yA-Vd(V)~m^l5z*ADN>d#=?j zI>q7b17HE^$>o9R{nXjYyfg2B9%T z+0MkUZ*2Qu0T^=mqW!}mZbP+|1SUUwIuqWQC|+~|PT5|-cynKIXF=-iU12;3w<*Tj z%$aLW*)e^4PFn~5-l&ZPaSV}#4Qn}bn$(|E&H8=bu9(8NzS~kjMcY{K z7O{2MpXm({Hr{$MtExP8)Ou#uPk`}@Q1{v~a%CK^#_o|nJ}EqA>CH|;cHyDS#aKF7 zwg;vw+t+({JuqZ#1Wt--R22)>6T|y!h+!s!4ikglVIr8AmnF2pjhoLgv%hfx5Y@xkbF+}#&j?Il)E)T;dP24 zY5GW;vsv2ZK;_16GKBxmif$-4>UF;y*SE7O+aodkW-3TY~bAYos^G0-wR5-~rwFKsn^7RJ;ZWnOT?HeUPhrDw( zrwv~Hagjt^U(z11jXn^x8=~+R&90|8>hbV6blYe`^_CLom5Vnw4noBCfF*l}s$XrQ zocGDD+}td_cKd2*S&0E{2a@0u4rR`2+IGKTluoXE{=5i{1d)v6f|mW$v!ak^LnR{oa;iCBwGDM?Z#&?yJ}pC_$USdkgOr-?DGblb8xdnQ~|l`wWMI z`2`G?zvy|W#`Za(>Z50BISR)^BHg(l%4epjHk_vq${UGQ>Gg*V)V9>)a{EZNRA+D;f{ z8JfW6i%0Yw72WRXFr0ZC9ho%>cknu@yyMzG!s!}k>}*z>dlNiIpf57qL?Zzr!fXW%lB+!_NH#AYx*Lr9LRWr2vlo}un8LyXQA48{l5^8NM1v7moFKy`Ap8XT}jG=q)rv9p10{vIGOFmU?`AFne<-TXYZ`}%t( Tj~lOrL}33<)_We96EFM+TwC#C literal 0 HcmV?d00001 diff --git a/flatpak/logo64.png b/flatpak/logo64.png new file mode 100644 index 0000000000000000000000000000000000000000..dba403d89c629aff3aa82fe6fda3d6808a21f3e7 GIT binary patch literal 1607 zcmZ`%Sy)p?7#)a|9jVB!vbexa0tOM4)G7$kgqs`IuqqfZKwJPJ6b-o{LGWW!mSRyr z1wo;TQj|@wvegeoR0K*~DvBr~TSY;v2Ga>X^`Q?lbLZUipL711N%vsw(AF@~003w+ z=&oLHby5s90-R4bNG9O2D8dL z08mc?U>H-}8(#Y84fV&X)m2*HFlXflE&S~G7G5kyZ7MrDLN}s(~A%!Ku7*Ko_;{bvx)S#V(i+}`i#WxHL zT@gVZ6mOVQM25$ru!A5@#*$WU=Ob_@#hywlst-AI`qm}WM24YZ>WHqvn`dz7oU--w$O(djwv&;0v=Tg`zuLL7K{PI^3wo&;#NCP~|9w(ut17dI+lW85m9Vt%~kq zyS0RRS|5DwI~lIdKK>n(1rgqQ&PL+4ZI&7)Wd9{KncAMV{=aby^=obg*USyxO=RdO zTl51ZWx7?Z$1C~a&aBTPu?-E)tZ-TDhl2@cBJ%EV|9;lk$k0kVyDt98*3mvaCKxeJ zFaF#ez@Rw4pKx5(w3=%x;|$7FvIJx8G+oKznpEeI&^qU(Xx}fMV|V?k7Gb`ZgI!9i zu?C6lc(2rS)U?V}a{EHoy+uFThiMvJbwGO_PCkAr&Y1k6)~xxoQzjDOmwz)cTixhU z^V*HQ&!Y+3b(a0{wX~zPJGabww~Eg3Ql}N}=I&d~sob51Zk?}6>sM-6<(=Y}k*~jz zUGivnIzL}EKMT>y7g)fa%w+!?saeP#+r$%3*QTfRwFf zSlKghJaSkqMwqHoqo>m3>T0OL&sZ*>j5AklN)+6Y44#QfBFmYB9+mE6W=8!wS~@c* zUYb)NEYV8s7#hlbXcxbGKYx$^ph?Dj?uLUYU-OMSNyA+knN+9FgG)EH4V~@YE|Gap zNRrbE_D%78<=u~OORvRDZO)8}bInE!ZG+GKd9R>4^1gMt->*lr_DP51E2>O49-9wp zV1%9d{hdxgOHkzc$iZuaQmNUd6<*yJvwQREW;@;ABNeYJ9^{>yeX}-rUH5o(@THlJ zh(W#Pk(^lrd933y>54DSb1m~Ji-iSihK8+ZavAN#&3g5a7RQ@Ut)g4LlPUX{9U7m} z3Y&&cZ#AFh&)>u0bE#pmT)2RBlywd^6bBo-4L)`b)b);3iam)!p;9OwRaegbr{F;J z9$xt2|1YqZHdqM@j1>;v(c%0APAnHBBqZ4KqV~mwabmc((XkOlQ=3g72{33Z*DKq0 GC;tmt5}ytL literal 0 HcmV?d00001 diff --git a/flatpak/scripts/README.md b/flatpak/scripts/README.md new file mode 100644 index 00000000..8d387451 --- /dev/null +++ b/flatpak/scripts/README.md @@ -0,0 +1,29 @@ +# Prerequisites +## Setting up the flathub_meta.json file +The dart scripts here require a metadata file as input. This file is already prepared for this repository — see ../flatpak_meta.json. + +## Publishing on Flathub +If uploading to Flathub for the first time, follow the official contributing guidelines: https://github.com/flathub/flathub/blob/master/CONTRIBUTING.md . + +# Local builds + +To run a local build: + +1. Build the Linux release using Flutter. +2. Add the new release version and date to the start of the "releases" list in the "spec.json" file (and adjust other parameters in the file if needed). +3. Run "dart flatpak_packager.dart --meta ../flatpak_meta.json -n pubspec.yaml" in this folder. +4. Run "dart manifest_generator.dart --meta ../flatpak_meta.json -n pubspec.yaml" in this folder. +5. Test the Flatpak using the guide at https://docs.flatpak.org/en/latest/first-build.html, using the generated json manifest as your Flatpak manifest. + +# Builds based on data fetched from Github + +To generate and publish a release on Github: + +1. Create a new release on Github using the app's version name for the tag (e.g. "1.0.0"), without any assets for now. +2. Build the Linux release using Flutter. +3. Run "dart flatpak_packager.dart --meta ../flatpak_meta.json --github -n pubspec.yaml" in this folder. +4. Upload the generated tar.gz file as a Github release. +5. Run "dart manifest_generator.dart --meta ../flatpak_meta.json --github -n pubspec.yaml" in this folder. +6. Upload your Flathub manifest file to your Flathub Github repo, overwriting any old manifest file you may have there. If you're building for only certain architectures and the "flathub.json" file is not in your Flathub Github repo yet, upload that too. + +All of this can be automated via Github Actions. \ No newline at end of file diff --git a/flatpak/scripts/flatpak_packager.dart b/flatpak/scripts/flatpak_packager.dart new file mode 100644 index 00000000..2caaf525 --- /dev/null +++ b/flatpak/scripts/flatpak_packager.dart @@ -0,0 +1,185 @@ +// ignore_for_file: avoid_print + +import 'dart:io'; + +import 'flatpak_shared.dart'; + +/// Creates an archive containing all the sources for the Flatpak package for a +/// specific architecture. +/// +/// arguments: +/// --meta [file] +/// Required argument for providing the metadata file for this script. + +/// --github +/// Use this option to pull release info from Github rather than the metadata file. + +/// --addTodaysVersion [version] +/// If pulling data from Github, this provides a way to specify the release to be released today. + +void main(List arguments) async { + if (!Platform.isLinux) { + throw Exception('Must be run under Linux'); + } + + // PARSE ARGUMENTS + final metaIndex = arguments.indexOf('--meta'); + if (metaIndex == -1) { + throw Exception( + 'You must run this script with a metadata file argument, using the --meta flag.', + ); + } + if (arguments.length == metaIndex + 1) { + throw Exception('The --meta flag must be followed by the path to the metadata file.'); + } + + final metaFile = File(arguments[metaIndex + 1]); + if (!(await metaFile.exists())) { + throw Exception('The provided metadata file does not exist.'); + } + + final fetchFromGithub = arguments.contains('--github'); + + final addTodaysVersionIndex = arguments.indexOf('--addTodaysVersion'); + if (addTodaysVersionIndex != -1 && arguments.length == addTodaysVersionIndex + 1) { + throw Exception('The --addTodaysVersion flag must be followed by the version name.'); + } + + final addedTodaysVersion = + addTodaysVersionIndex != -1 ? arguments[addTodaysVersionIndex + 1] : null; + + // GENERATE PACKAGE + + final meta = FlatpakMeta.fromJson(metaFile, skipLocalReleases: fetchFromGithub); + + final outputDir = Directory('${Directory.current.path}/flatpak_generator_exports'); + await outputDir.create(); + + final packageGenerator = PackageGenerator( + inputDir: metaFile.parent, + meta: meta, + addedTodaysVersion: addedTodaysVersion, + ); + + await packageGenerator.generatePackage( + outputDir, + await PackageGenerator.runningOnARM() ? CPUArchitecture.aarch64 : CPUArchitecture.x86_64, + fetchFromGithub, + ); +} + +class PackageGenerator { + final Directory inputDir; + final FlatpakMeta meta; + final String? addedTodaysVersion; + + const PackageGenerator({ + required this.inputDir, + required this.meta, + required this.addedTodaysVersion, + }); + + Future generatePackage( + Directory outputDir, + CPUArchitecture arch, + bool fetchReleasesFromGithub, + ) async { + final tempDir = await outputDir.createTemp('flutter_generator_temp'); + final appId = meta.appId; + + // desktop file + final desktopFile = File('${inputDir.path}/${meta.desktopPath}'); + + if (!(await desktopFile.exists())) { + throw Exception( + 'The desktop file does not exist under the specified path: ${desktopFile.path}', + ); + } + + await desktopFile.copy('${tempDir.path}/$appId.desktop'); + + // icons + final iconTempDir = Directory('${tempDir.path}/icons'); + + for (final icon in meta.icons) { + final iconFile = File('${inputDir.path}/${icon.path}'); + if (!(await iconFile.exists())) { + throw Exception('The icon file ${iconFile.path} does not exist.'); + } + final iconSubdir = Directory('${iconTempDir.path}/${icon.type}'); + await iconSubdir.create(recursive: true); + await iconFile.copy('${iconSubdir.path}/${icon.getFilename(appId)}'); + } + + // AppStream metainfo file + final origAppStreamFile = File('${inputDir.path}/${meta.appStreamPath}'); + if (!(await origAppStreamFile.exists())) { + throw Exception( + 'The app data file does not exist under the specified path: ${origAppStreamFile.path}', + ); + } + + final editedAppStreamContent = AppStreamModifier.replaceVersions( + await origAppStreamFile.readAsString(), + await meta.getReleases(fetchReleasesFromGithub, addedTodaysVersion), + ); + + final editedAppStreamFile = File('${tempDir.path}/$appId.metainfo.xml'); + await editedAppStreamFile.writeAsString(editedAppStreamContent); + + // build files + final bundlePath = + '${inputDir.path}/${meta.localLinuxBuildDir}/${arch.flutterDirName}/release/bundle'; + final buildDir = Directory(bundlePath); + if (!(await buildDir.exists())) { + throw Exception( + 'The linux build directory does not exist under the specified path: ${buildDir.path}', + ); + } + final destDir = Directory('${tempDir.path}/bin'); + await destDir.create(); + + final baseFilename = '${meta.lowercaseAppName}-linux-${arch.flatpakArchCode}'; + final packagePath = '${outputDir.absolute.path}/$baseFilename.tar.gz'; + final shaPath = '${outputDir.absolute.path}/$baseFilename.sha256'; + + await Process.run('cp', ['-r', '${buildDir.absolute.path}/.', destDir.absolute.path]); + await Process.run('tar', ['-czvf', packagePath, '.'], workingDirectory: tempDir.absolute.path); + + print('Generated $packagePath'); + + final preShasum = await Process.run('shasum', ['-a', '256', packagePath]); + final shasum = preShasum.stdout.toString().split(' ').first; + + await File(shaPath).writeAsString(shasum); + + print('Generated $shaPath'); + + await tempDir.delete(recursive: true); + } + + static Future runningOnARM() async { + final unameRes = await Process.run('uname', ['-m']); + final unameString = unameRes.stdout.toString().trimLeft(); + return unameString.startsWith('arm') || unameString.startsWith('aarch'); + } +} + +// updates releases in ${appName}.metainfo.xml +class AppStreamModifier { + static String replaceVersions(String origAppStreamContent, List versions) { + final joinedReleases = + versions.map((v) => '\t\t').join('\n'); + final releasesSection = '\n$joinedReleases\n\t'; //TODO check this + if (origAppStreamContent.contains('') + .replaceFirst(RegExp(''), releasesSection) + .replaceAll('<~>', '\n'); + } + return origAppStreamContent.replaceFirst( + '', + '\n\t$releasesSection\n', + ); + } +} diff --git a/flatpak/scripts/flatpak_shared.dart b/flatpak/scripts/flatpak_shared.dart new file mode 100644 index 00000000..eaf445a0 --- /dev/null +++ b/flatpak/scripts/flatpak_shared.dart @@ -0,0 +1,330 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:http/http.dart' as http; + +/// Shared files for the two Flatpak-related scripts. + +class Release { + final String version; + final String date; //TODO add resources + + const Release({required this.version, required this.date}); +} + +enum CPUArchitecture { + x86_64('x86_64', 'x64'), + aarch64('aarch64', 'arm64'); + + final String flatpakArchCode; + final String flutterDirName; + + const CPUArchitecture(this.flatpakArchCode, this.flutterDirName); +} + +class ReleaseAsset { + final CPUArchitecture arch; + final String tarballUrlOrPath; + final bool isRelativeLocalPath; + final String tarballSha256; + + const ReleaseAsset({ + required this.arch, + required this.tarballUrlOrPath, + required this.isRelativeLocalPath, + required this.tarballSha256, + }); +} + +class Icon { + static const _symbolicType = 'symbolic'; + final String type; + final String path; + late final String _fileExtension; + + Icon({required this.type, required this.path}) { + _fileExtension = path.split('.').last; + } + + String getFilename(String appId) => + (type == _symbolicType) ? '$appId-symbolic.$_fileExtension' : '$appId.$_fileExtension'; +} + +class GithubReleases { + final String githubReleaseOrganization; + final String githubReleaseProject; + List? _releases; + List? _latestReleaseAssets; + + GithubReleases(this.githubReleaseOrganization, this.githubReleaseProject); + + Future> getReleases(bool canBeEmpty) async { + if (_releases == null) { + await _fetchReleasesAndAssets(canBeEmpty); + } + return _releases!; + } + + Future?> getLatestReleaseAssets() async { + if (_releases == null) { + await _fetchReleasesAndAssets(false); + } + return _latestReleaseAssets; + } + + Future _fetchReleasesAndAssets(bool canBeEmpty) async { + final releaseJsonContent = (await http.get(Uri( + scheme: 'https', + host: 'api.github.com', + path: '/repos/$githubReleaseOrganization/$githubReleaseProject/releases', + ))) + .body; + final decodedJson = jsonDecode(releaseJsonContent) as List; + + DateTime? latestReleaseAssetDate; + + final releases = List.empty(growable: true); + + await Future.forEach(decodedJson, (dynamic releaseDynamic) async { + final releaseMap = releaseDynamic as Map; + + final releaseDateAndTime = DateTime.parse(releaseMap['published_at'] as String); + final releaseDateString = releaseDateAndTime.toIso8601String().split('T').first; + + if (latestReleaseAssetDate == null || + (latestReleaseAssetDate?.compareTo(releaseDateAndTime) == -1)) { + final assets = await _parseGithubReleaseAssets(releaseMap['assets'] as List); + if (assets != null) { + _latestReleaseAssets = assets; + latestReleaseAssetDate = releaseDateAndTime; + } + } + + releases.add(Release(version: releaseMap['name'] as String, date: releaseDateString)); + }); + + if (releases.isNotEmpty || canBeEmpty) { + _releases = releases; + } else { + throw Exception('Github must contain at least 1 release.'); + } + } + + Future?> _parseGithubReleaseAssets(List assetMaps) async { + String? x64TarballUrl; + String? x64Sha; + String? aarch64TarballUrl; + String? aarch64Sha; + for (final am in assetMaps) { + final amMap = am as Map; + + final downloadUrl = amMap['browser_download_url'] as String; + final filename = amMap['name'] as String; + final fileExtension = filename.substring(filename.indexOf('.') + 1); + final filenameWithoutExtension = filename.substring(0, filename.indexOf('.')); + + final arch = filenameWithoutExtension.endsWith('aarch64') + ? CPUArchitecture.aarch64 + : CPUArchitecture.x86_64; + + switch (fileExtension) { + case 'sha256': + if (arch == CPUArchitecture.aarch64) { + aarch64Sha = await _readSha(downloadUrl); + } else { + x64Sha = await _readSha(downloadUrl); + } + break; + case 'tar': + case 'tar.gz': + case 'tgz': + case 'tar.xz': + case 'txz': + case 'tar.bz2': + case 'tbz2': + case 'zip': + case '7z': + if (arch == CPUArchitecture.aarch64) { + aarch64TarballUrl = downloadUrl; + } else { + x64TarballUrl = downloadUrl; + } + break; + default: + break; + } + } + final res = List.empty(growable: true); + if (x64TarballUrl != null && x64Sha != null) { + res.add(ReleaseAsset( + arch: CPUArchitecture.x86_64, + tarballUrlOrPath: x64TarballUrl, + isRelativeLocalPath: false, + tarballSha256: x64Sha, + )); + } + if (aarch64TarballUrl != null && aarch64Sha != null) { + res.add(ReleaseAsset( + arch: CPUArchitecture.aarch64, + tarballUrlOrPath: aarch64TarballUrl, + isRelativeLocalPath: false, + tarballSha256: aarch64Sha, + )); + } + return res.isEmpty ? null : res; + } + + Future _readSha(String shaUrl) async => + (await http.get(Uri.parse(shaUrl))).body.split(' ').first; +} + +class FlatpakMeta { + final String appId; + final String lowercaseAppName; + final String appStreamPath; + final String desktopPath; + final List icons; + + // Flatpak manifest releated properties + final String freedesktopRuntime; + final List? buildCommandsAfterUnpack; + final List? extraModules; + final List finishArgs; + + // Properties relevant only for local releases + final List? _localReleases; + final List? _localReleaseAssets; + final String localLinuxBuildDir; + + // Properties relevant only for releases fetched from Github + final String? githubReleaseOrganization; + final String? githubReleaseProject; + late final GithubReleases? _githubReleases; + + FlatpakMeta({ + required this.appId, + required this.lowercaseAppName, + required this.githubReleaseOrganization, + required this.githubReleaseProject, + required List? localReleases, + required List? localReleaseAssets, + required this.localLinuxBuildDir, + required this.appStreamPath, + required this.desktopPath, + required this.icons, + required this.freedesktopRuntime, + required this.buildCommandsAfterUnpack, + required this.extraModules, + required this.finishArgs, + }) : _localReleases = localReleases, + _localReleaseAssets = localReleaseAssets { + if (githubReleaseOrganization != null && githubReleaseProject != null) { + _githubReleases = GithubReleases(githubReleaseOrganization!, githubReleaseProject!); + } + } + + Future> getReleases( + bool fetchReleasesFromGithub, + String? addedTodaysVersion, + ) async { + final releases = List.empty(growable: true); + if (addedTodaysVersion != null) { + releases.add(Release( + version: addedTodaysVersion, + date: DateTime.now().toIso8601String().split('T').first, + )); + } + if (fetchReleasesFromGithub) { + if (_githubReleases == null) { + throw Exception( + 'Metadata must include Github repository info if fetching releases from Github.', + ); + } + releases.addAll(await _githubReleases!.getReleases(addedTodaysVersion != null)); + } else { + if (_localReleases == null && addedTodaysVersion == null) { + throw Exception('Metadata must include releases if not fetching releases from Github.'); + } + if (_localReleases?.isNotEmpty ?? false) { + releases.addAll(_localReleases!); + } + } + return releases; + } + + Future?> getLatestReleaseAssets(bool fetchReleasesFromGithub) async { + if (fetchReleasesFromGithub) { + if (_githubReleases == null) { + throw Exception( + 'Metadata must include Github repository info if fetching releases from Github.', + ); + } + return _githubReleases!.getLatestReleaseAssets(); + } + if (_localReleases == null) { + throw Exception('Metadata must include releases if not fetching releases from Github.'); + } + return _localReleaseAssets; + } + + static FlatpakMeta fromJson(File jsonFile, {bool skipLocalReleases = false}) { + try { + final dynamic json = jsonDecode(jsonFile.readAsStringSync()); + return FlatpakMeta( + appId: json['appId'] as String, + lowercaseAppName: json['lowercaseAppName'] as String, + githubReleaseOrganization: json['githubReleaseOrganization'] as String?, + githubReleaseProject: json['githubReleaseProject'] as String?, + localReleases: skipLocalReleases + ? null + : (json['localReleases'] as List?)?.map((dynamic r) { + final rMap = r as Map; + return Release(version: rMap['version'] as String, date: rMap['date'] as String); + }).toList(), + localReleaseAssets: skipLocalReleases + ? null + : (json['localReleaseAssets'] as List?)?.map((dynamic ra) { + final raMap = ra as Map; + final archString = raMap['arch'] as String; + final arch = (archString == CPUArchitecture.x86_64.flatpakArchCode) + ? CPUArchitecture.x86_64 + : (archString == CPUArchitecture.aarch64.flatpakArchCode) + ? CPUArchitecture.aarch64 + : null; + if (arch == null) { + throw Exception( + 'Architecture must be either "${CPUArchitecture.x86_64.flatpakArchCode}" or "${CPUArchitecture.aarch64.flatpakArchCode}"', + ); + } + final tarballFile = + File('${jsonFile.parent.path}/${raMap['tarballPath'] as String}'); + final tarballPath = tarballFile.absolute.path; + final preShasum = Process.runSync('shasum', ['-a', '256', tarballPath]); + final shasum = preShasum.stdout.toString().split(' ').first; + if (preShasum.exitCode != 0) { + throw Exception(preShasum.stderr); + } + return ReleaseAsset( + arch: arch, + tarballUrlOrPath: tarballPath, + isRelativeLocalPath: true, + tarballSha256: shasum, + ); + }).toList(), + localLinuxBuildDir: json['localLinuxBuildDir'] as String, + appStreamPath: json['appStreamPath'] as String, + desktopPath: json['desktopPath'] as String, + icons: (json['icons'] as Map).entries.map((mapEntry) { + return Icon(type: mapEntry.key as String, path: mapEntry.value as String); + }).toList(), + freedesktopRuntime: json['freedesktopRuntime'] as String, + buildCommandsAfterUnpack: + (json['buildCommandsAfterUnpack'] as List?)?.map((dynamic bc) => bc as String).toList(), + extraModules: json['extraModules'] as List?, + finishArgs: (json['finishArgs'] as List).map((dynamic fa) => fa as String).toList(), + ); + } catch (e) { + throw Exception('Could not parse JSON file, due to this error:\n$e'); + } + } +} diff --git a/flatpak/scripts/manifest_generator.dart b/flatpak/scripts/manifest_generator.dart new file mode 100644 index 00000000..254bb1ae --- /dev/null +++ b/flatpak/scripts/manifest_generator.dart @@ -0,0 +1,167 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; +import 'dart:io'; +import 'flatpak_shared.dart'; + +/// Generates the Flatpak manifest. +/// (Separate from the package generation, as those are generated per each +/// architecture.) +/// +/// arguments: +/// --meta [file] +/// Required argument for providing the metadata file for this script. + +/// --github +/// Use this option to pull release info from Github rather than the metadata file. + +void main(List arguments) async { + if (Platform.isWindows) { + throw Exception('Must be run under a UNIX-like operating system.'); + } + + final metaIndex = arguments.indexOf('--meta'); + if (metaIndex == -1) { + throw Exception( + 'You must run this script with a metadata file argument, using the --meta flag.'); + } + if (arguments.length == metaIndex + 1) { + throw Exception('The --meta flag must be followed by the path to the metadata file.'); + } + + final metaFile = File(arguments[metaIndex + 1]); + if (!metaFile.existsSync()) { + throw Exception('The provided metadata file does not exist.'); + } + + final fetchFromGithub = arguments.contains('--github'); + + final meta = FlatpakMeta.fromJson(metaFile, skipLocalReleases: fetchFromGithub); + + final outputDir = Directory('${Directory.current.path}/flatpak_generator_exports'); + outputDir.createSync(); + + final manifestGenerator = FlatpakManifestGenerator(meta); + final manifestContent = await manifestGenerator.generateFlatpakManifest(fetchFromGithub); + final manifestPath = '${outputDir.path}/${meta.appId}.json'; + final manifestFile = File(manifestPath); + manifestFile.writeAsStringSync(manifestContent); + print('Generated $manifestPath'); + + final flathubJsonContent = await manifestGenerator.generateFlathubJson(fetchFromGithub); + if (flathubJsonContent != null) { + final flathubJsonPath = '${outputDir.path}/flathub.json'; + final flathubJsonFile = File(flathubJsonPath); + flathubJsonFile.writeAsStringSync(flathubJsonContent); + print('Generated $flathubJsonPath'); + } +} + +// ${appId}.json +class FlatpakManifestGenerator { + final FlatpakMeta meta; + Map? _githubArchSupport; + Map? _localArchSupport; + + FlatpakManifestGenerator(this.meta); + + Future generateFlatpakManifest(bool fetchFromGithub) async { + final appName = meta.lowercaseAppName; + final appId = meta.appId; + final assets = await meta.getLatestReleaseAssets(fetchFromGithub); + + if (assets == null) { + throw Exception('There are no associated assets.'); + } + + _lazyGenerateArchSupportMap(fetchFromGithub, assets); + + const encoder = JsonEncoder.withIndent(' '); + return encoder.convert({ + 'app-id': appId, + 'runtime': 'org.freedesktop.Platform', + 'runtime-version': meta.freedesktopRuntime, + 'sdk': 'org.freedesktop.Sdk', + 'command': appName, + 'separate-locales': false, + 'finish-args': meta.finishArgs, + 'modules': [ + ...meta.extraModules ?? [], + { + 'name': appName, + 'buildsystem': 'simple', + 'build-commands': [ + 'cp -R $appName/bin/ /app/$appName', + 'chmod +x /app/$appName/$appName', + 'mkdir -p /app/bin/', + 'mkdir -p /app/lib/', + 'ln -s /app/$appName/$appName /app/bin/$appName', + ...meta.buildCommandsAfterUnpack ?? [], + ...meta.icons.map((icon) => + 'install -Dm644 $appName/icons/${icon.type}/${icon.getFilename(appId)} /app/share/icons/hicolor/${icon.type}/apps/${icon.getFilename(appId)}'), + 'install -Dm644 $appName/$appId.desktop /app/share/applications/$appId.desktop', + 'install -Dm644 $appName/$appId.metainfo.xml /app/share/metainfo/$appId.metainfo.xml' + ], + 'sources': assets + .map((a) => { + 'type': 'archive', + 'only-arches': [a.arch.flatpakArchCode], + (fetchFromGithub ? 'url' : 'path'): a.tarballUrlOrPath, + 'sha256': a.tarballSha256, + 'dest': meta.lowercaseAppName + }) + .toList() + } + ] + }); + } + + Future generateFlathubJson(bool fetchFromGithub) async { + final assets = await meta.getLatestReleaseAssets(fetchFromGithub); + + if (assets == null) { + throw Exception('There are no associated assets.'); + } + + _lazyGenerateArchSupportMap(fetchFromGithub, assets); + + const encoder = JsonEncoder.withIndent(' '); + + final onlyArchListInput = fetchFromGithub ? _githubArchSupport! : _localArchSupport!; + + final onlyArchList = List.empty(growable: true); + for (final e in onlyArchListInput.entries) { + if (e.value == true) { + onlyArchList.add(e.key.flatpakArchCode); + } + } + + if (onlyArchList.length == CPUArchitecture.values.length) { + return null; + } else { + return encoder.convert({'only-arches': onlyArchList}); + } + } + + void _lazyGenerateArchSupportMap(bool fetchFromGithub, List assets) { + if (fetchFromGithub) { + if (_githubArchSupport == null) { + _githubArchSupport = { + for (final arch in CPUArchitecture.values) arch: false + }; + for (final a in assets) { + _githubArchSupport![a.arch] = true; + } + } + } else { + if (_localArchSupport == null) { + _localArchSupport = { + for (final arch in CPUArchitecture.values) arch: false + }; + for (final a in assets) { + _localArchSupport![a.arch] = true; + } + } + } + } +} diff --git a/flatpak/scripts/pubspec.lock b/flatpak/scripts/pubspec.lock new file mode 100644 index 00000000..68090fc3 --- /dev/null +++ b/flatpak/scripts/pubspec.lock @@ -0,0 +1,93 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + collection: + dependency: transitive + description: + name: collection + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.dev" + source: hosted + version: "1.19.0" + http: + dependency: "direct main" + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "40f592dd352890c3b60fec1b68e786cefb9603e05ff303dbc4dda49b304ecdf4" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" +sdks: + dart: ">=3.4.0 <4.0.0" diff --git a/flatpak/scripts/pubspec.yaml b/flatpak/scripts/pubspec.yaml new file mode 100644 index 00000000..4e974d68 --- /dev/null +++ b/flatpak/scripts/pubspec.yaml @@ -0,0 +1,8 @@ +name: flatpak_generator +version: 1.0.0 + +environment: + sdk: '>=2.18.5' + +dependencies: + http: ^1.4.0 \ No newline at end of file