From 581cf068c26d8a595c46793c21d22036fe4b97db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20K=C3=B6nig?= <33655937+jkoenig134@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:40:24 +0100 Subject: [PATCH] Add windows support (#392) * chore: add windows platform * chore: add windows * fiix: not request permissions on windows * feat: handle windows notifications * chore: add windows pipeline * fix: update windows icon * chore: simplify stuff * chore: better dummy values * fix: remove app id * ci: fix build * chore: windows path * feat: win notifications support * feat: app_links support * fix: add scrollcontrollers * fix: app links * fix: better check * chore: remove checks * feat: update msix settings * feat: auto msix building * fix: re-add android and ios to metadata * chore: remove todo * feat: use 512 icon * feat: specify languages * chore: pin countries * chore: bump runtime * fix: simplify config access * fix: get runtime config * chore: switch logger * fix: correctly handle runtime error * chore: remove unused code * chore: melos bootstrap * chore: rm lockfile * ci: only build windows maually --------- Co-authored-by: Nicole Ebken Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- apps/enmeshed/.metadata | 17 +- apps/enmeshed/assets/icon-512.png | Bin 0 -> 19809 bytes .../contacts/contact_detail_screen.dart | 8 + apps/enmeshed/lib/account/home/home.dart | 8 + apps/enmeshed/lib/core/setup_push.dart | 2 + apps/enmeshed/lib/main.dart | 3 +- apps/enmeshed/lib/splash_screen.dart | 50 ++- apps/enmeshed/pubspec.lock | 49 +++ apps/enmeshed/pubspec.yaml | 24 ++ apps/enmeshed/windows/.gitignore | 17 ++ apps/enmeshed/windows/CMakeLists.txt | 108 +++++++ apps/enmeshed/windows/flutter/CMakeLists.txt | 109 +++++++ .../flutter/generated_plugin_registrant.cc | 32 ++ .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 31 ++ apps/enmeshed/windows/runner/CMakeLists.txt | 40 +++ apps/enmeshed/windows/runner/Runner.rc | 121 ++++++++ .../windows/runner/flutter_window.cpp | 71 +++++ apps/enmeshed/windows/runner/flutter_window.h | 33 ++ apps/enmeshed/windows/runner/main.cpp | 83 +++++ apps/enmeshed/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 1386 bytes .../windows/runner/runner.exe.manifest | 14 + apps/enmeshed/windows/runner/utils.cpp | 65 ++++ apps/enmeshed/windows/runner/utils.h | 19 ++ apps/enmeshed/windows/runner/win32_window.cpp | 288 ++++++++++++++++++ apps/enmeshed/windows/runner/win32_window.h | 102 +++++++ codemagic.yaml | 65 +++- .../integration_test_runner/pubspec.lock | 8 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + .../lib/src/enmeshed_runtime.dart | 6 +- .../lib/src/javascript_handlers.dart | 59 ++++ packages/enmeshed_runtime_bridge/pubspec.yaml | 1 + 34 files changed, 1448 insertions(+), 20 deletions(-) create mode 100644 apps/enmeshed/assets/icon-512.png create mode 100644 apps/enmeshed/windows/.gitignore create mode 100644 apps/enmeshed/windows/CMakeLists.txt create mode 100644 apps/enmeshed/windows/flutter/CMakeLists.txt create mode 100644 apps/enmeshed/windows/flutter/generated_plugin_registrant.cc create mode 100644 apps/enmeshed/windows/flutter/generated_plugin_registrant.h create mode 100644 apps/enmeshed/windows/flutter/generated_plugins.cmake create mode 100644 apps/enmeshed/windows/runner/CMakeLists.txt create mode 100644 apps/enmeshed/windows/runner/Runner.rc create mode 100644 apps/enmeshed/windows/runner/flutter_window.cpp create mode 100644 apps/enmeshed/windows/runner/flutter_window.h create mode 100644 apps/enmeshed/windows/runner/main.cpp create mode 100644 apps/enmeshed/windows/runner/resource.h create mode 100644 apps/enmeshed/windows/runner/resources/app_icon.ico create mode 100644 apps/enmeshed/windows/runner/runner.exe.manifest create mode 100644 apps/enmeshed/windows/runner/utils.cpp create mode 100644 apps/enmeshed/windows/runner/utils.h create mode 100644 apps/enmeshed/windows/runner/win32_window.cpp create mode 100644 apps/enmeshed/windows/runner/win32_window.h diff --git a/apps/enmeshed/.metadata b/apps/enmeshed/.metadata index 6560a05f7..f079fb32b 100644 --- a/apps/enmeshed/.metadata +++ b/apps/enmeshed/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" channel: "stable" project_type: app @@ -13,14 +13,17 @@ project_type: app migration: platforms: - platform: root - create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 - base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: android - create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 - base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: ios - create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 - base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + - platform: windows + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a # User provided section diff --git a/apps/enmeshed/assets/icon-512.png b/apps/enmeshed/assets/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..ddf6388ea1dc78a19b3c7179e78108e11b158908 GIT binary patch literal 19809 zcmeFZhd<+R=Rzk|kI#$R$ zvJQ@Oe)sA9`Tib{@Bi?9l!wlFyAbwI1$hr*# za{Uzq!seCJXsiS-kU!GX(tr?%fAU+3pM!7Cd1;&bLLk&k#2+Z+bq+iDlH!TZZB2?l zPSR%;>r8WHlH*Tx^Y>l4bSWbBN!9clTEjV9g#pY#L#TJq?lNA|xxnRrsav4_un{1L@^{`~oV=HfXDJ^=xNpN3FX z4UJ2Y%n%I?4H@nK_u>DRL0{PsG&UA;-_>xvj<=WH*Q7qq{!KO0v82Anvg{?p8zXIn z0`7?&79kdzH%`J_x4e&dvl4sH>s%#+oX?W!SIcRpZTHSGuGfOEo~r!z$?E91866!h z@>17Sl;y*i=$Ok(KFUS3w^gR4M9fCx@?KDUw?KJ$`KM2xQU`t;VA+?VU}VsKPFiSe z-#g9+`-M;46FJ7d(mzE;L_m|d71az4>9taLiYhBp@c8iPn3&x#Ui8tqr%14Br*d8X zjbtW-KR3mM->y>1`1rl4si{|rzCu?Whu)QyC79Pbqz$j8Y`C*e)0wcRUNNFa#_Gpc z7c&Aj#4&cg$<>}{6*?ZJzPIWzYg=1ICZ$FanOZEnB3CX^AM`7Q@82JvQjo>ArYtEe z`zQj!t*orRWy`0KQL{>U&M8?~SR`}FD7@@t(Gep`qpG_6th)OuC5*V_(3=~->OFa5 z=y@Fi1KC~0D>fDzw@cIzGR1|y1L;lVY7h{G6apWbAz}43T~P0pwB4=H zw*K6N>LbA)G{9(=UkE1b1eUMl=JM3JPxpXz|NQy$)}(Y^fi8D-LBRkQBHTD!`a)@& zkFw-m8H71Ob7eSh`0GnC^Ex*UAn9H*uG`!%910+O0>WDwQD}B`l*RAgnbs@aY}WQW zb1g4)ivpFxo8yp4CK`oHv7Ja%#^IrVhNSh-`dC>O9D(=lE$8DKR0WP>@HKF0xrZI- zce3U&^>m$kVN(;A%?t-N$)#Bufj%lV6$Nr)6}R{jwECvE=JCK{o6b$u*AXyeZ1V?R zGi<%vFEvk3NkDecL);B#wzI*Px@1Vr1lDSg+3RFbXAsE*WGbp>&lp&{eLjD?I;_-H zJFKGT>FL=_hX51~&i9f<@KG|uF1;iQM#1jEnQ=k@phM|!((Xw>K#C}C&=T8I_gCyC z)wsow(w@GY+hlu=qVFgdU+Fs_fNFa^`0-9x41KNhPa*&98IHTQs6vn%^z9tOmT-)z{J13xsfSW z*__^;z{XoYSd}a0Y)a(7V|VAzpM~6zZQG;m+2y_CC85^GRyA7rCZ#rswp&vux5=sq zONrt+h;tygokQMV9&Sei0;28D_qSi@3jRHArXrMS52dm5@HpUc$ChCSW8h6@{HmO< zVMy&;g48AqqzcLEpDkDLr#p@V8RCJtz_q;3KWE~a<^o>Tuoy3D04Ar?y^;vs{M5laR0Z!Zp3h| zG7b|1e^*&)XoW9!-gM9rGpkr$FROfCR3y0VJmM>s1T5l`dXE@hvJEE_Fyl?py0a9N z+lUjsDMLy_PY+y>dk)(pFJsq369sW*CvqzNa2Ij%r}#4ge=6PM)Qq6g820^a>9F!G zt9duc6L_p1;PENokv!21C+|7;?fvcSzTZBa*|};Xdsd~UPVVl%Pc|wY7doS;pe6xe z$=e>yi-bwLl->DA`t;#dt+Pb^q_v>Rdu6~5!~Y5+O!!sThe|<&>l@W$B?#vRs&yk? zYc`Pw=V}4*0fDUcrCltP5PDblN?)D~k|vblc6KAVD?zfgkAz++f6-UB-r2B`5eg(^5i-_gEw7}B5o}3M_V5`5!N)+@w%N0%AokwTJOwuf{e}b3Is-DG1Mn=x~ zPrCyjfEzPX((2fgTfP2+te5D$|sYg))oOXl|Wwy{~s^B$Y>qN1Xx z;@sePkXOI7Tpc;mc%M$ZPluJ>jwV~)(_v%0oSBM@g+Tt?^7Wq}9(pAV1W+&<3dxcq zO7W4~1R+;BByc%R3;2NOC0fzr;FklBw?D7)h`#(*+QZs#(9B z(DNdPxdA~4=LueWYm7EyNd0#UX-_g29Ea)RzWzje34i%~)F-vK?RN~7yOS^6_#~?O zJ_nHXHEHVv-4Y@{3>(>FWibNbTy{)cXpbOi#&l8xqhg5{N)4su4O&jazNVWJy)X0}FhrFk z4l`6fcp;bSEYN>0f`X3Q1_&G(4YBtD{`c;cboANj(ca$y$>+nXV<(<6BcaIRyzuMW zWU(G>idMvTTs>SSY}O8&NMxcmD|Lc$dpja2epTC10r;gdo?1s&*I} zWCF?PCT`(V>ds!e#YRI9w`V~puY@6U!ARr;5q09S3UNlT0hnv5;+S`#1PaX9?{VZG!+C;bI zEi3?pt0;awGWMNn%|QTo_9@@@et02wudSC?Do_ClCoDbg4q*0NuQ%mWBnbuGd}DIr7P11s0UW-mbG286woxw(gf_+fb1E?<@ik>*&7z!Ja3RBV`3CTu~PZ`kSz9pk5Yvj8m2f9^g^Xe zE@~x@o*G^1ej;>U2Wm&ty5qaQW_%mM40+0zUtceex7azSc$a7v(n?C-=I8Sp31pNZ z&;m&Q(KG#LM5*o{92l3HKB-~*hysZKfoMB?mU<2_=ImuW;bCo^Cj`;`=FLq`P7V;k zfeN$EWkA6Tc1}(cSHnj=>;RU5DD!{5C(p>4yr!VQjwnB84Vym==Z5U(rrs%n@TD{M z-?kp5=IdbxWSNSN0g8)&a5ybZH}#6rsoD}5Nuj@eKF`JQU;cNu1(Z+6m>vm3>c0Rq zMkd6E%T7PAYhkP&XC%GZLa%ND-1-t(0aHsb4WT3=UnN*Q zjUBQq8t}cq<>!`kAIj9fYW#U~@yGl}6uTWpiT*neL`LAamEk|}qz(KYJrFBoxAU^9 z|NqILf}}Ga&;Y`MdE?*xA4;n4n?h2JEhmbML`F}g{_$a=CLF9&Grrg;AucYCT#4>0 zB?{1{|9P;UUV=lY-ZzVcC52CQb>l?7pU9opi36}mY51oU5@HJhQF)ys{eL2tRCRPL zh9SQIF%=f74?rGw5^bmeg?b541SHjaWRoBQ(WsBh5hbbtq?0F4o~*5Va%ASP5wj!6 zqe8d-iAD@M^!ge#`pC~VKe2XvF^C5w(L~RmqL6>=(nrQ5xD#w=n-a2?Fsw^@bcjl-jyhgZ5;-8 zdvR+4QA-g?ifs&A9eSgOvKUiB(p>E!Vp|*gyU4y$6W}XxC6ki(tN8Hs2Do2Lq|hj} zh$w0{STKFx7h-HfN(-rsNLn@+?LlNjsKQ!zevXZ?NJ5$a`OAweEh7}4Wpn*sI>4G4ZqDZf{TMT@-TegUZY$W{t z7n!u5bf!?X|8mSL-?F*N_ZkK%%!7S9e@~uMAL=uJ$&c zpEpaENB|2dG8YvvwGOvKv-|9c5xTU5;iCmAK4@}#Rq%n!^&2=3_AQwO-9Zo5LU{3jbL5zeM;DnIO@qJ<#=9)o0P#%!~~Ldy>(U%>H7q>LcUo zs_`11-@2eKF!*da&a-Fz@ws*#4*;J&mJ@gDy>;)qdUMC< z7In^i_Wf05OOhXlkU;^Ry;iL$Q=o(qi;_ma4FK9*Zpt+*Z>$9IREErM$fl^jkNR&Z z)^e1kPrqP}cMk^*E$xEB0%DyCmUMaj#JVk9nVL<4d+eZ+f$t8;ZSVRu$hy3_D;n2D zStTrEcTRVYm|>h+qX*WaFYlvV?lxyeHsT)>p|or%A0;U0$Y*C~0VGa&&6Lkzgy{Ds z{Hk^I{r%Fc2YqKD^G`m=9+%R)m8ULXRHxlAFs@R4kP57(aAfHfykpYb^cyM}XGpYTd~!+XgA&5^*^WTd3>3uNO8Ruw5> zAO&*Cdn|WSOPcOxMiZTr@wqYX`O$9$x-l1)3BRC$2Xmd8Y*rd53qeX4%ln_QhsQ1J zAIcO?H*4?Wirx}Errn;EupD+vd=!+bsELOIMVSxdHyQ(TixWolRrjxR*a2gE;_vSR zR96+a`-lbMtnivtXmhMb#>}QFkyM2DtW&cQm^1fmivc>3;a6?;nr-+yYrW9ZXAKVJUeS98!+&~EA3YgV9?t+ihW_?K8B^&~z& z`|P{=VN9|V#K#{H&T?!@faQ5R01>j-9tK7LA8;^bhz%DWOdsx^>}-4T$3&uGo)|D> z*8@zkf=NJr>mQ912yw~Mzou@JM*#E&S%{DLWPM-6;)hwODM;v7%xmmtI(g4N#<#so z1Lk+3=q)yHE%5nGQ1e#-&|c^Es}o>xk<5XAr8+Gac@KVT#QUBo3&0HJGC`DF z1#KXlH^GA?oY-AUyRP$K7d}#*(vk;CpE{4(uZLUHe>OI{j?SVfNtRe}Yl_KonEus0 zVi$r%?4H-Sx#P(AI4f@@P#_oad2Qca-ksg_6zYq6qyT&m5L;59>7*8QVMkC+~PSBI266yJdrFM<`F$Mw4rn|8u* zVcs%RQEvdS0ZG01vr{6{1>)2#db*_Gy`%cquK6_l+R~Z9{(012D5!S3AEh1dt=8If zz%s6wtC^Wilsrydx=m`jkm&%5X_=(gC~V5nk*2jU?+qCRg@5h1?d|u)#W6tEQc_Z? zD2t?k@q}wLIKaOus!EDL^!Qa@S7&mC)!EatxTWO{$5n^KsUC&v(Yq^>B`8UA0QY5RYbD>Bp_SKL zRs3sgYfyQ(Stis}uNc-2VbJ}s2B2O0_{S|+Lcqo;!CIrj@DwBya z#To8=&Mx_c9~Ai654y10wKd2HZ^K2@v_argc^gIo&w=<=A2h8&H(~$X@ucPtB2es@ zeB{}Mc)*39_U_uro*I7Tm2{vp=H=?z#`cW&=GIXXM;Ha|y(^|T&37Ztl&S_+mM@sZ zgEnHKetBzoq)>-%)mlfpoYU>iSAul4FqkSTB_d1DX53ur^vZn<=b9xq{@Y|P zJICiL~2KvJ2w>esSI%6IdrC~_mM=sU&e zJLZJ*L&Ru!BV~-pa;CK0FY#4$Ro*Ku2zZ=|#kd|lr=TKx^_hm%nv!~=qh`Q%SJP;X zJ#5B!_)7_YZ0Y6HpH?{d*&oB+>|Z6|6#(U?E(xL;<@mUJk8Z^~meSYFL8rHonLAM= z!<}9XtWGIa-)}rC_gYH)My5SZH-H%1};)iL7#LC(E#cY#=x! z@PZ=#x%U{1cG=Qo??Czrs;$m6qGn?p+h~tMx$EHUb%)f*4LSc2_ySr-fTlqeBFII@ za>ar?6CXS&_Fbh??n&CD-F&Y;t0`FF6IkJe^v3FQvYq3djx7ZFG%q#cz0rn2O2+B6 zgc7rSlMR+vZ44DB%9EI~1*)eaYt6dqjJ?L zKJgpd2J@f!aHkh2zCul}Tu~yaAm}wCy5fg0e-}NT7!ncIg#!Xf1)hOujoNpNAWpaJD)UzZ z7gm(f*lUvns!xqb+NOrPX1DZ)=-ChXGh^g&=MF!>zg(9{mKMbe{9tB=yw-ix3~yB9 zVco`2V=LQex>P&vmJ#glyXak{_3w-EgGU^HmfWo#KBJIFY#KUmyow+LxhfXa$Evm3 z5@ysNP!fbLjAt8Zb{n<01Uu@)P)A(f+)k%Azzwcp&ZmcCmogUD}}FR_%9k(^GmT#L}usZ^lRnjXf`=|Cn}dR zfF_tgk_MD-u*db-^H?r)_l?m7I>+PF^}l&EBX=&0zBg}B#Nsjx@SD586v&%-nPC6I zp9tjLSYS6HuCmiC9E$1ilbwV2ryG8z4-9JfT_mT{;LOv540-zB>e^7gBm+}i8*a|4 zAWv`7^saK&lR`BlStSIG9{GB@ot?r_EKD>Z&X;#~NK)^->~ob9#arLm%@gY1Ao)!G zK`^lE?@hezQioKEd8A$E7?dcXK4!=dp@*y157z5{Ph-Zc$(c&9hR=7^+8+#a&R^VU z`cwION{<=;Pb#8hNZB~Yg0XnQVbgp9oT85To&XEM`KlZ#RuCI?EiBZ@ik^gpf&xS2 zE#fldou(zuRqu?>Gph28%2XG14k3#FDb4}oZm(=7+z)tRZQ++#$dXd1JWHgcO7@%- zZF}PpUeUm73T1_Y) zZ9!(=lo#TpvYCkoKY=Mw5yjK;gV#_TW+pJhtKzc6B=|683Khlw?pKE-Nl4&t-Z$UI z2j0g$aG5^+kSqJlNugcZro1aO`cL!5Q8n03Yp~s|Jh~YldkJ_~O~VG7qBkmYC*pzU zQ`mz0dXuxrZVm+IHrJu#ePiX2K8B5{X2FldC?-2rmXZv4%h6rok7olL{%qo{C0z*Y ziEUoAJ&jFAHS~RV`y1YFH-a(y{6}~?qlU)1J~ZE0un7;vZ=@`|T3}v&9H}6*{n6sx>XPlV2#O$=sKU8k!Qv~NR3g`_4paiyd@pw0 z$min|06PuewR>12rwQETCmnuc2YLSEPbp@0&#%E5=yujLS<;mp7j`R? zJ|3HGz*}f+A$?6=^DKe$wF_IX8AJs9-a)>L6bLZ5vxE!_=hLvG@Y!n3qL4rgK@F{P z0E^YOx(ty`2YrGpPqp*lVjL>f_iYG*9j4j>5ipUbf+X2Ak0Z{3E4N3fQz_(1*;F;w z^?+zK$_caC3%@*_H`~kwL!dk-#Z&G#TT*n1+wA$Ih={+ilADfkOPZT~9gEhM~ex~PJ!}^!}P=$UtyvaY?dZ&;vEzuZKBJbMe zZHp`>kOVh&_VCLQT@ni zj~J2%w<<1v)ZrD7x&7~+o9rZ;2v&cUe0N0c7r(b{G{a%Sxa*i}^r{-~lVGaK<~L-< z+kQ_=-9MM`R2Hs>J!xvcimo`jk%@f|c8^Q|DHGI&_~CHjE9B$rxID5E7bJJpsqrL1 zKd@;`3T!OXlOY?czr4iH`&K#n-lrKo%M(xLI>a?&trsTlb%A|BBCXww$;YtfeRXzG z*Db{OtV72=}&+x-lhKWcVr(P5zFt4{gB!vzIH*276U-YGYmWrBrUA|OmBv03 zSH6uKYM7Ehy8H4^T=&Taj2lB02O4Q3zDB?T{9^4gG9>lZeaFZ5#}^dfE&>p z)3@i;=6`>?i#WF$wTv_u>#9r|5d|T^1cU_R8`bqsqJ%?vrGhWZh*R?Iv^QTvaekDKelK;NC47;>|^ws^`l|`(&1$b*Va9a)xi2ZG>t3XDp0l$f%8cUt4!Q8$YPM z$$ShGB-4!&I1btW9H-`Hpw9f`O!X)!W+xui9(IiQ6aS8^%JSJ|C+Cv?EH%FIEYnKG z1UrTqNM6%Ty=${82$1hREQ%D6kPAp)C+VRjw4Bg>&-kf?{3wkgP$O#}^3J_K0>Og` zs5{;wXm7lI@c=aO3)*AGxjzQ^Vo~0sCv0iXZy`pW;n$S@w5<#-=zAqbLu=X@Ten#{ zG4=B^7=Jq>7;_rvBZBCV_>o*;wcJ+E$g2GpgCF6veJbyRIN3*`w#x!CVE1PIYx0vL z2_^cBhu39j_y#30Z^h|Xn+0(evjhQ|BvWT%ET#Xj&^FOlT*r%)RbEm(ci&IFfh3=H z^h{FVC$Oct^e)Ki8S}R+`Z0(fiTn+&)-N0AJEt8 zGFm9Gjei*;sq+=)H6}pQtW?Tgg5ClL0cIh492Br&pU=1IfufiX_yXSPLBe8rJj`&hUgj= zNl??@)`r257;pt+4Klt)su>i5Qruut&D32RPOadf@X5EE!sgK2Nf;!^ELvs!3PTx@GoHZQRgL2Q5gTF=q_a{sDOzu$h|__ zWF_Lz(6gP+c$572Xns|%4F~sS~dpZ6p1*SRt1~HwTgMM7;}8Nr6aXMHS7M%z=AZvI(UGkpSEHW4uB{&D z($wAYf@HICqiW)eqsQW7BcQPZ=&N!01F}74;*8Gna8GK@RYjE~E*@di@d|mytmr>T z=m}JO+TP1vn5nD584pvnV86;pK8H8I?|q);cwUrrbb4)({_zGTI{>v8(RRZ9a^Ys; z#(C14?68jyubg)VdyCFe{&!Wq$(vVQrCh5IDN1S^rqSbvxZQbm`r*ag?W~!@JnP@` zDwqc^^aK;7fgXMs0U$5%LB_yc1;nm}Spw{swS;`(;ob+apQ7eClJ>z4VNkNPHIMdx zvwX=>yMoCh9OvOyJn=%mea!h)|IzvS3x7Mcdi&S11-4dl188U&XnLe~qwD8c=kKN_{!IOP7rhN5iiG{GU)^G+#o4I^(D?DC$S+NK~k z8u$12y_S;X|Nb#Vybe`KL9zCFFXo*;pG61MApejuaN!h#I#TnLXLjYHOtQ{gzLdC$ zajfcDRKO6Fe|1~K$27AYwfs)ork;Of;3HOn`5cKBiQ6$uq)pb(hTAv}-J)3Ko2~fg zN5^unx^n9?7Ke`z}nEt=K86R;>&-c-a2421Yg2VJK=UTJGp+&!-f4 zUeG!*5ks0&!AABL6&7%@AEK_AwLeffrzb%dqQysZ8?^}b%LE41pnPHmqg+)XS3$>S z{?Bx0fweYi#QS?>+`ysZx9(@WPQd)@-Q$*|3b zEo^)0o-+Ov3C;1sLu3srk)>ogE|AAILzWFV63i3<+C8*naR#*bSC+`@$z_QB$mATP zgbdooN~%IGsmy5*Eyf^mfd*z$?=Qt+k;sKFf6Rrq@?Gh8+y>=B({JtFx~I>6^ehUo zf2RZx@aH`w^|7596}vDFBUW`F7l`3q0IjNgdd2oKzifFQcJ{mbuH|8HB;!0(Z#Z%Q zPU(t+OJ(~Avb0( z>6Pjz>D6}|TB0y24MW_zk#j~qiE;HBM=f}#h~xVE(~I{+Z*#DeGydG*^<6R zu%FhSz1qg}5FT`}JM}{lZZI7wY#2MLi zoyMYFivMPmvHZdMZheS>E@+K@m4sbo11;5sj~h0FT(&*wX=rMfwp82=q3QuO5a@n@ zK?Z{>9HjYm0_}-U-fFS&T;GQ^hw^UD(uByxSkoUxmG6o!R7CctSSTY7dMm={7{u(PsR@e-`QN=4a})cRS-$?EfqEiNr;j%s;Rzd!AZ*F-Q$c)udT2?W zxvum|NN&POXC>R;vMpfrzMoQY&(emS+RQVU#&lFhFW5HEL3+uA+2#T^t~lkgBP^)&0Vl+pM@i z8S5tj2VzgbBI)E*5Seo>kA&H->WWWvuPe(qn$lr3cp1Ru^m9o z%p6=)J)y(5;Jz^LU74ks++_E2#2AzYW+C+XqmVyCwuMB?5w(3!z*IxlBP!_9g#g(` zJFQuVYShTZ1Us4@fSB~-%A{MT_FTnD$9vvr^swg`U(`%GP>LM|vub^^eZiF4__KwH zFJ#kxgAHVJdye(KD~l3CmP(O_<4PV| zim^Obt1{Z_6_Vq2cE*A^HC}@*dY`PzA%j%i+G#{L`{E)OYURl9Xm5XmCbvJro40B{jF(G}i3!2Pv?dx2472)PJa223_^I* zcAwn=eMXSmfXa5bn{amS)FAZ@I^TDmu@P^n6esxee0P;7df6>|`;jWC2IPgkY5%&jdYe!0y4}Zh2GgYRHkt(r;xgk-MQ&@rpi*Y(Sh>50*}|b3G21 z(b=2p;3Y*h!(N|tLtJwchRpl2D=oW|CO$g+dCFm{z)wTce@smUOZNve9eyv~GoMW6{ZGwyL9l4WTP3D)(c@b%VXN^e_a!cW7n66GVbmCkH*$Kq}J4s~= zhDHUumx<{S8;U<-m15@>wm7!oEC^6vw*POJnhHAd5$|M)wn*5x{^IN1R=)}A8Qja5 zNgOVG%EE_v@8QgpXI+NZ{*gj5l=>|4@s*7FmhfwBrwLr2Z4RDdJSVD(Ho@geVoC=ehNNruiDOjkl>c&<*u53kST0oK)_waXR*g+ zw6xq?Tn=L#wGDqJDwbD06pkAD)7EKIZ>@x5yLj-k6UM^6~sH>CX3CG$J_nm3wXQbAjUeBn)qIc*0=T6^xdJm*Le202fcprl~ zgSg%nqJFS1ueDHIeof18M~dLR>f78<8>2U5^#xdNxO?lfLIyrseLy*MOp;VVf$oK_ z3hyc6XzvChfJ^H%I%~w3a$<*KM0)I$<3puALthYGw_5TbFyS{%*7hUKbVF8(@;sa) zsYqGk1RnXdS$>=0+?M7)w>@p9D+-&B)X|Fr^(wcuTyt|)dzPkM`ja!7w{N;iYk4B# zJm-#;dQbG44?ByNeLc3ONKq|G#-2!7x4QoP2^goQvKg(2REoB3wFuzJ?un`|uVCO=X zFAjfB&&3@x`$!x&v(=ycUe>I9zIvVe50}HQP6L&_UU>h8kR@0O7B3~;J+wY*$ubenZD4uzy}!8?OL1JhoRR3Vawz)mPQB+_)T^bx)EVPK zCIw*Zoj3<3P<#Q#*o_X}dcoRE+vX=I#7?eXj+H;IQ2BX@3R>aXw`%Fpg5ZkE>2;M< zQvcsH|5tql(n|@5ipYg1PhltXr{|*E8yHv+rins&`_~nEjIIQR67ZQNSm=bCLT%kZ zff{l&1(-Y48z0KpUR+lO0n=H}#Jpnn)b^vd^!WICYy5Y6E^|>Qr_0L?OC4LJ<30!7 z17l2le#H4VlVV1glu*u=r=>3SoZ9!AC;GecVAti%ayS&E9$uAwnd6+Y1xP74v6IGp zBa1<`oT8mQ<_AuGss0U%0ha47?(qk~5Li4}3Ng-Jy%BHO=zlI;oKyoYEOQ~}XB+kT z(qel`FK!Aj4z-2GyL&YL5lTKdh|2s>r0YC{GMzTF!`VtD3!RSLtP##{xi*{^dZHD& zGgls1!Q@%{@#QZWt55V`c8%RL4`~?s*8#uniDz^iLA`LW;$TJSmt$Nzty?sj$nTXw zkDCVq;0Fd0mbDCMdM_(2b*PLuarjGHh7=194mE~iQsCXIJDYa59JwNPQm!1_*{u{K zo-~q@GIcnpQ4BrWLH9SCyYK#f22L1!0OJ7f&XKVyO+20!Q=VyRiF{Ozq=ktexPl4a zP&kp)pVM@hHvC~7HpC^f{|T5mbe36Y^er(B3twHT-|akXVcwIt`6SGOg%EA4uRtSJ zRvYCxbIhHG`x>cW&=A2OLP>0tGdMFESH~Xi60mIY-m6`bS6y$3jjeU6tebdfhui#d z6T_J0HRatl-0m5BrCY*lrQOu=wHB%WyN2u zyvFAp3%W`iM)n_wK{amqT@Ae9*x&<7Y}i@Gd?{gtRJyg%U1F(qHj&OpJ?x5d-_*_UDyOBC79+gTh}q>?mEz3B7fj zOSJ^N$b~JkP-}6*r?_D6Xk8YSzlKLWr(`AA0aM~IrnIYjC`u98hVMM%R+iH@Sn4fh zrX_n)$ATRxym8!FpO+XyPNd>9kqXh79`xv8n*7Y)Lbis3R`+jfB0cpw*U83BzJ=gs zGv&Oi93jJgMt?H|RewlUWvC53-sw9>5<|1NAEgg>+fy0ApGyHPt3l%%@Cb7 zr*N$YuDOVoE%QUaqnK=xvD5Z<&crD={n3*I|M=5Ial=Ig&Qs5OQ8@#-Bw?Y)A%;+! z!q1Qc6)7EMqN~MQ5bEdZlYrXamkSnG6NK!`Y0XHPmGHlt3`gXRI{jRW-CJgSUK8Jc z$>oeocyooYUT%1Y_S3X=EmZ*I*`p4-)9u30)or#2r@zIF$Q&N9tm!%Gr*<^MZ8L)@ zJhL1_xCt`E>wDZMG@-x8izB@LI{pn2U?NWUiOOxS6An`z&U@`JD4G#Ch+zq2rZ0J#LIrAP*LLlIp)<*he(gR@#AWGV_f z^H|2!q<3~_l0D?$^}LiZP-eV+0g>G=z86IA=1t4F8$KaSoR-)(HYC4ARDIi`_n_!B z%IAUnH%lEmv?sZ#@U6})SY&zD;t0`_qW?Y;h1!bPlYj?55xxtLje%+isqOHjF;|WE z=$#RZx#u?J)9be$I8GW@9r5 zEmccW6d~lDu3ZzS`Sw#BOcG@7mjm6xJ$Qe|r);drN-z}$Bx_Sm^5JlkzNZRuM&npS zv^f_Y$>tOyF;bai*Il4k*YSKBxnp^f;|ZML6EHJd=7HYyxeycaR%Wy+S_+nv704Ls?qQK`vJSK13=eDgAPMI1$dsz;Ox@)Wh;2Tk6m_5Ik!Ja=y= zGJTYI?#K)5axBd8i@ttt904FyayAq;!41=?V{zRQ3*oj8-v@tk-@X;p^%y1!jo=rxH4FWz-TZM2H37r9Ix zRA%Hd9MX{8#I_>6JYxGwK{z|FljX(e6!^H^wo7$K5-a<7{OPi3uCOASESn#4}v7lvHQdm z8MYkln6}s-ofVUI?b<>x!(3aR$0zKA&6^VSCVY+V}ah7PC$Z7MAIT?yLQra zJ%KbLsggqS7b|H-=+NWwL@9^}^xx40a9HCJ-1U}T^X1fg;b7Bx`17~CF*<;8{`WA; zsiyA@5mrmsH9}MsMSjt|w4(zxQtfSy%)hnYOtKBR7r#2{diJR%RjD2j6h7Ynp*eJ?GF3kjMBm7SV2DEis>;cOV$3CfdoJ-R z`G@fKAIEPX>n!Q#h=&`XuHpa)eHFBNQ5JCzW}kyf-Lt$WnRfyG-WB%W*$OC{zU(Z> zaj?+jK>F76)|zieB%mV5X}LlJY1IAn@2pSc)t)b|&BG7n5;C3&!5pl{>c71n>3nTL zkF@(lX=DJ#9JM2B<&-)|EI&E$zj2Rh&{D1GW^85q`cf1WlyL#zCOq8T9#|bkLaiT!av)R^!)c~LFC_N7L z5A!bUd{?hvRwl|}w}K-zgotOA9v8$}dp7#87`R*B?}7|gTJD-(Z+pHWUqM=On`&%B zf{BQTMak>yIOhi|A9S1RHL*+FMX`l4B{&d{=rcZtFHx=dqZTP{x}Sk)ZSVtBFqbq7 zn8L^Com~6(YUOhVj7key=CfZ(lf$c?)UgE)XO9DKdKX8`t46XMnBvMCF`utKD0MPK zzGvPd09dzDgG%`2HMfY7yLR@Nk$7LK^gIRBQ8IflX`wGLUAOr> z&gi4c3H8m}ZpbOA6j-i49WXrRgcVp$z_`+!_ZH^~x$Zid6Rd|_%9bIJFl?15Ei`gs z*8cq8=@=8cRTyr(Nv_JvfZ}(2r9j%Fkw!vcSXcAGhcV(0DQcQ{8ifdDe0rYQb%|vT zElwaD!W7ur?@Fp#XqKM=N^rwR56O)}#KCEpexjBi8Q+Y}fP~M#$@Ka))xA%j;eA1i zpAY;O5rGw5BdxaKzl$Yo&Rkg+Pj9-cJ{~}ichS*GyAP#t`o`iF4FI{ zCl7YM%imuF?HX*0^$exP0RGdq^*VET2=+CkVLXrnyEvRB-T%M{#;lL+i8d`lQie? zKbyGsM1-`D2sE7q4BSO*4!_+~*=>?f-OFiUDP{Qn>LaS5LLIFdN9)Sw`}6c6MahOW z*kLAC71x;ODcmgUpTTi2#n!mK`@uNx?g!r$Rk`3!rgCuVnO}IVvI0Sz=McDkNt83$ zjuJLNIEhMXUTXARQ4jv@nvwZK^(7vgIiHjUbIvdTjj zx;!5{Xr;Y1xqC_?3vm@!94;yjh|OzsphX;Kn~~TKCsDQO6N_RY<|xPntuVCJ;kPCJ zk0DxLW!2>NH*n^|vAlba8}v3537bK=xeH(cXo(!PJiib+3%(E&*Xx*^>V zkEaFM`98qH7c>UWPRPIJw7AJx6S4!OJnv;^;B>5rMa1W|MVY(s`8U83tqJ3(ecBgD zuV49U?fbFugm$l}ZqRf3+UsF|T#&WU&?a=IbAk9!^)mTQ4|juh&(=LgJnpP=^*-WU zVleEzdJQ(<)5>vYUfauS1FKFwZKE%V3eigz(_$h)9I?s$UEM(_x10&mQs`GZeQN^% zuTyagH{FYPJuqagHb%dAmqQe9Lf^CA7M!LZOO=Gm;G`d~0EwXaF2dBk`a6b?`AbPuWKf^r5!sNbH%I5q8{6rZvZVPK$kUJ0j zZW|hXqKK4^?$V+N8o2AYhwuny@^z?7evK*4XdBH2+>s}-!RmHYIuWOsfI!Tj*(ZMB z^f@UNR9N!$rdh=a4c%RAd4T);F#hx|Gc`{*!qPxZT3d-Df^yD+c?fttBZ$ey5;C&~V5XL-IyKp#L6W-YAN%s(Gh)Vy}3oDLuJP zt)Zt)jtz`|a)QGhv5=)60HGFJsr$SaS0xSPB z^8Y@G-YNKb%0fXk{bzbYYaX|`&FNn7>i8hg**tY?0#CDMYqKu8 z`;Jdm^1K>h_f@!rOUU&*bBxTfd!7oqsT>^;%LNu%tX! zeZR);di=Jgq=jKWgm?d4`N2C1*mOPNDQ)d(5XteI{q&uW7s~cky*9q{`-fhiob<8@ z-!;T+VvZ;1vTXZU#U`Vk^D%rCBd~p-Gi~W->&nO3;UC}4x9RV`rqi?gd$U7p{H%6h zfX~_VWP?M))Ac?gy<3(F{MNEr0H1*3+(1@0&aFIIqWUYZC2FzgBnsh_L+CN7K`Q z^`X*arCE&pW^%v3&nftsV3M*S`F)^VO_urW`S;HQhx|9zei!`3cAAH0s^KwUYr0}x z^XnNqJ{)VE^Vs~##*Y)2KB^pDarE79#;I8jJ0DH{EORUB6R0t2nSL%2Scz`>H>WkG z?BAmwhukk3vXzx|1Om7BfGyRsv|-q~uFfJaI_5tAjq7*MZ`PMhdc95#RCajp?%x3H zZSj5Q3)K5PWwqVaEQh799a(J$^s)1ZTGrXrrmivNJaf1h$^zeK?Ql;`sv}hU-FV ztA2aGdHrrVu-duzPw4osc1fvqMiHw5z8+rvI#OqCtIWLT28;JPxJNh6lywER+>GkO zq}Lvm**VAilgRWtuOvLqL|-V|JINN9hmCR{IW`sUdMBH#mb<6+O)2k&ggFy)YZSSd zdv}@yf=~1CaQ2wjrL&c@&1~b@g50JD8C!4uXgl)!@$UCmcZpj9YwL~<4>rrMb_~G7 zs%F*!%~Vp71lF%gN|PK%IcQ<<-`=3>oZgO^b1nmq8&fTDjVMV;EJ?LWE=mPb3`Pb< zrn&}(x`w792If|#rdGzr+6D$z1_l;M)$%ABa`RI%(}1HJxHNd$A6^X9z~JfX=d#Wz Gp$Pzi-sIo_ literal 0 HcmV?d00001 diff --git a/apps/enmeshed/lib/account/contacts/contact_detail_screen.dart b/apps/enmeshed/lib/account/contacts/contact_detail_screen.dart index cd1b5c610..c6ecfd157 100644 --- a/apps/enmeshed/lib/account/contacts/contact_detail_screen.dart +++ b/apps/enmeshed/lib/account/contacts/contact_detail_screen.dart @@ -18,6 +18,8 @@ class ContactDetailScreen extends ContactSharedFilesWidget { } class _ContactDetailScreenState extends State with ContactSharedFilesMixin { + late final ScrollController _scrollController; + late final Session _session; IdentityDVO? _contact; @@ -33,6 +35,8 @@ class _ContactDetailScreenState extends State with ContactS void initState() { super.initState(); + _scrollController = ScrollController(); + _session = GetIt.I.get().getSession(widget.accountId); _reload(); @@ -50,6 +54,8 @@ class _ContactDetailScreenState extends State with ContactS @override void dispose() { + _scrollController.dispose(); + for (final subscription in _subscriptions) { subscription.cancel(); } @@ -77,8 +83,10 @@ class _ContactDetailScreenState extends State with ContactS await loadSharedFiles(syncBefore: true); }, child: Scrollbar( + controller: _scrollController, thumbVisibility: true, child: ListView( + controller: _scrollController, children: [ ContactDetailHeader(contact: contact), ContactStatusInfoContainer(contact: contact), diff --git a/apps/enmeshed/lib/account/home/home.dart b/apps/enmeshed/lib/account/home/home.dart index bc09f1798..53ceb9391 100644 --- a/apps/enmeshed/lib/account/home/home.dart +++ b/apps/enmeshed/lib/account/home/home.dart @@ -20,6 +20,8 @@ class HomeView extends StatefulWidget { } class _HomeViewState extends State { + late final ScrollController _scrollController; + int _unreadMessagesCount = 0; List? _messages; List? _requests; @@ -31,6 +33,8 @@ class _HomeViewState extends State { void initState() { super.initState(); + _scrollController = ScrollController(); + _reload(); final runtime = GetIt.I.get(); @@ -46,6 +50,8 @@ class _HomeViewState extends State { @override void dispose() { + _scrollController.dispose(); + for (final subscription in _subscriptions) { subscription.cancel(); } @@ -67,8 +73,10 @@ class _HomeViewState extends State { return RefreshIndicator( onRefresh: () => _reload(syncBefore: true), child: Scrollbar( + controller: _scrollController, thumbVisibility: true, child: ListView( + controller: _scrollController, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16), diff --git a/apps/enmeshed/lib/core/setup_push.dart b/apps/enmeshed/lib/core/setup_push.dart index 97d9e58d8..f43f0a0ed 100644 --- a/apps/enmeshed/lib/core/setup_push.dart +++ b/apps/enmeshed/lib/core/setup_push.dart @@ -9,6 +9,8 @@ import 'package:logger/logger.dart'; import 'package:push/push.dart'; Future setupPush(EnmeshedRuntime runtime) async { + if (Platform.isWindows) return; + final logger = GetIt.I.get(); // this will timeout e.g. on the ios simulator diff --git a/apps/enmeshed/lib/main.dart b/apps/enmeshed/lib/main.dart index bded750c7..b4b5a5fea 100644 --- a/apps/enmeshed/lib/main.dart +++ b/apps/enmeshed/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:croppy/croppy.dart'; import 'package:enmeshed_runtime_bridge/enmeshed_runtime_bridge.dart'; @@ -26,7 +27,7 @@ import 'splash_screen.dart'; void main() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); + if (Platform.isAndroid || Platform.isIOS) FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding); timeago.setLocaleMessages('de', timeago.DeMessages()); timeago.setLocaleMessages('en', timeago.EnMessages()); diff --git a/apps/enmeshed/lib/splash_screen.dart b/apps/enmeshed/lib/splash_screen.dart index c98eb522d..18e02943e 100644 --- a/apps/enmeshed/lib/splash_screen.dart +++ b/apps/enmeshed/lib/splash_screen.dart @@ -11,6 +11,7 @@ import 'package:go_router/go_router.dart'; import 'package:logger/logger.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:renderers/renderers.dart'; +import 'package:win32_registry/win32_registry.dart'; import 'core/core.dart'; @@ -64,17 +65,20 @@ class _SplashScreenState extends State { Future _init(GoRouter router) async { await GetIt.I.reset(); + final logger = Logger(printer: SimplePrinter(colors: false)); + GetIt.I.registerSingleton(logger); + GetIt.I.registerSingleton(UrlLauncher()); + // TODO(jkoenig134): we should probably ask for permission when we need it - await Permission.camera.request(); + final cameraStatus = await Permission.camera.request(); + if (!cameraStatus.isGranted) { + logger.w('Camera permission is (permanently) denied'); + } if (Platform.isAndroid && kDebugMode) { await InAppWebViewController.setWebContentsDebuggingEnabled(true); } - final logger = Logger(printer: SimplePrinter(colors: false)); - GetIt.I.registerSingleton(logger); - GetIt.I.registerSingleton(UrlLauncher()); - final runtime = EnmeshedRuntime( logger: logger, runtimeConfig: ( @@ -87,6 +91,7 @@ class _SplashScreenState extends State { ), ); GetIt.I.registerSingletonAsync(() async => runtime.run()); + await GetIt.I.allReady(); await setupPush(runtime); @@ -101,10 +106,10 @@ class _SplashScreenState extends State { await runtime.registerUIBridge(AppUIBridge(logger: logger, router: router)); + await _registerWindowsSchemeForDebugMode('nmshd-dev'); + final appLinks = AppLinks(); - appLinks.uriLinkStream.listen((Uri? uri) { - if (uri != null) GetIt.I.get().stringProcessor.processURL(url: uri.toString()); - }); + appLinks.uriLinkStream.listen(_processUri); final accounts = await runtime.accountServices.getAccounts(); final accountsNotInDeletion = await runtime.accountServices.getAccountsNotInDeletion(); @@ -123,8 +128,33 @@ class _SplashScreenState extends State { } final initialAppLink = await appLinks.getInitialLink(); - if (initialAppLink != null) { - await GetIt.I.get().stringProcessor.processURL(url: initialAppLink.toString()); + await _processUri(initialAppLink); + } + + Future _processUri(Uri? uri) async { + if (uri == null) return; + + final uriString = uri.toString().replaceAll('nmshd-dev://', 'nmshd://').replaceAll('qr/#', 'qr#'); + GetIt.I.get().i("Processing URL '$uriString'"); + + final result = await GetIt.I.get().stringProcessor.processURL(url: uriString); + if (result.isError) { + GetIt.I.get().e("Processing URL '$uriString' failed with code '${result.error.code}' and message '${result.error.message}'"); } } } + +Future _registerWindowsSchemeForDebugMode(String scheme) async { + if (!Platform.isWindows || !kDebugMode) return; + + final appPath = Platform.resolvedExecutable; + + final protocolRegKey = 'Software\\Classes\\$scheme'; + const protocolRegValue = RegistryValue('URL Protocol', RegistryValueType.string, ''); + const protocolCmdRegKey = r'shell\open\command'; + final protocolCmdRegValue = RegistryValue('', RegistryValueType.string, '"$appPath" "%1"'); + + Registry.currentUser.createKey(protocolRegKey) + ..createValue(protocolRegValue) + ..createKey(protocolCmdRegKey).createValue(protocolCmdRegValue); +} diff --git a/apps/enmeshed/pubspec.lock b/apps/enmeshed/pubspec.lock index de4f93167..42bfa6018 100644 --- a/apps/enmeshed/pubspec.lock +++ b/apps/enmeshed/pubspec.lock @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.0" + console: + dependency: transitive + description: + name: console + sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a + url: "https://pub.dev" + source: hosted + version: "4.1.0" croppy: dependency: "direct main" description: @@ -762,6 +770,15 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + msix: + dependency: "direct dev" + description: + path: "." + ref: "Fix-Flutter-3.27" + resolved-ref: "955467338b11ddcd789a37d268e9cfd884a94b17" + url: "https://github.com/h4h13/msix.git" + source: git + version: "3.16.8" open_file: dependency: "direct main" description: @@ -826,6 +843,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.3" + package_config: + dependency: transitive + description: + name: package_config + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + url: "https://pub.dev" + source: hosted + version: "2.1.1" package_info_plus: dependency: "direct main" description: @@ -1002,6 +1027,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.4" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" push: dependency: "direct main" description: @@ -1373,6 +1406,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.10.0" + win32_registry: + dependency: "direct main" + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" + windows_notification: + dependency: transitive + description: + name: windows_notification + sha256: be3e650874615f315402c9b9f3656e29af156709c4b5cc272cb4ca0ab7ba94a8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" wolt_modal_sheet: dependency: "direct main" description: diff --git a/apps/enmeshed/pubspec.yaml b/apps/enmeshed/pubspec.yaml index a27603e2a..6143a3f4c 100644 --- a/apps/enmeshed/pubspec.yaml +++ b/apps/enmeshed/pubspec.yaml @@ -55,12 +55,17 @@ dependencies: value_renderer: ^1.0.0 vector_graphics: ^1.1.13 very_good_analysis: ^6.0.0 + win32_registry: ^1.1.5 wolt_modal_sheet: ^0.5.0 dev_dependencies: flutter_launcher_icons: ^0.14.1 flutter_test: sdk: flutter + msix: + git: + url: https://github.com/h4h13/msix.git + ref: Fix-Flutter-3.27 translations_cleaner: ^0.0.5 vector_graphics_compiler: ^1.1.12 @@ -81,6 +86,9 @@ flutter_icons: image_path: "assets/icon.png" android: true ios: true + windows: + generate: true + image_path: "assets/icon-512.png" remove_alpha_ios: true image_path_ios_dark_transparent: "assets/icon_dark.png" image_path_ios_tinted_grayscale: "assets/icon_grayscale.png" @@ -104,3 +112,19 @@ flutter_native_splash: android: true ios: true + +# dart run msix:create +msix_config: + display_name: enmeshed + publisher_display_name: js-soft + identity_name: js-soft.enmeshed + publisher: CN=CB3048C3-A086-471C-AF11-83ABB7396C98 + store: true + protocol_activation: nmshd + logo_path: assets/icon-512.png + start_menu_icon_path: assets/icon-512.png + tile_icon_path: assets/icon-512.png + icons_background_color: transparent + build_windows: false + languages: en-us, de-de + # capabilities: "internetClient,internetClientServer,privateNetworkClientServer" diff --git a/apps/enmeshed/windows/.gitignore b/apps/enmeshed/windows/.gitignore new file mode 100644 index 000000000..d492d0d98 --- /dev/null +++ b/apps/enmeshed/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/apps/enmeshed/windows/CMakeLists.txt b/apps/enmeshed/windows/CMakeLists.txt new file mode 100644 index 000000000..4fd7a3edc --- /dev/null +++ b/apps/enmeshed/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(enmeshed LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "enmeshed") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/apps/enmeshed/windows/flutter/CMakeLists.txt b/apps/enmeshed/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..903f4899d --- /dev/null +++ b/apps/enmeshed/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/apps/enmeshed/windows/flutter/generated_plugin_registrant.cc b/apps/enmeshed/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..757e57808 --- /dev/null +++ b/apps/enmeshed/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,32 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + PrintingPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PrintingPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WindowsNotificationPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowsNotificationPluginCApi")); +} diff --git a/apps/enmeshed/windows/flutter/generated_plugin_registrant.h b/apps/enmeshed/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/apps/enmeshed/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/enmeshed/windows/flutter/generated_plugins.cmake b/apps/enmeshed/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..f275b4fd0 --- /dev/null +++ b/apps/enmeshed/windows/flutter/generated_plugins.cmake @@ -0,0 +1,31 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + app_links + file_selector_windows + flutter_inappwebview_windows + permission_handler_windows + printing + url_launcher_windows + windows_notification +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + croppy +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/apps/enmeshed/windows/runner/CMakeLists.txt b/apps/enmeshed/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..394917c05 --- /dev/null +++ b/apps/enmeshed/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/apps/enmeshed/windows/runner/Runner.rc b/apps/enmeshed/windows/runner/Runner.rc new file mode 100644 index 000000000..4e0cb11c3 --- /dev/null +++ b/apps/enmeshed/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "eu.enmeshed" "\0" + VALUE "FileDescription", "enmeshed" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "enmeshed" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 eu.enmeshed. All rights reserved." "\0" + VALUE "OriginalFilename", "enmeshed.exe" "\0" + VALUE "ProductName", "enmeshed" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/apps/enmeshed/windows/runner/flutter_window.cpp b/apps/enmeshed/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..955ee3038 --- /dev/null +++ b/apps/enmeshed/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/apps/enmeshed/windows/runner/flutter_window.h b/apps/enmeshed/windows/runner/flutter_window.h new file mode 100644 index 000000000..6da0652f0 --- /dev/null +++ b/apps/enmeshed/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/apps/enmeshed/windows/runner/main.cpp b/apps/enmeshed/windows/runner/main.cpp new file mode 100644 index 000000000..773f4b28e --- /dev/null +++ b/apps/enmeshed/windows/runner/main.cpp @@ -0,0 +1,83 @@ +#include +#include +#include +#include "app_links/app_links_plugin_c_api.h" + +#include "flutter_window.h" +#include "utils.h" + +bool SendAppLinkToInstance(const std::wstring& title) { + // Find our exact window + HWND hwnd = ::FindWindow(L"FLUTTER_RUNNER_WIN32_WINDOW", title.c_str()); + + if (hwnd) { + // Dispatch new link to current window + SendAppLink(hwnd); + + // (Optional) Restore our window to front in same state + WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) }; + GetWindowPlacement(hwnd, &place); + + switch(place.showCmd) { + case SW_SHOWMAXIMIZED: + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + break; + case SW_SHOWMINIMIZED: + ShowWindow(hwnd, SW_RESTORE); + break; + default: + ShowWindow(hwnd, SW_NORMAL); + break; + } + + SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(hwnd); + // END (Optional) Restore + + // Window has been found, don't create another one. + return true; + } + + return false; +} + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + if (SendAppLinkToInstance(L"enmeshed")) { + return EXIT_SUCCESS; + } + + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"enmeshed", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/apps/enmeshed/windows/runner/resource.h b/apps/enmeshed/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/apps/enmeshed/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/apps/enmeshed/windows/runner/resources/app_icon.ico b/apps/enmeshed/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c5ba8c8df9f5601397f319a096dd3d8523aa6f5e GIT binary patch literal 1386 zcmV-w1(o^$0096205C8B0000W08|A402TlM0EtjeM-2)Z3IG5A4M|8uQUCw|FaQ7m zFbDH8najGb=DK zIxsLeVtXF|001R)MObuXVRU6WZEs|0W_bWIFfuhQFflDLHB>M+IyE&qGczkNFgh?W zNk7A*000EJNkl8l0w{5x&xgLgJ~*AuJI3U4IgCalN=r*QAyUCqfXCxOS63Gn z7Z+EJMIsT{?RGAvva%9NrBc!y%mjqPVf6I$#3a8GUauEcs}<$t<&w&YnE;#3#??I^ zDLOtnI*Q53Np4?=1rq_Z_Q%J^1+OTL8yg$jJOU`_>a&cGf|&q%%CL-&f|-ES)KtvO z%!rDY0ww}zP3Pp~aGO)eK8VIk0kaI`=H_BxU;uM-b8+M7hA%2Af=;KCG!L^=sJFKl zEiEmuSS--%^)WL+)svNfmLt!CY%B;95SRkaJinU!3eZKa-ZPDt4wXQm(Ikr> zF9Zl6HDWyp%uMh`QV|O4`@p_BK&egwJ$~>Dpfx^pR`?qOHvs#2z*Hrjl?d<)SRf~k z@9*VW7XljRy0(b~NNF1_MI=y>27UosiOJGtkqta4?2S}!<|B!*5&>bSP>}`r^^~wT zs_p~w%?VHv2YvzcXgSbKHu9Gw3!b>4T(yG+xNsu-0C*aRZk;czU&RaW5AEcPoSoz z25oI^(5%CSCfrI;5t(_w(QlS-X6GQuMu3pd2DY2XupDNRhJ|1VO-)UxtE=NIMKlxC z=mkSJF&79yzfFZLTVxwx=AuG0q@*4?7-L9=s;cGlzqz>?4Gj%wZT$#e{ba|P@2+Cl z;X#B5Q7IL8qF^Upd0{UOlX8!Bb1}=n@W{X7;vMlX$+f^tK!|Kq zF$jj^cd^$3GXc8{Iyl|J`<=VD>m|*@>=b&YG#mZboczI9-ZL~_3YcZUa>#^FItStP z&GW|YFM1IB9@{Bt9(K)L-Bx_|P6dv>bq;}rwd*@?cRD_OyJGWn=2n8PYWL}C9Q)uR zhW`9#70HUHjQH?)Ip>X}f~f#X_~Gmfod38Qmxo=rVsl~EA4FEV7EhA0$=(sWXDSBk s@;@;pS@P%(?nyT8{Zqfuxa$S{2P&+Ii}gIq*8l(j07*qoM6N<$f`!m*z5oCK literal 0 HcmV?d00001 diff --git a/apps/enmeshed/windows/runner/runner.exe.manifest b/apps/enmeshed/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..153653e8d --- /dev/null +++ b/apps/enmeshed/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/apps/enmeshed/windows/runner/utils.cpp b/apps/enmeshed/windows/runner/utils.cpp new file mode 100644 index 000000000..3a0b46511 --- /dev/null +++ b/apps/enmeshed/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/apps/enmeshed/windows/runner/utils.h b/apps/enmeshed/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/apps/enmeshed/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/apps/enmeshed/windows/runner/win32_window.cpp b/apps/enmeshed/windows/runner/win32_window.cpp new file mode 100644 index 000000000..60608d0fe --- /dev/null +++ b/apps/enmeshed/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/apps/enmeshed/windows/runner/win32_window.h b/apps/enmeshed/windows/runner/win32_window.h new file mode 100644 index 000000000..e901dde68 --- /dev/null +++ b/apps/enmeshed/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/codemagic.yaml b/codemagic.yaml index a2593500c..e82b5c4ec 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -79,7 +79,7 @@ workflows: tag_patterns: - pattern: "enmeshed-v+([0-9]).+([0-9]).+([0-9])" include: true - cancel_previous_builds: true + cancel_previous_builds: false environment: groups: - config-secrets @@ -135,3 +135,66 @@ workflows: script: | npm install -g @js-soft/codemagic-tools@1.0.2 jscm teams-publish --platform "ios" --projectName "enmeshed" + + windows-release: + name: Windows Release + max_build_duration: 30 + instance_type: windows_x2 + environment: + groups: + - config-secrets + - webhook-secrets + flutter: stable + triggering: + events: [] + # - tag + # tag_patterns: + # - pattern: "enmeshed-v+([0-9]).+([0-9]).+([0-9])" + # include: true + # cancel_previous_builds: false + + scripts: + - name: Install melos + script: dart pub global activate melos + + - name: Bootstrap repository + script: melos bootstrap + + - name: Build appbundle with Flutter + script: | + $VERSION = $CM_TAG -replace "enmeshed-v", "" + + flutter config --enable-windows-desktop + flutter build windows --release --build-number=$PROJECT_BUILD_NUMBER --build-name=$VERSION --dart-define="app_baseUrl=$app_baseUrl" --dart-define="app_clientId=$app_clientId" --dart-define="app_clientSecret=$app_clientSecret" + cd build/windows/x64/runner/Release + 7z a -r ../release.zip ./* + working_directory: apps/enmeshed + + - name: Build msix package + script: | + $VERSION = $CM_TAG -replace "enmeshed-v", "" + + flutter pub run msix:create --version "$VERSION.0" + working_directory: apps/enmeshed + + - name: Build finished successfully + script: touch ~/SUCCESS + + artifacts: + - apps/enmeshed/build/windows/x64/runner/release.zip + - apps/enmeshed/build/windows/**/*.msix + + publishing: + email: + recipients: + - julian.koenig@js-soft.com + notify: + success: true + failure: true + + # TODO: this script does not support setting windows as a platform yet + # scripts: + # - name: Custom MS-Teams integration publish + # script: | + # npm install -g @js-soft/codemagic-tools@1.0.2 + # jscm teams-production --platform "windows" --projectName "enmeshed" diff --git a/packages/enmeshed_runtime_bridge/integration_test_runner/pubspec.lock b/packages/enmeshed_runtime_bridge/integration_test_runner/pubspec.lock index 134092720..6ea053faf 100644 --- a/packages/enmeshed_runtime_bridge/integration_test_runner/pubspec.lock +++ b/packages/enmeshed_runtime_bridge/integration_test_runner/pubspec.lock @@ -514,6 +514,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.4" + windows_notification: + dependency: transitive + description: + name: windows_notification + sha256: be3e650874615f315402c9b9f3656e29af156709c4b5cc272cb4ca0ab7ba94a8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" xdg_directories: dependency: transitive description: diff --git a/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugin_registrant.cc b/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugin_registrant.cc index 3b4ee9033..29500ae63 100644 --- a/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugin_registrant.cc +++ b/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + WindowsNotificationPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowsNotificationPluginCApi")); } diff --git a/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugins.cmake b/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugins.cmake index 61c79a21d..f988e2f42 100644 --- a/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugins.cmake +++ b/packages/enmeshed_runtime_bridge/integration_test_runner/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_inappwebview_windows + windows_notification ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/packages/enmeshed_runtime_bridge/lib/src/enmeshed_runtime.dart b/packages/enmeshed_runtime_bridge/lib/src/enmeshed_runtime.dart index c56107891..107948a0e 100644 --- a/packages/enmeshed_runtime_bridge/lib/src/enmeshed_runtime.dart +++ b/packages/enmeshed_runtime_bridge/lib/src/enmeshed_runtime.dart @@ -152,8 +152,12 @@ class EnmeshedRuntime { 'platformClientId': runtimeConfig.clientId, 'platformClientSecret': runtimeConfig.clientSecret, }, - 'pushToken': null, 'databaseFolder': runtimeConfig.databaseFolder, + if (Platform.isWindows) + 'modules': { + 'pushNotification': {'enabled': false}, + 'sse': {'enabled': true}, + } }, ); diff --git a/packages/enmeshed_runtime_bridge/lib/src/javascript_handlers.dart b/packages/enmeshed_runtime_bridge/lib/src/javascript_handlers.dart index 4acd946d7..df084aebf 100644 --- a/packages/enmeshed_runtime_bridge/lib/src/javascript_handlers.dart +++ b/packages/enmeshed_runtime_bridge/lib/src/javascript_handlers.dart @@ -1,7 +1,11 @@ +import 'dart:io'; + import 'package:enmeshed_types/enmeshed_types.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:logger/logger.dart'; +import 'package:windows_notification/notification_message.dart'; +import 'package:windows_notification/windows_notification.dart'; import 'event_bus.dart'; import 'events/events.dart'; @@ -330,6 +334,16 @@ extension Filesystem on InAppWebViewController { extension LocalNotifications on InAppWebViewController { void addLocalNotificationsJavaScriptHandlers() { + if (Platform.isAndroid || Platform.isIOS || Platform.isLinux || Platform.isMacOS) { + _addLocalNotificationsJavaScriptHandlers(); + } else if (Platform.isWindows) { + _addWindowsNotificationsJavaScriptHandlers(); + } else { + throw Exception('Unsupported platform'); + } + } + + void _addLocalNotificationsJavaScriptHandlers() { final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); addJavaScriptHandler( @@ -367,4 +381,49 @@ extension LocalNotifications on InAppWebViewController { }, ); } + + void _addWindowsNotificationsJavaScriptHandlers() { + final winNotifyPlugin = WindowsNotification(applicationId: null); + + winNotifyPlugin.initNotificationCallBack((details) { + // TODO: handle notification click + }); + + addJavaScriptHandler( + handlerName: 'notifications_schedule', + callback: (args) async { + final title = args[0] as String; + final body = args[1] as String; + final id = args[2] as int; + + await winNotifyPlugin.showNotificationPluginTemplate( + NotificationMessage.fromPluginTemplate(id.toString(), title, body, group: 'nmshd'), + ); + }, + ); + + addJavaScriptHandler( + handlerName: 'notifications_clear', + callback: (args) async { + final id = args[0] as int; + + await winNotifyPlugin.removeNotificationId(id.toString(), 'nmshd'); + }, + ); + + addJavaScriptHandler( + handlerName: 'notifications_clearAll', + callback: (args) async { + await winNotifyPlugin.clearNotificationHistory(); + }, + ); + + addJavaScriptHandler( + handlerName: 'notifications_getAll', + callback: (args) { + // no support for listing notifications on Windows + return []; + }, + ); + } } diff --git a/packages/enmeshed_runtime_bridge/pubspec.yaml b/packages/enmeshed_runtime_bridge/pubspec.yaml index ffcda48be..4a2567433 100644 --- a/packages/enmeshed_runtime_bridge/pubspec.yaml +++ b/packages/enmeshed_runtime_bridge/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: flutter_local_notifications: ^18.0.0 logger: ^2.4.0 path_provider: ^2.1.5 + windows_notification: ^1.3.0 dev_dependencies: flutter_lints: ^5.0.0